博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Golang学习日志 ━━ http:一个函数中同时读取和写入可能导致无法获得post值的分析
阅读量:4116 次
发布时间:2019-05-25

本文共 3958 字,大约阅读时间需要 13 分钟。

出现bug

今天遇到个怪事,死活读不到post值,类似的代码如下

func test(w http.ResponseWriter,r *http.Request){
// 原本是读取一个html文件输出,这里使用一个长切片来说明问题 // m, err := ioutil.ReadFile("./test.html") // if err != nil {
// fmt.Println("Read file failed, err:", err) // return // } var m []byte //如果i<1000改成i<10,则不会出现问题 for i := 0; i < 1000; i++ {
m = append(m, []byte("hello, world")...) } // 输出 _, err := w.Write(m) // 解析表单并打印 err = r.ParseForm() if err != nil {
fmt.Println("parse form failed, err:", err) // 先不退出 // return } fmt.Println("request map:", r.Form) fmt.Println("post map:", r.PostForm)}func main(){
http.HandleFunc("/test", test) err := http.ListenAndServe("http://127.0.0.1:8088",nil) if err != nil {
fmt.Println("serve failed, err:", err) return }}

此时浏览器打开一个页面,页面内容为

提交表单后,golang控制台打印仅有a和b的值,c无法获取,并且有提示无法解析form的情况

parse form failed, err: http: invalid Read on closed Bodyrequest map: map[a:[1] b:[2]]post map: map[]

解决之道

到底是原因呢,总感觉是卡在.write()上了,于是看了一下源码注释

// Write writes the data to the connection as part of an HTTP reply.	//	// If WriteHeader has not yet been called, Write calls	// WriteHeader(http.StatusOK) before writing the data. If the Header	// does not contain a Content-Type line, Write adds a Content-Type set	// to the result of passing the initial 512 bytes of written data to	// DetectContentType. Additionally, if the total size of all written	// data is under a few KB and there are no Flush calls, the	// Content-Length header is added automatically.	//	// Depending on the HTTP protocol version and the client, calling	// Write or WriteHeader may prevent future reads on the	// Request.Body. For HTTP/1.x requests, handlers should read any	// needed request body data before writing the response. Once the	// headers have been flushed (due to either an explicit Flusher.Flush	// call or writing enough data to trigger a flush), the request body	// may be unavailable. For HTTP/2 requests, the Go HTTP server permits	// handlers to continue to read the request body while concurrently	// writing the response. However, such behavior may not be supported	// by all HTTP/2 clients. Handlers should read before writing if	// possible to maximize compatibility.	Write([]byte) (int, error)

谷歌翻译如下(凭感觉先看一下,再细品~~):

Write将数据作为HTTP回复的一部分写入连接。

如果尚未调用WriteHeader,则调用Write

写入数据之前,使用WriteHeader(http.StatusOK)。如果标题
不包含Content-Type行,Write添加了Content-Type集
将初始的512字节的写入数据传递到的结果
DetectContentType。另外,如果全部写总大小
数据不到几KB,并且没有Flush调用,
Content-Length标头会自动添加。

根据HTTP协议版本和客户端,调用

Write或WriteHeader可能会阻止以后读取
Request.Body。对于HTTP / 1.x请求,处理程序应读取任何内容
写入响应之前需要的请求正文数据。一旦
标头已被刷新(由于显式的Flusher.Flush
调用或写入足够的数据以触发刷新),请求正文
可能不可用。对于HTTP / 2请求,Go HTTP服务器允许
处理程序以继续并发读取请求主体
撰写回复。但是,可能不支持这种行为
通过所有HTTP / 2客户端。处理程序应在写入之前阅读,如果
可能最大化兼容性。

果然,.write()是有容量限制的。

原因:

注释第二段第四行写明了当超出了512字节的限制就可能会影响以后的读取Request.Body

解决办法:

第三段的第三行中,在写入前先读取。

func test(w http.ResponseWriter,r *http.Request){
u := r.URL h := r.Header b, _ := ioutil.ReadAll(r.Body) // 打印地址 fmt.Println("url:", u) // 打印header fmt.Println("header:", h) // 打印body fmt.Println("body:", string(b)) // 解析表单并打印 err := r.ParseForm() if err != nil {
fmt.Println("parse form failed, err:", err) // 先不退出 // return } fmt.Println("request map:", r.Form) fmt.Println("post map:", r.PostForm) var m []byte for i := 0; i < 1000; i++ {
m = append(m, []byte("hello, world")...) } // 输出 _, err = w.Write(m)}

打印

url: /test?a=1&b=2header: map[Accept:[...] Accept-Encoding:[gzip, deflate] Accept-Language:[...] Connection:[keep-alive] Content-Length:[14] Content-Type:[application/x-www-form-urlencoded] Cookie:[...] Origin:[http://127.0.0.1:8088] Referer:[http://127.0.0.1:8088/1.html] Upgrade-Insecure-Requests:[1] User-Agent:[...]]body: c=3request map: map[a:[1] b:[2] c:[3]]post map: map[c:[3]]

总结

综合原文注释和打印的结果,可以认为golang在获得get值的时候是从url中取值,而获得post值的时候是从body中取值,这也是为什么在.write()溢出后,get取值正常,post却无法取值。

在遇到只有get值时尽量使用.URL.Query(),而不是用.form

引申:要准确获取.Form.PostForm的值之前,需要先进行.ParseForm()操作,并判断是否正常。

转载地址:http://mvkpi.baihongyu.com/

你可能感兴趣的文章
为什么总有人说:“前端饱和了”、“前端不好找工作”?
查看>>
vue实现一个6个输入框的验证码输入组件
查看>>
【前端面试题】04—33道基础CSS3面试题(附答案)
查看>>
JQuery如何阻止事件冒泡
查看>>
JavaScript最新手机号码、电话号码正则表达式
查看>>
React 组件通信之发布订阅模式
查看>>
35道常见的前端vue面试题
查看>>
6种实现JavaScript数组拍平/扁平化的方法总汇
查看>>
【前端面试题】05—17道面向对象的面试题(附答案)
查看>>
JavaScript数组的几个经典API
查看>>
2种检查JavaScript数组是否为空的方法
查看>>
用CSS计数器美化数字有序列表
查看>>
总结:前端开发从入门到高薪中文指南.pdf
查看>>
【前端面试题】06—16道设计模式面试题(附答案)
查看>>
400 道前端常考必考面试题(附答案详解)
查看>>
Vue中实现输入框Input输入格式限制
查看>>
我的一些关于职场和职业发展的心得
查看>>
9种HTML中通过CSS方式隐藏元素的方法汇总
查看>>
JavaScript队列和双端队列
查看>>
一些常用的语音特征提取算法
查看>>