![34c4b07971e1b1fa386b9bbba6542fdc.png](https://i-blog.csdnimg.cn/blog_migrate/d2063b074626a65c632b4892df5f89bb.jpeg)
HTTP 报文
用于HTTP协议交互的信息被称为HTTP报文。请求端的HTTP报文叫请求报文,响应端的HTTP报文叫响应报文。HTTP报文本身是由多行(用CR+LF也就是回车换行分隔)数据构成的字符串文本。HTTP报文大致可分为报文首部和报文主体两块。两者由最早出现的CRLF来划分的。但通常并一定有报文主体,譬如GET请求。
![3c59783eb9291fdbee76742c135119be.png](https://i-blog.csdnimg.cn/blog_migrate/45aa3bcbd6a255295daa2ca183e9a887.jpeg)
报文主体因实际交互的内容而定,不是我们今天讨论的重点,我们主要来看一下报文首部。先来看一张图。
![df9a06c543a3d1cfdb4f30708f49a93e.png](https://i-blog.csdnimg.cn/blog_migrate/b8f0b7cfcc400c7195430bddc639706e.jpeg)
从图中可以看到请求报文由请求行首部字段其他字段状态行首部字段其他字段**构成。
- 请求行 包含用于请求的方法,请求URL和HTTP版本。
- 状态行 包含表明响应的状态码,原因短语和HTTP版本。
- 首部字段请求首部字段响应首部字段通用首部字段 和用于描述HTTP报文主体的 实体首部字段**
- 其他 其他HTTP RFC(请求意见稿,是由互联网工程任务组(IETF)发布的一系列备忘录) 里未定义的字段,譬如 Cookie等
再来看一个具体的例子,如下请求是从谷歌浏览器开发者工具中抓获的。
![d1c2ec7137602faa09084e6ed0de0658.png](https://i-blog.csdnimg.cn/blog_migrate/d881fe4b522057bc14aae881d9c72138.jpeg)
可以看到,请求首部字段响应首部字段通用首部字段 是客户端和服务器都可以使用的字段,可以在客户端、服务器之间提供一些非常有用的通用功能,如 Date 表示报文创建的时间。
最后今天的主角Content-Type终于要上场了,它说明了报文主体内对象的媒体类型。
只要能准确的区分出不同字段的含义,便能知道此字段是什么类型的首部字段。
Content-Type
Content-Type 属性用于说明请求和响应的HTTP报文主体的内容类型,通常客户端和服务端的交互可以分为两类,一类是请求文件,html、css、 js、image 等等,另一类是API接口数据请求。第一类比较简单,会在响应首部里指明相应的Content-Type的值为text/html
、text/css
、text/javascript
、image/png
等等。下面我们重点讨论第二类,也就是API接口数据请求的Content-Type。
RESTful API
提到API接口数据请求,自然要介绍RESTful,它是目前最流行的API设计规范,用于Web数据接口的设计,详细的介绍,请参考文末的参考资料
它的大原则容易把握,但是细节不容易做对。我们结合七鱼的现状列举两个比较重要的。
- URL设计遵循 动词+宾语 的设计
**
RESTful 的核心思想就是,客户端发出的数据操作指令都是"动词 + 宾语"的结构。比如,GET /kefu/list
这个命令,GET是动词,/kefu/list
是宾语。
具体来说,在HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别应该对应四种基本操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。
关于这一点,七鱼的接口设计时基本是按照RESTful的思路来的,但有两个明显不符合规范的地方:
第一个是方法只用到了GET和POST,本来应该PUT和DELETE来承载的内容,也用POST来做了。
第二个是七鱼部分接口URL中包含动词。譬如 /api/corp/service/tips/get
, URL中包含动词,基本上算是RESTful最常见的一种设计错误,因为"资源"表示一种实体,所以应该是名词,URL不应该有动词,动词应该放在HTTP协议中。
- 服务器响应
**
API 返回的数据格式,不应该是纯文本,而应该是一个 JSON 对象,因为这样才能返回标准的结构化数据。所以,服务器响应的 HTTP 头的Content-Type属性要设为application/json
。
客户端请求时,也要明确告诉服务器,可以接受 JSON 格式,即请求的 HTTP 头的ACCEPT属性也要设成application/json
。
关于这一点,七鱼是完全按照规范来的,所有请求返回的格式都是application/json
的。
关于 Content-Type
HTTP 协议规定 POST 提交的数据必须放在报文主体中。 GET 请求没有明确规定,但如果在报文主体中传输数据,不同服务的处理方式也不一定相同,有的可能会被处理,有的可能会被忽略,所以通常 GET 请求没有报文主体,当请求为 GET 时,浏览器用默认的x-www-form-urlencoded
的编码方式,把from数据转换成一个多个键值对的字符串(如appkey=3858be3c20ceb6298575736cf27858a7&foreignId=29118&deviceId=29118&bid=
),然后把这个字符串追加到URL后面,用问号?
分隔,加载这个新的URL。
从上面的介绍中,我们知道 Content-Type表示报文中主体的内容类型,而 GET 请求没有报文主体,所以 GET 请求不需要指定 Content-Type,指定Content-Type是没有任何意义的。
由于 GET 请求将参数使用form编码方式追加到URL中,而大多数浏览器限制 URL 长度在 2K 字节以内,于是当 GET 请求的参数太长时,我们就面临如下这个问题:
HTTP Error 404.15 - Not Found
The request filtering module is configured to deny a request where the query string is too long.
此时,我们就需要把请求改成 POST 方式,把参数放到报文主体中传输,有报文主体了,就相应有了 Content-Type 这个实体首部字段。所以 Content-Type 一般只存在于 POST 请求中。
几种 Content-Type
1. application/x-www-form-urlencoded
这应该是最常见的 POST 提交数据的方式了。浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded
方式提交数据。请求类似于下面这样(无关的请求头在本文中都省略掉了)
POST /api/statistics/report/staffworkload?token=GQTUAGDB31JM5PV27JHW6H4KGX8EJ0ZI HTTP/1.1
Content-Type: application/x-www-form-urlencoded
cType=0&endTime=1547135999999&groupIds=&hideColumns=&kefuIdList=&limit=20&offset=0&order=name%2Casc&startTime=1547049600000
从上例中可以看到,首先 Content-Type 被指定为 application/x-www-form-urlencoded
,其次,提交的数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL 转码。例如order=name%2Casc
中的%2C实际上是逗号,name,acsc
在一起作为order的参数。
这也是默认的方式
2. application/json
application/json 这个 Content-Type 作为响应首部大家肯定不陌生,七鱼几乎所有接口都是返回 JSON 数据结构的。实际上也可以把它作为请求首部字段,用来告诉服务端报文主体是序列化后的 JSON 字符串。由于 JSON 规范的流行,除了低版本 IE 之外的各大浏览器都原生支持 JSON.stringify,服务端语言也都有处理 JSON 的工具,使用 JSON 不会遇上什么麻烦。
JSON 格式支持比键值对复杂得多的结构化数据,可以方便的提交复杂的结构化数据,特别适合 RESTful 的接口,这一点也很有用。例如在呼叫中心的设置中像IVR导航等数据就非常复杂,具有多个层级结构。用JSON的方式就显得非常合适。
同样看一个实际的例子:
POST /api/callcenter/ivrsetting/set?token=83SV6TA1PJV73VKB4C3WR2ME8OBV3Q7S HTTP/1.1
Content-Type: application/json
{
"ivrName": "test",
"ivrSwitch": 0,
"ivrRules": "",
"children": [
{
"name": "a",
"ttstext": "b",
"key": "0",
"staffGroup": {
"id": 17019,
"name": "产品组"
},
"tts": null,
"customIVR": null,
"systemPlay": null,
"check": {
"enable": 0,
"maxCount": 3
},
"service": {
"enable": 0,
"id": null
},
"robot": {
"id": null,
"name": "",
"targetId": null,
"targetName": ""
},
"action": "1"
}
],
"type": 0,
"tts": null
}
3. multipart/form-data
这也是一个常见的 POST 数据提交的方式,通常用来上传图片等文件,七鱼便是用这种方式来实现文件上传的。关于 multipart/form-data
的详细定义,请前往 rfc1867 查看。 HTTP 协议中使用多部分对象集合的方法,使得发送的一份报文主体内可包含有多类型的实体。我们以设置客服头像举例说明。
![f75ece4468d44b2849c9a40f98c47450.png](https://i-blog.csdnimg.cn/blog_migrate/ea8d893a361c355c96fd611d4c23beb8.jpeg)
POST https://nos.netease.com/ysf HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryYx62yTrBxLi5l9ah
------WebKitFormBoundaryYx62yTrBxLi5l9ah
Content-Disposition: form-data; name="Object"
59230498979298fe19ceeb140b40a583
------WebKitFormBoundaryYx62yTrBxLi5l9ah
Content-Disposition: form-data; name="x-nos-token"
UPLOAD b7e7f6715c6a4cffab9d28b5404f6f52:VjoQWPxfyFz4xoJMSy5deCnbfFMRYVB47eBecIB2YTM=:eyJCdWNrZXQiOiJ5c2YiLCJPYmplY3QiOiI1OTIzMDQ5ODk3OTI5OGZlMTljZWViMTQwYjQwYTU4MyIsIkV4cGlyZXMiOjcyNTgwODk2MDAsIlJldHVybkJvZHkiOiJ7XCJ1cmxcIjpcImh0dHA6Ly9ub3MubmV0ZWFzZS5jb20vJChCdWNrZXQpLyQoT2JqZWN0KVwifSJ9
------WebKitFormBoundaryYx62yTrBxLi5l9ah
Content-Disposition: form-data; name="file"; filename="robot_portrait.png"
Content-Type: image/png
------WebKitFormBoundaryYx62yTrBxLi5l9ah--
首先指定 Content-Type 是以 multipart/form-data
来编码的,然后浏览器首先生成一个boundary字符串用于划分多部分对象集合指明的各类实体。
例如上例中----WebKitFormBoundaryYx62yTrBxLi5l9ah
(注意短横线的个数,4个)。在boundary字符串指定的各个实体的起始行之前插入"--"标记,也说是说每个部分都以'--boundary'开始,紧接着是内容描述信息,然后是ORLF,最后是具体内容。报文主体最后插入"--"作为结束。请留意上例中各处短横线的个数。
再看刚才的请求,它包含三个参数:
- 第一个是Object,值为 59230498979298fe19ceeb140b40a583。
- 第二个是x-nos-token,值为 UPLOAD b7e7f6715c6a4cffab9d28b5404f6f52:VjoQWPxfyFz4xoJMSy5deCnbfFMRYVB47eBecIB2YTM=:eyJCdWNrZXQiOiJ5c2YiLCJPYmplY3QiOiI1OTIzMDQ5ODk3OTI5OGZlMTljZWViMTQwYjQwYTU4MyIsIkV4cGlyZXMiOjcyNTgwODk2MDAsIlJldHVybkJvZHkiOiJ7XCJ1cmxcIjpcImh0dHA6Ly9ub3MubmV0ZWFzZS5jb20vJChCdWNrZXQpLyQoT2JqZWN0KVwifSJ9。
- 第三个是file。 除了传输的二进制的数据,还包括文件名和文件类型等信息
几项约定
1. API 尽量遵守 RESTful规范 动词一定要符合规范,该用 GET 就用 GET ,该用 POST 就用 POST ,不要乱用。
2. GET 接口不用指定Content-Type,没有什么意义
![1f693d714ea5a0133e2c3f19ccda0fac.png](https://i-blog.csdnimg.cn/blog_migrate/ca8110786bb72ba01e6e3875d83d0c56.jpeg)
3. 当 GET 请求的参数过长,导致URL可能会超限时,使用 POST 接口。
![72f5ee5bf8ce76c3da6c973b53b4ef4b.png](https://i-blog.csdnimg.cn/blog_migrate/431443b0b49c86350289886ecca4274d.jpeg)
4. 使用 POST 接口必须指定 Content-Type类型,默认指定 application/x-www-form-urlencoded
5. 当提交的数据含有对象,或者有层级结构等复杂的数据时,Content-Type类型指定为 application/json
![3d415735a9abc79f6efac199cbb8e162.png](https://i-blog.csdnimg.cn/blog_migrate/ef9ea0607702546da7f654aec9523dbf.jpeg)
参考资料
- RESTful API最佳实践
- 理解RESTful架构
- 《图解HTTP》