背景
新專案上線, 發現一個奇怪的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`)