uBrowserSync/cmd/ubsserver/main.go

291 lines
6.8 KiB
Go
Raw Permalink Normal View History

2021-05-08 16:03:21 +00:00
package main
import (
"encoding/json"
"flag"
"fmt"
2021-05-16 09:12:38 +00:00
"log"
2021-05-08 16:03:21 +00:00
"net/http"
"regexp"
"strings"
2021-05-17 20:08:26 +00:00
"time"
2021-05-08 16:03:21 +00:00
2021-05-21 23:13:50 +00:00
"gitlab.com/mporrato/uBrowserSync/bsync"
2021-05-08 16:03:21 +00:00
)
2021-05-17 15:31:34 +00:00
const (
apiVersion = "1.1.13"
infoMessage = "Powered by uBrowserSync"
defaultMaxSyncSize = 512000
defaultMaxSyncs = 10000
)
const (
statusOnline = 1
statusOffline = 2
statusReadOnly = 3
2021-05-17 15:31:34 +00:00
)
var (
2021-05-22 08:51:18 +00:00
config struct {
listen string
store string
maxSyncSize int
maxSyncs int
}
2021-05-21 23:13:50 +00:00
store bsync.Store
serviceStatus = statusOnline
2021-05-17 15:31:34 +00:00
sidRe = regexp.MustCompile("^[[:xdigit:]]{32}$")
2021-05-21 23:13:50 +00:00
invalidRequestError = bsync.NewSyncError(
2021-05-17 15:31:34 +00:00
"Invalid request",
"Malformed request body",
http.StatusBadRequest)
)
2021-05-16 15:46:05 +00:00
2021-05-16 12:51:39 +00:00
func sendJSON(w http.ResponseWriter, status int, data interface{}) {
2021-05-08 16:03:21 +00:00
body, err := json.Marshal(data)
if err != nil {
2021-05-16 12:51:39 +00:00
log.Printf("sendJSON(%v): json.Marshal() failed: %v", data, err)
return
2021-05-08 16:03:21 +00:00
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_, err = w.Write(body)
2021-05-16 12:51:39 +00:00
if err != nil {
log.Println("sendJSON() failed:", err)
}
2021-05-08 16:03:21 +00:00
}
2021-05-16 12:51:39 +00:00
func sendJSONOk(w http.ResponseWriter, data interface{}) {
sendJSON(w, http.StatusOK, data)
2021-05-08 16:03:21 +00:00
}
2021-05-16 12:51:39 +00:00
func sendJSONError(w http.ResponseWriter, err error) {
log.Println("ERROR: ", err)
2021-05-08 16:03:21 +00:00
switch e := err.(type) {
2021-05-21 23:13:50 +00:00
case bsync.SyncError:
2021-05-16 12:51:39 +00:00
sendJSON(w, e.StatusCode, e.Payload)
2021-05-08 16:03:21 +00:00
default:
2021-05-16 12:51:39 +00:00
sendJSON(w, http.StatusInternalServerError,
2021-05-21 23:13:50 +00:00
bsync.ErrorPayload{
2021-05-08 16:03:21 +00:00
Code: "Internal server error",
Message: e.Error()})
}
}
func info(w http.ResponseWriter, req *http.Request) {
if req.Method != "GET" || req.URL.Path != "/info" {
2021-05-21 23:13:50 +00:00
sendJSONError(w, bsync.NotImplementedError)
2021-05-08 16:03:21 +00:00
} else {
2021-05-16 09:12:38 +00:00
log.Println("info()")
2021-05-21 23:13:50 +00:00
serviceInfo := bsync.ServiceInfoResp{
2021-05-16 19:11:45 +00:00
Version: apiVersion,
Message: infoMessage,
Status: serviceStatus,
2021-05-22 08:51:18 +00:00
MaxSyncSize: config.maxSyncSize}
2021-05-16 15:46:05 +00:00
sendJSONOk(w, serviceInfo)
2021-05-08 16:03:21 +00:00
}
}
func ensureJSONRequest(w http.ResponseWriter, req *http.Request) bool {
contentType := strings.Split(strings.ToLower(req.Header["Content-Type"][0]), ";")[0]
if contentType != "application/json" {
2021-05-21 23:13:50 +00:00
sendJSONError(w, bsync.RequiredDataNotFoundError)
return false
}
return true
}
2021-05-08 16:03:21 +00:00
func createSync(w http.ResponseWriter, req *http.Request) {
if serviceStatus != statusOnline {
2021-05-21 23:13:50 +00:00
sendJSONError(w, bsync.NewSyncsForbiddenError)
return
}
if !ensureJSONRequest(w, req) {
return
}
2021-05-21 23:13:50 +00:00
body := new(bsync.CreateReq)
2021-05-17 20:08:26 +00:00
req.Body = http.MaxBytesReader(w, req.Body, 10000)
2021-05-08 16:03:21 +00:00
err := json.NewDecoder(req.Body).Decode(&body)
if err != nil {
sendJSONError(w, invalidRequestError)
return
}
2021-05-16 15:46:05 +00:00
if len(body.Version) > 20 {
sendJSONError(w, invalidRequestError)
return
}
2021-05-08 16:03:21 +00:00
resp, err := store.Create(body.Version)
if err != nil {
sendJSONError(w, err)
return
}
2021-05-22 08:51:18 +00:00
if config.maxSyncs > 0 && store.Count() >= config.maxSyncs {
serviceStatus = statusReadOnly
}
2021-05-08 16:03:21 +00:00
sendJSONOk(w, resp)
}
2021-05-16 12:51:39 +00:00
func getSync(syncId string, w http.ResponseWriter, _ *http.Request) {
2021-05-08 16:03:21 +00:00
resp, err := store.Get(syncId)
if err != nil {
sendJSONError(w, err)
return
}
sendJSONOk(w, resp)
}
2021-05-16 12:51:39 +00:00
func getLastUpdated(syncId string, w http.ResponseWriter, _ *http.Request) {
2021-05-08 16:03:21 +00:00
resp, err := store.GetLastUpdated(syncId)
if err != nil {
sendJSONError(w, err)
return
}
2021-05-21 23:13:50 +00:00
sendJSONOk(w, bsync.LastUpdatedResp{LastUpdated: resp})
2021-05-08 16:03:21 +00:00
}
2021-05-16 12:51:39 +00:00
func getVersion(syncId string, w http.ResponseWriter, _ *http.Request) {
2021-05-08 16:03:21 +00:00
resp, err := store.GetVersion(syncId)
if err != nil {
sendJSONError(w, err)
return
}
2021-05-21 23:13:50 +00:00
sendJSONOk(w, bsync.GetSyncVerResp{Version: resp})
2021-05-08 16:03:21 +00:00
}
func updateSync(syncId string, w http.ResponseWriter, req *http.Request) {
if !ensureJSONRequest(w, req) {
return
}
2021-05-21 23:13:50 +00:00
body := new(bsync.UpdateReq)
2021-05-22 08:51:18 +00:00
req.Body = http.MaxBytesReader(w, req.Body, int64(10000+config.maxSyncSize))
2021-05-08 16:03:21 +00:00
err := json.NewDecoder(req.Body).Decode(&body)
if err != nil {
sendJSONError(w, invalidRequestError)
return
}
2021-05-22 08:51:18 +00:00
if len(body.Bookmarks) > config.maxSyncSize {
2021-05-21 23:13:50 +00:00
sendJSONError(w, bsync.SyncDataLimitExceededError)
2021-05-16 15:46:05 +00:00
return
}
2021-05-08 16:03:21 +00:00
resp, err := store.Update(syncId, body.Bookmarks, body.LastUpdated)
if err != nil {
sendJSONError(w, err)
return
}
2021-05-21 23:13:50 +00:00
sendJSONOk(w, bsync.UpdateResp{LastUpdated: resp})
2021-05-08 16:03:21 +00:00
}
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 {
2021-05-21 23:13:50 +00:00
sendJSONError(w, bsync.MethodNotImplementedError)
return
}
2021-05-08 16:03:21 +00:00
}
syncId := elements[1]
if !sidRe.MatchString(syncId) {
2021-05-21 23:13:50 +00:00
sendJSONError(w, bsync.NotImplementedError)
2021-05-08 16:03:21 +00:00
return
}
if len(elements) == 2 {
if req.Method == "GET" {
2021-05-16 09:12:38 +00:00
log.Printf("getSync(%s)", syncId)
2021-05-08 16:03:21 +00:00
getSync(syncId, w, req)
return
}
if req.Method == "PUT" {
2021-05-16 09:12:38 +00:00
log.Printf("updateSync(%s)", syncId)
2021-05-08 16:03:21 +00:00
updateSync(syncId, w, req)
return
}
// TODO: Handle HEAD requests
2021-05-21 23:13:50 +00:00
sendJSONError(w, bsync.MethodNotImplementedError)
2021-05-08 16:03:21 +00:00
return
}
if len(elements) == 3 {
if elements[2] == "lastUpdated" {
if req.Method == "GET" {
2021-05-16 09:12:38 +00:00
log.Printf("getLastUpdated(%s)", syncId)
2021-05-08 16:03:21 +00:00
getLastUpdated(syncId, w, req)
return
}
2021-05-21 23:13:50 +00:00
sendJSONError(w, bsync.MethodNotImplementedError)
2021-05-08 16:03:21 +00:00
return
}
if elements[2] == "version" {
if req.Method == "GET" {
2021-05-16 09:12:38 +00:00
log.Printf("getVersion(%s)", syncId)
2021-05-08 16:03:21 +00:00
getVersion(syncId, w, req)
return
}
2021-05-21 23:13:50 +00:00
sendJSONError(w, bsync.MethodNotImplementedError)
2021-05-08 16:03:21 +00:00
return
}
}
2021-05-21 23:13:50 +00:00
sendJSONError(w, bsync.NotImplementedError)
2021-05-08 16:03:21 +00:00
}
2021-05-17 20:08:26 +00:00
func notFound(w http.ResponseWriter, _ *http.Request) {
2021-05-21 23:13:50 +00:00
sendJSONError(w, bsync.NotImplementedError)
2021-05-17 20:08:26 +00:00
}
2021-05-08 16:03:21 +00:00
func init() {
2021-05-17 15:31:34 +00:00
var (
2021-05-22 08:51:18 +00:00
err error
storeDrv bsync.StoreDriver
2021-05-17 15:31:34 +00:00
)
2021-05-08 16:03:21 +00:00
2021-05-22 08:51:18 +00:00
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")
2021-05-08 16:03:21 +00:00
flag.Parse()
2021-05-22 08:51:18 +00:00
storeFlagTokens := strings.Split(config.store, ":")
2021-05-08 16:03:21 +00:00
2021-05-16 12:51:39 +00:00
switch storeFlagTokens[0] {
2021-05-08 16:03:21 +00:00
case "fs":
2021-05-16 12:51:39 +00:00
if len(storeFlagTokens) != 2 {
2021-05-08 16:03:21 +00:00
err = fmt.Errorf("argument required for fs store driver")
} else {
2021-05-21 23:13:50 +00:00
storeDrv, err = bsync.NewFSStore(storeFlagTokens[1])
2021-05-08 16:03:21 +00:00
}
case "mem":
2021-05-21 23:13:50 +00:00
storeDrv, err = bsync.NewMemStore()
2021-05-08 16:03:21 +00:00
default:
2021-05-16 19:11:45 +00:00
err = fmt.Errorf("Invalid store driver: " + storeFlagTokens[0])
2021-05-08 16:03:21 +00:00
}
if err != nil {
2021-05-16 09:12:38 +00:00
log.Fatalf("store initialization failed: %v", err)
2021-05-08 16:03:21 +00:00
}
2021-05-21 23:13:50 +00:00
store = bsync.NewStore(storeDrv)
2021-05-08 16:03:21 +00:00
}
func main() {
2021-05-17 20:08:26 +00:00
mux := http.NewServeMux()
mux.HandleFunc("/", notFound)
mux.HandleFunc("/info", info)
mux.HandleFunc("/info/", info)
mux.HandleFunc("/bookmarks", bookmarks)
mux.HandleFunc("/bookmarks/", bookmarks)
2021-05-08 16:03:21 +00:00
2021-05-22 08:51:18 +00:00
log.Println("HTTP server listening on", config.listen)
2021-05-17 20:08:26 +00:00
server := &http.Server{
2021-05-22 08:51:18 +00:00
Addr: config.listen,
Handler: mux,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
2021-05-17 20:08:26 +00:00
MaxHeaderBytes: 5000}
err := server.ListenAndServe()
2021-05-16 09:12:38 +00:00
log.Println("HTTP server terminated", err)
2021-05-08 16:03:21 +00:00
}