关于HTTP中的数据协商

关于数据协商

  • 在客户端发送给服务端一个请求的时候,客户端会声明通过这个请求拿到的数据格式以及数据相关的一些限制要求
  • 服务端会有很多不同类型的数据返回,它会根据请求的这个声明做出一个判断,根据请求头信息来区分返回数据
  • 分类
    • 请求
      • 1 ) 客户端请求中通过Accept来声明我想要什么样的数据,Accept指定数据类型
      • 这个数据类型会根据MineType的声明进行一个限制, 来告诉服务端想要的数据类型
      • 2 ) 通过Accept-Encoding代表数据是一个怎样的编码方式来进行传输
      • 主要用来限制服务端如何来进行数据的压缩,如:gzip, deflate, br等
      • 3 ) 通过Accept-Language来展示不同的语言
      • 一般根据这个头判断返回是中文还是英文,根据设计方案不同,如果还包含其他语言也是可以的
      • 4 ) 通过User-Agent来判断浏览器相关信息
      • 一般移动端和PC端浏览器是不一样的,可以根据它来适配不同格式的页面
    • 返回
      • 1 ) 服务端返回中通过Content-Type来对应请求中的Accept中的数据格式
      • Content-Type从Accept中选择一种作为最终数据的返回格式
      • 在返回的时候就要明确声明返回的数据格式,用于客户端的判断显示
      • 2 ) 通过Content-Encoding来对应请求中的Accept-Encoding
      • 用于声明返回数据使用的压缩算法
      • 3 ) 通过Content-Language来对应请求中的Accept-Language
      • 用于声明返回数据中是否和请求中的语言一致

程序示例

  • test.html

    • 这里只简单的写一些标记,代表有数据即可
    <br>
    
  • server.js

    const http = require('http');
    const fs = require('fs');
    
    http.createServer((req, res) => {
        console.log('req come: ', req.url);
        const html = fs.readFileSync('test.html', 'utf8');
        res.writeHead(200, {
            'Content-Type': 'text/html'
        });
        res.end(html);
    }).listen(8000, () => {
        console.log('server is running on port 8000');
    });
    
  • 启动程序 $ node server.js

  • 使用谷歌浏览器访问:http://localhost:8000/

  • 打开开发者工具检查请求响应信息

  • 可以看到

    • 请求头
      • Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
        • 上面这是浏览器愿意接受的数据格式,服务端并不一定按照你的要求来返回这个格式
        • q表示的是权重的意思,值越大,权重越高
      • Accept-Encoding: gzip, deflate, br
        • 上面声明了三种流行的编码格式方法
        • br目前相对比较少,压缩比比较高,未来可能会成为主流
      • Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
        • 上面声明了可接收的语言,可知中文权重第一
        • 浏览器会根据系统使用语言来发送相应的Accept-Language
        • 我们发送Ajax请求时可自定义设置使用语言
        • 一般做国际化也不一定选择这个来作为判断的标准
      • User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36
        • Mozilla/5.0:浏览器一开始是网景开发的,默认头是这个,很多老服务器只支持这个头,所以在现在浏览器还是默认给加上
        • (Macintosh; Intel Mac OS X 10_15_7):表示浏览器所在的操作系统平台的信息
        • AppleWebKit/537.36:表示浏览器内核,现在的谷歌和Safari浏览器都使用该内核,WebKit内核是苹果公司开发的
        • (KHTML, like Gecko):表示渲染引擎的版本,声明使用的是KHTML类似于Gecko,Gecko是火狐浏览器的渲染引擎
        • Chrome/88.0.4324.96 Safari/537.36:这里声明谷歌浏览器版本号,后面声明了Safari的版本号是因为WebKit内核是苹果公司开发的
        • 如果查看Safari浏览器的User-Agent,我们会发现没有谷歌浏览器的相关信息,它会是这样:User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15
    • 响应头
      • Content-Type: text/html
        • Content-Type是和请求头上的Accept是有对应的关系
        • Content-Type里面声明的值有一个定义叫做Mine Types
        • 关于Mine Types参考: https://www.w3school.com.cn/media/media_mimeref.asp
        • 关于Mine Types只需要了解,知道大致类型即可
      • 关于Content-Type有一个很重要的内容是
        • 在服务端返回的时候,可以添加一个头, 如下
          res.writeHead(200, {
              'Content-Type': 'text/html',
              'X-Content-Type-Options': 'nosniff' // 添加这一行,保证安全性
          });
          
        • 有一个情况是在以前很早的时候你返回一个Content-Type声明了一个数据类型或者没有做声明,IE浏览器可能并不会轻易接受这个值
        • 它自己会去预测你返回的内容是什么格式,这会导致一些安全性的问题,比如原文是文本类型的代码,结果以脚本去运行了
        • 声明了这个X-Content-Type-Options之后,就不会主动去预测它了
        • 这个头一般不会遇到,因为现在浏览器都不会激进的预测内容,如果遇到了,可以添加这一行
      • 我们可以着重说一下Content-Encoding这个编码问题
        • 在浏览器 Network 选项中,查看 All, 可见访问localhost的请求中Size, 上面是150B, 下面是7B
        • 上面的150B表示数据在整个传输中的实际大小,这个大小会包含HTTP的首行信息,headers和body的信息
        • 下面的7B是实际内容的大小,是body数据拿到后并根据Content-Encoding解压后的大小, 并不是传输中的body大小
        • 我们可以尝试用gzip压缩后的大小变化,程序修改如下
          const http = require('http');
          const fs = require('fs');
          const zlib = require('zlib'); // 新增引入
          
          http.createServer((req, res) => {
              console.log('req come: ', req.url);
              const html = fs.readFileSync('test.html'); // 这里不使用utf8,gzip处理buffer,默认都是buffer
              res.writeHead(200, {
                  'Content-Type': 'text/html',
                  'Content-Encoding': 'gzip' // 使用gzip编码
              });
              res.end(zlib.gzipSync(html)); // 使用gzip处理数据
          }).listen(8000, () => {
              console.log('server is running on port 8000');
          });
          
        • 重启程序后发现,Size上面变成了195B(实际传输的大小变小了), 下面保持不变(因为我们传输的内容没有变)
        • 在传输的过程中,我们将body通过gzip给压缩过了,一般网站因为body的体量是巨大的
        • 在压缩之后,一般上面的值会比下面的要小(也就是实际传输大小比解压后的body要小), 这是压缩的好处
        • 目前是因为我们测试文本较少的原因导致了上面比下面要大
        • 我们可以选择浏览器发送的Accept-Encoding来选择一个压缩方式来进行压缩

发送请求时的Content-Type

  • 一般我们程序员遇到的是解决发送请求时候的Content-Type,这里着重来说明一下
  • 我们通过程序演示, 如下:
  • test.html
<form action='/form' method='POST' enctype='applicaion/x-www-form-urlencoded'>
    <input type='text' name='name' /> <br />
    <input type='password' name='password' /> <br />
    <input type='submit' />
</form>
  • 不用重启程序,访问:http://localhost:8000/
  • 在Network中勾选Preserve log
  • 随便在表单输入内容,选择提交,我们可以看到
    • 在请求头上: Content-Type: application/x-www-form-urlencoded
    • 独立于General,Response Headers,Request Headers之外多出了一栏Form Data
    • 在这里我们可以看到我们POST出去的数据内容
  • 我们将表单上的enctype修改成multipart/form-data, 保存并刷新浏览器, 可以看到
    • 在请求头上:Content-Type: multipart/form-data; boundary=----WebKitFormBoundarywR6gXppgzxUA6FMt
    • multipart表示请求是有多个部分的,用于上传文件使用,表单上传文件必须把文件拆出来,文件不能作为字符串来传输
    • 要作为二进制的数据进行传输,所以不能进行任何的字符串拼接
    • 在这个Content-Type中有这么一串东西:boundary=----WebKitFormBoundarywR6gXppgzxUA6FMt
    • 这个boundary它用于分割表单提交的每一项,通过这个字符串来进行分割:----WebKitFormBoundarywR6gXppgzxUA6FMt
    • 在Form Data栏中点击view source, 可见
      ------WebKitFormBoundarywR6gXppgzxUA6FMt
      Content-Disposition: form-data; name="name"
      
      lll
      ------WebKitFormBoundarywR6gXppgzxUA6FMt
      Content-Disposition: form-data; name="password"
      
      kkkk
      ------WebKitFormBoundarywR6gXppgzxUA6FMt--
      
    • 这里lll和kkkk是随便填写的表单name和password, 可见通过上述WebKit字符串来进行分割
    • 在最后加上–表示结束,被分割后的每一块中都有一个Content-Disposition数据格式的声明
    • 如果我们要传输文件,添加一个表单 <input type='file' name='file' /> <br />
    • 选择一个文件,点击提交后,发现无法在浏览器中发现上面的Request Payload的显示
    • 这时候,我们修改页面,改用js提交看看
      <form action='/form' id='form' method='POST' enctype='multipart/form-data'>
          <input type='text' name='name' /> <br />
          <input type='password' name='password' /> <br />
          <input type='file' name='file' /> <br />
          <input type='submit' />
      </form>
      
      <script>
      var form = document.getElementById('form');
      form.addEventListener('submit', function(e) {
          e.preventDefault();
          var formData = new FormData(form);
          fetch('/form', {
          method: 'POST',
          body: formData
          });
      })
      </script>
      
    • 随便输入表单内容,选择一个文件,点击提交,在Form Data栏中点击view source, 可见
      ------WebKitFormBoundaryUQzLsBIJMviVK5Ie
      Content-Disposition: form-data; name="name"
      
      lll
      ------WebKitFormBoundaryUQzLsBIJMviVK5Ie
      Content-Disposition: form-data; name="password"
      
      kk
      ------WebKitFormBoundaryUQzLsBIJMviVK5Ie
      Content-Disposition: form-data; name="file"; filename="1.jpeg"
      Content-Type: image/jpeg
      
      
      ------WebKitFormBoundaryUQzLsBIJMviVK5Ie--
      
    • 好的,这个Request Payload又出现了,我们可以看到每次用于分割的boundary都会不一样
    • 着重看一下文件相关的区域:
      • Content-Disposition: form-data; name=“file”; filename=“1.jpeg”
      • Content-Type: image/jpeg
    • 当然,根据你上传的不同的文件类型,可能会有
      • Content-Disposition: form-data; name=“file”; filename=“1.zip”
      • Content-Type: application/zip
    • 还会有
      • Content-Disposition: form-data; name=“file”; filename=“1.md”
      • Content-Type: text/markdown
    • 等等内容
  • 以上就是发送请求时的Content-Type的情况
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值