middleware.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. package httpserver
  2. import (
  3. "compress/gzip"
  4. "io"
  5. "log"
  6. "net/http"
  7. "strings"
  8. "sync"
  9. "time"
  10. )
  11. // gzipPool is a pool of gzip writers to reduce allocations.
  12. var gzipPool = sync.Pool{
  13. New: func() interface{} {
  14. return gzip.NewWriter(io.Discard)
  15. },
  16. }
  17. // gzipResponseWriter wraps http.ResponseWriter to provide gzip compression.
  18. type gzipResponseWriter struct {
  19. http.ResponseWriter
  20. writer *gzip.Writer
  21. }
  22. func (g *gzipResponseWriter) Write(data []byte) (int, error) {
  23. return g.writer.Write(data)
  24. }
  25. // compressionMiddleware adds gzip compression for responses.
  26. func (s *Server) compressionMiddleware(next http.Handler) http.Handler {
  27. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  28. // Check if client accepts gzip
  29. if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
  30. next.ServeHTTP(w, r)
  31. return
  32. }
  33. // Get gzip writer from pool
  34. gz := gzipPool.Get().(*gzip.Writer)
  35. gz.Reset(w)
  36. defer func() {
  37. gz.Close()
  38. gzipPool.Put(gz)
  39. }()
  40. // Set headers
  41. w.Header().Set("Content-Encoding", "gzip")
  42. w.Header().Del("Content-Length") // Length changes with compression
  43. // Wrap response writer
  44. gzw := &gzipResponseWriter{ResponseWriter: w, writer: gz}
  45. next.ServeHTTP(gzw, r)
  46. })
  47. }
  48. // loggingMiddleware logs HTTP requests.
  49. func (s *Server) loggingMiddleware(next http.Handler) http.Handler {
  50. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  51. start := time.Now()
  52. // Wrap response writer to capture status code
  53. lw := &loggingResponseWriter{ResponseWriter: w, statusCode: http.StatusOK}
  54. next.ServeHTTP(lw, r)
  55. duration := time.Since(start)
  56. log.Printf("%s %s %d %s", r.Method, r.URL.Path, lw.statusCode, duration)
  57. })
  58. }
  59. // loggingResponseWriter wraps http.ResponseWriter to capture status code.
  60. type loggingResponseWriter struct {
  61. http.ResponseWriter
  62. statusCode int
  63. }
  64. func (lw *loggingResponseWriter) WriteHeader(code int) {
  65. lw.statusCode = code
  66. lw.ResponseWriter.WriteHeader(code)
  67. }
  68. // corsMiddleware adds CORS headers.
  69. func (s *Server) corsMiddleware(next http.Handler) http.Handler {
  70. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  71. w.Header().Set("Access-Control-Allow-Origin", "*")
  72. w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
  73. w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Database")
  74. // Handle preflight
  75. if r.Method == http.MethodOptions {
  76. w.WriteHeader(http.StatusOK)
  77. return
  78. }
  79. next.ServeHTTP(w, r)
  80. })
  81. }
  82. // authMiddleware validates API keys.
  83. func (s *Server) authMiddleware(next http.Handler) http.Handler {
  84. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  85. // Skip auth for health check
  86. if r.URL.Path == "/health" {
  87. next.ServeHTTP(w, r)
  88. return
  89. }
  90. // Check Authorization header
  91. auth := r.Header.Get("Authorization")
  92. if auth == "" {
  93. writeError(w, http.StatusUnauthorized, "MISSING_AUTH", "Authorization header is required", nil)
  94. return
  95. }
  96. // Simple bearer token validation
  97. var token string
  98. if len(auth) > 7 && auth[:7] == "Bearer " {
  99. token = auth[7:]
  100. } else {
  101. writeError(w, http.StatusUnauthorized, "INVALID_AUTH", "Invalid authorization format", nil)
  102. return
  103. }
  104. // Validate token against API keys
  105. valid := false
  106. for _, key := range s.config.APIKeys {
  107. if token == key {
  108. valid = true
  109. break
  110. }
  111. }
  112. if !valid {
  113. writeError(w, http.StatusForbidden, "INVALID_API_KEY", "Invalid API key", nil)
  114. return
  115. }
  116. next.ServeHTTP(w, r)
  117. })
  118. }