New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: net/http: Custom handlers for 404 and 405 HTTP response codes #65648
Comments
see also #21548 Do you have a concrete proposal for the api? |
I just landed here whilst trying to find out if this was supported or not. This is my current workaround using a catch-all methods := []string{
http.MethodGet,
http.MethodHead,
http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodDelete,
http.MethodConnect,
http.MethodOptions,
http.MethodTrace,
}
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
method := r.Method
_, current := mux.Handler(r)
var allowed []string
for _, method := range methods {
// If we find a pattern that's different from the pattern for the
// current fallback handler then we know there are actually other handlers
// that could match with a method change, so we should handle as
// method not allowed
r.Method = method
if _, pattern := mux.Handler(r); pattern != current {
allowed = append(allowed, method)
}
}
r.Method = method
if len(allowed) != 0 {
w.Header().Set("allow", strings.Join(allowed, ", "))
http.Error(w, "Custom Method Not Allowed", http.StatusMethodNotAllowed)
return
}
http.Error(w, "Custom Not Found", http.StatusNotFound)
}) I haven't thought about it too deeply, so I don't know if there are cases where this workaround would be lacking. I don't have any strong feelings about how an API might look, or even if it's needed at all, but I wanted to show at least one solution that's working for me in my simple use-cases at the moment, and doesn't require changes to the existing serve mux API. |
CC @jba |
@polyscone At the moment, I'm using this workaround: https://github.com/denpeshkov/greenlight/blob/c68f5a2111adcd5b1a65a06595acc93a02b6380e/internal/http/middleware.go#L16-L71 However, as I mentioned earlier, this approach doesn't allow me to handle 404 errors differently depending on whether the route is actually registered or if my handler just returns a 404 (e.g., when a user with a given ID is not found). |
@denpeshkov Thanks for the link; this is roughly what I imagined you were doing based on your original explanation. Does the catch-all handler that I use in my own services not solve your problem? In the workaround I suggested your handlers are free to respond in any way they need to, and the catch-all is where you can implement generic 404 and 405 responses, all without the need for any middleware that wraps If you genuinely also needed to use the catch-all |
@polyscone Yeah, I think your approach should be working. It just seems that you're reimplementing functionality that is already provided by ServeMux (handling 404 and 405). So I thought that adding the ability to specify custom handlers, like, for example, is done here: https://pkg.go.dev/github.com/julienschmidt/httprouter#Router, might be a good alternative. |
@denpeshkov, I don't see how this is an example of your problem. If the user is not in the system, the handler for |
@denpeshkov, I read more deeply so I think I understand: you are calling the handler and using its response, but you don't know if a 404 is "I couldn't find a matching route" or "I couldn't find the user with that ID." So ignore my comment. |
To summarize: Before Go 1.22, you could produce a custom 404 page by registering it on "/". A As of Go 1.22, you can also produce a custom 404 page using "/". Everything will work as it did pre-1.22: no 405s will be served. So the only problem is that a custom 404 masks the new 405 behavior. How much does that matter? Well, no one cared about 405s before Go 1.22. So maybe very little. Also—this is conjecture and I would love a counterexample—if you're serving a custom 404 it's because your service is facing users and you want to show them a branded page of some kind. But 405s are not interesting to humans because they aren't typing raw PUT and DELETE requests to HTTP servers. They are only helpful to other computers. So maybe the use cases for custom 404s and automatic 405s are disjoint. (I'm aware of one use case for serving a custom 404 to a computer: if you want your server to return only JSON. But that's probably unrealistic and also hopeless with the existing If we allow people to customize 404, then we really should provide hooks for all errors, and custom error hooks have already been proposed and rejected. So I don't see a clear way forward here, but I also don't see a burning problem. Please correct me if I'm wrong on either count. |
Hi, @jba. Yes, you are right. The problem I was facing is developing a 'JSON-only' REST API. I looked into some popular REST services (GitHub API and Stripe API), and it seems they don't send 405 responses. I think I am going to use a catch-all route as in the pre-1.22 release. Thank you for your response |
Proposal Details
Go 1.22 introduced an enhanced HTTP routing. The current implementation utilizes unexported handlers for 404 and 405 responses. However, if there is a need for a custom 404 response (e.g., in a REST API with JSON responses), it is no longer possible to use a 'catch-all' pattern
/
, as it prevents the ability to return a 405 response. This issue is elaborated further in this discussion.To address this challenge, one can define a custom
http.ResponseWriter
to intercept responses and their status codes and handle them appropriately. Nonetheless, this approach precludes the ability to return a custom 404 response based on whether thehttp.ServeMux
couldn't locate the appropriate route or if the user's handler returned a response with a 404 status code. An example scenario is when responding toGET user/{id}
for a non-existent user in the system.Given these challenges, I believe it would be valuable to register custom 404 and 405 handlers.
The text was updated successfully, but these errors were encountered: