Something went wrong. Try again.
An ATProto Powered Reputation Network
Something went wrong. Try again.
2.8 kB · 87 lines
Go
at master
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788package httpx
import ( "context" "encoding/json" "errors" "io" "log/slog" "net/http" "os" "os/signal" "syscall" "time")
const ( MaxBodyBytes = 16 << 10 ReadTimeout = 10 * time.Second WriteTimeout = 2 * time.Minute IdleTimeout = time.Minute ShutdownTimeout = 10 * time.Second)
func JSON(w http.ResponseWriter, status int, value any) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Cache-Control", "no-store") w.WriteHeader(status) if err := json.NewEncoder(w).Encode(value); err != nil { slog.Warn("response encode failed", "error", err) }}
func Error(w http.ResponseWriter, status int, message string) { JSON(w, status, map[string]string{"error": message})}
func Decode(w http.ResponseWriter, r *http.Request, value any) error { r.Body = http.MaxBytesReader(w, r.Body, MaxBodyBytes) decoder := json.NewDecoder(r.Body) decoder.DisallowUnknownFields() if err := decoder.Decode(value); err != nil { return err } if err := decoder.Decode(new(any)); err != io.EOF { return errors.New("request must contain one JSON value") } return nil}
func Headers(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { started := time.Now() logger := slog.Default().With("component", "http", "method", r.Method, "path", r.URL.Path) logger.Debug("HTTP request started") defer func() { logger.Debug("HTTP request finished", "duration", time.Since(started)) }() w.Header().Set("X-Content-Type-Options", "nosniff") // Preserve form origins without sending referrers to other origins. w.Header().Set("Referrer-Policy", "same-origin") w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'") next.ServeHTTP(w, r) })}
func Context() (context.Context, context.CancelFunc) { return signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)}
func Serve(ctx context.Context, address string, handler http.Handler) error { server := &http.Server{Addr: address, Handler: Headers(handler), ReadHeaderTimeout: ReadTimeout, ReadTimeout: ReadTimeout, WriteTimeout: WriteTimeout, IdleTimeout: IdleTimeout, MaxHeaderBytes: MaxBodyBytes} go func() { <-ctx.Done() slog.Info("HTTP shutdown started", "address", address) shutdown, cancel := context.WithTimeout(context.Background(), ShutdownTimeout) defer cancel() if err := server.Shutdown(shutdown); err != nil { slog.Error("shutdown failed", "error", err) server.Close() } }() slog.Info("HTTP server starts", "address", address) err := server.ListenAndServe() if errors.Is(err, http.ErrServerClosed) { slog.Info("HTTP server stopped", "address", address) return nil } return err}