3

consider this scenario!

after successful execution of a http request, what if there is an error while performing json encoding, how to override the header code

func writeResp(w http.ResponseWriter, code int, data interface{}) {
    w.Header().Set("Content-Type", "application/json")

    //Here I set the status to 201 StatusCreated
    w.WriteHeader(code) 
    s := success{Data: data}

    //what if there is an error here and want to override the status to 5xx error
    //how to handle error here, panic?, http.Error() is not an option because as we already wrote header to 201, it just prints `http: multiple response.WriteHeader calls`
    if err := json.NewEncoder(w).Encode(s); err != nil {
        w.Header().Set("Content-Type", "application/json")

        //it throws http: multiple response.WriteHeader calls here as we already wrote header above to 201
        w.WriteHeader(code)
        e := errorResponse{
            Code:        code,
            Error:       error,
            Description: msg,
        }
        if err := json.NewEncoder(w).Encode(e); err != nil {
         //same how to handle here
        }
    }
}

I have multiple options here, if we do just fatal logging the user won't know exactly what happened, even if I write string using w.Write([]byte(msg)) still the status says 201 created, how to respond with error code 5xx

any help is greatly appreciated

  • You **must** **not** call WriteHeader until you know what status code to return. There is _absolutely_ _no_ _way_ to change that status after a call to WriteHeader. – Volker Mar 26 '18 at 03:11
  • but the `json.NewEncoder(w).Encode(e)` which does a Write internally sets the status to StatusOK if WriteHeader is not called explicitly, which is not what I needed, I wanted to set it to StatusCreated – Jagadeesh Venkata Mar 26 '18 at 03:16
  • 1
    Read the documentation. The internaly Write will sett 200 only if you did not WriteHeader yourself which you should do once you _know_ which status code you want to send. It really is simple. – Volker Mar 26 '18 at 05:30
  • 1
    Read the comment, what you described is exactly what I wrote in my previous comment, `json.NewEncoder(w).Encode(e) which does a Write internally sets the status to StatusOK if WriteHeader is not called explicitly`, lets say there is a failure in `json.NewEncoder(w).Encode(e)` and I want to set status code 201 for success and 5xx for encode failure, how would you do that? – Jagadeesh Venkata Mar 26 '18 at 14:19
  • Don't use json.Encoder. Use json.Marshal. It really is simple. Or write to an intermediate buffer. – Volker Mar 26 '18 at 16:23

1 Answers1

5

First of all, it does not seem very likely that you get an error when encoding.

See this question for reasons for Marshal to fail:

What input will cause golang's json.Marshal to return an error?

The other potential cause of error would be some problem with actually writing the data to the response stream, but in that case you'd not be able to write your custom error either.

Going back to your question, if you are concerned that encoding your object might fail, you can first Marshal your data (checking for error), then only write the 201 status code (and the encoded data) if marshalling succeeded.

Modifying your example a bit:

s := success{Data: data}
jsonData, err := json.Marshal(s)
if err != nil {
    // write your error to w, then return
}
w.WriteHeader(code)
w.Header().Set("Content-Type", "application/json")
w.Write(jsonData)

Now, that last write can also throw an error.

But if that happens, it will also fail when writing your custom error, so in that case you'd better log that in the server side (or send that error to a tracker such as New Relic, etc).

eugenioy
  • 11,825
  • 28
  • 35
  • 1
    thanks for the answer, this is the option I am following currently, the interesting question is `w.Write(Write([]byte) (int, error))` returns an error, how to handle that? – Jagadeesh Venkata Mar 26 '18 at 03:23
  • 2
    @JagadeeshVenkata: right, well, if you receive an error there I don't think you can notify the user using the http response (since writing your custom error response would also likely fail). You'd need to resort to server side logging and monitor those logs.. updated my answer to add that last part. – eugenioy Mar 26 '18 at 03:38
  • 1
    This answer can't be right: changing the header map after a call to `WriteHeader()` has no effect, as per https://pkg.go.dev/net/http@go1.17.1#ResponseWriter. – Agis Sep 23 '21 at 12:40