uBrowserSync/cmd/ubsserver/main.go

260 lines
6.0 KiB
Go

package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
"regexp"
"strings"
"time"
"gitlab.com/mporrato/uBrowserSync/syncstore"
)
const apiVersion = "1.1.13"
const infoMessage = "Powered by uBrowserSync"
const defaultMaxSyncSize = 512000
type serviceInfoResp struct {
Version string `json:"version"`
Message string `json:"message"`
MaxSyncSize int `json:"maxSyncSize"`
Status int `json:"status"`
}
type CreateReq struct {
Version string `json:"version"`
}
type UpdateReq struct {
Bookmarks string `json:"bookmarks"`
LastUpdated time.Time `json:"lastUpdated"`
}
type UpdateResp struct {
LastUpdated time.Time `json:"lastUpdated"`
}
type LastUpdatedResp struct {
LastUpdated time.Time `json:"lastUpdated"`
}
type GetSyncVerResp struct {
Version string `json:"version"`
}
var store syncstore.Store
var maxSyncSize = defaultMaxSyncSize
var serviceStatus = 1
var listen string
var sidRe = regexp.MustCompile("^[[:xdigit:]]{32}$")
func sendJSON(w http.ResponseWriter, status int, data interface{}) {
body, err := json.Marshal(data)
if err != nil {
log.Printf("sendJSON(%v): json.Marshal() failed: %v", data, err)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_, err = w.Write(body)
if err != nil {
log.Println("sendJSON() failed:", err)
}
}
func sendJSONOk(w http.ResponseWriter, data interface{}) {
sendJSON(w, http.StatusOK, data)
}
func sendJSONError(w http.ResponseWriter, err error) {
switch e := err.(type) {
case syncstore.SyncError:
sendJSON(w, e.StatusCode, e.Payload)
default:
sendJSON(w, http.StatusInternalServerError,
syncstore.ErrorPayload{
Code: "Internal server error",
Message: e.Error()})
}
}
func info(w http.ResponseWriter, req *http.Request) {
if req.Method != "GET" || req.URL.Path != "/info" {
sendJSONError(w, syncstore.NotImplementedError)
} else {
log.Println("info()")
serviceInfo := serviceInfoResp{
Version: apiVersion,
Message: infoMessage,
Status: serviceStatus,
MaxSyncSize: maxSyncSize}
sendJSONOk(w, serviceInfo)
}
}
var invalidRequestError = syncstore.NewSyncError(
"Invalid request",
"Malformed request body",
http.StatusBadRequest)
func createSync(w http.ResponseWriter, req *http.Request) {
body := new(CreateReq)
err := json.NewDecoder(req.Body).Decode(&body)
if err != nil {
sendJSONError(w, invalidRequestError)
return
}
if len(body.Version) > 20 {
sendJSONError(w, invalidRequestError)
return
}
resp, err := store.Create(body.Version)
if err != nil {
sendJSONError(w, err)
return
}
sendJSONOk(w, resp)
}
func getSync(syncId string, w http.ResponseWriter, _ *http.Request) {
resp, err := store.Get(syncId)
if err != nil {
sendJSONError(w, err)
return
}
sendJSONOk(w, resp)
}
func getLastUpdated(syncId string, w http.ResponseWriter, _ *http.Request) {
resp, err := store.GetLastUpdated(syncId)
if err != nil {
sendJSONError(w, err)
return
}
sendJSONOk(w, LastUpdatedResp{LastUpdated: resp})
}
func getVersion(syncId string, w http.ResponseWriter, _ *http.Request) {
resp, err := store.GetVersion(syncId)
if err != nil {
sendJSONError(w, err)
return
}
sendJSONOk(w, GetSyncVerResp{Version: resp})
}
func updateSync(syncId string, w http.ResponseWriter, req *http.Request) {
body := new(UpdateReq)
err := json.NewDecoder(req.Body).Decode(&body)
if err != nil {
sendJSONError(w, invalidRequestError)
return
}
if len(body.Bookmarks) > maxSyncSize {
sendJSONError(w, syncstore.SyncDataLimitExceededError)
return
}
resp, err := store.Update(syncId, body.Bookmarks, body.LastUpdated)
if err != nil {
sendJSONError(w, err)
return
}
sendJSONOk(w, UpdateResp{LastUpdated: resp})
}
func bookmarks(w http.ResponseWriter, req *http.Request) {
elements := strings.Split(strings.Trim(req.URL.Path, "/"), "/")
if len(elements) == 1 && req.Method == "POST" {
log.Println("createSync()")
createSync(w, req)
return
}
syncId := elements[1]
if !sidRe.MatchString(elements[1]) {
sendJSONError(w, syncstore.NotImplementedError)
return
}
if len(elements) == 2 {
if req.Method == "GET" {
log.Printf("getSync(%s)", syncId)
getSync(syncId, w, req)
return
}
if req.Method == "PUT" {
log.Printf("updateSync(%s)", syncId)
updateSync(syncId, w, req)
return
}
// TODO: Handle HEAD requests
sendJSONError(w, syncstore.MethodNotImplementedError)
return
}
if len(elements) == 3 {
if elements[2] == "lastUpdated" {
if req.Method == "GET" {
log.Printf("getLastUpdated(%s)", syncId)
getLastUpdated(syncId, w, req)
return
}
sendJSONError(w, syncstore.MethodNotImplementedError)
return
}
if elements[2] == "version" {
if req.Method == "GET" {
log.Printf("getVersion(%s)", syncId)
getVersion(syncId, w, req)
return
}
sendJSONError(w, syncstore.MethodNotImplementedError)
return
}
}
//sendJSON(w, http.StatusOK, map[string][]string{"elements": elements})
sendJSONError(w, syncstore.NotImplementedError)
}
func init() {
var err error
var storeFlag string
var storeDrv syncstore.StoreDriver
flag.StringVar(&listen, "listen", ":8090", "listen address and port")
flag.StringVar(&storeFlag, "store", "fs:data", "blob store driver")
flag.IntVar(&maxSyncSize, "maxsize", defaultMaxSyncSize, "maximum size of a sync in bytes")
flag.Parse()
storeFlagTokens := strings.Split(storeFlag, ":")
switch storeFlagTokens[0] {
case "fs":
if len(storeFlagTokens) != 2 {
err = fmt.Errorf("argument required for fs store driver")
} else {
storeDrv, err = syncstore.NewFSStore(storeFlagTokens[1])
}
case "mem":
storeDrv, err = syncstore.NewMemStore()
default:
err = fmt.Errorf("Invalid store driver: " + storeFlagTokens[0])
}
if err != nil {
log.Fatalf("store initialization failed: %v", err)
}
store = syncstore.NewStore(storeDrv)
}
func main() {
http.HandleFunc("/info", info)
http.HandleFunc("/bookmarks", bookmarks)
http.HandleFunc("/bookmarks/", bookmarks)
log.Println("HTTP server listening on", listen)
err := http.ListenAndServe(listen, nil)
log.Println("HTTP server terminated", err)
}