前言

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格式

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

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

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

根据连接模型划分为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中进行查阅。

参考资料

  1. 图解HTTP
  2. HTTP报文
  3. HTTP状态码
  4. HTTP首部
  5. HTTP请求方法
  6. HTTP在线测试工具
  7. TCP KeepAlive机制
  8. HTTP/2关于Connection字段
  9. HTTP内容编码与传输编码

留言评论
推荐阅读
  • ES6中的let和const关键字

    重新声明 在ES6之前可以重新声明定义同一var变量,例如: var x = 10; var x = 2; console.log(...

    ES6中的let和const关键字
  • JavaScript查漏补缺

    数组排序 arr.sort()可以按arr数组元素的首字母排序(依据ASCII值从小到大排序)。由于sort()函数会调用toStri...

    JavaScript查漏补缺
  • JS在网页上禁用鼠标右击等操作

    一本正经或许我们都养成了个“坏习惯”,在浏览器中碰到陌生的东西时,喜欢右击检查或按F12。然而,有时候却不能如你所愿 :T (此时你很...

    JS在网页上禁用鼠标右击等操作