HTTP是一种用于获取诸如HTML文档这类资源的协议。是一种客户端-服务端的协议。HTTP作为一种应用层协议,通过TCP或TLS(一种加密过的TCP连接)来发送。理论上,HTTP可以借助任何可靠的传输层协议。 由于HTTP的可扩展性(在HTTP/1.0中引入header),目前HTTP不仅可以获取超文本文档,还可以获取图片、视频或向服务端发送表单等。
Http工作流程
整体流程
HTTP请求的完整流程包括以下几个步骤:
-
域名解析:在浏览器中输入URL时,首先需要进行域名解析,将域名转换为IP地址。浏览器会查询DNS服务器,获取对应域名的IP地址。
-
建立TCP连接:一旦浏览器获得了目标服务器的IP地址,它会尝试与服务器建立TCP连接。这个过程涉及TCP三次握手,即客户端发送一个连接请求给服务器,服务器回复确认,然后客户端再次回复确认。一旦新连接建立起来且被接收,服务器将新连接添加到现存的Web服务器连接列表中,做好监视连接上数据传输的准备。
为什么三次握手而不是两次?
1、避免重复连接导致的连接错乱。
如果 TCP 握手的次数只有两次,它并不清楚这次的请求是正常的请求,还是由于网络环境问题而导致的过期请求,如果是过期请求的话就会造成错误的连接。所以如果 TCP 是三次握手的话,那么客户端在接收到服务器端 SEQ+1 的消息之后,就可以判断当前的连接是否为历史连接,如果判断为历史连接的话就会发送终止报文(RST)给服务器端终止连接
2、同步初始化序列
如果是两次握手的话,就无法进行序列号的确认工作了,因此也就无法得到一个可靠的序列号了,所以 TCP 连接至少需要三次握手
-
发送HTTP请求:TCP连接建立后,浏览器会通过该连接发送HTTP请求。HTTP请求包括请求行、请求头和请求体。
-
服务器处理请求:服务器接收到HTTP请求后,会解析请求行和请求头,并根据请求内容执行相应的处理操作,可能是查询数据库、读取文件等。
-
服务器返回HTTP响应:服务器处理完请求后,会返回HTTP响应给浏览器。HTTP响应包括状态行、响应头和响应体。状态行包含HTTP协议版本、状态码和状态描述。响应头包含附加信息,如Content-Type、Content-Length等。响应体包含响应数据。
-
接收和渲染页面:浏览器接收到HTTP响应后,会解析响应头和响应体。如果返回的是HTML页面,浏览器会渲染页面并显示在用户界面上。同时,浏览器还会解析HTML页面中的其他资源,如CSS、JavaScript、图片等,并发送相应的HTTP请求获取这些资源。
-
断开TCP连接:当浏览器完成页面渲染和资源加载后,会断开与服务器的TCP连接。这个过程涉及TCP四次挥手,即客户端发送关闭连接请求给服务器,服务器回复确认,然后服务器发送关闭连接请求给客户端,最后客户端回复确认。(全双工)
以上就是HTTP请求的完整流程,涉及域名解析、TCP连接建立、HTTP请求发送、服务器处理、HTTP响应返回、页面渲染和TCP连接断开等步骤。这个过程是客户端(浏览器)与服务器之间进行通信的基本流程,HTTP协议扮演了通信的桥梁。
HTTP发展史
Http0.9 | Http1.0 | Http1.1 | Http2.0 | Http3.0(QUIC) |
文档只读 | 新增Post,Head | 新增PUT,DELETE,OPTIONS | 二进制流,引入帧和流 | 基于UDP |
允许GET从服务器拿到HTML文档 | 支持传HTML文件以外的类型内容 | 引入更多缓存控制策略 | 多路复用 | 网络切换时连接保持。基于TCP的协议切换网络之后IP会改变,之前的连接无法继续保持。基于UDP的QUIC协议,则可以内建连接标识在网络完成切换之后,恢复之前与服务器的连接。 |
立即关闭连接 | 新增状态码,版本号,HTTP头 | 引入请求范围,允许响应分块 | 头部压缩算法 | 线头阻塞问题更加彻底。基于TCP的Http2在实际中,数据是 一帧一帧发送和接收,但是一旦某一个流的数据有丢包,则同样会阻塞在它之后的传输流数据传输。而基于UDP的Http3.0就没有这个问题,能够让不同的流之间实现真正的独立。 |
长连接 | 可以发送多个请求 | |||
强制要求HOST头 | 服务端可以主动推送到客户端 |
Http的组成
HTTP报文
HTTP报文是服务器和客户端之间交互数据的方式。报文由一行行简单的字符串组成,都是纯文本的,不是二进制代码,因此可以方便的进行读写。有两种类型的消息:请求(request)——由客户端发送出发服务端的动作;响应(response)——由服务端进行响应。
报文组成
-
起始行:用来说明要做什么或承载了状态信息和操作产生的所有结果。
-
请求起始行:
- HTTP方法,例如:GET、POST、OPTIONS等。
- 请求目标,通常是一个URL或者是协议、端口和域名的绝对路径。
- HTTP版本,决定了剩余消息的结构,对期待的相应版本指示。
-
响应起始行:
- HTTP版本
- 状态码
- 状态文本。一个简短的信息,帮助理解HTTP消息
-
-
首部(Header):起始行后有0或多个首部字段,每个首部字段都包含名字和值,两者之间用冒号分隔。首部以空行结束。首部可以分为以下几类
- 通用首部:既可以出现在请求报文中,也可以出现在响应报文中
- 请求首部:提供了更多关于请求的信息
- 响应首部:提供了更多的关于响应的信息
- 实体首部:描述主题的长度和内容或资源自身
- 扩展首部:规范没有定义的新首部
-
主体(Body):可选,主体是HTTP报文的负荷,也就是HTTP要传输的内容。
这里我们使用Wireshake来查看报文组成,选择HTTP的进行追踪流,可以看到一个HTTP的报文如下:
方法
-
GET:GET方法是最常用的方法。通常用于请求服务器发送某个资源。
-
HEAD:HEAD方法和GET很类似,但服务器在响应的时候只返回首部,不会返回实体的主体部分。因此使用HEAD可以:
- 在不获取资源的情况下了解资源的情况,如类型。
- 通过响应状态码看看某个对象是否存在。
- 通过查看首部,判断资源是否被修改。
-
PUT:** 向服务器中写入文档**,PUT的语义就是让服务器用请求的主题部分来创建一个由请求的URL命名的新文档,或者,如果该URL已经存在,就用主体替代它。由于PUT允许对内容进行修改,所以一般执行PUT前服务器要求密码登录。
-
POST:向服务器输入数据,通常用来支持HTML表单。
-
TRACE:客户端发起一个请求时,这个请求可能要穿过防火墙、代理、网关等应用程序,每个中间节点都可能会修改原始的请求。**TRACE方法允许客户端在最终请求发送给服务器时,看看它变成了什么样子。**TRACE请求会在行程的最后一站弹回TEACE响应,在响应的主体中携带它收到的原始的请求报文。
-
OPTIONS:请求Web服务器告知其支持的各种方法。方便客户端不用实际访问资源就可以判定访问该资源的最优方法。
-
DELETE:请求服务器删除请求URL所指定的资源。但是,由于HTTP规范允许服务器在不通知客户端的情况下撤销请求,所以客户端无法保证删除操作一定会被执行。
状态码
- 100~199: 信息状态码
状态码 | 含义 |
100 | 收到了请求的初始部分,请客户端继续。发出了这个状态码,服务端在收到请求之后必须进行响应。 |
101 | 服务器正在根据客户端的制定,将协议切换成Update首部所列协议 |
- 200~299: 成功状态码
状态码 | 含义 |
200 | 请求没问题,实体的主体部分包含了请求的资源 |
201 | 用于创建服务器对象的请求,如PUT,响应实体中包含各种引用了已创建资源的URL。 |
202 | 请求被接受,但服务器还没执行任何动作 |
203 | 实体首部包含的信息不是来自源服务器,而是来自资源的副本。 |
204 | 响应报文包含若干首部和一个状态行,没有实体的主体部分。 |
205 | 负责告诉浏览器清除当前页面所有的HTML表单元素 |
206 | 成功执行了部分或Range范围的请求。响应中必须包含Content-Range、Date以及Etag或Content-Location |
-
300~399:重定向状态码
重定向状态码要么告知客户端替换位置来访问资源,要么提供替换的响应而不是资源的内容。如果资源已经被移走,可发送一个重定向状态码和一个可选的Location首部来告知客户端。
状态码 | 含义 |
300 | 客户端的请求实际指向了多个资源的URL,返回这个状态码的时候会返回一个选项列表。永久重定向。 |
301 | 请求的URL已经被移除,响应的Location首部中包含这个资源现在的URL,将来请求还是使用老的URL,临时重定向??? |
302 | 与301类似,但是Location首部返回的URL来临时定位资源。 |
303 | 告知客户端应该使用另一个URL来获取资源,新URL在Location首部。 |
304 | 资源未修改,响应不包含实体的主题部分 |
305 | 必须通过代理来访问资源,代理的位置在Location中。 |
307 | 请求的URL已经被移除,响应的Location首部中包含这个资源现在的URL,有一个唯一的区别是不允许将请求方法从post改为get。 |
通过上述表格,我们发现302、303和307存在一些交叉。原因主要是源于HTTP/1.0和HTTP/1.1对状态码的处理方式的差异。当HTTP/1.0的客户端发送一个POST请求,响应302重定向时,会接受Location的URL,**对于POST请求,客户端会将其转换为GET请求,不会保留原始请求的数据。**使用303重定向时,**客户端对于POST请求会发送一个新的GET请求,保留原始请求的方法,但不会保留消息体。**使用307重定向时**,客户端对于POST请求会保持原始请求的方法和消息体,并将请求发送到新的URL。**
4. 400~499:错误状态码
状态码 | 含义 |
400 | 告知客户端发送了一个错误的请求。 |
401 | 授权,与适当的首部一起,这些首部请求客户端在获取对资源的访问之前进行认证。 |
403 | 请求被服务器拒绝 |
404 | 服务器无法找到请求的URL |
405 | 发起的请求中带有不支持的方法,在响应的Allow中告知用户允许使用的方法有哪些 |
406 | 客户端可以指定参数说明他愿意接收什么类型的实体,服务端没有与客户端可接受的URL匹配的资源时使用406 |
407 | 类似于401,但用户代理服务器 |
408 | 客户端完成请求花费时间太长,服务端发送此状态码并关闭连接 |
409 | 请求可能存在资源上引发的冲突 |
410 | 类似于404,只是服务器曾经拥有过该资源 |
411 | 服务器要求在请求报文只能怪包含content-type时使用 |
412 | 客户端发起条件请求,且其中一个条件失败了 |
413 | 客户端发送的实体部分比服务器能处理的要大 |
414 | 客户端发情的请求中请求的URL比服务器能处理的要长 |
415 | 服务器无法理解或支持客户端实体的内容类型 |
416 | 请求报文请求的资源的某个范围,而该范围无效或无法满足时 |
417 | 请求的Expect首部的期望,服务端无法满足 |
- 500~599:服务器错误状态码
状态码 | 含义 |
500 | 服务器错误 |
501 | **客户端发起的请求超出服务器的能力范围,**比如使用了服务器不支持的方法 |
502 | 作为代理或网关的服务器从请求相应链的下一个链路收到了一个伪响应 |
503 | 用来说明服务器为啥无法提供服务 |
504 | 类似于408,但是这里的响应只来自于网关或者代理,他们在等待另一个服务器的响应时超时了 |
505 | 服务器收到的请求使用了它不支持的协议版本 |
首部(Header)
具体内容参考后面
HTTP连接管理
HTTP使用了TCP连接,同时进行了优化,包括并行连接、持久化连接、管道化连接等,首先我们介绍TCP连接。
TCP连接
-
TCP连接是可靠的数据管道,TCP为HTTP提供了一条可靠的比特传输管道,从TCP连接的一端填入的字节会从另一端以原有的顺序正确的传输出来。HTTP要传输一个报文的时候,TCP收到数据流之后,会将数据流看成段,并将段封在IP分组中。每个IP分组中包含:
-
IP分组的首部:包含了源、目的IP地址、长度和其他标记
-
TCP段首部:TCP端口号、TCP控制标记以及用于数据排序和完整性检查的值
-
TCP数据块。
-
-
为保证TCP连接的正确性,TCP通过4个值来进行识别:
源IP地址, 源端口号, 目的IP地址, 目的端口号
,两条不同的TCP连接不能拥有4个完全一样的值。
由于HTTP位于TCP的上层,所有HTTP的性能很大程度上取决于TCP的性能。TCP存在的问题导致了HTTP的性能,常见的TCP时延包括:
-
连接握手
-
延迟确认,每个TCP段都有一个序列号和数据完整性校验和,如果发送者没有在指定的窗口时间内收到确认信息,会默认分组损坏,重发数据。
-
TCP慢启动,TCP连接在起初会限制连接的最大速度,如果数据传输成功,会随着时间推移提高传输速度,用以缓解突然过载和堵塞。
-
Nagle算法:可以将任意大小的数据放入TCP栈中,即使只有一个字节!但是每个TCP中至少有40个字节的标记和首部。为了解决这个问题,Nagle算法试图在发一个分组之前将大量的TCP数据绑定在一起,因此出现了等待永远无法出现的数据造成的延时问题。可以通过TCP_NODELAY禁用该算法,但是要保证想TCP中写入的都是大块的数据
并行连接
HTTP允许客户端打开多条连接,并行执行多个HTTP事务,每个事务都有自己TCP连接。并行连接并不一定会更快,大量的连接会消耗很多内存资源(实际上浏览器使用并行连接会将连接总数限制为一个较小的值,一般4个),但因为多个组件对象同时出现在屏幕上,并行连接会让用户觉得页面的加载的更快。
持久连接
HTTP/1.1允许HTTP在事务处理结束之后将TCP保持连接状态,方便之后的HTTP请求重用现存的连接,避免了TCP连接的时间消耗以及慢启动的问题。
-
HTTP/1.0通过keep-alive保持持久连接
-
HTTP/1.1停用了keep-alive的支持,用一种持久连接的改进型设计取代了它。默认情况下是激活的。在事务处理结束之后如果想关闭连接,在报文中添加Connection:close首部即可。
持久连接多个请求只需要建立一次TCP握手,等待一定时间自动挥手。
当持久连接关闭时,每个请求需要单独进行TCP连接。
管道化连接
HTTP/1.1在持久连接的基础上可选的使用请求管道,即:在响应到达之前,可以将多条请求放入队列,当第一条请求流向服务器的时候,后续请求也可以发送。注意管道化连接的限制:
- HTTP客户端必须确认连接是持久的
- 必须按照发送顺序回传响应
- HTTP客户端要做好连接关闭的准备,重发所有未完成的管道化请求
- HTTP客户端不应该使用管道化方法发送有副作用的请求,如POST。
缓存
为什么需要缓存?
- 冗余数据,同一份文档消耗了网络带宽,降低了传输速率。缓存可以保留第一条服务器的副本,后续请求可以由副本处理。
- 带宽瓶颈:缓存可以缓解网络瓶颈的问题。很多网络在本地网络的客户端提供的带宽比远程服务器宽,如果客户端从局域网的缓存中拿副本,可以提高性能。
- 瞬间拥塞:突发事件,如爆炸新闻、公告等,会导致很多人在同一个时刻去访问,导致瞬间堵塞,和服务器崩溃。混存可以适当缓解。
- 距离时延。
不同类型的缓存
- 私有缓存:是绑定到特定客户端的缓存——通常是浏览器缓存。由于存储的响应不与其他客户端共享,因此私有缓存可以存储该用户的个性化响应。如果想将响应存储在私有缓存中,可以通过:
catch-Control:private
设置,如果响应具有Authorization标头,不能将将其存储在私有缓存中。 - 共享缓存:共享缓存位于客户端和服务器之间,可以存储能在用户之间共享的响应。共享缓存可以进一步细分为代理缓存和托管缓存。代理缓存和托管缓存是两种不同类型的缓存策略,用于提高Web应用程序的性能和减少服务器负载。它们在实现和应用场景上有一些区别。
- 代理缓存:代理缓存是一种将缓存放置在代理服务器(如反向代理服务器或CDN)上的缓存策略。当客户端发送请求时,代理服务器将会处理请求并在本地缓存请求的响应。下次如果有相同的请求,代理服务器可以直接从缓存中返回响应,而无需将请求发送到原始服务器。代理缓存可以有效地减少响应时间并减轻原始服务器的负载。常见的代理缓存实现包括使用反向代理服务器(如Nginx、Apache等)或使用内容分发网络(CDN)来缓存静态资源,例如图片、脚本、样式表等。
- 托管缓存:托管缓存是一种将缓存放置在应用程序服务器本地的缓存策略。在托管缓存中,应用程序服务器直接将响应缓存到本地内存或磁盘中,以供后续相同请求使用。托管缓存适用于需要频繁访问的资源或需要在应用程序内部共享的数据。托管缓存通常由应用程序代码控制,可以使用内存缓存库(如Redis)或本地文件系统来实现。在托管缓存中,缓存的数据通常是针对特定用户或会话的,所以不同用户之间的缓存是相互独立的。
缓存策略
HTTP缓存处理的步骤
-
接收&解析:接收HTTP的请求报文,解析报文
-
缓存获取URL: 查找本地副本。
-
新鲜度检测:HTTP通过缓存将文档的副本保留一段时间,这段时间内,认为文档是“新鲜的”,缓存可以不联系服务器直接提供该文档。一旦超出时间,在提供该文档之前缓存要再次和服务器进行确认,查看文档是否发生变化。由于客户端发给缓存的所有请求首部都有可以强制缓存进行再验证或不验证,因此HTTP的新鲜度检测变得十分复杂。
-
文档过期:Cache-Control、Expires。HTTP让原始服务器向每个文档中添加了一个过期时间。不会向服务器发送请求,一般在Memory Cache和Disk Cache中。
-
Expire
:绝对时间,依赖于计算机本地时间的设置是否正确。影响命中。 -
Catch-Control:max-age
:使用相对时间而不是绝对时间。- public:既可以被客户端也可以被服务端缓存
- private:只可以被客户端缓存
- max-age:文档的新鲜状态秒数,其中s-maxage仅适用于共享缓存。
-
no-store:不缓存,缓存会向客户端发送no-store的响应然后删除对象。
-
no-cache:实际上会存储在本地缓存区,只是在与原始服务器进行新鲜度检测的之前,不会将其提供给客户端使用。下次检查Etag,目的就是为了防止从缓存中获取过期的资源。
-
-
Cache-Control > Expires
-
服务器再验证:判断缓存是否发生了变化。如果发生了变化,缓存会获取一分新的文档副本,如果文档未发生变化,返回304,缓存只需要获取新的首部,包括新的过期时间,并对缓存中首部进行更新。
-
Last-Midified:响应头中加Last-Modified表示最后修改时间,浏览器下次请求这个资源会在If-Modified-Since中加上这个值,服务器对比这个值;
Last-Modified即使打开文件也会改变,精确度在秒
-
Etag:资源的唯一标识符(服务器生成),浏览器发送请求的时候在If-None-Match中携带上一次的Etag,服务器进行比较
Etag的优先级高于Last-Modified。性能上:Last-Modified更高,因为Etag需要算法计算一个hash值,精度上Last-Modified<Etag
-
Last-Modified
与Content-Length
表示为十六进制组合而成 =Etag
-
-
-
创建响应:为了让缓存的响应看起来像原始的服务器一样,缓存将已缓存的响应首部作为作为响应首部的起点,然后对基础的首部进行修改和扩充。
-
发送:相应首部准备好之后,缓存将响应送到客户端。
-
保留日志:每个缓存事务结束之后,缓存会更新命中和未命中的统计数据,并插入包含请求类型、URL等信息的日志文件。