10 ms·
Better HTTP server routing in Go 1.22
- creshal 3y agoWill this finally end the eternal golang http router bikeshedding?
- kandros 3y agolol no, will start the proliferation of more different abstractions over it
- jerf 3y agoNot likely. Routers are so easy to write that the "different abstractions" have already been written, and are unaffected by this change to the standard library, because they don't build on the standard library, they plug in next to it. The primary utility of Go's net/http is that it is a "minimal framework" that provides a fairly common plug-level compatibility between various bits and pieces. The particular bits that it happens to provide by "default" are not really that consequential by comparison. I was actually surprised anyone touched the standard mux at all at this late date because there's so many other options already, and most of them just plug in with no fuss at all. All a router is is a handler that examines the request and then calls another handler as a result.
- kandros 3y agoI agree on paper, but story shown how the combination of "I like it, but I want it just a little bit different" combined with something somehow new is a recipe for new weekend projects
- jitl 3y agoI think so. Sure people will try to value add on top, but I know at least I can just pick the stdlib and not need to go check various package repos to see if they have maintainers and open security issues. I bet a lot of developers will feel the same way.
- Loic 3y agoNo because we don't have named routes and the ability to reverse build the URL like in the Django router. So, at lest this functionality will be again built on top with different opinions on the best way to do it.
- bojanz 3y agoIt doesn't have the performance or the features of something like Chi/Gin/Echo, so while it will improve the lives of people who prefer to stick to the stdlib, it probably won't convince many others.
- gempir 3y agoAs I commented[1] I think the syntax is flawed in the proposal. You need to weirdly create a magic string to define your handler. Why not make it an actual argument, then using already existing constants is easier. [1] https://github.com/golang/go/issues/61410#issuecomment-1658050983 https://github.com/golang/go/issues/61410#issuecomment-16580...
- avianlyric 3y agoIsn’t that just a consequence of Go’s backwards compatibility guarantee? Adding new arguments to the Mux interface would break existing code, given the method signature can’t be changed, these magic strings seem like a reasonable compromise. It not like HTTP verbs are going to change anytime soon, and it’s trivial to validate them during register, or via static analysis. So I’m not sure what value the use of constants would bring, especially if it either broke backwards compatibility, or forced the creation of a new, but slightly different mux API that would have to live in parallel with the old API forever.
- Macha 3y agoSurely the changes to panic on conflicting routes being discussed upstream already prove they're willing to flex on that guarantee?
- barsonme 3y agoThat’s not a breaking change.
- Macha 3y agoHow is "this api usage was allowed before and now panics" not a breaking change?
- avianlyric 3y agoWhich specific API usage was allowed before and now panics? As far as I can see the only historically allowable API usage that would now panic, is registering exactly the same path twice, which in my books is just a bug. Certainly it's technically a breaking change, but there's huge difference between making all existing usage of the mux interface incompatible with the new changes, and only making obviously incorrect and buggy usages of the interface incompatible.
- vbezhenar 3y agoGreat syntax. I didn't use any third-party routers, because implementing my own router is not a big deal, but this is just great.
- EspressoGPT 3y agoIt is a big deal if you want to have RESTful routing.
- fideloper 3y agoForcing a panic when 2 routes are matched seems counter intuitive to me (versus, idk, every other web framework which uses the first-to-be-registered route that matches). Are there go-specific reasons for that? The edge case of "you might register HTTP routes in a bunch of places and it's harder to find that if multiple routes match" seems like something that be worked around with tooling. I've (ab)used the behavior of "first to be matched wins" a ton in my career - there's been a bunch of "business case" times when I've needed like `/foo/bar` to be one registered route, and `/foo/{id}` to be another.
- eknkc 3y agoI believe it allows `/foo/bar` and `/foo/{id}` as the first one is more specific and has precedence. This looks fine to me. Looks like it will panic in case you have `/foo/{id}/delete` and `/foo/bar/{action}`. /foo/bar/delete will match both, none is more specific so it panics. Feels reasonable. Having a first one wins precedence might be better though.
- karmakaze 3y agoI would expect and prefer the longer literal prefix to match. If the goal is to not have so many different routers in use, having some options like this case would be better than having one opinionated Go-way, which is how we end up with many libraries to fill common gaps.
- the_gipsy 3y agoThe least surprising behavior would be matching `/boo/bar/{action}`, since we're dealing with path element separated by slashes, and the precedence case already exists otherwise.
- throwaway2990 3y agoNa it’s dumb. Bar is very specific. It should be picked.
- troupo 3y agoIt's a 404 Not Found or 500 Server Error. HTTP server panicking is never reasonable
- donatj 3y agoI would far rather the overlapping path’s just match in the order they were defined rather than panic. In their example here, had they defined the `/task/0/{action}/` path before the wildcard path, my expectation would have been for that to match first. This would allow for handlers for special cases to easily be defined. I’d far rather things just do what I say than fail in “helpful” ways. Smells really funny and not very Go.
- numbsafari 3y agoExcept the semantics of the language matter, and it is, actually, very Go. The original ServeMux was designed to not honor registration order, because it is risky to do so. One of the key design goals of Go is to support "programming at scale", and odd side-effects due to edits made to "distant code" is the exact kind of thing you want to avoid. In trivial examples, where all of the registrations are made in a single function in a single package, there is clarity around the intention of registration order. But you can't assume that that is how these things will always happen. In fact, for any sufficiently large codebase, these registrations will likely be happening in more than one location, and may be the result of reading input files or generated code where the person writing the spec for the generated code is unaware of the intricacies of execution order and how that may impact routing. It also means that changing the lexical sort of a set of package imports could result in an unexpected change in routing. Avoid unexpected results, making refactoring easier, and generally trying to make a complex program easier to reason about, are definitely very Go. You might not always agree with their choices, but those are their reasons and they are remarkably consistent.
- iudqnolq 3y agoWhy even allow registrations at a distance? I'm a complete go beginner but that feels more in the spirit of go to me: making you do something a little annoying that ends up being clear and simple. Of course it's too late now but I'm surprised this API was ever considered because it seems obviously scary and wrong to let a dependency just create it's own routes.
- 3y ago
- JodieBenitez 3y agoGo is not my main tool but I've used Gin which goes like this: router.GET("/", func(context *gin.Context) { ... } And it seems to be no different than many other languages/frameworks. Are there more examples that use the "GET /path/" way ?
- ilyt 3y agoHaven't seen in the wild. It's just bad way to do it.
- EspressoGPT 3y agoMost likely not because there's no reason to do it this way other than backwards compatibility ...
- tgv 3y agoI haven't read the article, but I would guess it's because the http header literally starts like that. You could match that against the first line, instead of having to break it into components. It does sound like a micro-optimization.
- qaq 3y agoGood one less external dependency. As a side note sqlc + postgres + templ (kindah jsx for go) + htmx + tailwinds has being extremely productive stack to develop in
- austinpena 3y agoI really wish that templ had intellij support! Love the idea
- qaq 3y agoI think that is in the works ? VS Code support is top notch
- austinpena 3y agoAnywhere I can see updates?
- brunoluiz 3y agoI thought you had mistyped template there but actually no, templ is some other package different from the stdlib one. I had a quick look on its documentation and it seems quite neat! I was hoping that bud with svelte compilation would fill this gap, but looks like templ is a nicer alternative.
- nebqr 3y agoHow do you handle tailwind here? I really like the idea of adding tailwind but if it means using npm I think I'd rather just write my own css.
- simantel 3y agoI'm not OP, but Tailwind ships a binary you can use (by bundling up a JS runtime): https://tailwindcss.com/blog/standalone-cli https://tailwindcss.com/blog/standalone-cli
- perfmode 3y ago
- jamescun 3y agoI don't like this. Is there a reason for using a stringified method prefix? I'd prefer the type safety of verb-specific methods (i.e. mux.Get, mux.Post etc) than magic strings validated at run time. Additionally editors can autocomplete/intellisense methods.
- c7DJTLrn 3y agoNot a fan either. I want to be sure that routing is going to work at compile time, not at runtime.
- kjksf 3y agoAdding this is so trivial. If it really, really bothers you that much: func Get(mux, uri, handler) { mux.HandleFunc("GET " + uri, handler) } Obviously skipped the types for brevity.
- tazjin 3y agoIf you want type safety, pick a type-safe language.
- badrequest 3y agoGo is type safe.
- the_gipsy 3y agoThe zero-values idea, nil, reflection and stringly-typing all over stdlib and the most popular libs makes go not type safe. Were you thinking of statically typed?
- Cupprum 3y agoThe gorrila/mux project is weird and I am confused by it. Last year the maintainers archived the gorrila/mux project. Therefore i switched to another multiplexor called gin-gonic. Now as i saw the OP mentioning it in their blog, i went to take a look at the gorrila/mux project again to verify i correctly remembered the project to be archived. Apparently its not archived anymore, which brings the stability of the whole project to the question, or maybe it proves that Open Source is actually very stable. That if a project is missing developers, some other people can step up and start maintaining the solution. But its nice that this functionality will be provided by Golang itself.
- figmert 3y agoThe ownership transfer was publicly announced in quite a few places. Most notably gorilla's blog[0], but also on Reddit[1] as well as HN[2], though it didn't get much reaction on HN. [0] https://gorilla.github.io/blog/2023-07-17-project-status-update/ https://gorilla.github.io/blog/2023-07-17-project-status-upd... [1] https://www.reddit.com/r/golang/comments/1528e25/gorilla_web_toolkit_new_maintainers_announcement/ https://www.reddit.com/r/golang/comments/1528e25/gorilla_web... [2] https://news.ycombinator.com/item?id=36935541 https://news.ycombinator.com/item?id=36935541
- Cupprum 3y agoYes, but the ownership transfer happened quite some time after the project was tagged as archived, which from my side was a flag to move to a solution which is actively maintained. But i am glad to see that the project is getting attention again, i liked using gorilla/mux. But its even nicer that there is going to be out of the support for similar baked into golang.
- danpalmer 3y agoIn my experience most developers are not connected to the development processes of their dependencies. Maybe they should be, I think there's an argument for it, but with so many dependencies there's only so much time and attention. I think any migration such as this should be done on the assumption that most people won't know it's happening.
- shp0ngle 3y agoOne thing I don't like about default ServeMux is that addresses are prefixes. So `mux.HandleFunc("/"...)` always handles _everything_. And there is no easy way to say "no, just handle exact matches, plus maybe ? queries". As gorilla/mux does. I don't think that is changed in the new go?
- mariusor 3y agoI would say that unless specified differently this bit of the documentation[1] still applies (second paragraph): > Longer patterns take precedence over shorter ones, https://pkg.go.dev/net/http#ServeMux https://pkg.go.dev/net/http#ServeMux
- jbamsterdam 3y agoNo, it does not. The length of the pattern isn't important anymore, only whether it overlaps with other patterns.
- mariusor 3y agoThat would be a breaking API change in my opinion. I bet you the length criterion will still apply for path patterns that don't include placeholders, even if not specifically mentioned in the proposal. [edit] Heh, I guess you'd know better. :D The comparison between the hn username and github username resolved favourably in the end. :P
- deleted 3y ago[deleted]
- FiloSottile 3y agoFrom the proposal: "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."
- the_gipsy 3y agoStringly typed, runtime panics...
- burnished 3y agoThe panics occur on service startup which is good - you know immediately what is wrong and what went wrong. The string typing thing.. I can understand why they did it even if it is not the thing I would have chosen.
- talent_deprived 3y ago[flagged]
- jjgreen 3y agoOracle over Google is debatable, but piss up a rope sideways is pure gold.
- talent_deprived 3y agoMost of us are running openjdk, not Oracle Java, openjdk, which is ... open...source.
- oppositelock 3y agoIt's a nice change for little experimental programs, but production servers need lots of functionality that third party routers offer, like request middleware, better error handling, etc. It's tedious to build these on top of the native router, so convenience will steer people to excellent packages like Gin, Echo, Fiber, Gorilla, Chi, etc.
- kunley 3y agoHonestly, there is a lot of praise of the middleware in these projects, but I recently found out that most of them are unable to handle parsing the Accept and Accept-Encoding header properly, that is: according to the RFC, with weights. This means that the general perception "these projects are production-quality but stdlib isn't" is misleading. If I have to choose web framework or library that implements feature X incorrectly versus one that doesn't have X at all and I have to write it by myself, I will with no doubt choose the latter.
- BodyCulture 3y agoPlease post issue numbers, thanks!
- kunley 3y agoExcuse me, what issue numbers?
- moomoo11 3y agoBig fan of chi. It is simple, and just works. Also matches the http.Handler interface, so for testing and otherwise just makes life so easy (like using httptest new server - pass it the chi.Mux)
- imetatroll 3y agoMan I really really dislike this proposal. Putting the http request method into the URI... an just sometimes... oh and maybe do "POST,PUT,PATCH /something". Just no thank you. Make a dedicated method that accepts http request method names if you must do something.
- thiht 3y agoWhy? It really doesn't change a lot. You can't "really dislike" the whole proposal for just a stylistic issue. > Make a dedicated method that accepts http request method names What problem does that solve compared to embedding the method in the string?
- burnished 3y agoYes, you can. Besides the API is more than just a stylistic issue. The problem it solves is not having string typed stuff which is a consistent pain point where it happens.
- greatgib 3y agoI agree, does not make much sense to design it like that. The function could have another argument for listing methods or options.
- rollulus 3y agoSlightly off-topic: where does one get an overview of what is planned to go into a new release?
- eliben 3y agoChanges are sometimes added to the release notes doc (in the master branch) during the dev cycle, but your best bet is watching the commit stream. The release notes are much more shaped up by the time RC rolls around (see https://github.com/golang/go/wiki/Go-Release-Cycle https://github.com/golang/go/wiki/Go-Release-Cycle)
- jchw 3y agoInteresting. Now that the mux can match methods, I wonder what happens when you match a route but not a method. Do you get a 404, or a 405? Apparently, a 405 with a properly-populated Allow header. https://cs.opensource.google/go/go/+/master:src/net/http/server.go;l=2553 https://cs.opensource.google/go/go/+/master:src/net/http/ser... I know people don't love the stringly-typed interface, but rather than typo the HTTP method name, I suspect I'm more likely to simply type the wrong thing in correctly anyways. So I'd be fine with having static analysis warn about bad syntax personally. For what it's worth, though, in my opinion, you probably shouldn't use the default serve mux if you already have advanced needs; there are plenty of options out there that are more suitable to different use cases, and if you're already doing dynamic route generation, it may wind up being less effort to just write your own router rather than try to munge whatever data structures you have into an existing router's.
- gavinray 3y agoIt seems like a very bizarre design choice, especially by an official language team, to use prefix strings instead of an enum. For example: (Http.GET, "/path") Rather than the current: ("GET /path") Odd.
- badrequest 3y agoIt adheres to the Go 1 compatibility promise.
- ilyt 3y agoIt's mistake they can't fix without breaking existing code or introducing new function. But I'd prefer just a bunch of mux.GET/mux.POST/etc functions instead of that.
- EspressoGPT 3y agoThis is a case where I, as a Go developer, would prefer to have function overloading for the sake of backwards compatibility . Because the compatibility promise is great, but the quirks caused by it aren't.
- Andrew018 3y ago[dead]
- ilyt 3y agoNeat but I still have no reason to use it as that "cottage industry" adds more than just fancier router.
- shortrounddev2 3y agoHow did this kind of syntax become so popular? I think express is the first to do the scheme of: app.handleGet("/route/goes/here", (req, res) => { }); Which seems like it's useful for making really quick and dirty micro-services (nano-services, even), but I still vastly prefer the more declarative and modular schemes of bootstrap or asp.net: // Middleware automatically routes "/Foo" to FooController [Controller] public class FooController : Controller { private IMyService _service { get; init; } // Declarative dependency injection FooController(IMyService service) { this._service = service; } // HTTP Method and Route are declarative [HttpGet("/")] public ActionResult GetBar([FromBody] Model myModel) // ^ automatic user input validation using reflection { this._service.create(myModel); // or whatever you need to do return Ok(); } } This has always seemed way more maintainable to me. Even microsoft has added the ability to do these quick and dirty HTTP routing methods along with top level statements.
- ramesh31 3y ago> This has always seemed way more maintainable to me. Even microsoft has added the ability to do these quick and dirty HTTP routing methods along with top level statements. Many people (myself included) hate decorators. Keep my code declarative and free of black magic, please. This pattern also conflates class structure with route structure. What if I wanted to assign a method in this class to another base route? You end up with routing strewn all over the application.
- shortrounddev2 3y agoThere's nothing "black magic" about them; they're very well documented features in Java, C#, and Python. They're officially experimental in Typescript but they're so heavily relied upon I can't imagine them getting deprecated
- ramesh31 3y ago>There's nothing "black magic" about them; they're very well documented features in Java, C#, and Python. Sure there is. It's metaprogramming. Anyone in the world who can read code can understand this immediately: app.get('/', (req, res) => { res.send('hello world') }) Throw decorators in the mix, and now I need to learn exactly what this specific environment is doing with those annotations. I have absolutely no way of understanding it at a glance. And you're now also stuck with vendor lockin to whatever framework/compiler was using them, and your code can no longer be fully isolated and unit tested without the framework.
- infogulch 3y agoI wonder if the new version takes the opportunity to make the muxer faster. I needed a path matcher / muxer and I settled on forking julienschmidt/httprouter [1] into my own project, pathmatcher [2]. What I like about the original it is that it uses a trie for mapping paths to handlers, which can look up routes very quickly [3], and is optimized for low/zero allocations. My changes generalizes it (the 'handler' value can be any generic type) and exposes the underlying data structure so you can do non-http.Handler things with it if you want. [1]: https://github.com/julienschmidt/httprouter https://github.com/julienschmidt/httprouter [2]: https://github.com/infogulch/pathmatcher https://github.com/infogulch/pathmatcher [3]: https://github.com/julienschmidt/go-http-routing-benchmark https://github.com/julienschmidt/go-http-routing-benchmark
- getknowledge 3y ago[flagged]
- dewey 3y agoPlease don't use ChatGPT to generate your comments.
- hknmtt 3y ago> Therefore, the new ServeMux documentation meticulously describes the precedence rules for patterns This is so backwards it's not even funny. It is supposed to be exactly the other way - conrete paths MUST TAKE PRECEDENCE over patterns.
- hashworks 3y agoIn the example there are no concrete path, both have variables. Is there an example in the documentation that allows paths with variables precedence over concrete paths?
- hknmtt 3y agoit is a quite form the article itself. they say that get /foo/{id} takes precedences over path get /foo/23 or get /foo/bar
- eliben 3y ago> conrete paths MUST TAKE PRECEDENCE over patterns. And indeed they do.
- bborud 3y agoI would actually have preferred that the old mux was left as is, and that the new mux had been a new package. This would have eliminated the need to be backwards compatible and allowed more freedom in creating a better API. I think that putting the method in the matching expression is a step back as it can't make use of the compiler to enforce correctness.