在Go语言编写HTTP服务时,正确读取客户端请求体是常见需求。当请求未被取消时,我们可以从r.Body中完整拿到数据;一旦连接中断,读取就会失败。理解未取消状态下的读取方式,有助于写出稳定的接口逻辑。

为什么需要关注请求是否取消
HTTP请求可能因为客户端关闭、网络断开或超时而被取消。如果在已取消的请求上读取body,会得到context.Canceled一类错误。相反,在请求未取消时,读取操作可以正常完成。我们通过请求的context来判断当前状态。
利用context监听取消信号
每个*http.Request都携带一个context,当请求被取消时,该context会进入结束状态。我们可以在读取前检查r.Context().Done()是否已关闭,从而确认请求是否仍有效。
基础读取示例
下面的代码展示了在请求未取消时,使用io.ReadAll读取请求体的标准做法:
package main
import (
"context"
"io"
"log"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
// 检查请求是否已取消
select {
case <-r.Context().Done():
// 请求已取消,不再读取
log.Println("request canceled")
return
default:
// 请求未取消,继续处理
}
// 未取消时安全读取请求体
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "read body failed", http.StatusInternalServerError)
return
}
defer r.Body.Close()
log.Printf("received body: %s", string(body))
w.Write([]byte("ok"))
}
func main() {
http.HandleFunc("/upload", handler)
log.Println("server start")
http.ListenAndServe("127.0.0.1:8080", nil)
}
结合context与读取的注意事项
- 始终先判断
r.Context().Err()是否为nil,再执行读取。 io.ReadAll会在连接断开时返回错误,此时说明请求已经取消。- 读取完成后必须调用
r.Body.Close()释放连接资源。
使用流式读取处理大请求体
当请求体较大时,不应一次性读入内存。可以用io.Copy配合未取消判断,分块处理:
func streamHandler(w http.ResponseWriter, r *http.Request) {
if r.Context().Err() != nil {
// 请求已取消
return
}
// 模拟将body写入某处,同时感知取消
buf := make([]byte, 1024)
for {
select {
case <-r.Context().Done():
log.Println("stream canceled")
return
default:
}
n, err := r.Body.Read(buf)
if n > 0 {
// 处理buf[:n]
log.Printf("read %d bytes", n)
}
if err == io.EOF {
break
}
if err != nil {
log.Println("read error", err)
return
}
}
}
小结
在HTTP请求未取消时正确读取请求体,核心是先通过r.Context()确认请求状态,再使用io.Reader相关方法读取r.Body。这样既不会在取消后做无用功,也能保证数据完整与资源回收。