瀏覽器有別_HTTP報文的回車換行

本來以為瀏覽器HTTP報文的生成應該是完全一致的。但最近在做一個項目的時候,發現Safari和Chrome提交同一份表單,後端的處理結果不一致。看提交結果呢,是因為Safari多了個回車。由於原項目的提交數據比較複雜,我就寫了簡單的測試來加以驗證。

說是測試,其實也是驗證心裡的想法:正常的HTTP報文每行結尾符一般用\r\n,那如果我提交的文本裡面帶了\r\n,那瀏覽器會不會主動補充成\r\n呢?

從現在的情況來看,Safari是會主動補充成\r\n,而Google不會。接下來就來測試一下。

測試前的準備

一、準備一個測試用的Web服務

const Koa = require('koa');
const koaBody = require('koa-body');
const Router = require("koa-router");
const router = new Router();
const app = new Koa();

app.use(koaBody({ multipart: true }));

router.get("/browser/returntest", (ctx) => {
    ctx.status = 200;
    ctx.body = "<form method='post' action='/browser/returntest'><textarea name='str'></textarea><button type='submit'></button></form>";
});

router.post("/browser/returntest", (ctx) => {
    console.log(ctx.request.body)
    ctx.status = 200;
    ctx.body = {
        isSuccessful: true,
        data: "post,成功",
        errCode: 0
    };
});
app.use(router.routes()).use(router.allowedMethods());
app.listen(3030);

二、選定測試參數

這次測試主要選定了幾個可以上傳鍵值對的Content Type,分別是application/json、application/x-www-form-urlencoded、multipart/form-data。

按理來說:json數據字元串經過轉義後\n就是\\n,本來就不是換行,而是反斜杠和字母n;urlencoded會進行url編碼,\n就是%0a,也不是換行;而form-data的換行就是換行。

三、選擇測試工具

我用的是Charles,用起來比起直接在瀏覽器看報文更加自由一點。

我平時主要是爬蟲需要進行模擬登錄時或者爬取手機端時,用它來分析請求報文。

簡書上的一篇教程

開始測試

一、編寫測試腳本

  1. application/json格式的請求

    var xhr = new XMLHttpRequest;
    xhr.open("post", "/browser/returntest");
    xhr.setRequestHeader("Content-Type", "application/json");
    xhr.send(JSON.stringify({
      str: "abc\nabc"
    }));
    // # 等效於
    // curl -H "Content-Type: application/json" -X POST --data '{"str":"abc\nabc"}' //localhost:3030/browser/returntest
    
  2. application/x-www-form-urlencoded格式的請求

    var xhr = new XMLHttpRequest;
    xhr.open("post", "/browser/returntest");
    xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    xhr.send("str=abc\nabc");
    // # 等效於
    // curl -H "Content-Type: application/x-www-form-urlencoded" -X POST --data 'str=abc
    // abc' //localhost:3030/browser/returntest
    
  3. multipart/form-data格式的請求

    var xhr = new XMLHttpRequest;
    var formData = new FormData
    formData.append("str", "abc\nabc");
    xhr.open("post", "/browser/returntest");
    xhr.send(formData);
    // # 等效於
    // curl -H "Content-Type: multipart/form-data" -X POST --form 'str=abc
    // abc' //localhost:3030/browser/returntest
    

二、測試

  1. 啟動Nodejs Web服務;
  2. 打開瀏覽器,輸入//localhost.charlesproxy.com:3030/browser/returntest
  3. 打開Charles;
  4. 打開瀏覽器控制台,輸入腳本進行測試。

三、測試結果

  1. 提交application/json請求(Google、Safari結果一致)

    把application/json格式的請求輸入到控制台,可以在Charles截獲到請求:

    image-20211021133155120

    此時nodejs的輸出為:

    image-20211021133239152

    Charles可以看到請求報文的數據是:

    image-20211021133320495

    Charles可以看到請求體的十六進位表示是:

    image-20211021133423434

    可以看到,\n就是\n\對應5cn對應6e。(想確認ASCII碼的可以看對照表

    application/json測試驗證猜想,json數據字元串經過轉義後\n就是\\n,本來就不是換行,而是反斜杠和字母n。

  2. 提交application/x-www-form-urlencoded請求(Google、Safari結果一致)

    把application/x-www-form-urlencoded格式的請求輸入到控制台,可以在Charles截獲到請求。

    此時nodejs的輸出為:

    image-20211021133239152

    Charles可以看到請求報文的數據是:

    image-20211021134403736

    Charles可以看到請求體的十六進位表示是:

    image-20211021134424754

    這和猜想的倒是有些不一樣,xhr.send的時候沒有把換行按urlencoded的格式轉成%0A,而是以換行的形式傳輸。更讓我意想不到的是,這次Safari也沒有在換行前加上回車了,我本來以為Safari補回車或換行是為了嚴謹性,結果太讓人失望了。

  3. 提交application/x-www-form-urlencoded請求的補充測試(Google、Safari結果一致)

    xhr.send不行,也許是application/x-www-form-urlencoded格式的數據大多數時候是以表單形式直接提交的吧,那我通過表單直接提交試一下。

    image-20211021135607282

    此時在Charles截獲到請求:

    image-20211021135637049

    請求體的十六進位表示是:

    image-20211021135739035

    這次編碼了,但是回車(%0D)也給加上了是什麼鬼,Google也是一樣的,這難道是瀏覽器統一的標準.

    我直接在本地複製一段只有換行的文本呢?好吧,也是一樣的。

    綜上,通過瀏覽器表單輸入的多行文本多會被補全成\r\n

    nodejs的輸出都為:

    image-20211021140223256

  4. 提交multipart/form-data請求(Google、Safari結果不一致)

    • Google

      把multipart/form-data格式的請求輸入到控制台,可以在Charles截獲到請求。

      此時nodejs的輸出為:

      image-20211021133239152

      Charles可以看到請求報文的數據是:

      image-20211021140612198

      Charles可以看到請求體的十六進位表示是:

      image-20211021140837073

      Google測試驗證猜想,abc和abc之間只有換行(0a)。

    • Safari

      把multipart/form-data格式的請求輸入到控制台,可以在Charles截獲到請求。

      此時nodejs的輸出為:

      image-20211021140223256

      Charles可以看到請求報文的數據是:

      image-20211021141453728

      Charles可以看到請求體的十六進位表示是:

      image-20211021141725023

      Safari測試驗證猜想,abc和abc之間補充上回車(0d)。

      除此之外,我還測試了\r上傳會不會補充\n,結果也是會的。

測試小結

實驗證明,如果以multipart/form-data數據格式上傳帶回車或換行的數據,在Safari里會補充成回車換行,而Chrome不會。如果不要回車換行其中的一個,後端還要做校驗才是。

這次實驗我本來以為Safari補全成回車換行是更嚴謹的做法,但它這做事做一半就讓人很不舒服,要補把application/x-www-form-urlencoded格式的數據也補了嘛。不過從合理性上考慮,畢竟叫urlencoded,我們做數據請求的時候還是要自己做一個編碼。

var xhr = new XMLHttpRequest;
xhr.open("post", "/browser/returntest");
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(encodeURI("str=abc\nabc"));
// # 等效於
// curl -H "Content-Type: application/x-www-form-urlencoded" -X POST --data 'str=abc%0aabc' //localhost:3030/browser/returntest

然後這次測試還不完善,像以multipart/form-data數據格式上傳帶回車或換行的文件是否會有問題沒有測,響應體是否也會被補充成回車換行沒有測。有興趣大家自己測一測吧。