Skip to content

proposal: net/http: return an error instead of panicking on http.ServeMux registration errors #55942

@matttproud

Description

@matttproud

This will fall into the Go 2 category bucket, because as proposed it is a breaking behavioral change for Go 1.

Proposal

Instead of panicking on invalid http.Handler registrations, (*http.ServeMux).Handle should return errors. This is germane in when the user attempts to register multiple http.Handler against the same path as demonstrated with http.DefaultServeMux:

http.Handle("/path", h)
http.Handle("/path", h)

// Output:
panic: http: multiple registrations for /path

Instead, I propose (*http.ServeMux).Handle gets a new method signature similar to this:

func (*http.ServeMux) Handle(string, Handler) error { ... }

In the case above, a sentinel or structured error value could be returned by package http to enable callers to handle the error:

package http

var ErrMultipleRegistrations = errors.New("http: more than one handler registered against the path")

// Or:

type MultipleRegistrationError struct {
  Path string
}

func (err *MultipleRegistrationError) Error() string { ... }

Motivation

Why is this panic worth handling at all? Shouldn't the user know about all registrations and be able to prevent multiple?

I believe the latter question is the first to start with. Based on our team's experience in offering a server framework used internally by several thousand customer teams, we got to see first-hand how the multiple registration behavior played in a gigantic monorepo, especially when combined with code that registered default debugging handlers using http.Handle in func init bodies. It played out terribly. This was because there were two registration pathways:

  1. through the server framework directly, which could catch duplicates of thing registered through it
  2. things registered directly by other infrastructure providers in func init on http.DefaultServeMux using http.Handle and similar

We could only control no. 1. Auditing no. 2 was a boil-the-ocean affair, particularly because there was no wisdom nor enforcement of avoiding mutating package state in registrations. Attempting to convert no. 2 to support per-http.ServeMux registration was a good compromise, but we could never catch up with the codebase.

In effect, this means that it's infeasible in large codebases to ensure that there is central control over who is registering what. We tried with a per-server http.ServeMux that falls back to http.DefaultServeMux for paths it doesn't recognize, but the result and behavior is not pretty. It's also error-prone.

Returning an error sentinel or structured error value would enable intermediate frameworks and middleware to more gracefully handle such cases, which is a huge win for developer experience.

Other Thoughts and Speculation

An API for unregistering existing handlers might be interesting to consider. A server framework could de-register a duplicate. Or as a twist on this, last-one-wins semantic for registration? I haven't thought about these super closely, but I can say the panic should go.

Perhaps a structured error return (as suggested above) could return information about the registrations: what package, what line, identifier type information, or interface value of the pre-existing http.Handler.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status

    Incoming

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions