background

In the process of developing wechat mini programs, it is necessary to connect with the content audit interface of wechat, that is, upload a picture to check whether it is illegal or not. An AXIos upload returns 412; a Request upload returns 412.

However, if the interface of wechat is replaced by its own, the data can be successfully received, so there must be some differences between axios and Request when sending HTTP requests. So how do you find the difference? The only way to find the difference is to catch the bag.

Caught screening

First open Charles, then run the axios and Request code respectively. Note that to configure the proxy in the code, let’s look at the request code:

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)
})
Copy the code

Packet capture results are as follows:

  1. request

    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"The content-type: image/jpeg y Ø ya...Copy the code
  2. The response

    HTTP/1.1 200 OK Date: Mon, 05 Apr 2021 06:59:26 GMT Content-Type: Application /json; 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"}Copy the code

Let’s look at the axios code

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)
  })
Copy the code

Packet capture results are as follows:

  1. request

    POST /wxa/img_sec_check? Accept: application/json, text/plain, */* Content-type: multipart/form-data; A boundary = -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - 571426341398317845770943 the 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 y Ø ya...Copy the code
  2. The response

    HTTP/1.1 412 Feed Additive Failed Date: Mon, 05-APR-2021 07:00:41 GMT Content-Length: 0 Connection: closeCopy the code

After comparison, axios has one more transfer-encoding header with a value of chunked than Request, while Request has one more Content-Length header than Axios. Therefore, the difference between these two headers is the primary culprit that causes the wechat interface to return 412.

Learn about Content-Length and Transfer-Encoding headers

I have checked a lot of information on the Internet, and found that piao Ruiqing’s article is very detailed, which is excerpted as follows:

  • Content-length: indicates the Length of an HTTP message. It is the number of eight-bit bytes expressed in decimal digits
  • Transfer-encoding: If the message length cannot be obtained before the request processing is complete, use transfer-encoding: chunked

How content-Length works

Content-length should be exact, otherwise it will cause an exception. This size includes all Content encoding. For example, if a text file is gzip compressed, the content-Length header refers to the compressed size, not the original size. What if the numbers provided are inaccurate?

  • Content-length > Actual Length: After reading the end of the message, the server or client waits for the next byte and does not respond until timeout.
  • Content-length < Actual Length: The first time the message is truncated and the second time it is parsed incorrectly.

How transfer-Encoding works

The data is sent as a series of blocks, and at the beginning of each block the length of the current block is added, in hexadecimal form, followed by \r\n, followed by the block itself, and also \r\n. The terminating block is a regular block, the difference being that its length is 0, as shown below:

Result after packet capture:

The solution

The first thing to do is to kill the Transfer-Encoding header in the Axios interceptor and add the Content-Length header:

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

It turns out that the content-Length and Transfer-Encoding headers do not coexist, which means that if I manually add the Content-Length header, Automatically kills transfer-encoding headers…

So how do you get the content-Length? Finally, I found the same problem in the CNodeJS community. I dug into the source code of Request, Axios, and form-data, and finally found a solution, which uses the getLength method of form-data to get the length. Then add the Content-Length header manually.

The code after the final transformation is as follows:

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()
Copy the code