Spaces:
Sleeping
Sleeping
| package api | |
| import ( | |
| "log/slog" | |
| "net/http" | |
| "time" | |
| "gpsbackend/internal/config" | |
| ) | |
| // withMiddleware wraps the mux with panic recovery, request logging and CORS. | |
| // Order matters: recovery is outermost so it catches panics from everything. | |
| func withMiddleware(next http.Handler, cfg config.Config) http.Handler { | |
| return recoverMiddleware(logMiddleware(corsMiddleware(next, cfg))) | |
| } | |
| // recoverMiddleware turns a handler panic into a 500 instead of crashing the | |
| // process or dropping the connection. | |
| func recoverMiddleware(next http.Handler) http.Handler { | |
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| defer func() { | |
| if rec := recover(); rec != nil { | |
| slog.Error("panic recovered", "err", rec, "path", r.URL.Path) | |
| http.Error(w, "internal server error", http.StatusInternalServerError) | |
| } | |
| }() | |
| next.ServeHTTP(w, r) | |
| }) | |
| } | |
| // statusRecorder captures the response status code for logging. | |
| type statusRecorder struct { | |
| http.ResponseWriter | |
| status int | |
| } | |
| func (r *statusRecorder) WriteHeader(code int) { | |
| r.status = code | |
| r.ResponseWriter.WriteHeader(code) | |
| } | |
| // Unwrap exposes the underlying ResponseWriter so the WebSocket library can | |
| // hijack the connection through http.ResponseController. | |
| func (r *statusRecorder) Unwrap() http.ResponseWriter { return r.ResponseWriter } | |
| func logMiddleware(next http.Handler) http.Handler { | |
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| start := time.Now() | |
| rec := &statusRecorder{ResponseWriter: w, status: http.StatusOK} | |
| next.ServeHTTP(rec, r) | |
| slog.Info("request", | |
| "method", r.Method, | |
| "path", r.URL.Path, | |
| "status", rec.status, | |
| "dur", time.Since(start).Round(time.Millisecond).String(), | |
| ) | |
| }) | |
| } | |
| // corsMiddleware adds permissive CORS headers so the dashboard (or any browser | |
| // tool) can call the API. Native Android HTTP clients ignore CORS, so this is | |
| // harmless for the device ingest path. | |
| func corsMiddleware(next http.Handler, cfg config.Config) http.Handler { | |
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| w.Header().Set("Access-Control-Allow-Origin", cfg.AllowedOrigin) | |
| w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") | |
| w.Header().Set("Access-Control-Allow-Headers", "Content-Type") | |
| if r.Method == http.MethodOptions { | |
| w.WriteHeader(http.StatusNoContent) | |
| return | |
| } | |
| next.ServeHTTP(w, r) | |
| }) | |
| } | |