Home > Software design >  Modify http.ResponseWriter when passed as a function argument
Modify http.ResponseWriter when passed as a function argument

Time:10-08

I have an auth middleware that handle app authentication, with several cases to checks, each check have the same logic in case of an error:

res, err := doSomeCheck()
if err != nil {
    log.Println("Authentication failed: %v", err)
    json.NewEncoder(w).Encode(struct {Error string}{Error: "something is broke!"})
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusForbidden)
    return
}

I want to write this logic once (the only difference between each case is the error and the client message) with some function like this:

func authError(w http.ResponseWriter, err error, clientMsg string) {
    log.Println("Authentication failed: %v", err)
    json.NewEncoder(w).Encode(struct {
        Error string
    }{Error: clientMsg})
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusForbidden)
    return
}

But w is not a pointer (I don't get it as a pointer to the middleware handler) so I can't change it from the function, authError() doesn't change the actual response. How can I make this work elegantly?

CodePudding user response:

w is not a pointer, but it's of an interface type, and it wraps a pointer under the hood. So you may pass it as-is, and when you call its methods, it will be reflected at the caller.

Just don't forget that if there's anything written to the response prior, you can't write the header (again). Same, if your authError() writes something to the output, the caller can't take that back. If authError() generates the response, the caller should return in that case.

Also note that you must first set headers, then call ResponseWriter.WriteHeader(), and only then can you write the response body.

If you call ResponseWriter.Write(), that will write the response status if it hasn't been (assuming HTTP 200 OK).

Quoting from ResponseWriter.Write():

// If WriteHeader has not yet been called, Write calls
// WriteHeader(http.StatusOK) before writing the data. If the Header
// does not contain a Content-Type line, Write adds a Content-Type set
// to the result of passing the initial 512 bytes of written data to
// DetectContentType. Additionally, if the total size of all written
// data is under a few KB and there are no Flush calls, the
// Content-Length header is added automatically.
Write([]byte) (int, error)

So your authError() should be something like this:

func authError(w http.ResponseWriter, err error, clientMsg string) {
    log.Println("Authentication failed: %v", err)
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusForbidden)

    err = json.NewEncoder(w).Encode(struct {
        Error string
    }{Error: clientMsg})
    if err != nil {
        log.Println("Failed to write response: %v", err)
    }

    return
}
  •  Tags:  
  • go
  • Related