前言
在TCP/IP四层模型
之下,我们的计算机按部就班地执行着自己的任务。在网上冲浪的过程中,客户端在我们看不见的地方默默地发送着报文,而呈现在我们面前的是文字、图像或视频等我们所需要的资源。由于我们普遍使用的是万维网
,所以这里所说的报文是指HTTP报文
,相关规范发布在了RFC(Request for Comments)。
报文结构
请求报文 | 响应报文 |
---|---|
请求行 | 状态行 |
请求首部字段 | 响应首部字段 |
通用首部字段 | 通用首部字段 |
实体首部字段 | 实体首部字段 |
CR+LF(空行) | CR+LF(空行) |
报文主体(有效负载) | 报文主体(有效负载) |
🚩客户端发送请求报文
GET / HTTP/1.1
Host: www.livejq.top
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36
🏁服务器端返回响应报文
HTTP/1.1 200 OK
Server: nginx
Date: Sat, 10 Oct 2020 15:25:43 GMT
Content-Type: text/html
Last-Modified: Thu, 01 Oct 2020 04:50:51 GMT
Transfer-Encoding: chunked
Connection: keep-alive
ETag: W/"5f75602b-34c2"
Expires: Sat, 17 Oct 2020 15:25:43 GMT
Cache-Control: max-age=604800
Strict-Transport-Security: max-age=31536000
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
X-Xss-Protection: 1
Vary: User-Agent
liveJQ: What do you want? <(-m-)>
Content-Encoding: gzip
<!DOCTYPE html>
<html class="scrollbar">
<body>
...
...
</body>
</html>
注意
- 请求行与状态行中声明的属性值需要用
空格
隔开 - 首部由字段名+冒号+空格+字段值组成
- 每行用\r\n结尾(CR+LF,即Carriage Return and Line Feed)
- 首部字段与报文主体用CR+LF分隔(实际上看存在
\r\n\r\n
)
请求行
请求方法 | 请求目标 | 协议版本 |
---|---|---|
GET | 通常是一个URI | HTTP/1.1 |
请求行/起始行中首先要考虑的是应该使用哪种请求方法
,目前常见的有9种
。对方法分类不仅是一种规范
,而且还可以使客户端的请求意图更加明确,省去了服务器端无谓的判断。
请求方法
方法名 | 携带主体? | 响应主体? | 幂等? | 安全? | 可缓存? | HTML表单? | 作用 |
---|---|---|---|---|---|---|---|
GET | 否 | 是 | 是 | 是 | 是 | 是 | 获取数据 |
POST | 是 | 是 | 否 | 否 | 否 | 是 | 发送数据 |
PUT | 是 | 否 | 是 | 否 | 否 | 否 | 新增/替换/更新资源 |
DELETE | 可选 | 可选 | 是 | 否 | 否 | 否 | 删除资源 |
HEAD | 否 | 否 | 是 | 是 | 是 | 否 | 获取资源信息 |
PATCH | 是 | 否 | 否 | 否 | 否 | 否 | 修改资源 |
TRACE | 否 | 否 | 是 | 否 | 否 | 否 | Debug服务器 |
OPTIONS | 否 | 是 | 是 | 是 | 否 | 否 | 获取请求方法 |
CONNECT | 否 | 是 | 否 | 否 | 否 | 否 | 创建通信隧道 |
HTTP/1.0支持前五个方法
- 幂等:多次请求效果一致,无副作用。
- 安全:根据是否与服务器产生联系或使数据/资源状态改变来判断。因此,是安全就一定是幂等。
因篇幅有限,将GET、POST、PUT、DELETE、PATCH放到RESTful设计中进行分析,HEAD、TRACE、OPTIONS、CONNECT则根据实际使用场景详细介绍。
URI与URL
区别
- URL(Uniform Resource Locator)是URI(Uniform Resource Identifier)的子集
- URI将资源以统一的
字符形式
表示,URL将资源以统一的网络位置字符形式
表示。 - 可以将URI(广义)和URL(狭义)比作水果和苹果。
示例
#打开本地html文件(Ubuntu)
file:///home/livejq/index.html
#点击邮件链接
mailto:cloud@livejq.xyz
#打开本站首页
https://www.livejq.top/
#访问FTP网络文件
ftp://192.168.111.1/files
这四条资源标识都可以称为URI
,但只有后两个才能称为URL
,因为它们在网络中能够被找到/访问。
绝对URI格式
请求目标
如果每次上网都需要按照上面那种格式自己构造HTTP报文,那会是有多糟糕呀 😶 。还好,现在的软件程序已经帮我们做了这一步,一般都可以通过UI界面
获取我们的访问信息。例如:在浏览器上注册时,只需要在表单中填写自己的信息,然后点击注册即可。浏览器自会根据请求方法
帮我们构造出相应的报文。
构造报文时,会生成如下四种形式的请求目标
origin form
最常用的形式,全部方法中除了CONNECT外都支持。浏览器会自动将请求的URL字符串分成域名(Host字段值)和访问路径(也就是请求目标)。
GET / HTTP/1.1
GET /index.html HTTP/1.1
DELETE /file?index=12138 HTTP/1.1
authority form
CONNECT方法所使用的形式,用以连接代理服务器,形如:
CONNECT myproxy.vpn.com:1080 HTTP/1.1
absolute form
此种形式主要出现在返回3xx重定向响应后的请求中,用以直接使用GET方法请求完整的目标URL,也就是响应中的Location
字段值,形如:
GET https://www.livejq.top/index.html HTTP/1.1
asterisk form
OPTIONS方法中的一种形式,搭配*星号,起通配符的作用,表示对整个站点的请求,也就是获取整个站点所支持的请求方法,常用在CORS
中。
OPTIONS * HTTP/1.1
状态行
协议版本 | 状态码 | 原因短语 |
---|---|---|
HTTP/1.1 | 200 | OK |
状态行中的状态码
表明了服务器对此次请求的处理结果,由三位数字组成。虽然状态码种类繁多,但常用/常见的也就十几个,需要时查阅一下便可。只要遵循类别定义,服务器也可改变已有状态码或者是返回自定义的状态码。
范围 | 类别 | 意义 |
---|---|---|
1xx | Informational | 请求正在处理 |
2xx | Success | 请求已成功处理 |
3xx | Redirection | 需要额外操作以完成请求 |
4xx | Client Error | 请求无法处理 |
5xx | Server Error | 请求处理时发生错误 |
状态码
状态码、请求方法和首部字段互相影响,结合着分析会比较好理解。接下来主要介绍常见/实用/较特别的状态码。
100/417
expect: 100-continue
请求时携带此首部字段,期待服务器返回100 Continue
,表明可处理客户端的请求(例如:发送超大文件前,先携带此字段询问是否能够处理),接着再发送主体。若无法处理,则返回417 Expectation Failed
。
101
Upgrade: TLS/1.2
请求时携带此首部字段,询问邻接/代理服务器
(与Connection: Upgrade一起使用)是否可以使用这个更高的协议版本。返回101 Switching Protocols
表明同意升级至此版本,可以跟下面的426对比。
不同意的话直接忽略即可,不影响后续对请求的处理
200/201/204
PUT方法
- 新增资源成功,返回
201 Created
- 替换/更新资源成功,响应有主体返回
200 OK
,不含主体返回204 No Content
206
range: bytes=1000-2000
请求资源的一部分,也就是分段获取,用过IDM应该都知道吧 👀,成功返回206 Partial Content
。通常还可以结合if-range: 123456
使用,表示若该资源的etag值等于123456,则按照范围请求该资源;若不相等,则返回完整的资源,当然状态码就变成了200 OK。
301/302/303/307
-
301 Moved Permanently
永久重定向到响应字段location: xxxx指定的URI,之前存的书签也会改变。 -
302 Found
临时重定向,服务器临时分配了新的URI,希望本次请求能够使用它。旧版客户端(浏览器)常常将请求方法替换为GET后跳转。 -
303 See Other
明确规定使用GET发起请求,客户端不会自动跳转。一般是在使用POST/PUT/DELETE后返回的一个显示结果的URI,如:显示上传成功页面的URI。 -
307 Temporary Redirect
临时重定向,明确规定请求方法和请求主体不会发生变化,302的补充。
状态码 | 自动跳转? | 利于搜索引擎? | 默认可缓存? | 改变请求方法? |
---|---|---|---|---|
301 | 是 | 是 | 是 | 否 |
302 | 是 | 否 | 否 | 旧版会改为GET |
303 | 否 | 否 | 否 | 规定使用GET |
307 | 是 | 否 | 否 | 否 |
304
校验缓存,校验一致返回304 Not Modified
,本站搜索HTTP缓存了解详情。
400
400 Bad Request
表示请求中出现了语法错误,需要修正。
401
401 Unauthorized
,若请求首部未携带authorization
字段,则此状态码表示需要通过目标服务器
的身份认证后才能处理此请求;若请求首部已携带包含用户认证信息的Authorization字段,则此状态码表示认证失败了。401响应中必定包含www-authenticate
字段,用以说明认证类型和相关描述。最常见的是使用下载工具下载需要登录后才能下载的资源,可以跟下面的407对比。记忆方法为:只有客户端才需要请求证明自己(authorization,含有z,自己),只有服务器才能授权(authenticate,不含z)。
403
403 Forbidden
,访问被拒。例如:没有权限访问该文件系统,又或者IP被列入了黑名单等。
404
不用多说了吧,大家最常见的状态码,404 Not Found
没有找到所请求的资源。
我加瓦表示不服,最常见的不是500嘛 ,想当年刚学JSP的时候 😂
405
405 Not Allowed
表示使用了服务器不允许的请求方法。
407
407 Proxy Authentication Required
,模式与401类似,只是字段名和返回的状态码有所区别,两者可以并存(通过代理服务器去请求资源)。请求首部为Proxy-Authorization
,407响应必将包含Proxy-Authenticate
字段(这两个属于hot-by-hot
首部,401属于end-to-end
首部)。认证通过则返回200 Connection Established
(不是OK)。401是资源认证(获取资源时 =》直接获取),407是代理认证(请求代理连接时 =》间接获取资源,如:VPN)。
412
if-match: 123456
条件请求,比对资源的etag值,不相等时返回412 Precondition Failed
。若相等,则返回200 OK。满足先决条件才能使请求生效,避免使不希望改变的资源发生了改变,例如:使用DELETE方法有可能出现误删的情况。
426
客户端使用了服务器不支持的协议版本,返回426 Upgrade Required
。在下一次的请求发送之前,需要先升级协议版本至响应首部字段upgrade中所指定的值 👈
500
500 Internal Server Error
,服务器在处理请求时发生错误。这个主要出现在程序的开发和测试阶段(Tomcat会直接输出Exception信息),在服务正式上线后一般很少遇到。
502/503/504
502 Bad Gateway
,作为网关的服务器收到了上游/目标服务器无法理解的响应(报错或断开连接)。503 Service Unavailable
,服务器暂时无法处理请求,常与retry-after: <秒>或retry-after: <具体时间>一起使用,告知用户大概在几秒后或在哪个时间点后可以重新发起请求。多数情况下是因为服务器正在调整/维护。504 Gateway Timeout
,作为网关的服务器等待上游/目标服务器响应超时。在不支持负载均衡的大流量网站上会很常见到,玩PT的应该都很清楚吧 🙃
首部
根据连接模型
划分为Hot-by-Hot
首部(8种,转发后即失效/移除)和End-to-End
首部(其余)。
当中间服务器在将HTTP/1.1的报文转发到采用HTTP/2的服务器之前,会先移除Hot-by-Hot首部。
用法
-
与连接管理有关:Connection,作用1,与Keep-Alive一起使用,管理
持久连接
;作用2,声明转发后需要移除的Hot-by-Hot字段(兼容作用,不一定非得全部列出来) -
与代理认证有关:Proxy-Authorization和Proxy-Authenticate,详情见上文。
-
与传输编码有关:Transfer-Encoding(在HTTP/1.1中表示从服务器传过来的分块编码内容)、TE(Accept-Transfer-Encoding之意,表示客户端能够接受的传输编码方式)
-
与分块编码传输有关:可以在分块传输内容的最后面携带字段,例如:Trailer:Expires,则表示分块传输最后面有个Expires字段需要客户端查收。
-
与协议有关:Upgrade,详情见上文。
Upgrade: TLS/1.2
#服务器对于keep-alive一般有默认值,声明后则覆盖
##空闲超过5秒后断开;此管道连接最大请求数为50,超过后断开重连
Keep-Alive: timeout=5, max=50
#HTTP/1.0默认值Close,HTTP/1.1默认值Keep-Alive
Connection: Keep-Alive, Upgrade
扩展
- 传输编码:对HTTP报文进行编码,然后决定是否分块传输(是否分块由客户端决定)。涉及到的字段有:Transfer-Encoding、TE。
- 内容编码:对HTTP报文中的实体进行编码,content-length无法使用。涉及到的字段有:content-encoding、accept-encoding。
End-to-End首部会根据相应的使用场景详细分析,如:与CORS有关的access-control-allow-xx、与缓存有关的cache-control、与body有关的content-type和与用户状态有关的cookie等等,完整的首部或状态码可以很方便地在MDN中进行查阅。
评论区