Home > Net >  content type of http response changes when using external clients but is correct in unit test
content type of http response changes when using external clients but is correct in unit test

Time:12-28

I have a strange situation. I want to return the content type application/json; charset=utf-8 from an http handler.

func handleTest() http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        if r.Header.Get("Accept") != "application/json" {
            w.WriteHeader(http.StatusNotAcceptable)
            return
        }
        w.WriteHeader(http.StatusOK)
        w.Header().Set("Content-Type", "application/json; charset=utf-8")
        json.NewEncoder(w).Encode(map[string]string{"foo": "bar"})
    }
}

When I check for this in my unit tests it is correct. This test does not fail.

func TestTestHandler(t *testing.T) {
    request, _ := http.NewRequest(http.MethodGet, "/test", nil)
    request.Header.Set("Accept", "application/json")
    response := httptest.NewRecorder()
    handleTest().ServeHTTP(response, request)
    contentType := response.Header().Get("Content-Type")
    if contentType != "application/json; charset=utf-8" {
        t.Errorf("Expected Content-Type to be application/json; charset=utf-8, got %s", contentType)
        return
    }
}

But when I try with curl (and other clients) it comes out as text/plain; charset=utf-8.

$ curl -H 'Accept: application/json' localhost:8080/test -v
*   Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /test HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.68.0
> Accept: application/json
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Tue, 28 Dec 2021 13:02:27 GMT
< Content-Length: 14
< Content-Type: text/plain; charset=utf-8
< 
{"foo":"bar"}
* Connection #0 to host localhost left intact

I have tried this with curl, insomnia and python. In all 3 cases the content type came out as text/plain; charset=utf-8.

What is causing this problem and how can I fix it?

CodePudding user response:

From the http package docs:

WriteHeader sends an HTTP response header with the provided status code.

and

Changing the header map after a call to WriteHeader (or Write) has no effect unless the modified headers are trailers.

So you are setting the "Content-Type" header after the header has already been sent out to the client. While mocking this likely works because the buffer where the headers are stored can be modified after the WriteHeader call. But when actually using a TCP connection you can't do this.

So simply move your w.WriteHeader(http.StatusOK) so it happens after the w.Header().Set(...)

  • Related