面試官:觀察過 chrome 調試工具的請求體麼?Form Data 和 Request Payload 有什麼區別?

前言

這篇文章旨在記錄本身解惑過程,好比前端

  1. 在 chrome 調試工具中,Form DataRequest Payload 有什麼區別?
  2. application/x-www-form-urlencodedapplication/json 有什麼區別?開發中咱們應該怎麼選擇?
  3. 爲何後端有時會沒法解析本身發送的數據?
  4. POST 的跨域請求中,有辦法不發送 OPTIONS 預檢請求也能發送數據的方法麼?

話很少說,直接進入主題。node

發現問題,從兩個截圖開始

微信請求

掘金請求

這兩個截圖就是寫這篇文章的初衷,微信文章在打開的時候是顯示的 Form Data,第二張圖是掘金在打開文章發起的請求,當時看到就特奇怪,Form DataRequest Payload 這倆貨有啥區別?爲啥都是 POST 請求,但卻有兩種發送數據的方式?git

我這我的就是屬於碰到這種奇怪的問題不把他搞清楚就睡不了覺的人,咱們直接在本地場景重現,好好看看這倆貨。github

若是不想看中間的分析過程,能夠直接點擊 總結 看杰倫。chrome

場景重現

本地起兩個服務,前端和後端,經過建立 XMLHttpRequest 對象來進行數據傳輸,並經過 setRequestHeader() 來改變 Content-Type,最終咱們在調試工具中完美重現了兩種模式。npm

文章裏的示例代碼均可以從這個倉庫裏找到,但願本身親自嘗試的小夥伴能夠點擊查看詳情 示例地址json

git clone -b demo/study-post-request https://github.com/jsjzh/tiny-codes.git

Request Payload

若是但願看到 Request Payload,須要設置請求頭部 Content-Type: application/json,再將數據通過 JSON.stringify 序列化後發送。後端

chrome application/json

你們能夠看到我這裏的 Origin: http://localhost.charlesproxy.com:3000,這是由於要用 charles 抓本地包,得用這作一層代理

直接上抓包的截圖跨域

application/json 抓包

上半部分就是一個完整的 http 請求,空行上面爲請求頭,空行下面是請求體,能夠看到咱們的請求體就是一個 json 序列化後的字符串。瀏覽器

下半部分,注意 JSONJSON Text 兩個 tab,這個是咱們設置了 Content-Type: application/json 了以後,charles 自動會給帶上的。

後端接到 http 請求後,就是截取空行後的這個請求體解析,由於咱們傳了 Content-Type: application/json,因此後端知道請求體是一個 json 字符串,就能夠用 JSON.parse 來解析。

發送的數據爲

{
  "name": "king",
  "age": 18,
  "isAdmain": true,
  "groups": [1, 2, 3],
  "address": "",
  "foo": null,
  "bar": undefined,
  "extra": { "wechat": "kimimi_king", "qq": 454075623 }
}

解析的數據爲

{
  "name": "king",
  "age": 18,
  "isAdmain": true,
  "groups": [1, 2, 3],
  "address": "",
  "foo": null,
  "extra": { "wechat": "kimimi_king", "qq": 454075623 }
}

能夠看到除了 bar: undefined 以外,numberbooleannull,數據類型都被正確的傳輸了。

Form Data

再來講說 Form Data,咱們須要設置 Content-Type: application/x-www-form-urlencoded,再將數據經過 qs.stringify 序列化後再發送。

qs 即爲 qs npm source,是一個將數據 querystring 化的庫

能夠簡單理解成他能夠把一個對象轉換成相似 get 請求中 ? 後面的查詢字段 key=data&key2=data2

若是不通過 qs 處理直接發送,方法會使用 toString() 來將數據轉爲字符串,若是傳輸的是對象,你會獲得 [object Object]

chrome application/x-www-form-urlencoded

這裏也直接貼出抓包的截圖

application/x-www-form-urlencoded 抓包

上半部分就是 http 請求,能夠看到當咱們設置 Content-Type: application/x-www-form-urlencoded 請求體也是放在了空行以後。

下半部分,對比剛纔的 application/json 就能發現不同的地方了,JSONJSON Text 的 tab 不見了,取而代之的是 Form tab。

後端接到 http 請求以後,也是截取的空行後面的請求體,並使用 qs.parse 進行解析。

發送的數據爲

{
  "name": "king",
  "age": 18,
  "isAdmain": true,
  "groups": [1, 2, 3],
  "address": "",
  "foo": null,
  "bar": undefined,
  "extra": { "wechat": "kimimi_king", "qq": 454075623 }
}

解析的數據爲

{
  "name": "king",
  "age": "18",
  "isAdmain": "true",
  "groups": ["1", "2", "3"],
  "address": "",
  "foo": "",
  "extra": { "wechat": "kimimi_king", "qq": "454075623" }
}

通過和 Content-Type: application/json 對比,咱們能夠看到,不只 numberboolean 的數據類型丟失,而且 foo: null 還被轉換成了 foo: ""

交換序列化方式

剛纔咱們嘗試了正確的 Content-Type 對應正確的序列化方式

application/json + JSON.stringify

application/x-www-form-urlencoded + qs.stringify

但其實咱們觀察到實際的 http 請求,這兩個 Content-Type 都是將數據放在空行後傳輸,因此咱們固然也能夠交換他們的序列化方式。

application/json + qs.stringify

image.png

image.png

這裏直接就說結論,咱們設置了 application/json,但使用 qs.stringify 序列化,結果就是

  1. chrome 調試工具的 Request Payload 沒法解析,遂沒法格式化數據
  2. charles 工具的 JSONJSON Text 沒法解析
  3. 最重要的,後端如果讀取了 Content-Typeapplication/json,就會使用 JSON.parse 來解析數據
在後端咱們固然能夠手動用 qs.parse 來進行解析,可是咱們爲何要給本身埋坑?

application/x-www-form-urlencoded + JSON.stringify

image.png

image.png

同理,使用了 Content-Type 和不正確的序列化方式,不只 chrome 和 charles 沒法解析,後端也會有疑惑,更重要的是會給本身埋坑。

總結

image.png

誒,沒錯,我就想皮一下

前面說了這麼多,如今來總結一下

  1. Form DataRequest Payload 就是由於請求的 Content-Type 不一樣,而不一樣的解析請求體後的呈現方式
  2. Content-Type 設置成 application/json 仍是 application/x-www-urlencoded 在 http 請求中,除了 Header 之外並沒有區別,都是將請求體放在空行後

那咱們在開發中應該如何選擇 Content-Type?建議若是不是項目有特別要求,都使用 application/json,緣由有如下幾點

  1. 原生自帶的 JSON.stringifyJSON.parse 不香麼?qs 在前端就有不少實現,好比 qsquery-string,還有 node 自帶的 querystring
  2. x-www-form-urlencoded 須要使用配套 qs.stringify後端解析數據後會丟失數據類型,好比 numberbooleannull
  3. 不一樣的框架對於 qs.parse 的實現方式不一樣,在項目剛開始對接時可能會有先後端對齊解析方式的操做
  4. 前端的 qs 倉庫默認只能處理 5 層對象,默認只能解析 1000 個參數(固然,這兩個配置均可以修改)舉一個例子
{
  "a": {
    "b": {
      "c": {
        "d": {
          "e": {
            "f": {
              "g": { "name": "king" }
            }
          }
        }
      }
    }
  }
}

由於對象嵌套的層數太深,解析後就成了以下

{
  "a": {
    "b": {
      "c": {
        "d": {
          "e": {
            "[f][g][name]": "king"
          }
        }
      }
    }
  }
}

固然,使用了 application/json 以後會有些不同

  1. 配置頭部 Content-Type: application/json 以後就不是簡單請求,會發起一個 Options 預檢請求
  2. 後端須要同步配置 Access-Control-Request-Headers: Content-Type,容許前端配置 Content-Type 頭部

固然,再說下去就是 CORS 的知識點了,這方面也有不少內容能夠掰開細說,我也正在整理這方面的內容,能夠小小的期待一下。

後語

不知道這篇文章是否給你帶來了一些幫助,若是有的話是個人榮幸,在平時碰到問題的時候不妨能夠挖的深一點,就像此次的 Form DataRequest Payload,當咱們挖掘到 http 請求層面就能發現二者其實並沒有區別,就是瀏覽器對於 http 協議的一種封裝,而正確的使用 Content-Type 就是咱們和後端聯調的一個約定,也是一個規範。

咱們固然能夠隨意設置 Content-Type,可是這就須要和後端進行非必要聯調,而且也不方便後續理解維護,因此咱們能簡單就簡單一些,有些框架會自動根據 Content-Type 的值來解析請求體,頭髮已經這麼少了,咱們就不要強行增長遊戲難度了。

頁腳

代碼即人生,我甘之如飴。

技術不斷在變
頭腦一直在線
前端路漫漫
咱們下期見

by --- 褲襠三重奏

我在這裏 gayhub@jsjzh 歡迎你們來找我玩兒。

歡迎小夥伴們直接加我,拉你進羣一塊兒搞事情,記得備註一下你是從哪裏看到文章的。

ps: 若是圖片失效,能夠加我 wechat: kimimi_king
相關文章
相關標籤/搜索