這篇文章旨在記錄本身解惑過程,好比前端
Form Data
和 Request Payload
有什麼區別?application/x-www-form-urlencoded
和 application/json
有什麼區別?開發中咱們應該怎麼選擇?POST
的跨域請求中,有辦法不發送 OPTIONS
預檢請求也能發送數據的方法麼?話很少說,直接進入主題。node
這兩個截圖就是寫這篇文章的初衷,微信文章在打開的時候是顯示的 Form Data
,第二張圖是掘金在打開文章發起的請求,當時看到就特奇怪,Form Data
和 Request 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
,須要設置請求頭部 Content-Type: application/json
,再將數據通過 JSON.stringify
序列化後發送。後端
你們能夠看到我這裏的
Origin: http://localhost.charlesproxy.com:3000
,這是由於要用 charles 抓本地包,得用這作一層代理
直接上抓包的截圖跨域
上半部分就是一個完整的 http 請求,空行上面爲請求頭,空行下面是請求體,能夠看到咱們的請求體就是一個 json 序列化後的字符串。瀏覽器
下半部分,注意 JSON
和 JSON 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
以外,number
、boolean
和 null
,數據類型都被正確的傳輸了。
再來講說 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]
這裏也直接貼出抓包的截圖
上半部分就是 http 請求,能夠看到當咱們設置 Content-Type: application/x-www-form-urlencoded
請求體也是放在了空行以後。
下半部分,對比剛纔的 application/json
就能發現不同的地方了,JSON
和 JSON 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
對比,咱們能夠看到,不只 number
和 boolean
的數據類型丟失,而且 foo: null
還被轉換成了 foo: ""
。
剛纔咱們嘗試了正確的 Content-Type
對應正確的序列化方式
application/json
+ JSON.stringify
application/x-www-form-urlencoded
+ qs.stringify
但其實咱們觀察到實際的 http 請求,這兩個 Content-Type
都是將數據放在空行後傳輸,因此咱們固然也能夠交換他們的序列化方式。
這裏直接就說結論,咱們設置了 application/json
,但使用 qs.stringify
序列化,結果就是
Request Payload
沒法解析,遂沒法格式化數據JSON
和 JSON Text
沒法解析Content-Type
爲 application/json
,就會使用 JSON.parse
來解析數據
在後端咱們固然能夠手動用
qs.parse
來進行解析,可是咱們爲何要給本身埋坑?
同理,使用了 Content-Type
和不正確的序列化方式,不只 chrome 和 charles 沒法解析,後端也會有疑惑,更重要的是會給本身埋坑。
誒,沒錯,我就想皮一下
前面說了這麼多,如今來總結一下
Form Data
和 Request Payload
就是由於請求的 Content-Type
不一樣,而不一樣的解析請求體後的呈現方式Content-Type
設置成 application/json
仍是 application/x-www-urlencoded
在 http 請求中,除了 Header
之外並沒有區別,都是將請求體放在空行後那咱們在開發中應該如何選擇 Content-Type
?建議若是不是項目有特別要求,都使用 application/json
,緣由有如下幾點
JSON.stringify
和 JSON.parse
不香麼?qs 在前端就有不少實現,好比 qs
和 query-string
,還有 node 自帶的 querystring
x-www-form-urlencoded
須要使用配套 qs.stringify
,後端解析數據後會丟失數據類型,好比 number
、boolean
、null
qs.parse
的實現方式不一樣,在項目剛開始對接時可能會有先後端對齊解析方式的操做qs
倉庫默認只能處理 5 層對象,默認只能解析 1000 個參數(固然,這兩個配置均可以修改)舉一個例子{ "a": { "b": { "c": { "d": { "e": { "f": { "g": { "name": "king" } } } } } } } }
由於對象嵌套的層數太深,解析後就成了以下
{ "a": { "b": { "c": { "d": { "e": { "[f][g][name]": "king" } } } } } }
固然,使用了 application/json
以後會有些不同
Content-Type: application/json
以後就不是簡單請求,會發起一個 Options
預檢請求Access-Control-Request-Headers: Content-Type
,容許前端配置 Content-Type
頭部固然,再說下去就是 CORS
的知識點了,這方面也有不少內容能夠掰開細說,我也正在整理這方面的內容,能夠小小的期待一下。
不知道這篇文章是否給你帶來了一些幫助,若是有的話是個人榮幸,在平時碰到問題的時候不妨能夠挖的深一點,就像此次的 Form Data
和 Request Payload
,當咱們挖掘到 http 請求層面就能發現二者其實並沒有區別,就是瀏覽器對於 http 協議的一種封裝,而正確的使用 Content-Type
就是咱們和後端聯調的一個約定,也是一個規範。
咱們固然能夠隨意設置 Content-Type
,可是這就須要和後端進行非必要聯調,而且也不方便後續理解維護,因此咱們能簡單就簡單一些,有些框架會自動根據 Content-Type
的值來解析請求體,頭髮已經這麼少了,咱們就不要強行增長遊戲難度了。
代碼即人生,我甘之如飴。
技術不斷在變
頭腦一直在線
前端路漫漫
咱們下期見by --- 褲襠三重奏
我在這裏 gayhub@jsjzh 歡迎你們來找我玩兒。
歡迎小夥伴們直接加我,拉你進羣一塊兒搞事情,記得備註一下你是從哪裏看到文章的。
ps: 若是圖片失效,能夠加我 wechat: kimimi_king