面试官:观察过 chrome 调试工具的请求体么?Form Data 和 Request Payload 有什么区别?

夏侯威
• 阅读 7346

面试官:观察过 chrome 调试工具的请求体么?Form Data 和 Request Payload 有什么区别?

前言

这篇文章旨在记录自己解惑过程,比如

  1. 在 chrome 调试工具中,Form DataRequest Payload 有什么区别?
  2. application/x-www-form-urlencodedapplication/json 有什么区别?开发中我们应该怎么选择?
  3. 为什么后端有时会无法解析自己发送的数据?
  4. POST 的跨域请求中,有办法不发送 OPTIONS 预检请求也能发送数据的方法么?

话不多说,直接进入主题。

发现问题,从两个截图开始

面试官:观察过 chrome 调试工具的请求体么?Form Data 和 Request Payload 有什么区别?

面试官:观察过 chrome 调试工具的请求体么?Form Data 和 Request Payload 有什么区别?

这两个截图就是写这篇文章的初衷,微信文章在打开的时候是显示的 Form Data,第二张图是掘金在打开文章发起的请求,当时看到就特奇怪,Form DataRequest Payload 这俩货有啥区别?为啥都是 POST 请求,但却有两种发送数据的方式?

我这个人就是属于碰到这种奇怪的问题不把他搞清楚就睡不了觉的人,我们直接在本地场景重现,好好看看这俩货。

如果不想看中间的分析过程,可以直接点击 总结 看杰伦。

场景重现

本地起两个服务,前端和后端,通过创建 XMLHttpRequest 对象来进行数据传输,并通过 setRequestHeader() 来改变 Content-Type,最终我们在调试工具中完美重现了两种模式。

文章里的示例代码都可以从这个仓库里找到,希望自己亲自尝试的小伙伴可以点击查看详情 示例地址

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 调试工具的请求体么?Form Data 和 Request Payload 有什么区别?

大家可以看到我这里的 Origin: http://localhost.charlesproxy.com:3000,这是因为要用 charles 抓本地包,得用这做一层代理

直接上抓包的截图

面试官:观察过 chrome 调试工具的请求体么?Form Data 和 Request Payload 有什么区别?

上半部分就是一个完整的 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 调试工具的请求体么?Form Data 和 Request Payload 有什么区别?

这里也直接贴出抓包的截图

面试官:观察过 chrome 调试工具的请求体么?Form Data 和 Request Payload 有什么区别?

上半部分就是 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

面试官:观察过 chrome 调试工具的请求体么?Form Data 和 Request Payload 有什么区别?

面试官:观察过 chrome 调试工具的请求体么?Form Data 和 Request Payload 有什么区别?

这里直接就说结论,我们设置了 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

面试官:观察过 chrome 调试工具的请求体么?Form Data 和 Request Payload 有什么区别?

面试官:观察过 chrome 调试工具的请求体么?Form Data 和 Request Payload 有什么区别?

同理,使用了 Content-Type 和不正确的序列化方式,不仅 chrome 和 charles 无法解析,后端也会有疑惑,更重要的是会给自己埋坑。

总结

面试官:观察过 chrome 调试工具的请求体么?Form Data 和 Request Payload 有什么区别?

诶,没错,我就想皮一下

前面说了这么多,现在来总结一下

  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 欢迎大家来找我玩儿。

欢迎小伙伴们直接加我,拉你进群一起搞事情,记得备注一下你是从哪里看到文章的。

面试官:观察过 chrome 调试工具的请求体么?Form Data 和 Request Payload 有什么区别?

ps: 如果图片失效,可以加我 wechat: kimimi_king
点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
夏侯威
夏侯威
Lv1
春风一夜吹乡梦,又逐春风到洛城。
文章
3
粉丝
0
获赞
0