@marcel91200 / caddy Mirror · read-only
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 01:00 UTC

master
go 631 lines 33.6 KB
Raw
1 package caddyhttp
2
3 import (
4 "context"
5 "crypto/tls"
6 "errors"
7 "net/http"
8 "net/http/httptest"
9 "strings"
10 "sync"
11 "testing"
12
13 "github.com/prometheus/client_golang/prometheus/testutil"
14
15 "github.com/caddyserver/caddy/v2"
16 )
17
18 func TestServerNameFromContext(t *testing.T) {
19 ctx := context.Background()
20 expected := "UNKNOWN"
21 if actual := serverNameFromContext(ctx); actual != expected {
22 t.Errorf("Not equal: expected %q, but got %q", expected, actual)
23 }
24
25 in := "foo"
26 ctx = context.WithValue(ctx, ServerCtxKey, &Server{name: in})
27 if actual := serverNameFromContext(ctx); actual != in {
28 t.Errorf("Not equal: expected %q, but got %q", in, actual)
29 }
30 }
31
32 func TestMetricsInstrumentedHandler(t *testing.T) {
33 ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()})
34 metrics := &Metrics{
35 init: sync.Once{},
36 httpMetrics: &httpMetrics{},
37 }
38 handlerErr := errors.New("oh noes")
39 response := []byte("hello world!")
40 h := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
41 if actual := testutil.ToFloat64(metrics.httpMetrics.requestInFlight); actual != 1.0 {
42 t.Errorf("Not same: expected %#v, but got %#v", 1.0, actual)
43 }
44 if handlerErr == nil {
45 w.Write(response)
46 }
47 return handlerErr
48 })
49
50 ih := newMetricsInstrumentedRoute(ctx, "bar", h, metrics)
51
52 r := httptest.NewRequest("GET", "/", nil)
53 w := httptest.NewRecorder()
54
55 if actual := ih.ServeHTTP(w, r); actual != handlerErr {
56 t.Errorf("Not same: expected %#v, but got %#v", handlerErr, actual)
57 }
58 if actual := testutil.ToFloat64(metrics.httpMetrics.requestInFlight); actual != 0.0 {
59 t.Errorf("Not same: expected %#v, but got %#v", 0.0, actual)
60 }
61
62 handlerErr = nil
63 if err := ih.ServeHTTP(w, r); err != nil {
64 t.Errorf("Received unexpected error: %v", err)
65 }
66
67 // an empty handler - no errors, no header written
68 emptyHandler := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
69 return nil
70 })
71 ih = newMetricsInstrumentedRoute(ctx, "empty", emptyHandler, metrics)
72 r = httptest.NewRequest("GET", "/", nil)
73 w = httptest.NewRecorder()
74
75 if err := ih.ServeHTTP(w, r); err != nil {
76 t.Errorf("Received unexpected error: %v", err)
77 }
78 if actual := w.Result().StatusCode; actual != 200 {
79 t.Errorf("Not same: expected status code %#v, but got %#v", 200, actual)
80 }
81 if actual := w.Result().Header; len(actual) != 0 {
82 t.Errorf("Not empty: expected headers to be empty, but got %#v", actual)
83 }
84
85 // handler returning an error with an HTTP status
86 errHandler := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
87 return Error(http.StatusTooManyRequests, nil)
88 })
89
90 ih = newMetricsInstrumentedRoute(ctx, "foo", errHandler, metrics)
91
92 r = httptest.NewRequest("GET", "/", nil)
93 w = httptest.NewRecorder()
94
95 if err := ih.ServeHTTP(w, r); err == nil {
96 t.Errorf("expected error to be propagated")
97 }
98
99 expected := `
100 # HELP caddy_http_request_duration_seconds Histogram of round-trip request durations.
101 # TYPE caddy_http_request_duration_seconds histogram
102 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="0.005"} 1
103 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="0.01"} 1
104 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="0.025"} 1
105 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="0.05"} 1
106 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="0.1"} 1
107 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="0.25"} 1
108 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="0.5"} 1
109 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="1"} 1
110 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="2.5"} 1
111 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="5"} 1
112 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="10"} 1
113 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="+Inf"} 1
114 caddy_http_request_duration_seconds_count{code="429",handler="foo",method="GET",server="UNKNOWN"} 1
115 # HELP caddy_http_request_size_bytes Total size of the request. Includes body
116 # TYPE caddy_http_request_size_bytes histogram
117 caddy_http_request_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="256"} 1
118 caddy_http_request_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="1024"} 1
119 caddy_http_request_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="4096"} 1
120 caddy_http_request_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="16384"} 1
121 caddy_http_request_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="65536"} 1
122 caddy_http_request_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="262144"} 1
123 caddy_http_request_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="1.048576e+06"} 1
124 caddy_http_request_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="4.194304e+06"} 1
125 caddy_http_request_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="+Inf"} 1
126 caddy_http_request_size_bytes_sum{code="200",handler="bar",method="GET",server="UNKNOWN"} 23
127 caddy_http_request_size_bytes_count{code="200",handler="bar",method="GET",server="UNKNOWN"} 1
128 caddy_http_request_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="256"} 1
129 caddy_http_request_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="1024"} 1
130 caddy_http_request_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="4096"} 1
131 caddy_http_request_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="16384"} 1
132 caddy_http_request_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="65536"} 1
133 caddy_http_request_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="262144"} 1
134 caddy_http_request_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="1.048576e+06"} 1
135 caddy_http_request_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="4.194304e+06"} 1
136 caddy_http_request_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="+Inf"} 1
137 caddy_http_request_size_bytes_sum{code="200",handler="empty",method="GET",server="UNKNOWN"} 23
138 caddy_http_request_size_bytes_count{code="200",handler="empty",method="GET",server="UNKNOWN"} 1
139 caddy_http_request_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="256"} 1
140 caddy_http_request_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="1024"} 1
141 caddy_http_request_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="4096"} 1
142 caddy_http_request_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="16384"} 1
143 caddy_http_request_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="65536"} 1
144 caddy_http_request_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="262144"} 1
145 caddy_http_request_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="1.048576e+06"} 1
146 caddy_http_request_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="4.194304e+06"} 1
147 caddy_http_request_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="+Inf"} 1
148 caddy_http_request_size_bytes_sum{code="429",handler="foo",method="GET",server="UNKNOWN"} 23
149 caddy_http_request_size_bytes_count{code="429",handler="foo",method="GET",server="UNKNOWN"} 1
150 # HELP caddy_http_response_size_bytes Size of the returned response.
151 # TYPE caddy_http_response_size_bytes histogram
152 caddy_http_response_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="256"} 1
153 caddy_http_response_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="1024"} 1
154 caddy_http_response_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="4096"} 1
155 caddy_http_response_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="16384"} 1
156 caddy_http_response_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="65536"} 1
157 caddy_http_response_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="262144"} 1
158 caddy_http_response_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="1.048576e+06"} 1
159 caddy_http_response_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="4.194304e+06"} 1
160 caddy_http_response_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="+Inf"} 1
161 caddy_http_response_size_bytes_sum{code="200",handler="bar",method="GET",server="UNKNOWN"} 12
162 caddy_http_response_size_bytes_count{code="200",handler="bar",method="GET",server="UNKNOWN"} 1
163 caddy_http_response_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="256"} 1
164 caddy_http_response_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="1024"} 1
165 caddy_http_response_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="4096"} 1
166 caddy_http_response_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="16384"} 1
167 caddy_http_response_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="65536"} 1
168 caddy_http_response_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="262144"} 1
169 caddy_http_response_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="1.048576e+06"} 1
170 caddy_http_response_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="4.194304e+06"} 1
171 caddy_http_response_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="+Inf"} 1
172 caddy_http_response_size_bytes_sum{code="200",handler="empty",method="GET",server="UNKNOWN"} 0
173 caddy_http_response_size_bytes_count{code="200",handler="empty",method="GET",server="UNKNOWN"} 1
174 caddy_http_response_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="256"} 1
175 caddy_http_response_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="1024"} 1
176 caddy_http_response_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="4096"} 1
177 caddy_http_response_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="16384"} 1
178 caddy_http_response_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="65536"} 1
179 caddy_http_response_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="262144"} 1
180 caddy_http_response_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="1.048576e+06"} 1
181 caddy_http_response_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="4.194304e+06"} 1
182 caddy_http_response_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="+Inf"} 1
183 caddy_http_response_size_bytes_sum{code="429",handler="foo",method="GET",server="UNKNOWN"} 0
184 caddy_http_response_size_bytes_count{code="429",handler="foo",method="GET",server="UNKNOWN"} 1
185 # HELP caddy_http_request_errors_total Number of requests resulting in middleware errors.
186 # TYPE caddy_http_request_errors_total counter
187 caddy_http_request_errors_total{handler="bar",server="UNKNOWN"} 1
188 caddy_http_request_errors_total{handler="foo",server="UNKNOWN"} 1
189 `
190 if err := testutil.GatherAndCompare(ctx.GetMetricsRegistry(), strings.NewReader(expected),
191 "caddy_http_request_size_bytes",
192 "caddy_http_response_size_bytes",
193 // caddy_http_request_duration_seconds_sum will vary based on how long the test took to run,
194 // so we check just the _bucket and _count metrics
195 "caddy_http_request_duration_seconds_bucket",
196 "caddy_http_request_duration_seconds_count",
197 "caddy_http_request_errors_total",
198 ); err != nil {
199 t.Errorf("received unexpected error: %s", err)
200 }
201 }
202
203 func TestMetricsInstrumentedHandlerPerHost(t *testing.T) {
204 ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()})
205 metrics := &Metrics{
206 PerHost: true,
207 ObserveCatchallHosts: true, // Allow all hosts for testing
208 init: sync.Once{},
209 httpMetrics: &httpMetrics{},
210 allowedHosts: make(map[string]struct{}),
211 }
212 handlerErr := errors.New("oh noes")
213 response := []byte("hello world!")
214 h := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
215 if actual := testutil.ToFloat64(metrics.httpMetrics.requestInFlight); actual != 1.0 {
216 t.Errorf("Not same: expected %#v, but got %#v", 1.0, actual)
217 }
218 if handlerErr == nil {
219 w.Write(response)
220 }
221 return handlerErr
222 })
223
224 ih := newMetricsInstrumentedRoute(ctx, "bar", h, metrics)
225
226 r := httptest.NewRequest("GET", "/", nil)
227 w := httptest.NewRecorder()
228
229 if actual := ih.ServeHTTP(w, r); actual != handlerErr {
230 t.Errorf("Not same: expected %#v, but got %#v", handlerErr, actual)
231 }
232 if actual := testutil.ToFloat64(metrics.httpMetrics.requestInFlight); actual != 0.0 {
233 t.Errorf("Not same: expected %#v, but got %#v", 0.0, actual)
234 }
235
236 handlerErr = nil
237 if err := ih.ServeHTTP(w, r); err != nil {
238 t.Errorf("Received unexpected error: %v", err)
239 }
240
241 // an empty handler - no errors, no header written
242 emptyHandler := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
243 return nil
244 })
245 ih = newMetricsInstrumentedRoute(ctx, "empty", emptyHandler, metrics)
246 r = httptest.NewRequest("GET", "/", nil)
247 w = httptest.NewRecorder()
248
249 if err := ih.ServeHTTP(w, r); err != nil {
250 t.Errorf("Received unexpected error: %v", err)
251 }
252 if actual := w.Result().StatusCode; actual != 200 {
253 t.Errorf("Not same: expected status code %#v, but got %#v", 200, actual)
254 }
255 if actual := w.Result().Header; len(actual) != 0 {
256 t.Errorf("Not empty: expected headers to be empty, but got %#v", actual)
257 }
258
259 // handler returning an error with an HTTP status
260 errHandler := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
261 return Error(http.StatusTooManyRequests, nil)
262 })
263
264 ih = newMetricsInstrumentedRoute(ctx, "foo", errHandler, metrics)
265
266 r = httptest.NewRequest("GET", "/", nil)
267 w = httptest.NewRecorder()
268
269 if err := ih.ServeHTTP(w, r); err == nil {
270 t.Errorf("expected error to be propagated")
271 }
272
273 expected := `
274 # HELP caddy_http_request_duration_seconds Histogram of round-trip request durations.
275 # TYPE caddy_http_request_duration_seconds histogram
276 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.005"} 1
277 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.01"} 1
278 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.025"} 1
279 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.05"} 1
280 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.1"} 1
281 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.25"} 1
282 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.5"} 1
283 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="1"} 1
284 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="2.5"} 1
285 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="5"} 1
286 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="10"} 1
287 caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1
288 caddy_http_request_duration_seconds_count{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN"} 1
289 # HELP caddy_http_request_size_bytes Total size of the request. Includes body
290 # TYPE caddy_http_request_size_bytes histogram
291 caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="256"} 1
292 caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="1024"} 1
293 caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="4096"} 1
294 caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="16384"} 1
295 caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="65536"} 1
296 caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="262144"} 1
297 caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="1.048576e+06"} 1
298 caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="4.194304e+06"} 1
299 caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1
300 caddy_http_request_size_bytes_sum{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN"} 23
301 caddy_http_request_size_bytes_count{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN"} 1
302 caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="256"} 1
303 caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="1024"} 1
304 caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="4096"} 1
305 caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="16384"} 1
306 caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="65536"} 1
307 caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="262144"} 1
308 caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="1.048576e+06"} 1
309 caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="4.194304e+06"} 1
310 caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1
311 caddy_http_request_size_bytes_sum{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN"} 23
312 caddy_http_request_size_bytes_count{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN"} 1
313 caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="256"} 1
314 caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="1024"} 1
315 caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="4096"} 1
316 caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="16384"} 1
317 caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="65536"} 1
318 caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="262144"} 1
319 caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="1.048576e+06"} 1
320 caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="4.194304e+06"} 1
321 caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1
322 caddy_http_request_size_bytes_sum{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN"} 23
323 caddy_http_request_size_bytes_count{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN"} 1
324 # HELP caddy_http_response_size_bytes Size of the returned response.
325 # TYPE caddy_http_response_size_bytes histogram
326 caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="256"} 1
327 caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="1024"} 1
328 caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="4096"} 1
329 caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="16384"} 1
330 caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="65536"} 1
331 caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="262144"} 1
332 caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="1.048576e+06"} 1
333 caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="4.194304e+06"} 1
334 caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1
335 caddy_http_response_size_bytes_sum{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN"} 12
336 caddy_http_response_size_bytes_count{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN"} 1
337 caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="256"} 1
338 caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="1024"} 1
339 caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="4096"} 1
340 caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="16384"} 1
341 caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="65536"} 1
342 caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="262144"} 1
343 caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="1.048576e+06"} 1
344 caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="4.194304e+06"} 1
345 caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1
346 caddy_http_response_size_bytes_sum{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN"} 0
347 caddy_http_response_size_bytes_count{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN"} 1
348 caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="256"} 1
349 caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="1024"} 1
350 caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="4096"} 1
351 caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="16384"} 1
352 caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="65536"} 1
353 caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="262144"} 1
354 caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="1.048576e+06"} 1
355 caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="4.194304e+06"} 1
356 caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1
357 caddy_http_response_size_bytes_sum{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN"} 0
358 caddy_http_response_size_bytes_count{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN"} 1
359 # HELP caddy_http_request_errors_total Number of requests resulting in middleware errors.
360 # TYPE caddy_http_request_errors_total counter
361 caddy_http_request_errors_total{handler="bar",host="example.com",server="UNKNOWN"} 1
362 caddy_http_request_errors_total{handler="foo",host="example.com",server="UNKNOWN"} 1
363 `
364 if err := testutil.GatherAndCompare(ctx.GetMetricsRegistry(), strings.NewReader(expected),
365 "caddy_http_request_size_bytes",
366 "caddy_http_response_size_bytes",
367 // caddy_http_request_duration_seconds_sum will vary based on how long the test took to run,
368 // so we check just the _bucket and _count metrics
369 "caddy_http_request_duration_seconds_bucket",
370 "caddy_http_request_duration_seconds_count",
371 "caddy_http_request_errors_total",
372 ); err != nil {
373 t.Errorf("received unexpected error: %s", err)
374 }
375 }
376
377 func TestMetricsCardinalityProtection(t *testing.T) {
378 ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()})
379
380 // Test 1: Without AllowCatchAllHosts, arbitrary hosts should be mapped to "_other"
381 metrics := &Metrics{
382 PerHost: true,
383 ObserveCatchallHosts: false, // Default - should map unknown hosts to "_other"
384 init: sync.Once{},
385 httpMetrics: &httpMetrics{},
386 allowedHosts: make(map[string]struct{}),
387 }
388
389 // Add one allowed host
390 metrics.allowedHosts["allowed.com"] = struct{}{}
391
392 h := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
393 w.Write([]byte("hello"))
394 return nil
395 })
396
397 ih := newMetricsInstrumentedRoute(ctx, "test", h, metrics)
398
399 // Test request to allowed host
400 r1 := httptest.NewRequest("GET", "http://allowed.com/", nil)
401 r1.Host = "allowed.com"
402 w1 := httptest.NewRecorder()
403 ih.ServeHTTP(w1, r1)
404
405 // Test request to unknown host (should be mapped to "_other")
406 r2 := httptest.NewRequest("GET", "http://attacker.com/", nil)
407 r2.Host = "attacker.com"
408 w2 := httptest.NewRecorder()
409 ih.ServeHTTP(w2, r2)
410
411 // Test request to another unknown host (should also be mapped to "_other")
412 r3 := httptest.NewRequest("GET", "http://evil.com/", nil)
413 r3.Host = "evil.com"
414 w3 := httptest.NewRecorder()
415 ih.ServeHTTP(w3, r3)
416
417 // Check that metrics contain:
418 // - One entry for "allowed.com"
419 // - One entry for "_other" (aggregating attacker.com and evil.com)
420 expected := `
421 # HELP caddy_http_requests_total Counter of HTTP(S) requests made.
422 # TYPE caddy_http_requests_total counter
423 caddy_http_requests_total{handler="test",host="_other",server="UNKNOWN"} 2
424 caddy_http_requests_total{handler="test",host="allowed.com",server="UNKNOWN"} 1
425 `
426
427 if err := testutil.GatherAndCompare(ctx.GetMetricsRegistry(), strings.NewReader(expected),
428 "caddy_http_requests_total",
429 ); err != nil {
430 t.Errorf("Cardinality protection test failed: %s", err)
431 }
432 }
433
434 func TestMetricsHTTPSCatchAll(t *testing.T) {
435 ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()})
436
437 // Test that HTTPS requests allow catch-all even when AllowCatchAllHosts is false
438 metrics := &Metrics{
439 PerHost: true,
440 ObserveCatchallHosts: false,
441 hasHTTPSServer: true, // Simulate having HTTPS servers
442 init: sync.Once{},
443 httpMetrics: &httpMetrics{},
444 allowedHosts: make(map[string]struct{}), // Empty - no explicitly allowed hosts
445 }
446
447 h := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
448 w.Write([]byte("hello"))
449 return nil
450 })
451
452 ih := newMetricsInstrumentedRoute(ctx, "test", h, metrics)
453
454 // Test HTTPS request (should be allowed even though not in allowedHosts)
455 r1 := httptest.NewRequest("GET", "https://unknown.com/", nil)
456 r1.Host = "unknown.com"
457 r1.TLS = &tls.ConnectionState{} // Mark as TLS/HTTPS
458 w1 := httptest.NewRecorder()
459 ih.ServeHTTP(w1, r1)
460
461 // Test HTTP request (should be mapped to "_other")
462 r2 := httptest.NewRequest("GET", "http://unknown.com/", nil)
463 r2.Host = "unknown.com"
464 // No TLS field = HTTP request
465 w2 := httptest.NewRecorder()
466 ih.ServeHTTP(w2, r2)
467
468 // Check that HTTPS request gets real host, HTTP gets "_other"
469 expected := `
470 # HELP caddy_http_requests_total Counter of HTTP(S) requests made.
471 # TYPE caddy_http_requests_total counter
472 caddy_http_requests_total{handler="test",host="_other",server="UNKNOWN"} 1
473 caddy_http_requests_total{handler="test",host="unknown.com",server="UNKNOWN"} 1
474 `
475
476 if err := testutil.GatherAndCompare(ctx.GetMetricsRegistry(), strings.NewReader(expected),
477 "caddy_http_requests_total",
478 ); err != nil {
479 t.Errorf("HTTPS catch-all test failed: %s", err)
480 }
481 }
482
483 func TestMetricsInstrumentedRoute(t *testing.T) {
484 ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()})
485 m := &Metrics{
486 init: sync.Once{},
487 httpMetrics: &httpMetrics{},
488 }
489
490 handlerErr := errors.New("oh noes")
491 response := []byte("hello world!")
492 innerHandler := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
493 if actual := testutil.ToFloat64(m.httpMetrics.requestInFlight); actual != 1.0 {
494 t.Errorf("Expected requestInFlight to be 1.0, got %v", actual)
495 }
496 if handlerErr == nil {
497 w.Write(response)
498 }
499 return handlerErr
500 })
501
502 ih := newMetricsInstrumentedRoute(ctx, "test_handler", innerHandler, m)
503
504 r := httptest.NewRequest("GET", "/", nil)
505 w := httptest.NewRecorder()
506
507 // Test with error
508 if actual := ih.ServeHTTP(w, r); actual != handlerErr {
509 t.Errorf("Expected error %v, got %v", handlerErr, actual)
510 }
511 if actual := testutil.ToFloat64(m.httpMetrics.requestInFlight); actual != 0.0 {
512 t.Errorf("Expected requestInFlight to be 0.0 after request, got %v", actual)
513 }
514 if actual := testutil.ToFloat64(m.httpMetrics.requestErrors); actual != 1.0 {
515 t.Errorf("Expected requestErrors to be 1.0, got %v", actual)
516 }
517
518 // Test without error
519 handlerErr = nil
520 w = httptest.NewRecorder()
521 if err := ih.ServeHTTP(w, r); err != nil {
522 t.Errorf("Unexpected error: %v", err)
523 }
524 }
525
526 func TestMetricsProvisionOTLPDisabled(t *testing.T) {
527 ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()})
528
529 m := &Metrics{OTLP: false}
530
531 if err := m.provisionOTLP(ctx); err != nil {
532 t.Fatalf("provisionOTLP returned unexpected error: %v", err)
533 }
534 if m.meterProvider != nil {
535 t.Fatalf("meterProvider should remain nil when OTLP is disabled")
536 }
537
538 // shutdown must be safe on a never-provisioned Metrics.
539 if err := m.shutdown(context.Background()); err != nil {
540 t.Fatalf("shutdown returned unexpected error: %v", err)
541 }
542 }
543
544 func TestMetricsProvisionOTLPNoopExporter(t *testing.T) {
545 // OTEL_METRICS_EXPORTER=none makes autoexport return its built-in
546 // no-op reader, which avoids any network I/O while still exercising
547 // the full provisionOTLP -> shutdown lifecycle.
548 t.Setenv("OTEL_METRICS_EXPORTER", "none")
549
550 ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()})
551
552 m := &Metrics{OTLP: true}
553
554 if err := m.provisionOTLP(ctx); err != nil {
555 t.Fatalf("provisionOTLP returned unexpected error: %v", err)
556 }
557 if m.meterProvider == nil {
558 t.Fatalf("provisionOTLP did not create a MeterProvider")
559 }
560
561 if err := m.shutdown(context.Background()); err != nil {
562 t.Fatalf("shutdown returned unexpected error: %v", err)
563 }
564 }
565
566 // shutdown on a nil receiver is a convenience so App.Stop can call it
567 // without guarding against app.Metrics being unset.
568 func TestMetricsShutdownNilReceiver(t *testing.T) {
569 var m *Metrics
570
571 if err := m.shutdown(context.Background()); err != nil {
572 t.Fatalf("shutdown on nil Metrics returned unexpected error: %v", err)
573 }
574 }
575
576 func BenchmarkMetricsInstrumentedRoute(b *testing.B) {
577 ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()})
578 m := &Metrics{
579 init: sync.Once{},
580 httpMetrics: &httpMetrics{},
581 }
582
583 noopHandler := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
584 w.Write([]byte("ok"))
585 return nil
586 })
587
588 ih := newMetricsInstrumentedRoute(ctx, "bench_handler", noopHandler, m)
589
590 r := httptest.NewRequest("GET", "/", nil)
591 w := httptest.NewRecorder()
592
593 b.ResetTimer()
594 b.ReportAllocs()
595 for i := 0; i < b.N; i++ {
596 ih.ServeHTTP(w, r)
597 }
598 }
599
600 // BenchmarkSingleRouteMetrics simulates the new behavior where metrics
601 // are collected once for the entire route.
602 func BenchmarkSingleRouteMetrics(b *testing.B) {
603 ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()})
604 m := &Metrics{
605 init: sync.Once{},
606 httpMetrics: &httpMetrics{},
607 }
608
609 // Build a chain of 5 plain middleware handlers (no per-handler metrics)
610 var next Handler = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
611 return nil
612 })
613 for i := 0; i < 5; i++ {
614 capturedNext := next
615 next = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
616 return capturedNext.ServeHTTP(w, r)
617 })
618 }
619
620 // Wrap the entire chain with a single route-level metrics handler
621 ih := newMetricsInstrumentedRoute(ctx, "handler", next, m)
622
623 r := httptest.NewRequest("GET", "/", nil)
624 w := httptest.NewRecorder()
625
626 b.ResetTimer()
627 b.ReportAllocs()
628 for i := 0; i < b.N; i++ {
629 ih.ServeHTTP(w, r)
630 }
631 }