学习记录
读懂 w http.ResponseWriter, r *http.Request:Go HTTP 处理函数的核心入口
从请求读取、响应写出到常见误区,系统梳理 Handler 里最常见的两个参数到底代表什么,以及它们在 HTTP 流程里的角色。
很多人第一次写 Go Web 服务,最常见的函数签名就是:
1func Handler(w http.ResponseWriter, r *http.Request)
看起来只有两个参数,但这两个参数几乎承载了一个 HTTP 请求处理过程里最核心的输入和输出。
背景
这个函数签名是 net/http 包的标准入口。它表达的是:
r:当前这次 HTTP 请求带进来的所有信息w:你准备如何把响应写回客户端
如果把它们理解清楚,后面再看中间件、路由、JSON 接口和文件上传都会顺很多。
核心概念
r *http.Request 代表什么
r 是一个请求对象,里面包含:
- 请求方法,如
GET、POST - URL 路径和查询参数
- 请求头
- 请求体
- 请求上下文
也就是说,你要读“客户端到底发了什么”,基本都从 r 里拿。
最常见的几个字段:
1r.Method
2r.URL.Path
3r.URL.Query()
4r.Header.Get("Content-Type")
5r.Body
6r.Context()
w http.ResponseWriter 代表什么
w 是响应写出接口。它负责把你的处理结果发回客户端。
它最核心的事情有三件:
- 写响应头
- 写状态码
- 写响应体
例如:
1w.Header().Set("Content-Type", "application/json")
2w.WriteHeader(http.StatusOK)
3_, _ = w.Write([]byte(`{"ok":true}`))
一个最小可运行示例
1package main
2
3import (
4 "fmt"
5 "net/http"
6)
7
8func Handler(w http.ResponseWriter, r *http.Request) {
9 name := r.URL.Query().Get("name")
10 if name == "" {
11 name = "guest"
12 }
13
14 w.Header().Set("Content-Type", "text/plain; charset=utf-8")
15 w.WriteHeader(http.StatusOK)
16 _, _ = fmt.Fprintf(w, "hello, %s", name)
17}
这段代码里:
- 从
r.URL.Query()读取查询参数 - 用
w.Header()设置响应头 - 用
w.WriteHeader()设置状态码 - 最后把正文写出去
ResponseWriter 最容易踩的点
1. 状态码一旦写出,就很难回头改
如果你已经调用了:
1w.WriteHeader(http.StatusOK)
后面再想改成 500 就晚了。
更常见的是:你没有显式调用 WriteHeader,但先调用了 Write。这时 Go 会默认把状态码当成 200 OK 发出去。
1_, _ = w.Write([]byte("hello")) // 隐式写出 200
所以如果你需要明确控制状态码,最好先写头、再写 body。
2. Header 必须在写 body 之前设置
这一点和状态码一样,一旦 body 开始写出,很多响应头就已经来不及改了。
错误示例:
1_, _ = w.Write([]byte("hello"))
2w.Header().Set("Content-Type", "text/plain")
正确顺序应该是:
1w.Header().Set("Content-Type", "text/plain")
2_, _ = w.Write([]byte("hello"))
Request 最容易踩的点
1. Body 不是随便反复读的
r.Body 通常是一个流,读过一次以后位置就往后走了。很多场景下你不能假设它能反复读取。
2. 取消、超时要看 r.Context()
如果客户端断开连接,或者上游超时,很多底层操作会通过 r.Context() 传播取消信号。
这也是为什么下游数据库、HTTP 调用最好都带上:
1ctx := r.Context()
实战里经常怎么配合
最常见的处理流程其实很固定:
- 从
r里读路径、参数、header、body - 做校验和业务处理
- 用
w设置响应头和状态码 - 把结果写回去
例如 JSON API 通常会这样写:
1func JSONHandler(w http.ResponseWriter, r *http.Request) {
2 if r.Method != http.MethodPost {
3 http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
4 return
5 }
6
7 w.Header().Set("Content-Type", "application/json; charset=utf-8")
8 w.WriteHeader(http.StatusOK)
9 _, _ = w.Write([]byte(`{"message":"ok"}`))
10}
我的总结
r *http.Request代表请求输入w http.ResponseWriter代表响应输出- 读请求看
r,写响应看w - 设置 header 和状态码要早于写 body
r.Body通常不是可重复消费的数据源- 真正把这两个参数吃透后,再看中间件、路由、JSON 编解码会轻松很多