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) }