Files
dicom-iso/internal/route/route.go
2026-06-05 08:11:44 +07:00

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())
}