Go httptest WriteHeader无效问题

2021/11/24 实践总结Go

# 背景

测试一个调用http接口时,使用了httptest.NewServer来mock一个http服务端,在验证响应异常状态码时发现奇怪问题,明明设置了异常状态码,但是http.Get始终返回的状态码都是正常的200:

mockHttpSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("hello world"))
	w.WriteHeader(http.StatusBadRequest)
}))
resp , err:= http.Get(mockHttpSrv.URL)
if err != nil {
	return
}
defer resp.Body.Close()
fmt.Println(resp.StatusCode) // 输出200
1
2
3
4
5
6
7
8
9
10

# 分析

查阅WriteHeader的文档时才发现原来当w.WriteHeader没明确调用时,默认第一次调用Write方法时,会隐式的触发WriteHeader(http.StatusOK):

// WriteHeader sends an HTTP response header with the provided
// status code.
//
// If WriteHeader is not called explicitly, the first call to Write
// will trigger an implicit WriteHeader(http.StatusOK).
// Thus explicit calls to WriteHeader are mainly used to
// send error codes.
//
// The provided code must be a valid HTTP 1xx-5xx status code.
// Only one header may be written. Go does not currently
// support sending user-defined 1xx informational headers,
// with the exception of 100-continue response header that the
// Server sends automatically when the Request.Body is read.
WriteHeader(statusCode int)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

我的单测代码将WriteHeader写在了Write方法之后,所以默认会先触发了WriteHeader(http.StatusOK)。

但是,为什么后来我调用WriteHeader(http.StatusBadRequest)会无效呢,正常不是应该覆盖前面的方法才对?!

认真阅读了 WriteHeader源码,原来WriteHeader只允许写入一次Header(通过一个布尔变量:wroteHeader,写入后被标志为true,再次调用就直接return):

// WriteHeader implements http.ResponseWriter.
func (rw *ResponseRecorder) WriteHeader(code int) {
   if rw.wroteHeader {
      return
   }
   rw.Code = code
   rw.wroteHeader = true
   if rw.HeaderMap == nil {
      rw.HeaderMap = make(http.Header)
   }
   rw.snapHeader = rw.HeaderMap.Clone()
}
1
2
3
4
5
6
7
8
9
10
11
12

这样问题就清晰了,是我WriteHeader顺序写错了,应该放在Write方法之前。

# 总结

WriteHeader只能调用一次,如果一开始先调用Write,默认会触发WriteHeader(http.StatusOK),后续再调用WriteHeader就无效。

所以在开发中如果要测试非200的状态时,需要先设置响应状态码WriteHeader(http.StatusBadRequest),然后再调用Write方法。