最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 偶遇 Content-Length 和 Transfer-Encoding

    正文概述 掘金(乔珂力)   2021-04-05   642

    背景介绍

    但是如果把微信的接口换成自己的,都可以成功接收到数据,所以肯定是用 axios 和 request 发送 HTTP 请求的时候,有一些区别。那如何找到这个区别呢,只能通过抓包了。

    抓包排查

    首先打开 Charles,然后分别运行 axios 和 request 的代码,注意,要在代码里面配上代理,我们看下 request 的代码:

    var request = require('request')
    var fs = require('fs')
    var options = {
      method: 'POST',
      url:
        'https://api.weixin.qq.com/wxa/img_sec_check?access_token=xxx',
      headers: {
        'Content-Type': 'application/json',
      },
      formData: {
        contentType: 'image/jpeg',
        value: fs.createReadStream('/Users/keliq/Pictures/jzm.jpeg'),
      },
      proxy: 'http://127.0.0.1:8888',
      rejectUnauthorized: false,
    }
    request(options, function (error, response) {
      if (error) throw new Error(error)
      console.log(response.body)
    })
    

    抓包结果如下:

    1. 请求

      POST /wxa/img_sec_check?access_token=xxx HTTP/1.1
      Content-Type: multipart/form-data; boundary=--------------------------630489123810205833486663
      host: api.weixin.qq.com
      content-length: 11998
      Connection: close
      
      ----------------------------630489123810205833486663
      Content-Disposition: form-data; name="contentType"
      
      image/jpeg
      ----------------------------630489123810205833486663
      Content-Disposition: form-data; name="value"; filename="jzm.jpeg"
      Content-Type: image/jpeg
      
      ÿØÿà...
      
    2. 响应

      HTTP/1.1 200 OK
      Date: Mon, 05 Apr 2021 06:59:26 GMT
      Content-Type: application/json; encoding=utf-8
      RetKey: 11
      LogicRet: 42001
      Content-Length: 81
      Connection: close
      
      {"errcode":42001,"errmsg":"access_token expired rid: 606ab54e-4b90bc8a-66e6fc25"}
      

    再看 axios 的代码

    var axios = require('axios')
    var FormData = require('form-data')
    var fs = require('fs')
    var data = new FormData()
    data.append('contentType', 'image/jpeg')
    data.append('value', fs.createReadStream('/Users/keliq/Pictures/jzm.jpeg'))
    
    var config = {
      method: 'post',
      url:
        'https://api.weixin.qq.com/wxa/img_sec_check?access_token=xxxx',
      headers: {
        'Content-Type': 'application/json',
        ...data.getHeaders(),
      },
      data: data,
      proxy: {
        host: '127.0.0.1',
        port: 8888,
      },
    }
    
    axios(config)
      .then(function (response) {
        console.log(JSON.stringify(response.data))
      })
      .catch(function (error) {
        console.log(error)
      })
    

    抓包结果如下:

    1. 请求

      POST /wxa/img_sec_check?access_token=xxx HTTP/1.1
      Accept: application/json, text/plain, */*
      Content-Type: multipart/form-data; boundary=--------------------------571426341398317845770943
      User-Agent: axios/0.21.1
      host: api.weixin.qq.com
      Transfer-Encoding: chunked
      Connection: close
      
      ----------------------------571426341398317845770943
      Content-Disposition: form-data; name="contentType"
      
      image/jpeg
      ----------------------------571426341398317845770943
      Content-Disposition: form-data; name="value"; filename="jzm.jpeg"
      Content-Type: image/jpeg
      
      ÿØÿà...
      
    2. 响应

      HTTP/1.1 412 Precondition Failed
      Date: Mon, 05-Apr-2021 07:00:41 GMT
      Content-Length: 0
      Connection: close
      

    经过对比发现,axios 比 request 多了一个 Transfer-Encoding 头部,值为 chunked,而 request 比 axios 多了一个 Content-Length 头部,因此这两个头部的区别才是导致微信接口返回 412 的元凶。

    了解 Content-Length 和 Transfer-Encoding 头

    网上查了很多资料,发现了朴瑞卿的这一篇文章 里面说得非常详细,在此摘录部分内容如下:

    • Content-Length:表示 HTTP 消息长度,用十进制数字表示的八位字节的数目
    • Transfer-Encoding:如果在请求处理完成前无法获取消息长度,此时应该使用 Transfer-Encoding: chunked

    Content-Length 的工作原理

    Content-Length 应该是精确的,否则就会导致异常,这个大小是包含了所有内容编码的,比如对文本文件进行了 gzip 压缩的话,Content-Length 首部指的就是压缩后的大小而不是原始大小。那如果提供的数值不准确会怎样呢?

    • Content-Length > 实际长度:服务端/客户端读取到消息结尾后会等待下一个字节,会无响应直到超时。
    • Content-Length < 实际长度:第一次消息被截断,第二次解析混乱。

    Transfer-Encoding 的工作原理

    数据以一系列分块的形式进行发送,在每一个分块的开头需要添加当前分块的长度,以十六进制的形式表示,后面紧跟着 \r\n ,之后是分块本身,后面也是 \r\n,终止块是一个常规的分块,不同之处在于其长度为 0,图示如下:

    偶遇 Content-Length 和 Transfer-Encoding

    抓包后的结果:

    偶遇 Content-Length 和 Transfer-Encoding

    解决方案

    找到问题就好办了,第一反应就是在 axios 的拦截器中把 Transfer-Encoding 头干掉,然后把 Content-Length 头给加上:

    axios.interceptors.request.use(
      config => {
        console.log('config', config.headers)
        delete config.headers['Transfer-Encoding']
        return config
      },
      error => Promise.reject(error)
    )
    

    然而还是太天真了,Transfer-Encoding 根本干不掉,后来发现在Content-Length 和 Transfer-Encoding 这两个头部是不共存的,也就是说,如果我手动增加了 Content-Length 头,会自动干掉 Transfer-Encoding 头...

    那如何获取 Content-Length 长度呢?最后在 cnodejs 社区竟然发现了一模一样的问题,作者深入研究 request、axios 和 form-data 源码,最终找到了解决方案,就是利用 form-data 的 getLength 方法获取长度,然后手动添加 Content-Length 头。

    最后改造后的代码如下:

    var axios = require('axios')
    var FormData = require('form-data')
    var fs = require('fs')
    
    async function request() {
      var data = new FormData()
      data.append('contentType', 'image/jpeg')
      data.append('value', fs.createReadStream('/Users/keliq/Pictures/jzm.jpeg'))
      var len = await new Promise((resolve, reject) => {
        return data.getLength((err, length) => (err ? reject(err) : resolve(length)))
      })
      var config = {
        method: 'post',
        url: 'https://api.weixin.qq.com/wxa/img_sec_check?access_token=xxx',
        headers: {
          'Content-Type': 'application/json',
          ...data.getHeaders(),
          'Content-Length': len,
        },
        data: data,
        proxy: {
          host: '127.0.0.1',
          port: 8888,
        },
      }
    
      axios(config)
        .then(function (response) {
          console.log(JSON.stringify(response.data))
        })
        .catch(function (error) {
          console.log('error')
        })
    }
    
    request()
    

    起源地下载网 » 偶遇 Content-Length 和 Transfer-Encoding

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元