記一次Content-Length引起的血案

背景

新項目上線, 發現一個奇怪的BUG, 請求接口有很小的機率返回400 Bad Request,拿到日誌記錄的請求的參數於POSTMAN中測試請求接口, 發現可以正常響應.json

排查過程

  • 首先服務器可以正常響應400 Bad Request, 排除接口故障問題.
  • 對比日誌過程當中發現
{
    "hello":"world"
}複製代碼

接口可以正常響應業務數據.瀏覽器

{
    "hello":"world",
    "kw":"我是八阿哥"
}複製代碼

則接口返回400錯誤,接口的請求方式均爲post json,因而開始review代碼.發如今發送請求時設置了Content-Length,在含中文字符的狀況下接口均返回400,定位到緣由.請求的僞代碼以下bash

let param = {
    "hello":"world",
    "kw":"我是八阿哥"
}

let _options = {
    headers: {
        'Content-Type': 'application/json',
        'Content-Length': JSON.stringify(param).length
    },
    url: url,
    method: 'POST',
    json: true,
    time: true,
    timeout: 5 * 1000,
    body: param
}

return new Promise((resolve,reject)=>{
    request(_options,(error, response, body)=>{
        ///XXXX
    ])
})複製代碼

分析結果

首先, 來講說什麼是Content-Length,在http的協議中Content-Length首部告訴瀏覽器報文中實體主體的大小。這個大小是包含了內容編碼的,好比對文件進行了gzip壓縮,Content-Length就是壓縮後的大小(這點對咱們編寫服務器很是重要)。除非使用了分塊編碼,不然Content-Length首部就是帶有實體主體的報文必須使用的。使用Content-Length首部是爲了可以檢測出服務器崩潰而致使的報文截尾,並對共享持久鏈接的多個報文進行正確分段.服務器

其次,爲何含有中文字符的請求參數返回400,由於Content-Length是計算請求參數的字節數,而非字符數.而JSON.stringify(param).length返回的是字符數.含中文字符的狀況下app

console.log('八阿哥'.length)  //3, 即3個字符複製代碼
console.log(Buffer.byteLength('八阿哥', 'utf8'));  //9, utf-8編碼下,一個漢字是3字節存儲的複製代碼

致使接口層拿到的Content-Length小於真實的字節長度, 於是沒法正確的解析數據, 從而返回400 Bad Request.所以須要將設置Content-Length的長度改成Buffer.byteLength(JSON.stringify(param),'utf8')post

相關文章
相關標籤/搜索