enhance http.ServeMux routing #60227
Replies: 28 comments 79 replies
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
I welcome a proposal meant to beef up the capabilities of
This change would introduce a new way of restricting methods: within the pattern (in addition to within the handler itself). That could cause some confusion. Besides, imagine I want to configure
Or would I also have to register the I think you could argue that the concern of restricting methods is best left out of the pattern. |
Beta Was this translation helpful? Give feedback.
-
I like it! If |
Beta Was this translation helpful? Give feedback.
This comment has been hidden.
This comment has been hidden.
-
Great idea! I'm a bit confused with the Changes Example 4 Since |
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
Good Idea My question why not add an additional parameter to the handle function to implement the first part "distinguishing requests based on HTTP method (GET, POST, ...)" How its at the moment: mux.Handle("/api/", apiHandler{}) How it would look with an additional parameter mux.Handle("GET","/api/", apiHandler{}) |
Beta Was this translation helpful? Give feedback.
-
In general this looks really nice, thanks, particularly the order-independence invariant. One thing though:
I might well be missing a fundamental issue with this, but this seems overly restrictive to me.
So I don't really understand why something similar to the "longest literal prefix wins" rule couldn't apply |
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
Thank you so much @jba! I just finished reading the thread and have some ideas/questions that I hope will be helpful in finalizing the details before they become a proposal. 1. What data structure will the new
|
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
Isn't that a compatibility breaking change?
|
Beta Was this translation helpful? Give feedback.
This comment has been minimized.
This comment has been minimized.
-
If there were an initial draft of this API, even one that was mostly stubbed out in term of actual implementation, I could try porting our existing routing code to it to discover what does and does not work. (It is important that it actually be something that I can send to the compiler, though, otherwise subtle issues tend not to get noticed.) Are there any plans to post such a rough or draft implementation for people to try out? |
Beta Was this translation helpful? Give feedback.
-
I am in favor of this proposal. I'd like to ask to rename the proposed Generally speaking, I'm in favor of short names. However, I feel adding the This would be an 8 character field name. But for comparison, the mean and median length of existing field names on the |
Beta Was this translation helpful? Give feedback.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
-
Should this be introduced as a golang.org/x package and then moved into Go proper once stabilized? |
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
I don't understand the problem this is meant to solve. By way of contrast, I understand that the problem to be solved for log/slog was that there were a lot of third party structured loggers, but if you made a stand alone library, you couldn't count on which one of the many loggers your consumers would want you to interface with. The new slog package provides a common denominator, so that whatever consumer facing log API you use or whatever log sink backend you use, it can all interface with log/slog and everyone is happy. There was a clear problem statement. What's the problem statement for http.ServeMuxt? So, obviously, there are lots of other HTTP routers for Go. Is the problem to be solved that they're incompatible? In that case, then I guess Request.Vars is good to have, although it's unfortunate that it has to go on HTTP client requests as well. In any event, existing third party routers have mostly settled on either wrapping the standard library with their own (e.g. gin.Context) or passing things through context.Context (which is not ideal in terms of type safety, but Request.Vars doesn't help here either). I don't see how this makes them significantly more compatible. In the short run, things will be worse because a new crop of third party routers will use Vars, while the old ones will all continue to exist and not use Vars. Maybe the problem to be solved is that the standard ServeMux is just slightly underpowered? That might be true, but it's not clear why that problem needs to be solved and where to draw the line in solving it. I guess the urgency is because gorilla/mux is archived? It's hard to understand why the line is being drawn where it is and what the advantage of doing so is. It just feels a bit arbitrary to include methods and path parameters but not something else. |
Beta Was this translation helpful? Give feedback.
-
Focusing on Request.Vars, existing routers have mostly been using context.Context to convey path variables. Why not an API like this, so they could be backwards compatibly adapted to using path vars also: func PathVars(context.Context) map[string]any
// WithPathVar adds one path var
func WithPathVar(context.Context, string, any) context.Context
// WithPathVars adds many path vars
func WithPathVar(context.Context, map[string]any) context.Context
// Convenience method:
func (r *Request) PathVars() map[string]any { return PathVars(r.Context()) } |
Beta Was this translation helpful? Give feedback.
-
Here is another idea for a method handler: // MethodHandler maps each handler to the corresponding method.
// Responds with "method not allowed" if none match.
type MethodHandler map[string]http.HandlerFunc
// ServeHTTP dispatches the request to the handler whose method matches,
// otherwise it responds with "method not allowed".
func (mh MethodHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
allow := make([]string, 0, len(mh))
for method, handler := range mh {
if method == r.Method {
handler(w, r)
return
}
allow = append(allow, method)
}
w.Header().Set("Allow", strings.Join(allow, ", "))
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
} mux := http.NewServeMux()
mux.Handle("/foo", MethodHandler{
http.MethodGet: getHandler,
http.MethodPort: postHandler,
}) What do you think? |
Beta Was this translation helpful? Give feedback.
-
Very welcome proposal ! |
Beta Was this translation helpful? Give feedback.
-
For not affecting who already uses |
Beta Was this translation helpful? Give feedback.
-
I think rule 4 should be changed. It says that if there are two patterns that match the same request and aren't distinguished by the first three rules, then the longer pattern wins over the shorter one. This leads to surprising results. For instance, consider the patterns I added rule 4 for one reason: to select
This rule would allow both |
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
This is a discussion that we hope will lead to a proposal.
We would like to expand the standard HTTP mux's capabilities by adding two features: distinguishing requests based on HTTP method (GET, POST, ...) and support for wildcards in the matched paths. Both features are particularly important to REST API servers.
Background
The current mux has a few important properties that we want to preserve:
The semantics of the mux do not depend on the order of the Handle or HandleFunc calls. Being order-independent makes it not matter what order packages are initialized (for init-time registrations) and allows easier refactoring of code. This is why the tie breakers are not based on registration order and why duplicate registrations panic (only order could possibly distinguish them). It remains a key design goal to avoid any semantics that depend on registration order.
The mux is fairly simple and straightforward to understand. It is not a goal to add every last possible bell and whistle. Other custom or more full-featured muxes should remain easy to write and a well-supported part of the Go web ecosystem.
As a refresher, today's patterns used in Handle and HandleFunc take the form
[host]/path[/]
. The rules are:In the (likely) event that multiple registered patterns match a request, ties are broken as follows (in order):
Potential Changes
First, a pattern can start with an optional method followed by a space, as in
GET /codesearch
orGET codesearch.google.com/
. A pattern with a method is only used to match requests with that method. So it is possible to have the same same path pattern registered with different methods:Second, a pattern can include a wildcard path element of the form
{name}
or{name...}
. For example,/b/{bucket}/o/{objectname...}
. The name must be a valid Go identifier; that is, it must fully match the regular expression[_\pL][_\pL\p{Nd}]*
.These wildcards must be path elements, meaning they must be preceded by a slash and then be followed by either a slash or the end of the string. For example,
/b_{bucket}
is not a valid pattern. (It is not a goal to support every possible URL schema with these patterns; for special cases, using other routers will continue to be a good choice.)Normally a wildcard matches only a single path element, ending at the next literal slash (not %2F) in the request URL. If the
...
is present, then the wildcard matches the remainder of the URL, including slashes. (Therefore it is invalid for a...
wildcard to appear anywhere but at the end of a pattern.)The tie-breaking rules change to:
Two patterns with the same literal prefix must not have any possible overlap. For example, it is OK to register both of these:
because the third path element keeps them from ever matching the same URL.
But it is not OK to register both of these:
Both of these match, for example,
/b/_/o/_
and they have the same length literal prefix (/b/), so there is no clear winner. Instead, the second registration will panic during mux.Handle / mux.HandleFunc.In contrast, these two are OK, because the longer literal prefix breaks any ties:
There is one last, special wildcard:
{$}
matches only the end of the URL, allowing writing a pattern that ends in slash but does not match all extensions of that path. For example, the pattern/{$}
matches the root page/
but (unlike the pattern/
today) does not match a request for/anythingelse
.Examples
Say the following patterns are registered:
In the examples that follow, the host in the request is example.com and the method is GET unless otherwise specified.
API
To support this API, the net/http package adds a new field to Request:
The
ServeMux
fills outVars
before calling a registered handler'sServeHTTP
method. Of course, other routers may wish to useVars
for their own variables, and they can easily do so. All the values ofVars
will be strings if we adopt the design above. But the value type ofVars
isany
to support those other routers, and to allow for future extensions.Beta Was this translation helpful? Give feedback.
All reactions