Beego解决WriteHeader后导致Content-Type强制为text/plain(ServeJSON后Content-Type不为application/json)

5 篇文章 0 订阅
2 篇文章 0 订阅

Beego解决WriteHeader后导致Content-Type强制为text/plain

(ServeJSON后Content-Type不为application/json)

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-Typetext/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.问题分析

从源码入手,因为WriteHeaderServeJSON前面执行,所以看一下WriteHeaderServeJSON方法的做了什么。最终发现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显示正常。Postman测试截图


网上资料比较少,此文章水平不堪,望能帮助各位,如果有问题请在下方留言。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
import requests from lxml import etree import csv headers={ "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 Edg/112.0.1722.64" } url = 'https://www.bilibili.com/v/channel/17532487/?tab=featured' # headers如前 xpath_videoplay='//ul[@class="card-list"]/div/div[@class="video-card"]/div[@class="video-card__content"]/a/div[@class="video-card__info"]/span[@class="play-text"]/span[@class="count"]/text()' xpath_videolike='//ul[@class="card-list"]/div/div[@class="video-card"]/div[@class="video-card__content"]/a/div[@class="video-card__info"]/span[@class="like-text"]/span[@class="count"]/text()' xpath_videotime='//ul[@class="card-list"]/div/div[@class="video-card"]/div[@class="video-card__content"]/a/div[@class="video-card__info"]/span[@class="play-duraiton"]/text()' xpath_videoername='//ul[@class="card-list"]/div/div[@class="video-card"]/a/span[@class="up-name__text"]/text()' xpath_videoname='//ul[@class="card-list"]/div/div[@class="video-card"]/a[@class="video-name"]/text()' response = requests.get(url, headers=headers) response.encoding = 'utf8' dom = etree.HTML(response.text) videoplays=dom.xpath(xpath_videoplay) videolikes=dom.xpath(xpath_videolike) videotimes=dom.xpath(xpath_videotime) videoernames=dom.xpath(xpath_videoername) videonames=dom.xpath(xpath_videoname) data = [] for i in range(len(videoplays)): t = {} t['视频制作者']=videoernames[i] t['视频标题']=videonames[i] t['视频时长']=videotimes[i] t['视频播放量'] = videoplays[i] t['视频点赞数'] = videolikes[i] data.append(t) # for t in data: # print(t) # print(t.items()) # save_data(xpath_videoername, xpath_videoname,xpath_videotime, xpath_videoplay, xpath_videolike) # def save_data(xpath_videoername, xpath_videoname,xpath_videotime, xpath_videoplay, xpath_videolike)';' # with open('./video.csv', 'a+', encoding='utf-8-sig') as f; # video_info=f'{xpath_videoername},{xpath_videoname},{xpath_videotime},{xpath_videoplay},{xpath_videolike}\n' # f.write(video_info) file_path="D:/python/up主数据.csv" with open(file_path,"w",encoding="utf-8-sig",newline='') as f: fieldnames = list(t[0].keys()) f_csv=csv.DictWriter(f,fieldnames=fieldnames) f_csv.writeheader() for row in t: writer.writerow(row)
05-05
这这段这段代码这段代码用这段代码用于这段代码用于导这段代码用于导入这段代码用于导入Python这段代码用于导入Python中这段代码用于导入Python中的这段代码用于导入Python中的requests这段代码用于导入Python中的requests和这段代码用于导入Python中的requests和l这段代码用于导入Python中的requests和lxml这段代码用于导入Python中的requests和lxml库这段代码用于导入Python中的requests和lxml库,并这段代码用于导入Python中的requests和lxml库,并且这段代码用于导入Python中的requests和lxml库,并且也这段代码用于导入Python中的requests和lxml库,并且也导这段代码用于导入Python中的requests和lxml库,并且也导入这段代码用于导入Python中的requests和lxml库,并且也导入了这段代码用于导入Python中的requests和lxml库,并且也导入了csv这段代码用于导入Python中的requests和lxml库,并且也导入了csv库这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。接这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。接下这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。接下来这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。接下来,这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。接下来,代码这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。接下来,代码中这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。接下来,代码中的这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。接下来,代码中的url这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。接下来,代码中的url是这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。接下来,代码中的url是获取这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。接下来,代码中的url是获取B这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。接下来,代码中的url是获取B站这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。接下来,代码中的url是获取B站某这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。接下来,代码中的url是获取B站某个这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。接下来,代码中的url是获取B站某个频这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。接下来,代码中的url是获取B站某个频道这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。接下来,代码中的url是获取B站某个频道的这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。接下来,代码中的url是获取B站某个频道的页面这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。接下来,代码中的url是获取B站某个频道的页面地址这段代码用于导入Python中的requests和lxml库,并且也导入了csv库。通过设置headers,伪装成浏览器进行访问。接下来,代码中的url是获取B站某个频道的页面地址。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值