123 lines
3.6 KiB
Go
123 lines
3.6 KiB
Go
package route
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"runtime/debug"
|
|
"time"
|
|
|
|
"mkiso-server/internal/config"
|
|
"mkiso-server/internal/handler"
|
|
"mkiso-server/internal/middleware"
|
|
"mkiso-server/internal/service"
|
|
)
|
|
|
|
// Setup creates the HTTP handler with all routes wired.
|
|
// Stage 1: no auth.
|
|
func Setup(cfg *config.Config, isoSvc *service.ISOService, relaySvc *service.RelayService) http.Handler {
|
|
mux := http.NewServeMux()
|
|
|
|
// Health check — always public
|
|
mux.HandleFunc("GET /api/health", handler.Health(cfg))
|
|
|
|
// ISO download endpoints
|
|
mux.HandleFunc("GET /api/iso/download", handler.DownloadSingle(isoSvc))
|
|
mux.HandleFunc("GET /api/iso/download-multiple", handler.DownloadMultiple(isoSvc))
|
|
|
|
// Print/relay endpoint (single endpoint handles both single and multi via comma detection)
|
|
mux.HandleFunc("GET /api/iso/print", handler.PrintISO(relaySvc))
|
|
|
|
// Wrap with middleware chain: Recovery → RequestID → Logging
|
|
return middleware.Chain(
|
|
mux,
|
|
recoveryMiddleware,
|
|
requestIDMiddleware,
|
|
loggingMiddleware,
|
|
)
|
|
}
|
|
|
|
// SetupSecure creates the HTTP handler with API key authentication on /api/iso/*.
|
|
// Stage 2: auth enabled.
|
|
func SetupSecure(cfg *config.Config, isoSvc *service.ISOService, relaySvc *service.RelayService) http.Handler {
|
|
mux := http.NewServeMux()
|
|
|
|
// Health check — always public
|
|
mux.HandleFunc("GET /api/health", handler.Health(cfg))
|
|
|
|
// Protected routes
|
|
apiKeyMw := middleware.APIKey(cfg.Auth.APIKey)
|
|
mux.Handle("GET /api/iso/download", apiKeyMw(http.HandlerFunc(handler.DownloadSingle(isoSvc))))
|
|
mux.Handle("GET /api/iso/download-multiple", apiKeyMw(http.HandlerFunc(handler.DownloadMultiple(isoSvc))))
|
|
mux.Handle("GET /api/iso/print", apiKeyMw(http.HandlerFunc(handler.PrintISO(relaySvc))))
|
|
|
|
return middleware.Chain(
|
|
mux,
|
|
recoveryMiddleware,
|
|
requestIDMiddleware,
|
|
loggingMiddleware,
|
|
)
|
|
}
|
|
|
|
// recoveryMiddleware catches panics, logs the stack trace, and returns 500.
|
|
func recoveryMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
defer func() {
|
|
if rec := recover(); rec != nil {
|
|
slog.Error("handler panic",
|
|
"method", r.Method,
|
|
"path", r.URL.Path,
|
|
"panic", rec,
|
|
"stack", string(debug.Stack()),
|
|
)
|
|
http.Error(w, `{"error":"internal server error"}`, http.StatusInternalServerError)
|
|
}
|
|
}()
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
// requestIDMiddleware adds a unique request ID to each request context.
|
|
func requestIDMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
rid := r.Header.Get("X-Request-ID")
|
|
if rid == "" {
|
|
rid = generateRequestID()
|
|
}
|
|
w.Header().Set("X-Request-ID", rid)
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
// loggingMiddleware logs each request with method, path, status, and duration.
|
|
func loggingMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
start := time.Now()
|
|
lrw := &loggingResponseWriter{ResponseWriter: w, statusCode: http.StatusOK}
|
|
next.ServeHTTP(lrw, r)
|
|
slog.Info("request",
|
|
"method", r.Method,
|
|
"path", r.URL.Path,
|
|
"query", r.URL.RawQuery,
|
|
"status", lrw.statusCode,
|
|
"duration_ms", time.Since(start).Milliseconds(),
|
|
)
|
|
})
|
|
}
|
|
|
|
// loggingResponseWriter wraps http.ResponseWriter to capture the status code.
|
|
type loggingResponseWriter struct {
|
|
http.ResponseWriter
|
|
statusCode int
|
|
}
|
|
|
|
func (lrw *loggingResponseWriter) WriteHeader(code int) {
|
|
lrw.statusCode = code
|
|
lrw.ResponseWriter.WriteHeader(code)
|
|
}
|
|
|
|
// generateRequestID returns a simple unique request ID.
|
|
func generateRequestID() string {
|
|
return fmt.Sprintf("req-%d", time.Now().UnixNano())
|
|
}
|