uBrowserSync/cmd/ubsserver/main.go

291 lines
6.8 KiB
Go

package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
"regexp"
"strings"
"time"
"gitlab.com/mporrato/uBrowserSync/bsync"
)
const (
apiVersion = "1.1.13"
infoMessage = "Powered by uBrowserSync"
defaultMaxSyncSize = 512000
defaultMaxSyncs = 10000
)
const (
statusOnline = 1
statusOffline = 2
statusReadOnly = 3
)
var (
config struct {
listen string
store string
maxSyncSize int
maxSyncs int
}
store bsync.Store
serviceStatus = statusOnline
sidRe = regexp.MustCompile("^[[:xdigit:]]{32}$")
invalidRequestError = bsync.NewSyncError(
"Invalid request",
"Malformed request body",
http.StatusBadRequest)
)
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) {
log.Println("ERROR: ", err)
switch e := err.(type) {
case bsync.SyncError:
sendJSON(w, e.StatusCode, e.Payload)
default:
sendJSON(w, http.StatusInternalServerError,
bsync.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, bsync.NotImplementedError)
} else {
log.Println("info()")
serviceInfo := bsync.ServiceInfoResp{
Version: apiVersion,
Message: infoMessage,
Status: serviceStatus,
MaxSyncSize: config.maxSyncSize}
sendJSONOk(w, serviceInfo)
}
}
func ensureJSONRequest(w http.ResponseWriter, req *http.Request) bool {
contentType := strings.Split(strings.ToLower(req.Header["Content-Type"][0]), ";")[0]
if contentType != "application/json" {
sendJSONError(w, bsync.RequiredDataNotFoundError)
return false
}
return true
}
func createSync(w http.ResponseWriter, req *http.Request) {
if serviceStatus != statusOnline {
sendJSONError(w, bsync.NewSyncsForbiddenError)
return
}
if !ensureJSONRequest(w, req) {
return
}
body := new(bsync.CreateReq)
req.Body = http.MaxBytesReader(w, req.Body, 10000)
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
}
if config.maxSyncs > 0 && store.Count() >= config.maxSyncs {
serviceStatus = statusReadOnly
}
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, bsync.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, bsync.GetSyncVerResp{Version: resp})
}
func updateSync(syncId string, w http.ResponseWriter, req *http.Request) {
if !ensureJSONRequest(w, req) {
return
}
body := new(bsync.UpdateReq)
req.Body = http.MaxBytesReader(w, req.Body, int64(10000+config.maxSyncSize))
err := json.NewDecoder(req.Body).Decode(&body)
if err != nil {
sendJSONError(w, invalidRequestError)
return
}
if len(body.Bookmarks) > config.maxSyncSize {
sendJSONError(w, bsync.SyncDataLimitExceededError)
return
}
resp, err := store.Update(syncId, body.Bookmarks, body.LastUpdated)
if err != nil {
sendJSONError(w, err)
return
}
sendJSONOk(w, bsync.UpdateResp{LastUpdated: resp})
}
func bookmarks(w http.ResponseWriter, req *http.Request) {
elements := strings.Split(strings.Trim(req.URL.Path, "/"), "/")
if len(elements) == 1 {
if req.Method == "POST" {
log.Println("createSync()")
createSync(w, req)
return
} else {
sendJSONError(w, bsync.MethodNotImplementedError)
return
}
}
syncId := elements[1]
if !sidRe.MatchString(syncId) {
sendJSONError(w, bsync.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, bsync.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, bsync.MethodNotImplementedError)
return
}
if elements[2] == "version" {
if req.Method == "GET" {
log.Printf("getVersion(%s)", syncId)
getVersion(syncId, w, req)
return
}
sendJSONError(w, bsync.MethodNotImplementedError)
return
}
}
sendJSONError(w, bsync.NotImplementedError)
}
func notFound(w http.ResponseWriter, _ *http.Request) {
sendJSONError(w, bsync.NotImplementedError)
}
func init() {
var (
err error
storeDrv bsync.StoreDriver
)
flag.StringVar(&config.listen, "listen", ":8090", "listen address and port")
flag.StringVar(&config.store, "store", "fs:data", "blob store driver")
flag.IntVar(&config.maxSyncSize, "maxsize", defaultMaxSyncSize, "maximum size of a sync in bytes")
flag.IntVar(&config.maxSyncs, "maxsyncs", defaultMaxSyncs, "maximum number of syncs")
flag.Parse()
storeFlagTokens := strings.Split(config.store, ":")
switch storeFlagTokens[0] {
case "fs":
if len(storeFlagTokens) != 2 {
err = fmt.Errorf("argument required for fs store driver")
} else {
storeDrv, err = bsync.NewFSStore(storeFlagTokens[1])
}
case "mem":
storeDrv, err = bsync.NewMemStore()
default:
err = fmt.Errorf("Invalid store driver: " + storeFlagTokens[0])
}
if err != nil {
log.Fatalf("store initialization failed: %v", err)
}
store = bsync.NewStore(storeDrv)
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", notFound)
mux.HandleFunc("/info", info)
mux.HandleFunc("/info/", info)
mux.HandleFunc("/bookmarks", bookmarks)
mux.HandleFunc("/bookmarks/", bookmarks)
log.Println("HTTP server listening on", config.listen)
server := &http.Server{
Addr: config.listen,
Handler: mux,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
MaxHeaderBytes: 5000}
err := server.ListenAndServe()
log.Println("HTTP server terminated", err)
}