背景
隨時隨地給大家提供技術支援的葡萄又來了。這次的事情是這樣的,提供demo屬於是常規操作,但是前兩天客戶突然反饋壓縮傳輸模組丟擲異常,具體情況是壓縮內容傳輸到服務端後無法解壓。
由於程式碼沒有發生任何變動,前端相關依賴也沒有升級,服務端java版本也沒有變化,所以我們可以推定為環境問題;進一步仔細檢查,經過反覆對比後突然發現服務端收到的壓縮內容變長了;和前端請求內容進行對比,發現所有的\r和\n都變成了\r\n。
綜合以上分析我們初步判斷:問題出在了瀏覽器轉譯之中。為了驗證猜想是否正確,葡萄將chrome版本回退到92版,異常消失,服務端接收的內容也沒有被替換。
問題是順利解決了,但是Chrome POST資料內容居然會在傳輸過程中發生變化。一直擅長大前端技術的葡萄絕不認輸,為了弄明白這一原因,我們來看看POST的細節操作到底有什麼。
控制字元
首先我們需要搞清楚幾個控制字元的含義。
- 回車符(CR)和換行符(LF)是文字檔案用於標記換行的控制字元(control characters)或位元組碼(bytecode)。
- CR = Carriage Return,回車符號(\r,十六進位制 ascii 碼為0x0D,十進位制 ascii 碼為13),用於將滑鼠移動到行首,並不前進至下一行。
- LF = Line Feed,換行符號( \n, 十六進位制 ascii 碼為 0x0A,十進位制 ascii 碼為10)。
緊鄰的 CR 和 LF(組成 CRLF,\r\n,或十六進位制 0x0D0A)將滑鼠移動到下一行行首。(Windows 作業系統預設的文字換行符為 CRLF;Linux 以及 macOS 系統預設使用 LF,早期的 mac os 系統使用 CR 換行。)
在程式碼管理中,在不同作業系統下CRLF會有很大不同。下面在不同系統中為大家實際演示一下:
在Mac Visual Code中新建一個文件預設為LF,而Windows中為CRLF,可以選擇切換行尾序列的內容的型別。
Mac版Visual Code
Windows 版
面對這種情況,需要開發者統一CRLF,以免不同作業系統開發導致程式碼管理的混亂。
POST傳輸的資料變化
弄明白了在不同系統中,控制字元會出現不同的原因,接下來我們就需要搞清楚為什麼POST的資料在傳輸過程中發生了變化。
我們來寫個簡單Demo測試一下。先在頁面上放一個允許換行的textarea, 輸入帶換行的文字,獲取內容看到只有\n轉譯。通過FormData直接post資料到服務端,然後直接返回,看到\n全部變成了\r\n。
var uploadData = document.getElementById("ta").value
var formData = new FormData();
formData.append("data", uploadData)
fetch("http://localhost:8088/spread/getpdf", {
body: formData,
method: "POST"
}).then(resp => resp.text())
.then(text => {
console.log(JSON.stringify(text));
document.getElementById("result").innerHTML= JSON.stringify(text)
})
瀏覽器標識:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36
回退Chrome到92版本,傳送和接收文字此時編為一致:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36
深入探究這一原因,我們瞭解到網際網路請求意見稿2046(RFC 2046)4.1.1.中有明確說明:
“ The canonical form of any MIME‘text’ subtype MUST always represent a
line break as a CRLF sequence. “
這裡我們可以看到所有的文字型別都要使用CRLF,而Chrome只是修復了一個“bug”,對於使用者而言,在普通文字中使用者感知不到CR、LF和CRLF的區別,但是當使用場景轉換到解壓的文字內容就變得十分重要。
三種解決方式
大家都知道POST是HTTP的一個常用方法,而另一個我們常用的方法是GET。
關於GET和POST區別以及使用相關問題這裡不做贅述,要解決POTS傳輸的資料變化問題,最相關的是Content-Type。
首先我們來了解Content-Type和MIME分別是什麼:
Content-Type,內容型別,一般是指網頁中存在的Content-Type,用於定義網路檔案的型別和網頁的編碼,決定瀏覽器將以什麼形式、什麼編碼讀取這個檔案,這就是經常看到一些Asp網頁點選的結果卻是下載到的一個檔案或一張圖片的原因。
在POST中常用的Content-Type有application/x-www-form-urlencoded、multipart/form-data和application/json。
1、 application/x-www-form-urlencoded
將需要內容提交表單後,內容會按照name1=value1&name2=value2的方式編碼,並且key和valu e都會進行URL轉碼。
對於"\n"和"\r" 會被轉碼為'%0A'和'%0D',通過這種傳輸方式,避免了瀏覽器的對CRLF的修正可以解決以上問題。
但是這樣轉碼會增加文字長度,原本1個字元變成了3個,結果是壓縮的文字又變長了。
2、multipart/form-data
當需要想伺服器提交檔案時,就需要使用這種方式。前面程式碼中我們可以看到當formData是普通文字是會被修正,為了解決這個情況我們可以將string內容封裝到Blob中作為檔案流傳輸,來避免修正。
這樣傳輸,服務端會以檔案方式收到內容,直接讀取Stream內容;對於壓縮文字,這種處理方式最優。
var formData = new FormData();
formData.append("data", uploadData)
formData.append("data1", new Blob( [uploadData]))
上圖展示了同樣的內容,使用不同方式進行傳輸。
3、 application/json
Json也是目前比較流行的傳輸方式,json的內容在post傳輸中也不會被改變,如果文字內容不長,也是不錯的方式。
fetch("http://localhost.charlesproxy.com:8088/spread/postjson", {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({data: uploadData}),
method: "POST"
}).then(resp => resp.text())
.then(text => {
console.log(JSON.stringify(text));
document.getElementById("result").innerHTML= JSON.stringify(text)
})
總結
作為一個前端er,除了HTML、CSS和Javascript三大件,熟練使用Axios等類庫呼叫API,更不可忽視的是要了解如何除錯網路請求,在專案出現問題時能快速定位到問題的所在。
這裡提供了在 Angular 框架下動態載入js檔案時返回 Content-Type 為text/html 的Demo,大家感興趣的可以自行下載試試。