-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.go
More file actions
139 lines (114 loc) · 3.48 KB
/
Copy pathserver.go
File metadata and controls
139 lines (114 loc) · 3.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package vercelreceiver
import (
"bytes"
"context"
"io"
"net/http"
"sync"
"go.uber.org/zap"
)
// httpServer manages the HTTP server for receiving Vercel drain data
type httpServer struct {
logger *zap.Logger
cfg *Config
// Handlers for each drain type
logsHandler http.HandlerFunc
tracesHandler http.HandlerFunc
speedInsightsHandler http.HandlerFunc
analyticsHandler http.HandlerFunc
httpServer *http.Server
mux *http.ServeMux
mu sync.Mutex
}
// newHTTPServer creates a new HTTP server instance
func newHTTPServer(cfg *Config, logger *zap.Logger) *httpServer {
s := &httpServer{
logger: logger,
cfg: cfg,
mux: http.NewServeMux(),
}
return s
}
// registerHandlers registers all HTTP handlers for the server
func (s *httpServer) registerHandlers() {
// Logs endpoint
if s.logsHandler != nil {
s.mux.HandleFunc(s.cfg.Logs.Route, s.withSignatureAuth(s.cfg.GetLogsSecret(), s.logsHandler))
}
// Traces endpoint
if s.tracesHandler != nil {
s.mux.HandleFunc(s.cfg.Traces.Route, s.withSignatureAuth(s.cfg.GetTracesSecret(), s.tracesHandler))
}
// Speed Insights endpoint
if s.speedInsightsHandler != nil {
s.mux.HandleFunc(s.cfg.SpeedInsights.Route, s.withSignatureAuth(s.cfg.GetSpeedInsightsSecret(), s.speedInsightsHandler))
}
// Web Analytics endpoint
if s.analyticsHandler != nil {
s.mux.HandleFunc(s.cfg.WebAnalytics.Route, s.withSignatureAuth(s.cfg.GetWebAnalyticsSecret(), s.analyticsHandler))
}
// Health check endpoint
s.mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("OK")) // intentionally ignore error for simple health check
})
}
// withSignatureAuth wraps a handler with signature verification middleware
func (s *httpServer) withSignatureAuth(secret string, handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Read body once
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
s.logger.Error("Failed to read request body", zap.Error(err))
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
// Close body and ignore error (intentionally)
_ = r.Body.Close()
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
// Verify signature using global algorithm from config
algorithm := s.cfg.GetSignatureAlgorithm()
if err := verifyRequest(r, secret, bodyBytes, algorithm); err != nil {
s.logger.Warn("Signature verification failed", zap.Error(err))
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
handler(w, r)
}
}
// start starts the HTTP server
func (s *httpServer) start() error {
s.mu.Lock()
defer s.mu.Unlock()
if s.httpServer != nil {
// Server already started
return nil
}
s.registerHandlers()
// Use configured endpoint or default to :8080
listenAddr := s.cfg.Endpoint
if listenAddr == "" {
listenAddr = ":8080"
}
s.httpServer = &http.Server{
Addr: listenAddr,
Handler: s.mux,
}
go func() {
if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
s.logger.Error("HTTP server failed", zap.Error(err))
}
}()
s.logger.Info("HTTP server started", zap.String("address", listenAddr))
return nil
}
// shutdown gracefully shuts down the HTTP server
func (s *httpServer) shutdown(ctx context.Context) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.httpServer == nil {
return nil
}
s.logger.Info("Shutting down HTTP server")
return s.httpServer.Shutdown(ctx)
}