Live data from Hacker News

The problem with Go’s default HTTP handlers

preslav.me

81–90 of 97 posts

Re: The problem with Go’s default HTTP handlers

#81
I am writing a large Go web application right now and I disagree with this. I do not want to return an error. I want to write an error page or an error JSON message depending on the handler. By the time you are deep in a web application you are copy and pasting all your error blocks anyway. This [1] is a sample of one of my functions. I honestly love Go for how consistent it is.

I wrote a rudimentary static checker [2] to make sure that any function does not call both the jsonError() and errorPage() functions. This was quite a problem given how much I reuse code. I disagree with the author's take because it is incredibly obvious and quite easy to track down when you forget a return. You will have a big block of garbage in the middle of your page and JSON, and your unique error message should lead you directly to the line.

My problem is actually in executing the templates at the end of the functions [1], when you finally write out the page. If that has an error, you can't really back track. I've been thinking about changing that to write to a temporary buffer which then writes to the http.ResponseWriter but haven't made the change yet.

1. https://gist.github.com/TACIXAT/24621013248e2d91bebd16c5a02a...

2. https://gist.github.com/TACIXAT/1000e7d873f0a8a738b3e720b4a7...

Re: The problem with Go’s default HTTP handlers

#83
I can see the problem, but, I don't know, it's just not something that's ever bothered me. How big are his handlers anyway!? They should be pretty small IMO...

I've managed to write some pretty complicated REST services with just the standard HTTP library and Mux for routing.

In fact I've never used any of the bigger libraries like Gin or Echo.

While all of my Node JS colleagues are drowning in dependabot PRs we're just cruising by in the Golang team with our small handful of dependencies. Heaven!

Re: The problem with Go’s default HTTP handlers

#84
post #19
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…

That is what Gin does.

No, that's exactly the opposite of what it does. The article above explicitly criticizes gin for not returning a value.

Echo is closer to the pattern described above, but:

1. Echo lets you return an error response, but doesn't let you customize how the error is rendered _in a modular way_. You can only have a single global custom error handler that decides how to serialize the errors, and it needs to have knowledge of how to properly serialize every kind of error in your app. I have to admit this is generally good enough for most of the apps, and I don't think you can do any better in Go, considering it doesn't have type classes like Rust. The best that you can do is to let errors implement a CustomHttpRenderer interface on their own and try to cast into it — but this is already doable with a custom error renderer in Echo.

2. Echo doesn't let you return a non-error Response from the handler - instead it's written directly to the ReponseWriter. This approach makes writing unit tests several orders of magnitude harder and also means you can forget to write a successful response in some cases. You can also both write a response directly and return an error, which cannot result in a valid behavior (Echo will take the safe route and just ignore the error code and do nothing).

Re: The problem with Go’s default HTTP handlers

#85

Earlier quoted context omitted.

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.

This all is much more straightforward in languages with syntactic sugar for iterators, such as "yield" in C# and Python:

https://flask.palletsprojects.com/en/2.2.x/patterns/streamin...

Re: The problem with Go’s default HTTP handlers

#86

Earlier quoted context omitted.

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.

Try to write this, really. It’s such an unergonomic mess compared to for loop in the handler. It will also allocate a very complex reader that the actual version doesn’t need to.

Re: The problem with Go’s default HTTP handlers

#87
post #85

Earlier quoted context omitted.

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.

This all is much more straightforward in languages with syntactic sugar for iterators, such as "yield" in C# and Python: https://flask.palletsprojects.com/en/2.2.x/patterns/streamin...

Love too make those async thunks!

Re: The problem with Go’s default HTTP handlers

#88
In my team we used Gin and migrated to Echo (because Gin didn't support conflicting routes at the time, like /foo/{variable} and /foo/bar) and we got to the same conclusion. Forgetting to return with Gin (and with net/http) is an issue that actually occurs, and we've been bitten by it more times than we care to admit.

Of course it's not worth migrating to Echo just for that but it's good to know that some routers implement it differently, if it's something that bothers you.

Re: The problem with Go’s default HTTP handlers

#90
post #8
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…

I’ve not yet gotten to use it, but I understand Axum also implements IntoResponse for Result , which is very convenient for the sort of error code of the above. It’s one of my biggest annoyance with Warp: it only implements Reply for `Result `, so if you have a faillible handler with more complicated error types (e.g. with a json body, or even a 200 response for RPC handlers) you’re in the awkward position of having…

Correct, I spent about half an hour implementing IntoResponse for my own project's error type, but now I can just use normal error handling and I get presented with an error page on any issues. It's quite magic (not really, very streamlined).
Post reply on HN