4.1 响应相关名词
- ResponseWriter
- 标准库写响应的接口,Gin 在其上封装了状态码追踪、字节计数、Content-Type 设置等。
- c.JSON / c.XML / c.String
- Gin 提供的渲染方法,自动设置 Content-Type 并序列化数据后写回。
- 状态码(HTTP Status Code)
- 2xx 成功、3xx 重定向、4xx 客户端错误、5xx 服务端错误。用
net/http常量(如http.StatusOK)保持可读。 - gin.H
map[string]any别名,构造临时 JSON 对象最方便。- c.Redirect
- 返回 3xx 状态并设置 Location 头,让浏览器跳转。
- SSE(Server-Sent Events)
- 基于 HTTP 的单向流式推送,Content-Type 为
text/event-stream,适合实时日志、AI 逐字输出。 - 统一响应结构
- 约定所有接口返回同一外层格式(code/message/data),前端解析一致,是工程规范。
4.2 各类渲染
// JSON(最常用)
c.JSON(http.StatusOK, gin.H{"user": "alice"})
// 缩进美化的 JSON(调试用,生产别开,浪费带宽)
c.IndentedJSON(200, obj)
// 防 JSON 劫持前缀 while(1);
c.SecureJSON(200, arr)
// 纯文本 / 格式化
c.String(200, "hello %s", name)
// XML
c.XML(200, gin.H{"code": 0})
// YAML / ProtoBuf 也支持
c.YAML(200, obj)
// 原始字节 + 自定义 Content-Type
c.Data(200, "image/png", pngBytes)
4.3 状态码语义
| 场景 | 状态码 | 常量 |
|---|---|---|
| 查询成功 | 200 | http.StatusOK |
| 创建成功 | 201 | http.StatusCreated |
| 成功无返回体 | 204 | http.StatusNoContent |
| 参数错误 | 400 | http.StatusBadRequest |
| 未认证 | 401 | http.StatusUnauthorized |
| 无权限 | 403 | http.StatusForbidden |
| 资源不存在 | 404 | http.StatusNotFound |
| 服务端错误 | 500 | http.StatusInternalServerError |
4.4 重定向
// 外部/内部跳转(302)
c.Redirect(http.StatusFound, "https://gufacode.com")
// 永久重定向(301)
c.Redirect(http.StatusMovedPermanently, "/new-path")
// 内部路由转发(不改 URL,重新进入路由)
c.Request.URL.Path = "/v2/users"
r.HandleContext(c)
4.5 SSE 流式响应
func stream(c *gin.Context) {
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
tokens := []string{"古", "法", "编", "程"}
c.Stream(func(w io.Writer) bool {
if len(tokens) == 0 {
return false // 返回 false 结束流
}
c.SSEvent("message", tokens[0])
tokens = tokens[1:]
time.Sleep(300 * time.Millisecond)
return true // 返回 true 继续
})
}
流式适合 AI 场景
逐 token 输出大模型回复、实时日志、进度条都用 SSE。注意关闭中间缓冲(如 Nginx 的 proxy_buffering off),否则客户端会一次性收到全部内容。
4.6 统一响应结构封装
真实项目里,让所有接口返回一致外层格式,前端更好处理:
// pkg/resp/resp.go
package resp
import "github.com/gin-gonic/gin"
type Response struct {
Code int `json:"code"` // 业务码,0 表示成功
Msg string `json:"message"` // 提示信息
Data any `json:"data,omitempty"` // 业务数据
}
func OK(c *gin.Context, data any) {
c.JSON(200, Response{Code: 0, Msg: "success", Data: data})
}
func Fail(c *gin.Context, httpStatus, code int, msg string) {
c.JSON(httpStatus, Response{Code: code, Msg: msg})
}
// handler 里干净利落
func getUser(c *gin.Context) {
u, err := svc.FindUser(c.Param("id"))
if err != nil {
resp.Fail(c, 404, 10001, "用户不存在")
return
}
resp.OK(c, u)
}
// 成功: {"code":0,"message":"success","data":{...}}
// 失败: {"code":10001,"message":"用户不存在"}
HTTP 码 vs 业务码
两者可并存:HTTP 状态码给网关/缓存/监控用(是否成功、要不要重试);业务 code 给前端做细粒度分支。也有团队坚持「HTTP 200 + 业务码」以简化前端,取舍看团队约定。
4.7 响应最佳实践小结
- 能用
net/http状态码常量就别写魔法数字,语义清晰。 - 生产禁用
IndentedJSON;数组响应用SecureJSON防劫持。 - SSE 做流式,记得关代理缓冲;大文件用
c.File/c.DataFromReader流式返回。 - 统一响应结构 + 辅助函数,让 handler 保持三五行的可读性。
- 下一章进入 中间件——理解洋葱模型与 c.Next/c.Abort。