Live data from Hacker News

The problem with Go’s default HTTP handlers

preslav.me

71–80 of 97 posts

Re: The problem with Go’s default HTTP handlers

#71
post #67

Earlier quoted context omitted.

If you return something whose body is a stream you still have to construct that thing, including the stream. And if you return something whose body is a stream you didn't fill in yet, you need to create entire async thunks or threads to fill that data in. You have also gained ~nothing.

> There are major performance advantages You didn't outline the performance advantage. You've got "you need to create entire async thunks or threads to fill that data in", but that absolutely isn't true. Let's look at how we might stream a file in response in go with the current model vs this new model, and you can point out where the performance difference is: // current func responseHandler(rw http.ResponseWriter,…

Now do a body that requires literally any transformation or generation at all. Say, a series of DB query results as a CSV. (Oh, and make sure to cancel work when the request disconnects!)

Or heck, just try adding some error handling from the `io.Copy` result to what you have.

Re: The problem with Go’s default HTTP handlers

#75
post #67

Earlier quoted context omitted.

> There are major performance advantages You didn't outline the performance advantage. You've got "you need to create entire async thunks or threads to fill that data in", but that absolutely isn't true. Let's look at how we might stream a file in response in go with the current model vs this new model, and you can point out where the performance difference is: // current func responseHandler(rw http.ResponseWriter,…

Now do a body that requires literally any transformation or generation at all. Say, a series of DB query results as a CSV. (Oh, and make sure to cancel work when the request disconnects!) Or heck, just try adding some error handling from the `io.Copy` result to what you have.

Just return an io.Reader that wraps the DB cursor and outputs CSV data... I fail to see what makes this so difficult. If the "mutable" approach involves passing down a mutable *http.ResponseWriter, then the dual approach of passing an io.Reader upstream is equivalent.

Re: The problem with Go’s default HTTP handlers

#76
For fairness's sake, I'd like to point out that Nodejs has the same behaviour. Helping a new developer create a web server, we fell into the bugs the author is talking about a few times. I was always quick to spot the problem with the benefit of experience, but a newbie debugging a "response was already sent" error was not the simplest thing in the world.

Re: The problem with Go’s default HTTP handlers

#77

Well yeah, the default stdlib HTTP handlers are very basic. The cool thing is that the standard library is replete enough to build tons on top of that, though. CORS headers using nothing but stdlib for example: origin := http.StripPrefix("/", http.FileServer(http.Dir("www"))) wrapped := http.HandlerFunc(func(writer http.ResponseWriter, req *http.Request) { writer.Header().Set("Access-Control-Allow-Origin", "whatever.…

This is a good example of how standard libraries should be. They are fine for simple use cases, and they provide the basic primitives for more complex third-party libraries to build on top of. Stuff like basic HTTP functionality.

Anything more complex than the example can be streamlined or abstracted away by a third-party library. People get very opinionated about how those kinds of abstractions should work, so it's probably better for the core Go team not to pick a model and spend time building it.

Re: The problem with Go’s default HTTP handlers

#78
post #4

I like the way Axum for Rust handles this. All response handlers return something that implements IntoResponse. Lots of types implement IntoResponse, thus making the simple cases really streamlined. async fn create_user( Json(payload): Json , ) -> impl IntoResponse { let user = User { id: 1337, username: payload.username, }; // this will be converted into a JSON response // with a status code of `201 Created` (Status…

Thank you! I ran into this branching problem the other day, ended up trying to return a complicated boxed future and gave up.

In retrospect it makes sense to do it the way you mentioned. I think because I’m not as confident with traits and rust’s brand of async I tried to get it to work the same way I would in other languages, which is to mess with the function’s return type myself, rather than let the compiler figure it out.

Re: The problem with Go’s default HTTP handlers

#79
post #5

Earlier quoted context omitted.

"In the HTTP handlers it makes sense that you don't have return values, because: What would you do with that value exactly?" I agree that the baseline net/http implementation is correct. There is no appropriate default error (return) handler that the framework could implement, at the level it lives at, that would be correct and wouldn't be limiting. However I very often in my own Go code immediately create an abstrac…

I agree with you that an error return would be superfluous and probably misused, but middleware with the default handler could be a lot more usable if you could just query a ResponseWriter's written status code. Instead everyone tries to do it by putting in their own implementation of the ResponseWriter interface proxying back to the 'real' one, which works perfectly but with a lot of extra code about 80% of the time…

I agree totally, and it's worth highlighting part of the problem for those who don't write Go. http.ResponseWriter superficially looks relatively simple, but it has a number of optional interfaces attached to it that allow for things like "hijacking" the connection (for things like websockets, that initiate an HTTP connection but use it to transform into something else). Writing a wrapper around the easy and declared use case is not that hard, but blows up in weird ways if you don't also handle all the optional interfaces, and that gets even more fun if you start having frameworks trying to layer multiple of these together.

Go interfaces are fun and all but optional interfaces can get a bit spiky when you start trying to use the decorator pattern on them. And in Go, decorator is a fundamental pattern in the toolkit used quite frequently, not an obscure novelty to pull out once every few years.

Re: The problem with Go’s default HTTP handlers

#80
post #4

I like the way Axum for Rust handles this. All response handlers return something that implements IntoResponse. Lots of types implement IntoResponse, thus making the simple cases really streamlined. async fn create_user( Json(payload): Json , ) -> impl IntoResponse { let user = User { id: 1337, username: payload.username, }; // this will be converted into a JSON response // with a status code of `201 Created` (Status…

Thank you! I ran into this branching problem the other day, ended up trying to return a complicated boxed future and gave up. In retrospect it makes sense to do it the way you mentioned. I think because I’m not as confident with traits and rust’s brand of async I tried to get it to work the same way I would in other languages, which is to mess with the function’s return type myself, rather than let the compiler figur…

Yep, it's a good tip, something I'd been hitting too!
Post reply on HN