資料傳輸POST心法分享,做前端的你還解決不了這個bug?

語言: CN / TW / HK

背景

隨時隨地給大家提供技術支援的葡萄又來了。這次的事情是這樣的,提供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,大家感興趣的可以自行下載試試。

Demo 地址: https://gcdn.grapecity.com.cn/forum.php?mod=attachment&aid=NDc5OTJ8YmU1Mjk0NDN8MTY1MDI2MTI0M3wxfDUwOTgw