47
return handlerErr
48
})
49
50
- mh := middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error {
51
- return h.ServeHTTP(w, r)
52
- })
53
-
54
- ih := newMetricsInstrumentedHandler(ctx, "bar", mh, metrics)
50
+ ih := newMetricsInstrumentedRoute(ctx, "bar", h, metrics)
51
52
r := httptest.NewRequest("GET", "/", nil)
53
w := httptest.NewRecorder()
54
59
- if actual := ih.ServeHTTP(w, r, h); actual != handlerErr {
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 {
60
}
61
62
handlerErr = nil
67
- if err := ih.ServeHTTP(w, r, h); err != 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
72
- mh = middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error {
68
+ emptyHandler := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
69
return nil
70
})
75
- ih = newMetricsInstrumentedHandler(ctx, "empty", mh, metrics)
71
+ ih = newMetricsInstrumentedRoute(ctx, "empty", emptyHandler, metrics)
72
r = httptest.NewRequest("GET", "/", nil)
73
w = httptest.NewRecorder()
74
79
- if err := ih.ServeHTTP(w, r, h); err != nil {
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 {
83
}
84
85
// handler returning an error with an HTTP status
90
- mh = middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error {
86
+ errHandler := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
87
return Error(http.StatusTooManyRequests, nil)
88
})
89
94
- ih = newMetricsInstrumentedHandler(ctx, "foo", mh, metrics)
90
+ ih = newMetricsInstrumentedRoute(ctx, "foo", errHandler, metrics)
91
92
r = httptest.NewRequest("GET", "/", nil)
93
w = httptest.NewRecorder()
94
99
- if err := ih.ServeHTTP(w, r, nil); err == nil {
95
+ if err := ih.ServeHTTP(w, r); err == nil {
96
t.Errorf("expected error to be propagated")
97
}
98
221
return handlerErr
222
})
223
228
- mh := middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error {
229
- return h.ServeHTTP(w, r)
230
- })
231
-
232
- ih := newMetricsInstrumentedHandler(ctx, "bar", mh, metrics)
224
+ ih := newMetricsInstrumentedRoute(ctx, "bar", h, metrics)
225
226
r := httptest.NewRequest("GET", "/", nil)
227
w := httptest.NewRecorder()
228
237
- if actual := ih.ServeHTTP(w, r, h); actual != handlerErr {
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 {
234
}
235
236
handlerErr = nil
245
- if err := ih.ServeHTTP(w, r, h); err != 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
250
- mh = middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error {
242
+ emptyHandler := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
243
return nil
244
})
253
- ih = newMetricsInstrumentedHandler(ctx, "empty", mh, metrics)
245
+ ih = newMetricsInstrumentedRoute(ctx, "empty", emptyHandler, metrics)
246
r = httptest.NewRequest("GET", "/", nil)
247
w = httptest.NewRecorder()
248
257
- if err := ih.ServeHTTP(w, r, h); err != nil {
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 {
257
}
258
259
// handler returning an error with an HTTP status
268
- mh = middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error {
260
+ errHandler := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
261
return Error(http.StatusTooManyRequests, nil)
262
})
263
272
- ih = newMetricsInstrumentedHandler(ctx, "foo", mh, metrics)
264
+ ih = newMetricsInstrumentedRoute(ctx, "foo", errHandler, metrics)
265
266
r = httptest.NewRequest("GET", "/", nil)
267
w = httptest.NewRecorder()
268
277
- if err := ih.ServeHTTP(w, r, nil); err == nil {
269
+ if err := ih.ServeHTTP(w, r); err == nil {
270
t.Errorf("expected error to be propagated")
271
}
272
389
// Add one allowed host
390
metrics.allowedHosts["allowed.com"] = struct{}{}
391
400
- mh := middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error {
392
+ h := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
393
w.Write([]byte("hello"))
394
return nil
395
})
396
405
- ih := newMetricsInstrumentedHandler(ctx, "test", mh, metrics)
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()
411
- ih.ServeHTTP(w1, r1, HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { return nil }))
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()
417
- ih.ServeHTTP(w2, r2, HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { return nil }))
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()
423
- ih.ServeHTTP(w3, r3, HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { return nil }))
415
+ ih.ServeHTTP(w3, r3)
416
417
// Check that metrics contain:
418
// - One entry for "allowed.com"
444
allowedHosts: make(map[string]struct{}), // Empty - no explicitly allowed hosts
445
}
446
455
- mh := middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error {
447
+ h := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
448
w.Write([]byte("hello"))
449
return nil
450
})
451
460
- ih := newMetricsInstrumentedHandler(ctx, "test", mh, metrics)
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()
467
- ih.ServeHTTP(w1, r1, HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { return nil }))
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()
474
- ih.ServeHTTP(w2, r2, HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { return nil }))
466
+ ih.ServeHTTP(w2, r2)
467
468
// Check that HTTPS request gets real host, HTTP gets "_other"
469
expected := `
480
}
481
}
482
491
-type middlewareHandlerFunc func(http.ResponseWriter, *http.Request, Handler) error
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
493
-func (f middlewareHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request, h Handler) error {
494
- return f(w, r, h)
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 BenchmarkMetricsInstrumentedRoute(b *testing.B) {
527
+ ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()})
528
+ m := &Metrics{
529
+ init: sync.Once{},
530
+ httpMetrics: &httpMetrics{},
531
+ }
532
+
533
+ noopHandler := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
534
+ w.Write([]byte("ok"))
535
+ return nil
536
+ })
537
+
538
+ ih := newMetricsInstrumentedRoute(ctx, "bench_handler", noopHandler, m)
539
+
540
+ r := httptest.NewRequest("GET", "/", nil)
541
+ w := httptest.NewRecorder()
542
+
543
+ b.ResetTimer()
544
+ b.ReportAllocs()
545
+ for i := 0; i < b.N; i++ {
546
+ ih.ServeHTTP(w, r)
547
+ }
548
+}
549
+
550
+// BenchmarkSingleRouteMetrics simulates the new behavior where metrics
551
+// are collected once for the entire route.
552
+func BenchmarkSingleRouteMetrics(b *testing.B) {
553
+ ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()})
554
+ m := &Metrics{
555
+ init: sync.Once{},
556
+ httpMetrics: &httpMetrics{},
557
+ }
558
+
559
+ // Build a chain of 5 plain middleware handlers (no per-handler metrics)
560
+ var next Handler = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
561
+ return nil
562
+ })
563
+ for i := 0; i < 5; i++ {
564
+ capturedNext := next
565
+ next = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
566
+ return capturedNext.ServeHTTP(w, r)
567
+ })
568
+ }
569
+
570
+ // Wrap the entire chain with a single route-level metrics handler
571
+ ih := newMetricsInstrumentedRoute(ctx, "handler", next, m)
572
+
573
+ r := httptest.NewRequest("GET", "/", nil)
574
+ w := httptest.NewRecorder()
575
+
576
+ b.ResetTimer()
577
+ b.ReportAllocs()
578
+ for i := 0; i < b.N; i++ {
579
+ ih.ServeHTTP(w, r)
580
+ }
581
}