使用 Ctx.Output.Status = HTTPStatusCode
代替 Ctx.ResponseWriter.WriteHeader(HTTPStatusCode)
直接设置状态码
0.运行环境
├── Bee : v1.10.0
├── Beego : 1.12.0
├── GoVersion : go1.12.9
├── GOOS : windows
├── GOARCH : amd64
├── NumCPU : 8
├── Compiler : gc
└── Date : Monday, 18 Nov 2019
1.解决方法(直接)
使用 Ctx.Output.Status = HTTPStatusCode
代替 Ctx.ResponseWriter.WriteHeader(HTTPStatusCode)
直接设置状态码
2.具体问题
使用 Ctx.ResponseWriter.WriteHeader
设置Response的HTTP Status Code(状态码)后会导致Header中的 Content-Type
为 text/plain; charset=utf-8
,影响JSON数据类型识别。
3.问题演示
// 测试代码
func (c *YourController) Get() {
c.Ctx.ResponseWriter.WriteHeader(405)
c.Data["json"] = data
c.ServeJSON()
}
Postman测试截图
(可见Content-Type被强制设置为text/plain; charset=utf-8)
4.问题分析
从源码入手,因为
WriteHeader
在ServeJSON
前面执行,所以看一下WriteHeader
和ServeJSON
方法的做了什么。最终发现WriteHeader
方法锁定了Response
,导致ServeJSON
无法修改Response
。
func (r *Response) WriteHeader(code int) {
// 判断状态码是否大于0
if r.Status > 0 {
//prevent multiple response.WriteHeader calls
return
}
r.Status = code // 设置状态码
r.Started = true // 其他都很正常,就这个started不太清楚
r.ResponseWriter.WriteHeader(code)
}
可见started
变量不太清楚,所以我们找一下这个变量用途,我们可以在相同文件(context.go)中看到一个Response
结构体中有一段对started
变量的解释:
started set to true if response was written to then don’t execute other handler(如果
started
变量设置为true
时就不会再执行其他方法)
这里可以大致猜出来,started
变量用于识别Response
是否已经发出(锁定?),意味着调用WriteHeader
方法就会锁定Response
不能再修改。再看一下ServeJSON
方法
// ServeJSON sends a json response with encoding charset.
func (c *Controller) ServeJSON(encoding ...bool) {
var (
hasIndent = BConfig.RunMode != PROD
hasEncoding = len(encoding) > 0 && encoding[0]
)
// 这里调用了c.Ctx.Output.JSON方法,我们直接看这个方法
c.Ctx.Output.JSON(c.Data["json"], hasIndent, hasEncoding)
}
<<==============================================>>
// JSON writes json to response body.
// if encoding is true, it converts utf-8 to \u0000 type.
func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, encoding bool) error {
// 最重要的是这一句,设置了Header
output.Header("Content-Type", "application/json; charset=utf-8")
......
return output.Body(content)
}
我们可以看到ServeJSON
方法中设置了Header
,但是在此之前我们已经使用了WriteHeader
方法导致Response
锁定了,所以这里设置Header
自然失效。
5.解决问题
继续看源码,发现Beego的作者已经提供了解决方法。就在上面
JSON
方法最终return的output.Body
方法中。下面发出的是重要一部分,源码中其它代码,请自行查阅。
func (output *BeegoOutput) Body(content []byte) error {
......
// Write status code if it has been set manually
// Set it to 0 afterwards to prevent "multiple response.WriteHeader calls"
// 如果Status Code被设置就写入
// 然后设置为0,防止多个response.WriteHeader被执行
if output.Status != 0 {
// 此处调用了WriteHeader方法
output.Context.ResponseWriter.WriteHeader(output.Status)
output.Status = 0
} else {
// 如果没有设置Status Code就锁定Response
output.Context.ResponseWriter.Started = true
}
.......
return nil
}
所以如果我们使用ServeJSON
方法的同时需要设置HTTP Status Code
,只需要设置output.Status
就可以了。如下所示:
// 测试代码
func (c *YourController) Get() {
c.Ctx.Output.Status = 405
// c.Ctx.ResponseWriter.WriteHeader(405)
c.Data["json"] = data
c.ServeJSON()
}
Postman测试截图
可见
Content-Type
成功顺利被设置为application/json; charset=utf-8
,同时HTTP Status Code
显示正常。
网上资料比较少,此文章水平不堪,望能帮助各位,如果有问题请在下方留言。