Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check errors when calling http.ResponseWriter.Write()

Say I have this http handler:

func SomeHandler(w http.ResponseWriter, r *http.Request) {
    data := GetSomeData()
    _, err := w.Write(data)
}

Should I check the error returned by w.Write? Examples I've seen just ignore it and do nothing. Also, functions like http.Error() do not return an error to be handled.

like image 928
Dumitru Avatar asked May 15 '17 09:05

Dumitru


1 Answers

It's up to you. My advice is that unless the documentation of some method / function explicitly states that it never returns a non-nil error (such as bytes.Buffer.Write()), always check the error and the least you can do is log it, so if an error occurs, it will leave some mark which you can investigate should it become a problem later.

This is also true for writing to http.ResponseWriter.

You might think ResponseWriter.Write() may only return errors if sending the data fails (e.g. connection closed), but that is not true. The concrete type that implements http.ResponseWriter is the unexported http.response type, and if you check the unexported response.write() method, you'll see it might return a non-nil error for a bunch of other reasons.

Reasons why ResponseWriter.Write() may return a non-nil error:

  • If the connection was hijacked (see http.Hijacker): http.ErrHijacked
  • If content length was specified, and you attempt to write more than that: http.ErrContentLength
  • If the HTTP method and / or HTTP status does not allow a response body at all, and you attempt to write more than 0 bytes: http.ErrBodyNotAllowed
  • If writing data to the actual connection fails.

Even if you can't do anything with the error, logging it may be of great help debugging the error later on. E.g. you (or someone else in the handler chain) hijacked the connection, and you attempt to write to it later; you get an error (http.ErrHijacked), logging it will reveal the cause immediately.

Tip for "easy" logging errors

If you can't do anything with the occasional error and it's not a "showstopper", you may create and use a simple function that does the check and logging, something like this:

func logerr(n int, err error) {
    if err != nil {
        log.Printf("Write failed: %v", err)
    }
}

Using it:

logerr(w.Write(data))

Tip for "auto-logging" errors

If you don't even want to use the logerr() function all the time, you may create a wrapper for http.ResponseWriter which does this "automatically":

type LogWriter struct {
    http.ResponseWriter
}

func (w LogWriter) Write(p []byte) (n int, err error) {
    n, err = w.ResponseWriter.Write(p)
    if err != nil {
        log.Printf("Write failed: %v", err)
    }
    return
}

Using it:

func SomeHandler(w http.ResponseWriter, r *http.Request) {
    w = LogWriter{w}
    w.Write([]byte("hi"))
}

Using LogWriter as a wrapper around http.ResponseWriter, should writes to the original http.ResponseWriter fail, it will be logged automatically.

This also has the great benefit of not expecting a logger function to be called, so you can pass a value of your LogWriter "down" the chain, and everyone who attempts to write to it will be monitored and logged, they don't have to worry or even know about this.

But care must be taken when passing LogWriter down the chain, as there's also a downside to this: a value of LogWriter will not implement other interfaces the original http.ResponseWriter might also do, e.g. http.Hijacker or http.Pusher.

Here's an example on the Go Playground that shows this in action, and also shows that LogWriter will not implement other interfaces; and also shows a way (using 2 "nested" type assertions) how to still get out what we want from LogWriter (an http.Pusher in the example).

like image 147
icza Avatar answered Oct 31 '22 02:10

icza