golang中设置http头用 w.Header().Set() ,设置状态码用 w.WriteHeader() , 设置body用 w.Write() 。但他们的调用顺序是有要求的。正确的调用顺序如下:

func HandleHello(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("X-name","john")
	w.WriteHeader(http.StatusOK)
	w.Write([]byte("hello world!"))
}
分析源码

我们来看看http.response中status code 和 header 相关的数据结构。golang源码的版本如下:

go version go1.15.7 darwin/amd64

response

// net/http/server.go:418

// A response represents the server side of an HTTP response.
type response struct {
	//...

	wroteHeader      bool               // reply header has been (logically) written

	w  *bufio.Writer // buffers output in chunks to chunkWriter
	cw chunkWriter

	// handlerHeader is the Header that Handlers get access to,
	// which may be retained and mutated even after WriteHeader.
	// handlerHeader is copied into cw.header at WriteHeader
	// time, and privately mutated thereafter.
	handlerHeader Header
	//...

}
  • wroteHeader 是否已经设置过http头的标记
  • w 写入到chunkWriter的writer
  • cw 内容会被输出到响应连接的buffer
  • handlerHeader

WriteHeader()

// net/http/server.go:1133

func (w *response) WriteHeader(code int) {

    //...

	if w.wroteHeader {
		caller := relevantCaller()
		w.conn.server.logf("http: superfluous response.WriteHeader call from %s (%s:%d)", caller.Function, path.Base(caller.File), caller.Line)
		return
	}
	checkWriteHeaderCode(code)
	w.wroteHeader = true
	w.status = code

	if w.calledHeader && w.cw.header == nil {
		w.cw.header = w.handlerHeader.Clone()
	}

    //...
}

如果已经设置过header,那么打印一行错误日志后直接返回。如果没有设置过header,先设置已经写入header的标记 w.wroteHeader = true 。接着把 w.handlerHeader 的内容拷贝进 chunkWriter 。可知,设置状态码接口 WriteHeader() 只有第一次调用才是有效的,并且该接口把header写入到输出buffer中。所以 w.Header().Set() 必须在能在 WriteHeader() 之前调用。

write()


// net/http/server.go:1573

// either dataB or dataS is non-zero.
func (w *response) write(lenData int, dataB []byte, dataS string) (n int, err error) {

	//...

	if !w.wroteHeader {
		w.WriteHeader(StatusOK)
	}

	//...

	if dataB != nil {
		return w.w.Write(dataB)
	} else {
		return w.w.WriteString(dataS)
	}
}

如果write()之前没有设置过状态码,则默认调用 WriteHeader() 设置状态码为200 OK。但 WriteHeader() 只能第一次是有效调用,所以调用***write()*** 后再调用 *WriteHeader() 是无效的。

结论

通过对源码的分析,设置http的头,状态码,body的顺序如下:w.Header().Set()w.WriteHeader()w.Write()