@marcel91200 / caddy Mirror · read-only / commits / d80774cb
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, 11:00 UTC

metrics: Add nil check for metricsHandler in AdminMetrics.serveHTTP (#7553)

* metrics: Add nil check for metricsHandler in AdminMetrics.serveHTTP Prevents panic when the admin metrics endpoint is accessed before the module is fully provisioned. Returns a proper API error instead of crashing. * admin: provision router modules before registering routes Instead of adding a nil check for metricsHandler, address the root cause by provisioning admin router modules before calling Routes(). This ensures all handler state is initialized before routes are registered on the mux. Merge newAdminHandler and provisionAdminRouters into a single step, removing the two-phase setup where routes were registered first and modules provisioned later. The AdminConfig.routers field is no longer needed since provisioning happens inline. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: go fmt admin.go --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Br1an committed May 12, 2026 at 07:27 UTC d80774cb3fe6abcd0fea9dd0c68812ee94c7cf2a
3 files changed +21 -59
admin.go
+12 -37
@@ -120,10 +120,6 @@ type AdminConfig struct {
120 //
121 // EXPERIMENTAL: This feature is subject to change.
122 Remote *RemoteAdmin `json:"remote,omitempty"`
123 -
124 - // Holds onto the routers so that we can later provision them
125 - // if they require provisioning.
126 - routers []AdminRouter
123 }
124
125 // ConfigSettings configures the management of configuration.
@@ -222,7 +218,7 @@ type AdminPermissions struct {
218
219 // newAdminHandler reads admin's config and returns an http.Handler suitable
220 // for use in an admin endpoint server, which will be listening on listenAddr.
225 -func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool, _ Context) adminHandler {
221 +func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool, ctx Context) (adminHandler, error) {
222 muxWrap := adminHandler{mux: http.NewServeMux()}
223
224 // secure the local or remote endpoint respectively
@@ -279,34 +275,21 @@ func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool, _ Co
275 // register third-party module endpoints
276 for _, m := range GetModules("admin.api") {
277 router := m.New().(AdminRouter)
282 - for _, route := range router.Routes() {
283 - addRoute(route.Pattern, handlerLabel, route.Handler)
284 - }
285 - admin.routers = append(admin.routers, router)
286 - }
278
288 - return muxWrap
289 -}
290 -
291 -// provisionAdminRouters provisions all the router modules
292 -// in the admin.api namespace that need provisioning.
293 -func (admin *AdminConfig) provisionAdminRouters(ctx Context) error {
294 - for _, router := range admin.routers {
295 - provisioner, ok := router.(Provisioner)
296 - if !ok {
297 - continue
279 + // provision the router before registering its routes, so
280 + // handlers have access to all provisioned state
281 + if provisioner, ok := router.(Provisioner); ok {
282 + if err := provisioner.Provision(ctx); err != nil {
283 + return adminHandler{}, fmt.Errorf("provisioning admin router module %s: %v", m.ID, err)
284 + }
285 }
286
300 - err := provisioner.Provision(ctx)
301 - if err != nil {
302 - return err
287 + for _, route := range router.Routes() {
288 + addRoute(route.Pattern, handlerLabel, route.Handler)
289 }
290 }
291
306 - // We no longer need the routers once provisioned, allow for GC
307 - admin.routers = nil
308 -
309 - return nil
292 + return muxWrap, nil
293 }
294
295 // allowedOrigins returns a list of origins that are allowed.
@@ -430,11 +413,7 @@ func replaceLocalAdminServer(cfg *Config, ctx Context) error {
413 return err
414 }
415
433 - handler := cfg.Admin.newAdminHandler(addr, false, ctx)
434 -
435 - // run the provisioners for loaded modules to make sure local
436 - // state is properly re-initialized in the new admin server
437 - err = cfg.Admin.provisionAdminRouters(ctx)
416 + handler, err := cfg.Admin.newAdminHandler(addr, false, ctx)
417 if err != nil {
418 return err
419 }
@@ -558,11 +537,7 @@ func replaceRemoteAdminServer(ctx Context, cfg *Config) error {
537
538 // make the HTTP handler but disable Host/Origin enforcement
539 // because we are using TLS authentication instead
561 - handler := cfg.Admin.newAdminHandler(addr, true, ctx)
562 -
563 - // run the provisioners for loaded modules to make sure local
564 - // state is properly re-initialized in the new admin server
565 - err = cfg.Admin.provisionAdminRouters(ctx)
540 + handler, err := cfg.Admin.newAdminHandler(addr, true, ctx)
541 if err != nil {
542 return err
543 }
admin_test.go
+9 -15
@@ -340,7 +340,10 @@ func TestAdminHandlerBuiltinRouteErrors(t *testing.T) {
340 if err != nil {
341 t.Fatalf("Failed to parse address: %v", err)
342 }
343 - handler := cfg.Admin.newAdminHandler(addr, false, Context{})
343 + handler, err := cfg.Admin.newAdminHandler(addr, false, Context{})
344 + if err != nil {
345 + t.Fatalf("Failed to create admin handler: %v", err)
346 + }
347
348 tests := []struct {
349 name string
@@ -461,7 +464,10 @@ func TestNewAdminHandlerRouterRegistration(t *testing.T) {
464 admin := &AdminConfig{
465 EnforceOrigin: false,
466 }
464 - handler := admin.newAdminHandler(addr, false, Context{})
467 + handler, err := admin.newAdminHandler(addr, false, Context{})
468 + if err != nil {
469 + t.Fatalf("Failed to create admin handler: %v", err)
470 + }
471
472 req := httptest.NewRequest("GET", "/mock", nil)
473 req.Host = "localhost:2019"
@@ -473,10 +479,6 @@ func TestNewAdminHandlerRouterRegistration(t *testing.T) {
479 t.Errorf("Expected status code %d but got %d", http.StatusOK, rr.Code)
480 t.Logf("Response body: %s", rr.Body.String())
481 }
476 -
477 - if len(admin.routers) != 1 {
478 - t.Errorf("Expected 1 router to be stored, got %d", len(admin.routers))
479 - }
482 }
483
484 type mockProvisionableRouter struct {
@@ -514,19 +516,16 @@ func TestAdminRouterProvisioning(t *testing.T) {
516 name string
517 provisionErr error
518 wantErr bool
517 - routersAfter int // expected number of routers after provisioning
519 }{
520 {
521 name: "successful provisioning",
522 provisionErr: nil,
523 wantErr: false,
523 - routersAfter: 0,
524 },
525 {
526 name: "provisioning error",
527 provisionErr: fmt.Errorf("provision failed"),
528 wantErr: true,
529 - routersAfter: 1,
529 },
530 }
531
@@ -562,8 +561,7 @@ func TestAdminRouterProvisioning(t *testing.T) {
561 t.Fatalf("Failed to parse address: %v", err)
562 }
563
565 - _ = admin.newAdminHandler(addr, false, Context{})
566 - err = admin.provisionAdminRouters(Context{})
564 + _, err = admin.newAdminHandler(addr, false, Context{})
565
566 if test.wantErr {
567 if err == nil {
@@ -574,10 +572,6 @@ func TestAdminRouterProvisioning(t *testing.T) {
572 t.Errorf("Expected no error but got: %v", err)
573 }
574 }
577 -
578 - if len(admin.routers) != test.routersAfter {
579 - t.Errorf("Expected %d routers after provisioning, got %d", test.routersAfter, len(admin.routers))
580 - }
575 })
576 }
577 }
caddy.go
-7
@@ -440,13 +440,6 @@ func run(newCfg *Config, start bool) (Context, error) {
440 }
441 }()
442
443 - // Provision any admin routers which may need to access
444 - // some of the other apps at runtime
445 - err = ctx.cfg.Admin.provisionAdminRouters(ctx)
446 - if err != nil {
447 - return ctx, err
448 - }
449 -
443 // Start
444 err = func() error {
445 started := make([]string, 0, len(ctx.cfg.apps))