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

huangapple go评论87阅读模式
英文:

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

问题

我有一个奇怪的情况。我想从一个HTTP处理程序中返回内容类型为application/json; charset=utf-8

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"})
	}
}

当我在单元测试中检查时,它是正确的。这个测试没有失败。

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
	}
}

但是当我使用curl(和其他客户端)尝试时,它显示为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

我已经尝试过使用curl、insomnia和python。在这3种情况下,内容类型都显示为text/plain; charset=utf-8

是什么导致了这个问题,我该如何解决?

英文:

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(&quot;Accept&quot;) != &quot;application/json&quot; {
			w.WriteHeader(http.StatusNotAcceptable)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set(&quot;Content-Type&quot;, &quot;application/json; charset=utf-8&quot;)
		json.NewEncoder(w).Encode(map[string]string{&quot;foo&quot;: &quot;bar&quot;})
	}
}

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, &quot;/test&quot;, nil)
	request.Header.Set(&quot;Accept&quot;, &quot;application/json&quot;)
	response := httptest.NewRecorder()
	handleTest().ServeHTTP(response, request)
	contentType := response.Header().Get(&quot;Content-Type&quot;)
	if contentType != &quot;application/json; charset=utf-8&quot; {
		t.Errorf(&quot;Expected Content-Type to be application/json; charset=utf-8, got %s&quot;, contentType)
		return
	}
}

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

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

答案1

得分: 4

http package docs中可以看到:

> WriteHeader使用提供的状态码发送HTTP响应头。

> 在调用WriteHeader(或Write)之后更改头映射不起作用,除非修改后的头是尾部。

所以在将头部发送给客户端后,您设置了"Content-Type"头部。虽然在模拟时可能有效,因为存储头部的缓冲区可以在WriteHeader调用后进行修改。但是,在实际使用TCP连接时,您不能这样做。

所以只需将w.WriteHeader(http.StatusOK)移到w.Header().Set(...)之后即可。

英文:

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(...)

huangapple
  • 本文由 发表于 2021年12月28日 21:05:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/70507501.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定