package view import ( "encoding/json" "errors" "fmt" "io" "local/dndex/config" "local/dndex/storage" "local/gziphttp" "log" "net/http" "strings" "time" "golang.org/x/time/rate" ) var GitCommit string func JSON(g storage.RateLimitedGraph) 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.RateLimitedGraph) http.Handler { mux := http.NewServeMux() routes := []struct { path string foo func(g storage.RateLimitedGraph, 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, }, { path: "/version", foo: version, noauth: true, }, } for _, route := range routes { foo := route.foo auth := !route.noauth mux.HandleFunc(route.path, func(w http.ResponseWriter, r *http.Request) { if err := delay(w, r); err != nil { http.Error(w, err.Error(), 499) } 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 } func version(_ storage.RateLimitedGraph, w http.ResponseWriter, _ *http.Request) error { enc := json.NewEncoder(w) enc.SetIndent("", " ") return enc.Encode(map[string]string{"version": GitCommit}) } func delay(w http.ResponseWriter, r *http.Request) error { select { case <-time.After(config.New().Delay): case <-r.Context().Done(): return errors.New("client DCd") } return nil }