Live data from Hacker News

The problem with Go’s default HTTP handlers

preslav.me

1–10 of 97 posts

Re: The problem with Go’s default HTTP handlers

#2
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.

Re: The problem with Go’s default HTTP handlers

#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 point to the risk of missing return value, you just run the risk of complicating the request flow by adding it. I'd argue that most people would view the code path as having ended, once the client receives a response. The request shouldn't really continue on the server end after that. There can be background processing, go routines or something like that, but that's a bit different, as it doesn't involve the original requests-response.

Re: The problem with Go’s default HTTP handlers

#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`
    (StatusCode::CREATED, Json(user))
}

If the handler is complex enough that branches can't all return the same type, you can just add .into_response() to each return site, and now you're just returning a response, which also implements IntoResponse (as a no-op).

This avoids the problem discussed in this article while also avoiding much of the boilerplate of manually building responses.

Re: The problem with Go’s default HTTP handlers

#5
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 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 abstraction that does exactly this, because it is a good default for everything I'm doing. It just isn't suitable for everything (e.g., what do you do with this in the case of a protocol upgrade to websockets? the HTTP handlers need to be able to just terminate gracefully without being forced to "return" something).

What I would say is, net/http is a little too low level to directly use. You don't need a full framework necessarily, though if you want to use one that does this more power to you. But if you understand HTTP, it doesn't take much wrapping to make net/http useful for "normal" APIs or websites. It takes a little, though.

Re: The problem with Go’s default HTTP handlers

#6
This isn't so much a critique of the HTTP handlers themselves but relying on (and stuffing a ton of shit into) context as a way of dealing with complex request/response patterns was a major headache.

Trying to implement a circuit breaker against multiple upstreams for a single request in Go was a nightmare.

Re: The problem with Go’s default HTTP handlers

#7

    func (api \*App) HandleOnError(w http.ResponseWriter, 
    err error, status int, context string) bool {
     if err != nil {
      api.logging.Error(err, context)
      http.Error(w, err.Error(), http.StatusInternalServerError)
      return true
     }
     return false
    }
and then it's used in the handler funcs

    if api.HandleOnError(w, err, http.StatusInternalServerError, "Getting stuff for things") {
         return
    }

Makes the logging verbose and handling always properly taken care of, makes the error handling in handlers verbose enough from the caller but abstract enough to not be a majority of the handlers body.

Re: The problem with Go’s default HTTP handlers

#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 to join the paths back (usually by converting everything to a Response by hand), which makes mistakes easier.

Re: The problem with Go’s default HTTP handlers

#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
Post reply on HN