解释 HTTP 中的内容协商,怎样根据客户端偏好返回符合的内容? ...

打印 上一主题 下一主题

主题 1023|帖子 1023|积分 3069

一、什么是内容协商?

内容协商(Content Negotiation)是HTTP协议中客户端和服务端协商返回内容格式的机制。
就像顾客进餐厅点餐时说"我要牛排,五分熟不要香菜",服务端根据请求头中的偏好设置返回最符合的内容版本。
典型协商维度:

  • 语言(Accept-Language)
  • 内容类型(Accept)
  • 编码(Accept-Encoding)
  • 字符集(Accept-Charset)
二、协商机制工作原理

客户端请求时携带:
  1. GET /data HTTP/1.1
  2. Accept: application/json;q=0.9, text/html;q=0.8
  3. Accept-Language: en-US, en;q=0.9, zh-CN;q=0.7
  4. Accept-Encoding: gzip, deflate
复制代码
服务端响应:
  1. HTTP/1.1 200 OK
  2. Content-Type: application/json
  3. Content-Language: en-US
  4. Content-Encoding: gzip
  5. Vary: Accept, Accept-Language, Accept-Encoding
复制代码
三、服务端处理实战示例

  1. // Express示例 - 处理多语言内容
  2. const express = require('express');
  3. const accepts = require('accepts'); // 重量级库推荐
  4. const app = express();
  5. // 中间件解析语言偏好
  6. app.get('/news', (req, res) => {
  7.   const accept = accepts(req);
  8.   
  9.   // 按优先级获取最佳语言
  10.   const langs = ['en', 'zh', 'ja'];
  11.   const language = accept.language(langs) || 'en';
  12.   
  13.   // 根据语言返回内容
  14.   const content = {
  15.     en: { title: "Breaking News" },
  16.     zh: { title: "突发新闻" },
  17.     ja: { title: "速報" }
  18.   };
  19.   
  20.   res.json(content[language]);
  21. });
  22. // 处理内容类型协商
  23. app.get('/data', (req, res) => {
  24.   const accept = req.headers.accept || '';
  25.   
  26.   if (accept.includes('application/json')) {
  27.     res.type('json').send({ data: "JSON format" });
  28.   } else if (accept.includes('text/html')) {
  29.     res.type('html').send('<div>HTML format</div>');
  30.   } else {
  31.     // 默认回退方案
  32.     res.type('text').send('Default format');
  33.   }
  34. });
复制代码
四、前端开辟使用建议


  • 正确设置请求头
  1. // Axios示例:主动声明内容偏好
  2. axios.get('/api/data', {
  3.   headers: {
  4.     'Accept': 'application/json, text/plain;q=0.8',
  5.     'Accept-Language': 'zh-CN, en;q=0.7'
  6.   }
  7. });
复制代码

  • 处理多语言资源
  1. <!-- 配合lang属性实现精准匹配 -->
  2. <html lang="zh-CN">
  3.   <script type="application/json" id="i18n">
  4.     {
  5.       "en": {"greeting": "Hello"},
  6.       "zh-CN": {"greeting": "你好"},
  7.       "zh-TW": {"greeting": "你好"}
  8.     }
  9.   </script>
  10. </html>
复制代码

  • 缓存控制计谋
  1. // 必须声明Vary头避免错误缓存
  2. app.use((req, res, next) => {
  3.   res.set('Vary', 'Accept-Language, Accept');
  4.   next();
  5. });
复制代码
五、注意事项及常见题目


  • 权重排序陷阱
    错误实现:
  1. // 错误:未正确处理q值权重
  2. const langs = req.headers['accept-language'].split(',');
  3. return langs[0]; // 直接取第一个
复制代码
正确方式应解析q参数:
  1. // 正确解析示例
  2. function parseLanguage(header) {
  3.   return header.split(',')
  4.     .map(lang => {
  5.       const [value, q = '1'] = lang.split(';q=');
  6.       return { value: value.trim(), q: parseFloat(q) };
  7.     })
  8.     .sort((a, b) => b.q - a.q);
  9. }
复制代码

  • 缓存雪崩题目
    错误设置:
  1. # 错误:忽略Vary头配置
  2. location /data {
  3.   proxy_cache_key "$uri";
  4. }
复制代码
正确设置:
  1. # 正确:包含协商头作为缓存key
  2. location /data {
  3.   proxy_cache_key "$uri$http_accept$http_accept_language";
  4.   proxy_cache_valid 200 1h;
  5. }
复制代码

  • 移动端特殊处理
  1. // 识别移动设备返回优化内容
  2. function mobileOptimized(req, res, next) {
  3.   const ua = req.headers['user-agent'];
  4.   const isMobile = /Mobile|iP(hone|od)|Android/.test(ua);
  5.   
  6.   req.accepts = req.accepts || [];
  7.   if(isMobile) {
  8.     req.accepts.unshift('text/html; version=mobile');
  9.   }
  10.   next();
  11. }
复制代码
六、进阶技巧


  • 主动协商覆盖
  1. // 允许URL参数覆盖Header设置
  2. app.use((req, res, next) => {
  3.   if(req.query.format) {
  4.     req.headers.accept = `${req.query.format}, ${req.headers.accept}`;
  5.   }
  6.   next();
  7. });
复制代码

  • 多维度综合匹配
  1. // 综合设备类型+语言+内容类型决策
  2. function decideResponse(req) {
  3.   const device = detectDevice(req);
  4.   const lang = detectLanguage(req);
  5.   const format = detectFormat(req);
  6.   
  7.   return {
  8.     view: `${device}/${lang}/view.${format}`,
  9.     headers: {
  10.       'Content-Type': getMimeType(format),
  11.       'Content-Language': lang
  12.     }
  13.   };
  14. }
复制代码
七、调试技巧


  • cURL测试下令
  1. # 测试多语言支持
  2. curl -H "Accept-Language: zh-CN;q=0.8, en;q=0.9" http://api.example.com/news
  3. # 测试内容类型协商
  4. curl -H "Accept: text/html, application/json;q=0.9" http://api.example.com/data
复制代码

  • Chrome开辟者工具
    Network面板右键表头 -> 点击"Accept"等头名称 -> 可快速编辑重发请求
八、总结建议


  • 优先使用标准化的内容协商机制,而非自行发明轮子
  • 对于复杂场景建议使用成熟库:

    • Node.js: accepts, negotiator
    • Python: werkzeug.http.parse_accept_header
    • Java: ContentNegotiationStrategy

  • 始终设置Vary响应头避免缓存污染
  • 在API文档中明白声明支持的内容类型
  • 对不支持的类型返回406 Not Acceptable而非强行响应
正确实施内容协商可以使你的Web应用:


  • 提升多语言支持质量
  • 优化差别装备的用户体验
  • 进步API的兼容性
  • 减少不须要的网络传输
  • 避免客户端解析错误

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

钜形不锈钢水箱

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表