package view import ( "encoding/json" "errors" "fmt" "io" "local/dndex/config" "local/dndex/storage" "local/gziphttp" "log" "net/http" "strings" "golang.org/x/time/rate" ) func JSON(g storage.Graph) error { port := config.New().Port log.Println("listening on", port) err := http.ListenAndServe(fmt.Sprintf(":%d", port), jsonHandler(g)) return err } func jsonHandler(g storage.Graph) http.Handler { mux := http.NewServeMux() routes := []struct { path string foo func(g storage.Graph, w http.ResponseWriter, r *http.Request) error noauth bool }{ { path: "/port", foo: port, }, { path: "/who", foo: who, }, { path: "/register", foo: register, noauth: true, }, { path: config.New().FilePrefix + "/", foo: files, }, } for _, route := range routes { foo := route.foo auth := !route.noauth mux.HandleFunc(route.path, func(w http.ResponseWriter, r *http.Request) { if auth { if err := Auth(g, w, r); err != nil { return } } if err := foo(g, w, r); err != nil { status := http.StatusInternalServerError if strings.Contains(err.Error(), "collision") { status = http.StatusConflict } b, _ := json.Marshal(map[string]string{"error": err.Error()}) http.Error(w, string(b), status) } }) } return rateLimited(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if gziphttp.Can(r) { gz := gziphttp.New(w) defer gz.Close() w = gz } r.Body = struct { io.Reader io.Closer }{ Reader: io.LimitReader(r.Body, config.New().MaxFileSize), Closer: r.Body, } mux.ServeHTTP(w, r) })) } func rateLimited(foo http.HandlerFunc) http.HandlerFunc { sysRPS := config.New().SysRPS limiter := rate.NewLimiter(rate.Limit(sysRPS), sysRPS) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := limiter.Wait(r.Context()); err != nil { http.Error(w, err.Error(), http.StatusTooManyRequests) } foo(w, r) }) } func getAuthNamespace(r *http.Request) (string, error) { namespace, err := getNamespace(r) return strings.Join([]string{namespace, AuthKey}, "."), err } func getNamespace(r *http.Request) (string, error) { if strings.HasPrefix(r.URL.Path, config.New().FilePrefix) { path := strings.TrimPrefix(r.URL.Path, config.New().FilePrefix+"/") if path == r.URL.Path { return "", errors.New("no namespace on files") } path = strings.Split(path, "/")[0] if path == "" { return "", errors.New("empty namespace on files") } return path, nil } namespace := r.URL.Query().Get("namespace") if len(namespace) == 0 { return "", errors.New("no namespace found") } return namespace, nil }