Golang学习日志 ━━ http:一个函数中同时读取和写入可能导致无法获得post值的分析

出现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
	}
}

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

<form method="post" action="http://127.0.0.1:8088/test?a=1&b=2">
	<input type="text" name="c" value="3" />
	<button type="submit">发送</button>
</form>

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

parse form failed, err: http: invalid Read on closed Body
request 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=2
header: 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=3
request 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()操作,并判断是否正常。

发布了44 篇原创文章 · 获赞 1 · 访问量 3595

猜你喜欢

转载自blog.csdn.net/snans/article/details/104971495
今日推荐