go语言需手动解析accept头并按q值排序匹配,strings.contains会误判vnd前缀或带参数类型;应使用mime.parsemediatype拆解、白名单映射,并严格对齐content-type与序列化方式。

Go 语言不自动做内容协商,Accept 头必须手动解析、排序、匹配,再决定用 json.Marshal 还是 xml.Marshal —— 框架(如 Gin、Echo)或生成工具(如 oapi-codegen)最多提供钩子,核心逻辑得你自己写。
为什么 strings.Contains(r.Header.Get("Accept"), "json") 会出错
这种写法看似简单,实际漏判、误判频发:
-
application/vnd.api+json会被忽略(vnd 前缀被截断) -
application/json; charset=utf-8因含分号和参数,strings.Contains可能匹配失败 -
text/xml, application/xml;q=0.9, */*这类带权重的列表,直接查字符串无法识别application/xml是第二优先级 - 更糟的是,它还会把
application/json-ld或application/x-json这类非法类型也当作 JSON 匹配
正确做法是用 mime.ParseMediaType 拆解每个逗号分隔项,提取 type/subtype,再按 q= 值排序取最高有效项。
怎么写一个健壮的 negotiateContentType 函数
硬编码 switch 支持有限格式(比如只写 "application/json" 和 "application/xml"),容易漏掉 text/xml、application/atom+xml 等合法类型。推荐用映射表 + 显式白名单:
func negotiateContentType(h http.Header) string {
accept := h.Get("Accept")
if accept == "" {
return "application/json"
}
var candidates []struct{ mt string; q float64 }
for _, s := range strings.Split(accept, ",") {
s = strings.TrimSpace(s)
if s == "" {
continue
}
mediaType, params, err := mime.ParseMediaType(s)
if err != nil {
continue
}
q := params["q"]
weight := 1.0
if q != "" {
if w, e := strconv.ParseFloat(q, 64); e == nil {
weight = w
}
}
candidates = append(candidates, struct{ mt string; q float64 }{mt: mediaType, q: weight})
}
// 按 q 降序排序
sort.SliceStable(candidates, func(i, j int) bool {
return candidates[i].q > candidates[j].q
})
// 白名单匹配(只比 type/subtype,忽略参数)
supported := map[string]string{
"application/json": "application/json",
"text/json": "application/json",
"application/xml": "application/xml",
"text/xml": "application/xml",
"application/atom+xml": "application/xml",
}
for _, c := range candidates {
if ct, ok := supported[c.mt]; ok {
return ct
}
}
return "application/json"
}
注意:别在映射里写 "*/*" 或 "*/json" —— mime.ParseMediaType 不支持通配符解析,写了也进不了 supported 查表分支。
Content-Type 和序列化方式必须严格一致
返回 "application/xml" 但用 json.Marshal 输出,浏览器可能拒解析、触发下载,甚至某些代理直接 406;反之亦然。还要注意两个细节:
-
encoding/xml默认不输出<?xml version="1.0"?>声明,而有些老系统依赖它识别 XML 类型。如需声明,得手动拼接:append([]byte("<?xml version=\"1.0\"?>"), xmlBytes...) -
xml.Marshal遇到nil字段会跳过,输出为空标签(<field></field>);json.Marshal则输出null。前端若靠字段是否存在做判断,行为会不一致
协商不是“选个 MIME 类型贴上去”,而是整条链路(头、序列化、结构体 tag、空值语义)都得对齐。最容易被忽略的,是 Accept 解析后没 fallback 到默认格式,或者 fallback 时没重设 Content-Type 头 —— 这会导致客户端拿到 JSON 内容却收到 application/xml 头,直接卡死。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











