Live data from Hacker News

The problem with Go’s default HTTP handlers

preslav.me

21–30 of 97 posts

Re: The problem with Go’s default HTTP handlers

#22
post #9

Another possible extension of that design is using error types for HTTP codes. Something like: type HTTPError struct { Err error Code int } And then, with a couple of wrappers like this: func errorNotFound(err error) (wrapped error) { return &HTTPError{ Err: err, Code: http.StatusNotFound, } } you could do something like this: return errorNotFound(err) or this: return errorInternal(err) // 500 Internal Server Error

I do similar: I create a `handleError` function that deals with the error (logs it, reports it, redirects to the appropriate page), and call that in a return statement.

It's like 5 minute's work to code up, and I get to set whatever logging destination and redirect logic works best for my project.

Re: The problem with Go’s default HTTP handlers

#23
post #21

This is such a tiny problem to quibble about. Just create a helper function that indeed force you to return values. The default leaves you room to do harder things like custom chunk streaming.

They did in the article? They're not asking it to change, they're just expressing their opinion.

Re: The problem with Go’s default HTTP handlers

#24
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…

"net/http" doesn't seem to come with a lot of great defaults that almost every web app would use such as named path parameters which you'd need to write a bunch of code and regex to extract, middleware, routing based on HTTP method etc. This is all stuff you'd have to implement anyway I'd much prefer using something that's an abstraction of it like Gin, Chi, Echo and the like. They're fairly performative and add very…

I use Gin and enjoy the conveniences. Part of the reason is that it is opinionated. However, for the standard library, I think a strong, but minimal base is more important. One could argue about how extensible net/http is, but it seems reasonable as a foundation and it has been extended in a variety of opinionated (to varying degrees) ways!

Re: The problem with Go’s default HTTP handlers

#25

I agree, I wouldn't have been against a required return, but the mitigation is relatively straightforward by using custom handlers like mentioned at the end, if needed. I really like Go's default HTTP handlers because it offers that straightforward flexibility. Nothing rocket-sciencey.

Well yeah, I think it’s important to let you write bytes to the socket and close it when you want to, and if people are paranoid about making obvious, easy to spot bugs, they can use some abstraction to protect themselves.

Re: The problem with Go’s default HTTP handlers

#26
So, the issue is that there’s an implicit contract that says something like “a handler must write to the ResponseWriter or call http.Error before returning”, but the compiler doesn’t enforce that contract.

The proposed improvement makes some ways to accidentally not adhere to the contract more obvious to humans, but still doesn’t enforce the contract.

I wonder whether there are languages that allow one to write a library that enforces such contracts at compile time.

(One way to do that would be to pass the Request and the ResponseWriter together with a state machine that is hard-coded in the API as a single object to the handler, with the equivalents of w.Write and http.Error declaring their state transitions, and having a language functionality that requires such object to be in an end state of that state machine whenever the function returns)

Re: The problem with Go’s default HTTP handlers

#27
With Rust you could write a function that consumes the HTTP client, not allowing you use it after, resulting in a compiler error for your example.

In Go you could write a function taking the HTTP client reference and setting it to null. This would at least produce a runtime error if it ever comes across the path rather than doing something else that is unexpected.

Re: The problem with Go’s default HTTP handlers

#28
post #13

Streamed-response use cases get weird/complicated if you have to return the response.

Not necessarily? The type used for Response can include a "body" type that supports streaming. E.g., body: AsyncRead in Rust. In Go I think it would resemble a channel that emitted the contents of the body.

(As a library, though, you'd also want to make the simple non-streaming case of "just return this blob of bytes" simple for the consumer, too.)

Re: The problem with Go’s default HTTP handlers

#29

I agree, I wouldn't have been against a required return, but the mitigation is relatively straightforward by using custom handlers like mentioned at the end, if needed. I really like Go's default HTTP handlers because it offers that straightforward flexibility. Nothing rocket-sciencey.

> the mitigation is relatively straightforward by using custom handlers like mentioned at the end, if needed.

Why not implement ServeHTTP on the custom handler though? That's not exactly difficult:

    type MyHandlerFunc func(w http.ResponseWriter, r *http.Request) error

    func (f MyHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
     err := f(w, r)
     if err != nil {
      http.Error(w, err.Error(), http.StatusInternalServerError)
     }
    }
Then you can just register it with

    http.Handle("/hello", MyHandlerFunc(someHandler))

Re: The problem with Go’s default HTTP handlers

#30
post #3

It's not something I even thought about, but I guess I see the point, I just don't agree. In the HTTP handlers it makes sense that you don't have return values, because: What would you do with that value exactly? The HTTP handler should always ensure that "something" is returned, at the very least a return code. Once something has been returned to the client, can you really argue that there's an error? There's a poin…

> In the HTTP handlers it makes sense that you don't have return values, because: What would you do with that value exactly?

I think that approach used by clojure's ring shows an elegant way to represent http responses https://github.com/ring-clojure/ring/wiki/Concepts#responses. They are essentially structs with the following fields:

status := number

headers := map of string->string

body := stream | string | seq | inputstream

Request handlers are handed a request struct that is similar. The handler is a function that maps a request to a response (it doesn't actually write to streams itself).

I like this style for an http library for a couple of reasons:

1. HTTP resources can be viewed as functions whose domain is the request, and range is the response. Having the abstraction match that makes for really nice code. 2. If you model the request/response structs symmetrically and expose your http client as a handler itself, you can write proxies very easily. For an example of this, see http4k (https://www.http4k.org/documentation/) (a kotlin library).

I won't argue that the net/http abstraction should change, but I do agree with the author's take on the desired shape of an http library. I'd probably just write my own abstraction on top to fill the gap.

Post reply on HN