Closed moraes closed 8 years ago
gorilla/multiplex
or even just gorilla/router
(naming is hard)Other thoughts:
Pattern
interface similar to Goji's.Also worth reading (in case you haven't yet) is the plan to move net/context
into the std. lib and make Context
part of the http.Request
struct: https://groups.google.com/d/msg/golang-dev/cQs1z9LrJDU/6_9FMmGNCQAJ (may factor into design decisions)
Keen to see where this is going.
Edit: I moved the API draft to a gist to update it as proposals arrive. Here it is:
https://gist.github.com/moraes/fba14ffd6c3e091b2c68
Declaration syntax draft. Nothing is set in stone, etc.
// creates a new router
r := New()
// -------------------------------------------
// basic usage: host, path and method matching
// -------------------------------------------
// matches a static path
r.Handle("/foo/bar", myHandler)
// matches a path with a variable
r.Handle("/foo/{bar}", myHandler)
// matches a path prefix
r.Handle("/foo/{*}", myHandler)
// matches a path with a variable and the given HTTP verb.
r.Handle("/foo/{bar}", myHandler).Methods("GET")
// matches a host and path
r.Handle("www.mydomain.com/foo/bar", myHandler)
// ------------------------------
// extended usage: path subrouter
// ------------------------------
s := r.Sub("/users")
s.Handle("/new", myHandler) // matches "/users/new"
s2 := s.Sub("/{id}")
s2.Handle("/", myHandler) // matches "/users/{id}/"
s2.Handle("/tickets", myHandler) // matches "/users/{id}/tickets"
// ------------------------------
// extended usage: host subrouter
// ------------------------------
s := r.Sub("{subdomain}.mydomain.com")
s.Handle("/foo/{bar}", myHandler) // matches "{subdomain}.mydomain.com/foo/{bar}"
I think we should wait till the net/context situation is resolved before making the API. It could totally change the design and drop the need to use gorilla/context at all. The other option is to build it with the current x/net/context for now and then migrate to the standard library version.
Setting/retrieving variables can be left as "undefined" or "subject to changes" until a context resolution comes, and we take our time to discuss features, declaration syntax and the broader general API.
That said, I'll talk a bit about...
The general idea is that we don't need to have Schemes()
, Host()
, Path()
and PathPrefix()
methods: we can support them all in the declared pattern to be matched. As mentioned before:
r := mux.New()
r.Handle("/foo", myHandler) // matches "/foo" in any scheme and host
r.Handle("/bar/{*}", myHandler) // matches the path prefix "/bar/" in any scheme and host
s1 := r.Sub("//{subdomain}.mydomain.com")
s1.Handle("/{baz}", myHandler) // matches "//{subdomain}.mydomain.com/{baz}" in any scheme
s2 := r.Sub("https://{subdomain}.mydomain.com")
s2.Handle("/{baz}", myHandler) // matches "https://{subdomain}.mydomain.com/{baz}"
Notes:
{name}
;{*}
is used exclusively in the end of a path to declare path prefixes;/user-{id}
path; it must be /{user-id}
);/:name
) was considered, but several concerns came to mind: 1. It doesn't look good in host definitions (e.g. www.:domain.com
; 2. It looks even worse with an eventual support for mixed static and variable parts in a segment (e.g. /user-:id
); 3. In case point 2 ever happens, or no matter what happens, colon is a valid character in hosts and paths (RFC 3986) and curly braces are not valid characters in any URI part (this was taken into account to choose curly braces for gorilla/mux; the other considered options were []
or <>
);That's all for now. Let me know what you hate most, other ideas etc. :v:
I like the model where dispatching is logically by path and then method:
type Router interface {
Route(pattern string) *Route // defines a pattern to match: scheme, host, path and/or path prefix definitions
...
}
type Route interface {
// Get specifies handler for GET and HEAD if not otherwise specified.
Get(h http.HandlerFunc) Route
// Post specifies the handler for POST.
Post(h http.HandlerFunc) Route
// Handler specifies the handler the specified methods. If len(m) == 0, then h is
// used as a fallback when there are no specific method matches for the route.
Handler(h http.HandlerFunc, m ...string) Route
}
Examples:
r.Route("/foo/{bar}").Get(showFoo).Post(updateFoo)
r.Route("/a").Handler(aHandler, "GET", "POST") // GET and POST go to aHandler
r.Route("/b").Handler(bHandler) // all methods go to bHandler
If there's no match on method for a given route, than the mux replies with status 405.
To handle URI path segments containing an encoded "/", the router should dispatch on req.RequestURI instead of req.URL.Path. Matched variables are percent decoded. For example:
r.Handle("/foo/{bar}/baz", serveFooBaz)
matches "/foo/hello%2fworld/baz" with the variable bar set to "hello/world".
More specific patterns should override less specific patterns. In this example,
r.Handle("/{user}", serveUser)
r.Handle("/help", serveHelp)
the path "/help" should dispatch to serveHelp.
Left to right order should be used as a tiebreaker. In this example
r.Handle("/{user}/followers", serveUserFollwers)
r.Handle("/help/{topic}", serveHelpTopic)
the path "/help/folllowers" should dispatch to serveHelpTopic with variable "topic" set to followers.
The router should automatically handle redirects for "/" at end of the path.
@margaery: I took a quick look at RFC 6570, and that's a huge spec. We are doing simple string expansion with {name}
, which looks like their syntax; but this proposal has a lot more strict rules for their use ("alone in path or host segments"), so the similarity ends where it began. I like that it reminds RFC 6570, though; it suggests that braces are a good choice: they stand out and make variables easy to scan at a glance.
@garyburd:
r.Route("/foo/{bar}").Get(showFoo).Post(updateFoo)
(or .Handler(aHandler, "GET", "POST")
): this looks much better than the initial proposal, and makes more sense too.Gary's requests are all great - automatic slashes are very useful.
One additional thing on my list: support for automatic OPTIONS responses, whether built-in or via providing a hook to construct the response. Being able to OPTIONS /some-path and get the supported methods makes CORS much easier to reason with.
Some more suggestions:
Specify router options using functional options on the New() function. Here's an example:
type Option struct { /* unexported fields */ } // this can also be func(*options)
func New(options ...Option) Router
// WithErrorHandler specifies the function the router calls to generate error
// responses. The router calls http.Error by default.
func WithErrorHandler(f func(status int, w http.ResponseWriter, r *http.Request)) Option
Use(...http.Handler)
method or Middleware
method that you can use to chain http.Handler
s to all the routes within a subrouter is definitely useful for auth, verbose logging, CSRF, etc. Middleware applied to a top-level router should also apply to all subrouters below it by default. ErrorHandler(http.Handler)
as a method on *Router
is probably easier for most to understand. ErrorHandler
should accept a http.Handler
so you pass it anything you want - either a simple http.HandlerFunc
or something more complex.@elithrar: A default OPTIONS handler would be trivial to define, as well as a 405 handler that sets a proper Allow header (somewhat related). This remained unsolvable in current gorilla/mux because the same URL pattern can lead to different Routes, and it is not easy to identify "same URL pattern" because of regexps and separated matchers for host, scheme etc. In the new design each URL pattern always leads to the same Route, which knows about the methods it supports.
@garyburd, @elithrar: RE middleware in subrouters: I think we are again on the right direction here, because subrouters are just dumb factories that create routes with a given pattern prefix (they are not "nested"; they just carry a prefix). A subrouter could also carry a middleware list to pass to routes (or to sub-subrouters, which could have extra middleware). The exact API is something to discuss (.Sub(pattern string, midlewares ...InterfaceOrWhatever)
?).
Re: functional options: I need to think about it. The example was not very obvious to me, but I'm tired or something is missing.
I moved the API draft to a gist to update it as proposals arrive. Here it is:
@moraes How about creating a repo for the new mux and writing the proposal as godoc? People can propose changes or request features using issues and PRs in that repo.
New repo or new branch? I want to push my prototype in the following days.
I suggested a new repo because I assumed that the new mux will be published in a new repo. Where do you plan publish the new mux?
New repo on a non-master branch (and push a one-liner README to the master)? Will prevent people from pulling it 'accidentally'.
On Mon, Oct 26, 2015 at 9:28 AM Gary Burd notifications@github.com wrote:
I suggested a new repo because I assumed that the new mux will be published in a new repo. Where do you plan publish the new mux?
— Reply to this email directly or view it on GitHub https://github.com/gorilla/mux/issues/130#issuecomment-150998473.
New repo is ok. :) The spice must flow.
This discussion has now its own place: gorilla/muxy. It may be a temporary repo or not. We will see.
Thanks for the great feedback so far. :+1:
+1 to the idea of returning HTTP 405's in the appropriate places (eg. if a request is made to a URI that has a .Methods() filter on it that doesn't allow it)
The new (in progress) https://github.com/gorilla/muxy library should be able to handle this.
On Thu, Jan 7, 2016 at 12:42 PM Nathan Sullivan notifications@github.com wrote:
+1 to the idea of returning HTTP 405's in the appropriate places (eg. if a request is made to a URI that has a .Methods() filter on it that doesn't allow it)
— Reply to this email directly or view it on GitHub https://github.com/gorilla/mux/issues/130#issuecomment-169799054.
I'd like to start a conversation and brainstorm about the future of gorilla/mux.
Background: gorilla/mux has evolved to be the most featureful router in Go, but this came with a price: the API feels bloated and has unneeded complications. It is also far from shining in benchmarks compared to other routers (not that gorilla/mux is a bottleneck to care about, but not-so-shiny numbers give a bad impression to new users).
I'd like to take gorilla/mux to the next level with these goals in mind:
After playing around, I came up with an implementation draft that is a radical departure from what gorilla/mux is now, and to achieve the slim-intuitive-fast goals some cuts were made:
...but the most controversial ones are probably:
The initial result is a much slimmer mux which, besides the elemental features (scheme, host, path and method matching), offers some conveniences not easily found in other packages (subrouting, URL building), and a damn high speed (teaser: it almost beats net/http in static matching and knocks out everybody else in matching with variables; and, oh, it doesn't make a single memory allocation for any match — zero, zip, zilch, nada).
But this is not gorilla/mux. It is something else. Maybe it doesn't even exist! Well, maybe it does. Enough teasing for now: I'd like to hear your crazy ideas, frustrations and aspirations for gorilla/mux, and to ask how could we manage an eventual API breakage in the best possible way.