出现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()
操作,并判断是否正常。