記一次Content-Length引發的血案

HeyZero發表於2019-02-28

背景

新專案上線, 發現一個奇怪的BUG, 請求介面有很小的概率返回400 Bad Request,拿到日誌記錄的請求的引數於POSTMAN中測試請求介面, 發現能夠正常響應.

排查過程

  • 首先伺服器能夠正常響應400 Bad Request, 排除介面故障問題.
  • 對比日誌過程中發現
{
    "hello":"world"
}複製程式碼

介面能夠正常響應業務資料.

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

則介面返回400錯誤,介面的請求方式均為post json,於是開始review程式碼.發現在傳送請求時設定了Content-Length,在含中文字元的情況下介面均返回400,定位到原因.請求的虛擬碼如下

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返回的是字元數.含中文字元的情況下

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`)

相關文章