需手动解析Accept头:用mime.ParseMediaType拆解各条目,按q权重排序取最高优先级type/subtype,忽略参数但保留vnd前缀,空或无效时fallback为application/json。

怎么用 net/http 读取 Accept 头并做格式判断
Go 标准库不自动解析 Accept 头,得自己手动提取、解析、匹配。别指望 http.ServeContent 或路由框架(比如 Gin)默认帮你干这事——它们顶多提供快捷方法,底层逻辑还得你写。
常见错误是直接用 strings.Contains(r.Header.Get("Accept"), "json"),这会误判 application/vnd.api+json 或漏掉 application/json; charset=utf-8 这类带参数的值。
- 用
mime.ParseMediaType()拆解每个Accept条目,拿到主类型、子类型和参数 - 按 RFC 7231 规则,需按权重(
q=)排序,取最高优先级的有效类型 - 只比对
type/subtype,忽略参数(如charset),但保留vnd这类关键前缀 - 记得 fallback:当
Accept为空或无法解析时,返回默认格式(通常是 JSON)
怎么封装一个可复用的协商函数(negotiateContentType)
硬编码 if-else 判断太脆弱,容易漏掉 application/xml、text/xml 甚至 application/atom+xml 这些合法 XML 类型。应该把支持的格式声明为常量 + 映射表,再让协商逻辑集中处理。
示例逻辑:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
func negotiateContentType(h http.Header) string {
accept := h.Get("Accept")
if accept == "" {
return "application/json"
}
for _, s := range strings.Split(accept, ",") {
mediaType, _, _ := mime.ParseMediaType(strings.TrimSpace(s))
switch mediaType {
case "application/json", "text/json":
return "application/json"
case "application/xml", "text/xml", "application/atom+xml":
return "application/xml"
}
}
return "application/json" // 默认
}
- 不要在 switch 里写
*/*或*/json—— Go 的mime不支持 glob 匹配,写了也白写 - 如果项目用
encoding/xml,注意它不支持nil字段输出空标签,而 JSON 会输出null;这点会影响前端是否认为字段“存在” - 返回的 Content-Type 值必须和实际序列化方式严格一致,否则浏览器可能拒绝解析或触发下载
为什么 xml.Marshal 输出的 XML 缺少声明头(<?xml version="1.0"?>)
encoding/xml 默认不加 XML 声明,这是设计使然,不是 bug。但某些老系统或测试工具会依赖这个声明来识别内容类型,导致协商后返回 XML 却被当成纯文本。
- 手动拼接声明头:先写
<?xml version="1.0" encoding="UTF-8"?>,再写xml.Marshal结果 - 注意:不能直接往
http.ResponseWriter写两次,要确保一次写入(避免 header 已发送错误) - 更稳妥的做法是用
bytes.Buffer先组装完整 body,再统一写入响应 - 如果用了
xml.Header自定义声明,务必确认编码与Content-Type中的charset一致(例如charset=UTF-8)
Gin / Echo 等框架里怎么避免重复实现协商逻辑
框架通常提供 Context.Negotiate 或 Context.Render,但它们只负责“选模板”,不自动读 Accept。真正起作用的是你注册的 NegotiationMap 或中间件。
- Gin:调用
c.Negotiate(200, gin.Negotiate{Offered: []string{"application/json", "application/xml"}, JSON: data, XML: data}),框架内部才去查Accept - Echo:要用
c.Negotiate并提前注册echo.HTTPError和echo.HTTPRenderer,否则会 panic - 别信文档里“自动协商”的说法——没显式传
Offered列表,框架根本不知道你支持哪些格式 - 自定义中间件比依赖框架协商更可控,尤其当你需要兼容非标准 Accept 值(如
text/x-json)时
omitempty、XML 的 xml:",omitempty" 行为不同;时间字段默认格式不同;nil slice 在 JSON 是 null,在 XML 可能直接消失。这些细节不统一,前端就容易出隐性 bug。

















