[HN Gopher] The problem with Go's default HTTP handlers
       ___________________________________________________________________
        
       The problem with Go's default HTTP handlers
        
       Author : p5v
       Score  : 142 points
       Date   : 2022-08-09 15:01 UTC (7 hours ago)
        
 (HTM) web link (preslav.me)
 (TXT) w3m dump (preslav.me)
        
       | n4jm4 wrote:
       | I've made that same mistake before. And not just in HTTP
       | handlers.
       | 
       | There is a case to be made for a linter rule that warns when a
       | conditional evaluates an error value, without presenting a return
       | path.
        
       | mrweasel wrote:
       | 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.
        
         | gorjusborg wrote:
         | > 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.
        
           | morelisp wrote:
           | There are major performance advantages, especially once
           | you're past HTTP/1.1, to passing a mutable response (i.e. in
           | Go a ResponseWriter) to the handler rather than expecting the
           | handler to return something created whole-cloth.
        
             | dap wrote:
             | What advantages are you thinking of? The obvious one is
             | that you might want to stream the response rather than
             | construct it in-memory at once. But as I read the parent
             | (and as I've used similar interfaces in Rust), you don't
             | have to have constructed the whole response with this
             | interface. You can return something whose body is a stream.
        
               | morelisp wrote:
               | 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.
        
               | TheDong wrote:
               | > 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, req *http.Request) {             fi,
               | err := os.OpenFile("/tmp/file")             if err != nil
               | {                 rw.WriteHeader(500)
               | rw.Write([]byte("error opening file"))             }
               | io.Copy(rw, fi)             fi.Close()         }
               | // returning a streaming Response object         func
               | responseHandler(req *http.Request) *http.HandlerResponse
               | {             fi, err := os.OpenFile("/tmp/file")
               | if err != nil {                 return
               | &http.HandlerResponse{Status: 500, Body:
               | ioutil.NopCloser(bytes.NewReader([]byte("error opening
               | file"))}             }             return
               | &http.HandlerResponse{Status: 200, Body: fi}         }
               | 
               | In the happy path, there's no difference in number of
               | goroutines or anything, right? In both cases, this all
               | runs in the goroutine or thread or whatever that the
               | caller _already_ created for the request handler. What
               | difference does it make if the request handler calls
               | `io.Copy(response, body)` or if your function does, in
               | both case it's the same goroutine/thread.
               | 
               | > You have also gained ~nothing.
               | 
               | You were the one that claimed you lose massive
               | performance, and that's what we're asking about. The
               | ergonomics are a separate thing from performance
        
         | jerf wrote:
         | "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.
        
           | morelisp wrote:
           | 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, sort of works but often
           | takes some performance hit 19% of the time, and blows up
           | utterly when it composes poorly with someone else's 1% of the
           | time.
        
           | aniforprez wrote:
           | "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 little overhead on top of the standard package
        
             | biomcgary wrote:
             | 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!
        
       | jamal-kumar wrote:
       | 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.com")
       | writer.Header().Set("Access-Control-Allow-Methods", "POST, GET,
       | OPTIONS, PUT, DELETE")             writer.Header().
       | Set("Access-Control-Allow-Headers", "Accept, Content-Type,
       | Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
       | origin.ServeHTTP(writer, req)         })
       | http.Handle("/", wrapped)
       | 
       | You end up writing a ton of boilerplate like this if you're
       | sticking with the standard library, but having a ton of awareness
       | and control over every aspect of what you're doing is really just
       | so easy in this language. If I don't understand anything in the
       | standard library I just go to the definition of it in the golang
       | source code and usually end up figuring out what I need. It's
       | really impressive how much you can get done without using any
       | packages if you're writing anything net-facing that just needs to
       | do one thing and do it well.
        
       | ainar-g wrote:
       | 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
        
         | marcus_holmes wrote:
         | 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.
        
         | _adamb wrote:
         | I usually create an abstraction like this in an API to require
         | all returned errors to come with some sort of machine readable
         | code. You either have to classify this response with an
         | existing code in the list or add a new one. That list then gets
         | generated as part of the documentation so consumers are getting
         | possible error states straight from the code (without having to
         | look at said code).
        
       | alectic wrote:
       | Streamed-response use cases get weird/complicated if you have to
       | return the response.
        
         | deathanatos wrote:
         | 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.)
        
         | masklinn wrote:
         | Sure but is that the usual / default? Why not have a second
         | adapter?
        
       | alingse wrote:
       | we have already do this in prod.
       | 
       | type HandlerFuncErr func(w http.ResponseWriter, r _http.Request)
       | error
       | 
       | func HandleErr(h HandlerFuncErr) http.HandlerFunc { return func(w
       | http.ResponseWriter, r _http.Request) { ... } }
        
       | didip wrote:
       | 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.
        
         | badhombres wrote:
         | They did in the article? They're not asking it to change,
         | they're just expressing their opinion.
        
       | Someone wrote:
       | 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)
        
         | jayd16 wrote:
         | Well its a bit of an issue with escape analysis to know for
         | sure that something is or isn't called before the handler
         | returns. Imagine a scenario where multiple threads get
         | involved. That would be a lot to track.
         | 
         | I don't think its a great pattern but just speaking
         | hypothetically, if you ensure that only valid Response
         | instances exist (because you organized the constructors to only
         | make valid instances and nulls are invalid) you can force the
         | user to return a valid instance.
        
           | mjevans wrote:
           | ... Warning ${function} took too long to check and was
           | skipped; consider refactoring if possible or add
           | UNSAFE.NoEscapeTracking to the function signature.
           | 
           | Maybe something like that to let the humans know and make
           | higher level choices?
        
           | Someone wrote:
           | Yes, but there's related prior art.
           | 
           | Firstly, Java requires the compiler can prove variables are
           | initialized before first use, and there's a precisely
           | described, fairly restricted algorithm that must be able to
           | do that.
           | 
           | Similarly, Rust can't correctly accept all code without
           | ownership issues, so it supports only a subset of such code
           | (in this case, AFAIK, that set is defined by the compiler,
           | and growing over time)
           | 
           | In both cases the language designers on purpose limited what
           | programs are valid so that the compiler can enforce certain
           | constraints, eradicating a class of bugs.
           | 
           | I wonder whether there's a language that works similar for
           | this kind of constraints.
           | 
           | C++ destructors can do it for simple things such as "you must
           | close this file before returning", but that's because the
           | compiler will insert the call where needed.
           | 
           | Enforcing "you must call _f_ before you ever call _g_ " also
           | is doable in simple cases: have _f_ return a value of a type
           | that must be passed to _g_ or an object _foo_ on which one
           | _g_ is implemented as a member: _foo.g()_.
           | 
           | I'm not aware of any language that allows for more complex
           | state transitions, though.
           | 
           | Edit: I think Rust can do (?most of?) this by having a
           | function take ownership of an object passed in, thus
           | preventing further calls to it, and returning a new object on
           | which only the then permissible calls can be made. I'm not
           | sure one can require that to end with a 'stopping state'
           | object, though.
        
       | oppositelock wrote:
       | The low level HTTP library is phenomenal for building more
       | friendly packages on top, think of it as assembly language for
       | HTTP in go. What isn't obvious about that Go http design is that
       | it's very parallel. Your callback gets invoked in a goroutine for
       | each request, so you can write easy to understand code, linearly,
       | without worrying about things like promises or callbacks or
       | message queues.
       | 
       | I'm opinionated here, because I also think all API's should be
       | built starting from a spec, such as OpenAPI, and to that end,
       | I've written a library to do just that, generate models and echo
       | handlers from API specs, and other contributors have added gin,
       | chi, etc [1]
       | 
       | 1: https://github.com/deepmap/oapi-codegen
        
       | binwiederhier wrote:
       | I agree that they are awkward. I've solved it similarly by
       | defining a custom handleFunc [1] and a custom errHTTP struct [2]
       | which contains the HTTP response code and even more detailed
       | error codes. It is very nice to work with.
       | 
       | Like so:                 if err == util.ErrLimitReached {
       | return errHTTPEntityTooLargeAttachmentTooLarge       }
       | 
       | Or so:                 if err != nil {         return
       | wrapErrHTTP(errHTTPBadRequestActionsInvalid, err.Error())       }
       | 
       | [1]
       | https://github.com/binwiederhier/ntfy/blob/main/server/serve...
       | 
       | [2]
       | https://github.com/binwiederhier/ntfy/blob/main/server/error...
        
       | blablabla123 wrote:
       | Seems a rather theoretical problem to me because usually an
       | external library is used. Also in the underlying http response
       | first the return code is written before the body. This matters a
       | lot when for instance copying a buffer into the response
        
       | timvisee wrote:
       | 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.
        
       | skybrian wrote:
       | There's a subtle problem in the proposed solution: if an error
       | happens after you already started writing the response, you can't
       | change the http status code.
       | 
       | So, what should you do with an error that happens while writing
       | the HTTP response itself? Maybe keep statistics on how often it
       | happens, in case happens often enough that it seems relevant for
       | debugging. But there's no good way to report an error to the
       | client because the network connection is likely broken.
       | 
       | If you're not going to keep statistics, dropping errors from
       | writing an http response is reasonable and arguably correct.
        
       | [deleted]
        
       | dralley wrote:
       | 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<CreateUser>, ) -> 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.
        
         | laumars wrote:
         | There are Go HTTP libraries that behave that way too. This
         | article only refers to the one in standard library.
        
         | masklinn wrote:
         | I've not yet gotten to use it, but I understand Axum also
         | implements IntoResponse for Result<A: IntoResponse, B:
         | IntoResponse>, 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<T, http::error::Error>`, 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.
        
         | merb wrote:
         | the problem is that when you use NO_CONTENT that you prolly can
         | violate the http spec. (you can do that with both)
        
         | pram wrote:
         | That is what Gin does.
        
           | maccard wrote:
           | Gin is excellent. It's the right balance of batteries
           | included, sensible defaults, and black magic
        
           | suremarc wrote:
           | You don't return anything in Gin.
        
       | lhorie wrote:
       | IMHO this boils down to purity vs side effects. Given a black box
       | function, it's a lot easier to reason about what it did if it
       | behaves in a pure manner returning some data structure that
       | represents some form of state, than passing some mutable complex
       | real world thing into it and then trying to inspect what the
       | thing did after the fact.
       | 
       | But the matter of purity vs side effects is completely orthogonal
       | to the default HTTP handler design. The handler provides the
       | inputs and the output sinks that represent the "real world". But
       | it's entirely up to you how you manage the data therein. You can
       | continue to procedurally consume these input/output entities
       | turtles all the way down, or you can work in a functional style
       | until you are actually ready to flush the result of the
       | computation down the writer.
        
       | halfmatthalfcat wrote:
       | 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.
        
         | morelisp wrote:
         | People put way too much stuff in the context (whether that's
         | the req.Context() or the gin.Context or whatever). It should
         | only be for things that are request-scoped _and_ need to cross
         | middlewares that aren 't aware of them. Most such middlewares
         | (logging, metrics, last-ditch error handling) get put at the
         | front of the chain anyway.
         | 
         | A circuit-breaker is basically by definition not request-
         | scoped, so should not go in the context.
        
       | silentprog wrote:
       | I was in the same camp as you a few years ago and also wrote an
       | utility to handle this. Might be of any interest to you.
       | 
       | https://github.com/barthr/web-util
       | 
       | And the blog post explaining it:
       | https://bartfokker.com/posts/clean-handlers
        
       | JustSomeNobody wrote:
       | They're not functions, they're procedures. Procedures don't
       | return values. Write your code accordingly.
        
       | boredumb wrote:
       | 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.
        
       | donatj wrote:
       | So the problem with these "good" examples is that they expose the
       | end user to _Error_ s, messages likely not written by you or your
       | team and moreso not intended for general consumption - you need
       | to be really careful about doing so because an unexpected error
       | could expose lots of troubling things like credentials and
       | general information about your system you'd rather not expose.
       | 
       | We've got a general rule of thumb never to expose the message of
       | an Error to the end user.
        
         | collinvandyck76 wrote:
         | Yep, that's why you'd want the error handler to try and type
         | assert the error to something like an HTTPError where that's an
         | interface you control. If it is an HTTPError, then you can
         | "trust" the code/message and write that back to the user.
         | Otherwise, 500 with a generic response body.
        
       | badhombres wrote:
       | To me this reads as a positive to the default handlers, as it
       | shows how easy it was to make it work into a format that works
       | for the programmer's preference. I appreciated the solution to
       | the complaint
        
       | adontz wrote:
       | To me it seems like a problem induced by the way Go handles
       | errors. With exceptions it would be something like
       | try         {             // Some dangerous code which may
       | throw/raise             w.Write([]byte("Hello, World!"))
       | }         catch( FileException )         {
       | w.Write([]byte("Screw your file!"))         }         catch( ...
       | )         {             w.Write([]byte("Screw you in general!"))
       | }
       | 
       | and no such problem
        
         | randomdata wrote:
         | There is nothing special about errors, though. This same
         | problem is present for any kind of branching operation.
         | age := getUserAge()       if age < 18 {
         | w.Write([]byte("You are too young"))          // Forgot to
         | return here...       }       w.Write([]byte("<insert adult
         | content>"))
        
         | laumars wrote:
         | Putting aside discussions about Gos error handing and opinions
         | about try and catch blocks, this specific problem (wrt the HTTP
         | handler) can emulate the same code you've got above using a
         | defer block.                 var err error       defer func() {
         | if err != nil {           w.Write([]byte(err.Error())         }
         | }()
        
       | AtNightWeCode wrote:
       | Surely one can make the exact same mistake with Echo?
       | 
       | if id == "" { fmt.Errorf("get album: missing id") }
        
       | chrsig wrote:
       | I dont necessarily agree they should return a value, but I do
       | have some issues with the api:
       | 
       | - by default, you can't tell if a handler has already written to
       | the body or emitted a status code.
       | 
       | - by default, you can't read the body more than once
       | 
       | - r.PostForm vs r.Form ...that they both exist, and that they're
       | only available after a manual call to r.ParseForm
       | 
       | - the http server accepts an unbounded number of connections.
       | it'll just keep making goroutines as they come in. there's no way
       | that I know of to apply back pressure.
       | 
       | there's probably more that I grump about, but those are the ones
       | at the top of my mind. for the most part it works well, and i can
       | work around my gripes.
        
         | morelisp wrote:
         | _- by default, you can 't read the body more than once_
         | 
         | This is a reasonable default - probably the only reasonable
         | default. `TeeReader` and `MultiReader` are easily available if
         | you want to spare the memory. (But you're right that the
         | converse on the ResponseWriter isn't true, it's much more
         | difficult to get your own implementation right.)
         | 
         |  _- the http server accepts an unbounded number of connections.
         | it 'll just keep making goroutines as they come in. there's no
         | way that I know of to apply back pressure._
         | 
         | `net.Listener` is an interface you may implement as you choose.
         | But in most cases a CPU limit is more than sufficient
         | backpressure.
        
       | jayd16 wrote:
       | Maybe this is a subtlety of Go I'm not aware of, but at least in
       | most languages, the issue with the explicit return value is that
       | you must first return before you can send the value.
       | 
       | By being able to call into the response object, the handler is
       | able to send to the client immediately, and then the handler
       | writer can do other bookkeeping before exiting the handler.
       | 
       | With a return value, work would need to be fire off
       | asynchronously before the return value is sent.
        
       | topicseed wrote:
       | 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.
        
         | antihero wrote:
         | 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.
        
         | andrewstuart2 wrote:
         | The beauty is in the simplicity. `http.Handler` is all about
         | what is needed to handle HTTP and nothing else. It doesn't
         | intend to make things pretty, or safe, or anything really,
         | except to express the minimum surface area required to interact
         | with HTTP while abstracting the commonalities. Beyond that, as
         | the article mentions, it's quite easy to abstract and implement
         | your own sugar layer.
        
         | masklinn wrote:
         | > 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))
        
           | andrewstuart2 wrote:
           | There are definitely edge cases here (like what if the
           | handler already wrote a body and it was sent on the wire,
           | etc), but this is absolutely the pattern to use, and one of
           | my favorite parts of a type system that allows methods on
           | function types.
        
             | catlifeonmars wrote:
             | Yes, or what if you want to upgrade the connection to a
             | websocket?
        
           | skrtskrt wrote:
           | Actually a bunch of router/web helper libraries do this in
           | different ways which makes it a huge pain to compose
           | middleware from different libraries.
           | 
           | Even Mux's middleware library is not consistent in how it
           | does this for different middlewares so they have to be
           | applied different ways or even have wrapper functions written
        
             | masklinn wrote:
             | Here it just implements the standard library's interface
             | rather than use some sort of conversion closure adapter,
             | there's nothing special to it.
        
             | catlifeonmars wrote:
             | This. Middlewares typically expect to wrap an http.Handler
             | so you lose all the middleware composition ergonomics by
             | adding a return value to your custom handler signature
        
       | weltensturm wrote:
       | Why is it http.Error(w, ...) and not w.Write(http.Error(...))?
       | The latter would be way more clear in showing that nothing
       | magical happens and a return (if wanted) is still necessary (I
       | don't write Go, so I don't know the conventions)
        
         | tptacek wrote:
         | `http.Error` is a convenience for writing an error message and
         | setting the code. You can just use `w.Write` if you want. The
         | reason `Error` isn't part of the interface signature for
         | `ResponseWriter` is presumably that everything that implements
         | `ResponseWriter` would have to duplicate the logic.
         | 
         | Either way, it wouldn't address the bug here.
        
       | vrnvu wrote:
       | I dislike the example.
       | 
       | The author says that because we have a complex function, and the
       | HTTP handler is designed the way it is, we cannot avoid running
       | into problems. I disagree. This are two separate things.
       | 
       | You could take a more functional approach and separate the main
       | logic and the side effect. Which is writing the response.
       | func aMoreAdvancedHandler(w http.ResponseWriter, r *http.Request)
       | {             res, err := helper()             if err {
       | http.Error(w, err.Error(), err.Status())             }
       | w.write(res)          }              func helper() ([]byte, err
       | error) {            // todo         }
       | 
       | Where now the helper function has the problem of dealing with
       | multiple operations that can potentially fail. Which is exactly
       | the problem in the example. Not the HTTP handler itself...
       | 
       | For example, we could use the defer function call with a named
       | error to check if something fails and guarantee that we always
       | return an appropriate error. Similar to the pattern used to free
       | resources... I don't know how is this commonly called. But again,
       | the problem is the helper function not the handler.
       | 
       | I don't want to use the FP terminology, probably most people are
       | familiar with this pseudo-code:                   func helper()
       | (res, err) {            return foo().bar().baz()         }
       | 
       | Where the chain foo, bar, baz will continue to be executed if all
       | the calls are success, but early terminate if an error occurs.
       | 
       | So, where is the problem?
        
         | FpUser wrote:
         | I very much prefer this approach as well. Way better than
         | endless chaining of this().that().repeat().million().times().
        
       | [deleted]
        
       | zxcvbn4038 wrote:
       | Who cares? Rando with a blog, maybe he'll like nodejs better.
        
       ___________________________________________________________________
       (page generated 2022-08-09 23:00 UTC)