内容协商

客户端通过 Accept、Accept-Language 与 Accept-Encoding 声明偏好,服务端选择表述并在 Content-Type 中声明结果;版本升级也可借媒体类型承载。

内容协商是什么

内容协商是客户端与服务端就「用哪种表述返回」达成一致的过程:客户端通过 Accept、Accept-Language、Accept-Encoding 声明偏好与权重,服务端选择自己支持的一种表述,并在响应头 Content-Type 与 Content-Encoding 中声明实际结果。它让同一资源以不同结构、语言或压缩形式服务不同调用方,而不必为每种格式各开一组接口。

声明不支持时怎么处理

当客户端声明的格式服务端都不支持时,规范做法是返回 406 并在响应中说明服务端支持哪些格式,而不是默默退回默认格式。现实中大量接口固定返回结构体格式并忽略其余偏好,这本身可以接受,但必须写进文档,否则接入方会以为切换格式的声明是生效的,直到解析失败才察觉。协商结果与缓存的关系也要理清:编码与语言不同,缓存必须分开存放。

基本信息

请求头Accept 声明媒体类型偏好并用 q 值表达权重,Accept-Language 声明语言,Accept-Encoding 声明压缩算法
响应头Content-Type 声明实际媒体类型,Content-Encoding 声明实际压缩编码
权重规则q 值从高到低选择,未声明 q 视为默认优先级
失败处理所有偏好都不支持时返回 406,并说明服务端支持的格式
缓存关联不同编码与不同语言的响应必须使用不同缓存键

常见请求头

请求头作用示例值
Accept声明可接受的媒体类型application/json;q=1.0, application/xml;q=0.5
Accept-Encoding声明可接受的压缩算法gzip, br
Accept-Language声明语言偏好zh-CN, en;q=0.8
Content-Type声明请求体的实际类型application/json

典型使用场景

  • 同一份接口同时服务浏览器与第三方系统,需要返回不同格式
  • 用媒体类型承载接口版本,避免把版本号塞进路径
  • 客户端声明可接受的压缩算法,由服务端或网关按能力压缩响应
  • 多语言场景下按语言偏好返回不同文案,而不是靠额外参数
协商偏好要写进接口文档并给出默认规则:固定返回一种格式并忽略其他偏好是常见做法,但写不明白就会让接入方误解。
缓存键不含编码与语言维度时,压缩内容与原文、不同语言版本会互相覆盖,表现为偶发的解析失败,排查难度很高。

易错点

  • 忽略 Accept 头固定返回一种格式,且不声明实际内容类型,接入方只能靠猜
  • 客户端声明的格式全不支持却仍返回默认格式,接入方误判协商已经生效
  • 缓存键未包含编码或语言维度,压缩内容与原文相互覆盖导致偶发解析失败
  • 用媒体类型承载接口版本但文档未写清,接入方按路径版本的经验猜测规则
  • 响应压缩声明与实际编码不一致,客户端按声明解压失败并报出难懂的错

数据来源与参考

  • RFC 9110 HTTP Semantics(第 12 章内容协商)
  • RFC 9111 HTTP Caching
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌