HTTP有哪些方法
- HTTP 1.0 定义了三种请求方法: GET, POST 和 HEAD 方法。
- HTTP 1.1 新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT。
HTTP请求方法的作用
请求方法 | 说明 |
---|---|
GET | 通常用于请求服务器发送某些资源。 |
HEAD | 请求资源的头部信息, 并且这些头部与 HTTP GET 方法请求时返回的一致。该请求方法的一个使用场景是在下载一个大文件前先获取其大小再决定是否要下载, 以此可以节约带宽资源。 |
OPTIONS | 用于获取目的资源所支持的通信选项。 |
POST | 发送数据给服务器。 |
PUT | 用于新增资源或者使用请求中的有效负载替换目标资源的表现形式。 |
DELETE | 用于删除指定的资源。 |
PATCH | 用于对资源进行部分修改。 |
CONNECT | HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。 |
TRACE | 回显服务器收到的请求,主要用于测试或诊断。 |
HTTP GET与POST对比
HTTP 中的 GET 一般用于获取/查询资源信息,也就是从指定的资源请求数据。 而 POST 一般用于向指定的资源提交要被处理的数据,也就是更新资源信息。
GET与POST比较
下面的表格比较了两种 HTTP 方法:GET 和 POST。
GET | POST | |
---|---|---|
后退按钮/刷新 | 无害 | 数据会被重新提交(浏览器应该告知用户数据会被重新提交) |
书签 | 可收藏为书签 | 不可收藏为书签 |
缓存 | 能被缓存 | 不能缓存 |
编码类型 | application/x-www-form-urlencoded | application/x-www-form-urlencoded 或 multipart/form-data。为二进制数据使用多重编码。 |
历史 | 参数保留在浏览器历史中。 | 参数不会保存在浏览器历史中。 |
对数据长度的限制 | 是的。当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符) | 无限制 |
对数据类型的限制 | 只允许 ASCII 字符 | 没有限制,也允许二进制数据 |
安全性 | 与 POST 相比,GET 的安全性较差,因为所发送的数据是 URL 的一部分。在发送密码或其他敏感信息时绝不要使用 GET | POST 比 GET 更安全,因为参数不会被保存在浏览器历史或 web 服务器日志中 |
可见性 | 数据在 URL 中对所有人都是可见的 | 数据不会显示在 URL 中 |
GET和POST的区别
GET 请求的数据会附在 URL 之后(就是把数据放置在 HTTP 协议头中),以 ?
分割 URL 和传输数据,参数之间以 &
相连,如:
login.action?name=hyddd&password=idontknow&verify=%E4%BD%A0%E5%A5%BD
如果数据是英文字母或数字,则原样发送;如果是空格,转换为 +
;如果是中文或其他字符,则直接把字符串用 BASE64 加密,得出如:%E4%BD%A0%E5%A5%BD,其中 %XX 中的 XX 为该符号以 16 进制表示的 ASCII 码值。而与之对应的,POST 把提交的数据放置在 HTTP 包的包体中。
POST 的安全性要比 GET 的安全性高。注意:这里所说的安全性和上面 GET 提到的 “安全” 不是同个概念。上面 “安全” 的含义仅仅是不作数据修改,而这里安全的含义是真正的 Security 的含义。比如:通过 GET 提交数据,用户名和密码将明文出现在 URL 上,因为:(1)登录页面有可能被浏览器缓存,(2)其他人查看浏览器的历史纪录,那么别人就可以拿到你的账号和密码了,除此之外,使用 GET 提交数据还可能会造成 Cross-site request forgery 攻击(CSRF,跨站请求伪造,也被称为:one click attack/session riding)。
银行网站A,它以GET请求来完成银行转账的操作,如:http://www.mybank.com/Transfer.php?toBankId=11&money=1000
危险网站B,它里面有一段HTML的代码如下:< img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000/>
首先,你登录了银行网站 A,然后访问危险网站 B,噢,这时你会发现你的银行账户少了 1000 块。
为什么会这样呢?原因是银行网站 A 违反了 HTTP 规范,使用 GET 请求更新资源。在访问危险网站 B 之前,你已经登录了银行网站 A,而 B 中的 <img …/>
以 GET 的方式请求第三方资源(这里的第三方就是指银行网站 A 了。原本这是一个合法的请求,但这里被不法分子利用了),所以你的浏览器会带上你的银行网站 A 的 Cookie 发出 Get 请求,去获取资源 “http://www.mybank.com/Transfer.php?toBankId=11&money=1000”
,结果银行网站服务器收到请求后,认为这是一个更新资源操作(转账操作),所以就立刻进行转账操作。
为了杜绝上面的问题,银行改用 POST 请求完成转账操作。
GET和POST的误区
误区一:POST 可以比 GET 提交更多更长的数据?
由于使用 GET 方法提交数据时,数据会以 &
符号作为分隔符的形式,在 URL 后面添加需要提交的参数,有人会说,浏览器地址栏输入的参数是有限的,而 POST 不用再地址栏输入,所以 POST 就比 GET 可以提交更多的数据。难道真的是这样的么?
而实际上,URL 不存在参数上限的问题,HTTP 协议规范没有对 URL 长度进行限制。这个限制是特定的浏览器及服务器对它的限制。IE 对 URL 长度的限制是 2083 字节(2K+35)。对于其他浏览器,如 Netscape、FireFox 等,理论上没有长度限制,其限制取决于操作系统的支持。所以 POST 也是没有大小长度限制的,HTTP 协议规范也没有进行大小限制。起限制作用的是服务器的处理能力。总归一句话,这个限制是针对所有 HTTP 请求的,与 GET、POST 没有多少关系。
GET和POST区别总结
- GET 使用 URL 或 Cookie 传参,而 POST 将数据放在 BODY 中,这个是因为 HTTP 协议用法的约定。并非它们的本身区别。
- GET 方式提交的数据有长度限制,则 POST 的数据则可以非常大,这个是因为它们使用的操作系统和浏览器设置的不同引起的区别。也不是 GET 和 POST 本身的区别。
- POST 比 GET 安全,因为数据在地址栏上不可见,这个说法没毛病,但依然不是 GET 和 POST 本身的区别。
http-interview-http-resp-header
通用首部
通用首部字段(General Header Fields),也就是请求报文和响应报文两方都会使用的首部:
首部 | 描述 |
---|---|
Cache-Control | 控制缓存 |
Connection | 连接管理、逐条首部 |
Upgrade | 升级为其他协议 |
via | 代理服务器的相关信息 |
Wraning | 错误和警告通知 |
Transfor-Encoding | 报文主体的传输编码格式 |
Trailer | 报文末端的首部一览 |
Pragma | 报文指令 |
Date | 创建报文的日期 |
请求首部字段
请求首部字段(Reauest Header Fields),也就是客户端向服务器发送请求的报文时使用的首部:
首部 | 描述 |
---|---|
Accept | 客户端或者代理能够处理的媒体类型 |
Accept-Encoding | 优先可处理的编码格式 |
Accept-Language | 优先可处理的自然语言 |
Accept-Charset | 优先可以处理的字符集 |
If-Match | 比较实体标记(ETage) |
If-None-Match | 比较实体标记(ETage)与 If-Match 相反 |
If-Modified-Since | 比较资源更新时间(Last-Modified) |
If-Unmodified-Since | 比较资源更新时间(Last-Modified),与 If-Modified-Since 相反 |
If-Rnages | 资源未更新时发送实体 byte 的范围请求 |
Range | 实体的字节范围请求 |
Authorization | web 的认证信息 |
Proxy-Authorization | 代理服务器要求 web 认证信息 |
Host | 请求资源所在服务器 |
From | 用户的邮箱地址 |
User-Agent | 客户端程序信息 |
Max-Forwrads | 最大的逐跳次数 |
TE | 传输编码的优先级 |
Referer | 请求原始放的 url |
Expect | 期待服务器的特定行为 |
响应首部字段
响应首部字段(Response Header Fields)也就是从服务器向客户端响应时使用的字段:
首部 | 描述 |
---|---|
Accept-Ranges | 能接受的字节范围 |
Age | 推算资源创建经过时间 |
Location | 令客户端重定向的 URI |
vary | 代理服务器的缓存信息 |
ETag | 能够表示资源唯一资源的字符串 |
WWW-Authenticate | 服务器要求客户端的验证信息 |
Proxy-Authenticate | 代理服务器要求客户端的验证信息 |
Server | 服务器的信息 |
Retry-After | 和状态码 503 一起使用的首部字段,表示下次请求服务器的时间 |
实体首部字段
实体首部字段(Entiy Header Fields)也就是针对请求报文和响应报文的实体部分使用首部:
首部 | 描述 |
---|---|
Allow | 资源可支持 http 请求的方法 |
Content-Language | 实体的资源语言 |
Content-Encoding | 实体的编码格式 |
Content-Length | 实体的大小(字节) |
Content-Type | 实体媒体类型 |
Content-MD5 | 实体报文的摘要 |
Content-Location | 代替资源的 uri |
Content-Rnages | 实体主体的位置返回 |
Last-Modified | 资源最后的修改资源 |
Expires | 实体主体的过期资源 |
Http中的Keep-Alive
HTTP 持久连接(HTTP persistent connection,也称作 HTTP keep-alive 或 HTTP connection reuse,翻译过来可以是保持连接或者连接复用)是使用同一个 TCP 连接来发送和接收多个 HTTP 请求/应答,而不是为每一个新的请求/应答打开新的连接的方式。
HTTP 协议采用 “请求-应答” 模式,当使用普通模式,即非 KeepAlive 模式时,每个请求/应答客户和服务器都要新建一个连接,完成之后立即断开连接(HTTP 协议为无连接的协议),每次请求都会经过三次握手四次挥手过程,效率较低;当使用 Keep-Alive 模式时,客户端到服务器端的连接不会断开,当出现对服务器的后继请求时,客户端就会复用已建立的连接。
下图是每次新建连接和连接复用在通信模型上的区别:
在 Http 1.0 中,Keep-Alive 是没有官方支持的,但是也有一些 Server 端支持,这个年代比较久远就不用考虑了。
Http1.1 以后,Keep-Alive 已经默认支持并开启。客户端(包括但不限于浏览器)发送请求时会在 Header 中增加一个请求头 Connection: Keep-Alive,当服务器收到附带有 Connection: Keep-Alive 的请求时,也会在响应头中添加 Keep-Alive。这样一来,客户端和服务器之间的 HTTP 连接就会被保持,不会断开(断开方式下面介绍),当客户端发送另外一个请求时,就可以复用已建立的连接。
现在的 Http 协议基本都是 Http 1.1 版本了,不太需要考虑 1.0 的兼容问题。
为什么要Keep-Alive
通常一个网页可能会有很多组成部分,除了文本内容,还会有诸如:js、css、图片等静态资源,有时还会异步发起 AJAX 请求。只有所有的资源都加载完毕后,我们看到网页完整的内容。然而,一个网页中,可能引入了几十个 js、css 文件,上百张图片,如果每请求一个资源,就创建一个连接,然后关闭,代价实在太大了。
基于此背景,我们希望连接能够在短时间内得到复用,在加载同一个网页中的内容时,尽量的复用连接,这就是 HTTP 协议中 keep-alive 属性的作用。
- HTTP 1.0 中默认是关闭的,需要在 http 头加入 “Connection: Keep-Alive”,才能启用 Keep-Alive;
- http 1.1 中默认启用 Keep-Alive,如果加入 "Connection: close ",才关闭。
HTTP Keep-Alive优缺点
优点
- 节省了服务端 CPU 和内存适用量
- 降低拥塞控制 (TCP 连接减少)
- 减少了后续请求的延迟(无需再进行握手)
缺点
对于某些低频访问的资源/服务,比如一个冷门的图片服务器,一年下不了几次,每下一次连接还保持就比较浪费了(这个场景举的不是很恰当)。Keep-Alive 可能会非常影响性能,因为它在文件被请求之后还保持了不必要的连接很长时间,额外占用了服务端的连接数。
连接复用后会有什么问题
在没有连接复用时,Http 接收端(注意这里是接收端,并没有特指 Client/Server,因为 Client/Server 都同是发送端和接收端)只需要读取 Socket 中所有的数据就可以了,解决 “拆包” 问题即可;但是连接复用后,无法区分单次 Http 报文的边界,所以还需要额外处理报文边界问题。当然这个通过 Http中Header 的长度字段,按需读取即可解决。
Http连接复用后包边界问题处理
由于 Http 中 Header 的存在,通过定义一些报文长度的首部字段,可以很方便的处理包边界问题。在 Http 中,有两种方式处理包边界问题:
Content-Length处理包边界
这个是最通常的处理方式,接收端处理报文时首先读取完整首部(Header),然后通过 Header 中的 Content-Length 来确认报文大小,读取报文时按此长度读取即可,超出长度的报文(“粘包”)不读取,不够长度的报文缓存等待继续读取(“拆包”)。
Chunked处理包边界
对于无法确认总报文大小的情况,可以使用 Chunked 的方式来对报文进行分块传输,每一块内标示报文大小。比如 Nginx,开启 Gzip 压缩后,就会开启 Chunked 的传输方式。
当服务端返回 Transfer-Encoding: chunked header 时,报文会通过 chunked 的形式进行编码。
断开连接
通过 Keep-Alive 已经做到连接复用了,但复用之后什么时候断开连接呢,不然一直保持连接,造成资源的浪费。Http 协议规定了两种关闭复用连接的方式:
通过Keep-Alive Timeout标识
如果服务端 Response Header 设置了 Keep-Alive:timeout={timeout},客户端会就会保持此连接 timeout(单位秒)时间,超时之后关闭连接。现在在服务端设置响应 Header:
Keep-Alive:timeout=15
通过Connection close标识
还有一种方式是接收端通在 Response Header 中增加 Connection close 标识,来主动告诉发送端,连接已经断开了,不能再复用了;客户端接收到此标示后,会销毁连接,再次请求时会重新建立连接。
注意:配置 close 配置后,并不是说每次都新建连接,而是约定此连接可以用几次,达到这个最大次数时,接收端就会返回 close 标识。
Nginx中设置Keep-Alive
Keep-Alive timeout配置
Syntax: keepalive_timeout timeout [header_timeout];
Default: keepalive_timeout 75s;
Context: http, server, location
第一个参数设置一个超时,在此期间保持活动的客户机连接将在服务器端保持打开状态。如果为0则禁用保 Keep-Alive。第二个可选参数在 “Keep-Alive: timeout=time” 响应头字段中设置一个值。
“Keep-Alive: timeout=time” 报头字段被 Mozilla 和 Konqueror 识别。MSIE 在大约 60 秒内自动关闭保持连接。
Keep-Alive requests(连接可用次数)配置
Syntax: keepalive_requests number;
Default: keepalive_requests 100;
Context: http, server, location
设置通过一个保持活动连接可以服务的请求的最大数量。在发出最大数量的请求之后,连接关闭。
TCP中的Keep-Alive
TCP 中的 KeepAlive 和 Http 的 Keep-Alive 可不是一回事,HTTP 中是做连接复用的,而 TCP 中的 KeepAlive 是 “心跳监测”,定时发送一个空的 TCP Segment,来监测连接是否存活。
什么是HTTPS
HTTPS(全称:HyperText Transfer Protocol over Secure Socket Layer),是为了保证客户端与服务器之间数据传输的安全。 近两年,Google、Baidu、Facebook 等这样的互联网巨头,不谋而合地开始大力推行 HTTPS, 国内外的大型互联网公司很多也都已经启用了全站 HTTPS,这也是未来互联网发展的趋势。
2021 年离全网使用 HTTPS 已经不远了,列举几个各大互联网公司为鼓励使用 HTTPS 而提出的要求:
- Google 的搜索引擎算法,让采用 HTTPS 的网站在搜索中排名更靠前;
- 苹果要求 App Store 中的所有应用都必须使用 HTTPS 加密连接;
- 微信小程序也要求必须使用 HTTPS 协议;
- 新一代的 HTTP/2 协议的支持需以 HTTPS 为基础;
- 新版本 chrome 已将 HTTP 协议网站标记不安全。
HTTP的不足
HTTP 协议从诞生至今已经具有相当优秀和方便的一面,然而 HTTP 并非只有好的一面,事物皆具两面性,它的不足之处也是很明显:
- 通信使用明文传输,内容可能会被窃听。
- 不验证通信方的身份,因此有可能遭遇伪装。
- 无法证明报文的完整性,所以有可能已经遭到篡改。
除此之外,HTTP 本身还有很多缺点。而且,还有像某些特定的 Web 服务器和特定的 Web 浏览器在实际应用中存在的不足(也可以说成是脆弱性或安全漏洞),另外,用 Java 和 PHP 等编程语言开发的 Web 应用也可能存在安全漏洞。
通信使用明文可能会被窃听
由于 HTTP 本身不具备加密的功能,所以也无法做到对通信整体(使用 HTTP 协议通信的请求和响应的内容)进行加密。所以,HTTP 报文使用明文方式发送。如果要问为什么通信时不加密是一个缺点,这是因为,按 TCP/IP 协议族的工作机制,通信内容在所有的通信线路上都有可能遭到窥视。
所谓互联网,是由能连通到全世界的网络组成,无论世界哪个角落的服务器在和客户端通信时,在此通信线路上的某些网络设备、光缆、计算机等都不可能是个人的私有物,所以不排除某个环节中会遭到恶意窥视行为。
即使已经过加密处理的通信,也会被窥视到通信内容,这点和未加密的通信是相同的。只是说如果通信经过加密,就有可能让人无法破解报文信息的含义,但加密处理后的报文信息本身还是会被看到。窃听相同段上的通信并非难事。只需要收集在互联网上流动的数据包就行。对于收集来的数据包的解析工作,可以交给那些抓包或嗅探工具。
不验证通信方的身份就可能遭到伪装
HTTP 协议中的请求和相应不会对通信方进行确认。也就是说存在 “服务器是否就是发送请求中 URI 真正指定的主机,返回的响应是否真的返回到实际提出请求的客户端” 等类似问题。
在 HTTP 协议通信时,由于不存在确认通信方的处理步骤,任何人都可以发送请求,同时,服务器只要接收到请求,只要发送端的 IP 地址和端口号没有被 Web 服务器设定限制访问,不管对方是谁都会返回一个响应,因此会存在以下各种隐患:
- 无法确定请求发送至目标的 Web 服务器是否是按真实意图返回响应的那台服务器,有可能是已伪装的 Web 服务器。
- 无法确定响应返回到的客户端是否是按真实意图接收响应的那个客户端,有可能是已伪装的客户端。
- 无法确定正在通信的对方是否具备访问权限。因为某些 Web 服务器上保存着重要的信息,指向发给特定用户通信的权限。
- 无法判定请求是来自何方、出自谁手。
- 即使是无意义的请求也会照单全收。无法阻止海量请求下的 DoS 攻击(Denial of Service,拒绝服务攻击)。
无法证明报文的完整性,可能已遭到篡改
所谓完整性是指信息的准确度。若无法证明其完整性,通常也就意味着无法判断信息是否准确。因此,在请求或响应送出之后知道对方接收之前的这段时间内,即使请求或相应的内容遭到篡改,也没有办法获悉。
换句话说,没有任何办法确认,发出的请求、响应和接收到的请求、响应是前后相同的。文件内容在传输中可能已经被村改为其他内容,像这样,请求或响应在传输途中遭攻击者拦截并篡改内容的攻击成为中间人攻击(Man-in-the-Middle attack,MITM)。
解决:HTTP+加密+认证+完整性保护=HTTPS
上面提了那么多 HTTP 的缺点自然在 HTTPS 中我们得解决它,下面我们来看看 HTTPS 是如何保证我们数据传输安全的。
HTTPS其实是身披SSL外壳的HTTP
HTTPS 并非是应用层的一种新协议。只是 HTTP 通信接口部分用 SSL(Secure Socket Layer,安全套阶层)和 TLS(Transport Layer Security,安全传输层协议)协议代替而已。
通常,HTTP 直接和 TCP 通信。当使用 SSL 时,则变成先和 SSL 通信,再由 SSL 和 TCP 通信了。简单来说,与 SSL 组合使用的 HTTP 被称为 HTTPS(HTTP Secure,超文本传输安全协议)或 HTTP over SSL。
采用了 SSL 后,HTTP 就拥有了 HTTPS 的加密、证书和完整性保护这些功能。SSL 是独立于 HTTP 的协议,所以不光是 HTTP 协议,其它运行在应用层的 SMTP 和 Telnet 等协议均可配合 SSL 协议使用。可以说 SSL 是当今世界上应用最为广泛的网络安全技术。
HTTPS的加密原理
近代的加密算法中加密算法是公开的,而密钥是保密的。通过这种方式来保持加密方法的安全性。
加密和解密要用到密钥,如果没有密钥就没有办法对密码解密。换句话来说,任何人只要持有密钥就能够对密文进行解密。
HTTPS 在加密过程中使用了非对称加密技术和对称加密技术。
对称加密算法
采用单钥密码系统的加密方式,同一个密钥可以同时做信息的加密和解密,这种加密的方法称为对称加密,也称为单密钥加密。下面会把对称加密算法称为共享密钥加密算法。
假如现在,SSL 在通信过程中,使用了对称加密算法,也就是说客户端和服务器同时共享一个密钥。
于是,以共享密钥的方式加密,必须将密钥发给对方。这个时候,假如通信过程被监听,密钥被攻击者获取了,那么这个时候也就失去了加密的意义了。
那么,有没有办法解决这个问题呢?答案是肯定的,也就是使用两把密钥。下面先看使用两把密钥的非对称加密算法。
非对称加密算法
与对称加密算法相反,非对称加密算法需要两个密钥来进行加密和解密,这两个密钥是配对的,分别是公开密钥(公钥)和私有密钥(私钥)。
一般情况下,公钥是可以被公开的,它主要用来加密明文。而相应的,私钥不能被公开,用来解密公钥加密的密文。值得注意的是:公钥加密后的密文只能通过对应的私钥来解密,而私钥加密的密文却可以通过对应的公钥来解密。
以上,公钥加密私钥解密用来加密,私钥加密公钥解密用来签名。相关用途后面会讲到。下面会把非对称加密算法称为公开密钥加密算法。
于是现在,假设现在由服务器来生成一对公钥和密钥。当客户端第一次发请求和服务器协商的时候,服务器就生成了一对公钥和私钥。紧接着,服务器把公钥发给客户端(明文,不需要做任何加密),客户端接收后,随机生成一个密钥,使用服务器发过来的公钥进行加密。
再接着,客户端把使用公钥加密的密钥发给服务器,服务器接收到了以后,用配对的私钥进行解密,就得到了客户端随机生成的那个密钥。这个时候,客户端和服务端所持的密钥都是相同的。此时,交换密钥环节就完成了。
于是通信开始时就可进行上面所述的共享密钥加密方式来进行加密。
同时使用
可能,有小伙伴就会问,为什么要大费周章使用非对称加密的方式,然后再得到相同的密钥,进行共享密钥加密的通信呢?由于公开密钥加密处理起来比共享密钥加密方式更为复杂,因此在通信的时候使用公开密钥加密的方式,效率很低。
于是,我们需要使用非对称加密的方式来保证密钥共享的过程中密钥的安全性,而后在通信的过程中使用对称加密算法,这是最合理的设计方式,在保证安全性的同时又保证了性能。
所以,HTTPS 采用共享密钥加密和公开密钥加密两者并用的混合加密机制。在交换密钥使用环节使用公开密钥加密方式,之后建立的通信交换报文阶段则使用共享密钥加密方式。
以上,大概就是使用对称加密和非对称加密的过程。看似过程很完美,其实还存在着一个问题,就是:如何保证服务器传过来的公开密钥的正确性。换句话说,就是保证它不被拦截篡改。
使用证书保证公钥的正确性
假如现在正准备和某台服务器建立公开密钥加密方式下的通信,如何证明客户端收到的公开密钥就是原本预想的那台服务器发行的公开密钥呢?或许,在公开密钥传输的过程中,真正的公开密钥可能已经被攻击者替换掉了。
为了解决这个问题,可以使用由数字证书机构和其相关颁发的公开密钥证书。下面阐述一下数字证书认证机构(简称CA)的业务流程:
首先,服务器的运营人员向数字证书机构提出公开密钥的申请。数字证书认证机构在判明提出申请者的身份之后,会对已申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公钥证书后绑定在一起。我们用白话文来翻译一下上面这段话:
首先,CA 会向申请者颁发一个证书,这个证书里面的内容有:签发者、证书用途、服务器申请的时候附带的公钥、服务器的加密算法、使用的 HASH 算法、证书到期的时间等等。紧接着,把上面所提到的内容,做一次 HASH 求值,得到一个 HASH 值。
再接着,用 CA 的私钥进行加密,这样就完成了数字签名。而用 CA 的私钥加密后,就生成了类似人体指纹的签名,任何篡改证书的尝试,都会被数字签名发现。最后,把数字签名,附在数字证书的末尾,传输回来给服务器。
接下来,服务器会把这份由数字证书认证机构颁发的公钥证书发给客户端。这个时候,客户端可以使用数字证书机构的公开密钥对其进行验证。一旦验证成功,客户端便能够确定这个公开密钥是可信的。我们再用白话文来翻译一下:
客户端拿到这个数字证书以后,用 CA 私钥对应的公钥,可以解密数字证书末尾的数字签名,得到原始的 HASH 值。紧接着,客户端按照证书中的 HASH 算法,对证书的内容求 HASH 值。如果通过 CA 公钥解密的 HASH 和通过计算求得的 HASH 值相同,那么认证通过,否则失败。
如果认证通过,就可以取得服务器的公开密钥。那客户端上面的 CA 公钥是从哪里来的呢?多数浏览器开发商发布版本时,会事先在内部植入常用认证机关的公开密钥。这样,就方便客户端对于数字证书真实性的验证。
其具体过程是这样子的(图中简化了数字签名的过程):
这里其实就用到了非对称加密算法,只不过现在这个加密算法用来签名而不是加密。使用私钥加密,公钥解密,用于公钥的持有者验证通过私钥加密的内容是否被篡改,但是不用来保证内容是否被他人获得。
而使用公钥加密,私钥解密,则是相反的,它不保证信息被他人截获篡改,但是保证信息无法被中间人获得。
客户端证书
HTTPS 中不仅可以使用服务器证书,还可以使用客户端证书。以客户端证书进行客户端认证,它的作用与服务器证书是相同的。由于客户端获取证书需要用户自行安装客户端证书,同时也面临着费用的问题。
因此,现状是,安全性极高的认证机构可办法客户端证书但是仅用于特殊用途的业务。比如那些可支撑客户端证书支出费用的业务。
例如,银行的网上银行就采用了客户端证书。在登录网银时不仅要求用户确认输入 ID 和密码,还会要求用户的客户端证书,以确认用户是否从特定的终端访问网银。
HTTPS的安全通信机制
为了更好的理解 HTTPS,下面给大家画了下图来一起观察一下 HTTPS 的通信步骤:
步骤1:客户端通过发送 Client Hello 报文开始 SSL 通信。报文中包含客户端支持的 SSL 的指定版本、加密组件列表(所使用的加密算法及密钥长度等)。
步骤2:服务器可进行 SSL 通信时,会以 Server Hello 报文作为应答。和客户端一样,在报文中包含 SSL 版本以及加密组件。服务器的加密组件内容是从接收到的客户端加密组件内筛选出来的。
步骤3:之后服务器发送 Certificate 报文。报文中包含公开密钥证书。
步骤4:最后服务器发送 Server Hello Done 报文通知客户端,最初阶段的 SSL 握手协商部分结束。
步骤5:SSL 第一次握手结束之后,客户端以 Client Key Exchange 报文做为回应。报文中包含通信加密中使用的一种被称为 Pre-master secret 的随机密码串。该报文已用步骤 3 中的公开密钥进行加密。
步骤6:接着客户端继续发送 Change Cipher Spec 报文。该报文会提示服务器,在此报文之后的通信会采用 Pre-master secret 密钥加密。
步骤7:客户端发送 Finished 报文。该报文包含连接至今全部报文的整体效验值。这次握手协商是否能够成功,要以服务器是否能够正确解密该报文作为判定标准。
步骤8:服务器同样发送 Change Cipher Spec 报文。
步骤9:服务器同样发送 Finished 报文。
步骤10:服务器和客户端的 Finished 报文交换完毕之后,SSL连接就算建立完成,当然,通信会受到 SSL 的保护。从此处开始进行应用层协议的通信,即发送 HTTP 请求。
步骤11:应用层协议通信,即发送 HTTP 响应。
步骤12:最后由客户端断开连接。断开连接时,发送 close_notify 报文。上图做了一些省略,这步之后再发送 TCP FIN 报文来关闭与 TCP 的通信。
在以上流程中,应用层发送数据时会附加一种叫做 MAC(Message Authentication Code)的报文摘要。MAC 能够查知报文是否遭到篡改,从而保护报文的完整性。
那现在有一个问题,整个过程中产生的三个随机数有什么用呢?还有,后面进行 HTTP 通信的时候,是用哪一个密钥进行加密,还有怎么保证报文的完整性。看下面这张图:
对于客户端:
当其生成了 Pre-master secret 之后,会结合原来的 A、B 随机数,用 DH 算法计算出一个 master secret,紧接着根据这个 master secret 推导出 hash secret 和 session secret。
对于服务端:
当其解密获得了 Pre-master secret 之后,会结合原来的 A、B 随机数,用 DH 算法计算出一个 master secret,紧接着根据这个 master secret 推导出 hash secret 和 session secret。
在客户端和服务端的 master secret 是依据三个随机数推导出来的,它是不会在网络上传输的,只有双方知道,不会有第三者知道。同时,客户端推导出来的 session secret 和 hash secret 与服务端也是完全一样的。
那么现在双方如果开始使用对称算法加密来进行通讯,使用哪个作为共享的密钥呢?过程是这样子的:
双方使用对称加密算法进行加密,用 hash secret 对 HTTP 报文做一次运算生成一个 MAC,附在 HTTP 报文的后面,然后用 session-secret 加密所有数据(HTTP+MAC),然后发送。
接收方则先用 session-secret 解密数据,然后得到 HTTP+MAC,再用相同的算法计算出自己的 MAC,如果两个 MAC 相等,证明数据没有被篡改。至此,整个过程介绍完毕。
HTTP1.0与HTTP1.1区别
可扩展性
可扩展性的一个重要原则:如果 HTTP 的某个实现接收到了自身未定义的头域,将自动忽略它。
- 在消息中增加版本号,用于兼容性判断。注意,版本号只能用来判断逐段(hop-by-hop)的兼容性,而无法判断端到端(end-to-end)的兼容性。
例如,一台 HTTP/1.1 的源服务器从使用 HTTP/1.1 的 Proxy 那儿接收到一条转发的消息,实际上源服务器并不知道终端客户使用的是 HTTP/1.0 还是 HTTP/1.1。因此,HTTP/1.1 定义 Via 头域,用来记录消息转发的路径,它记录了整个路径上所有发送方使用的版本号。
- HTTP/1.1 增加了 OPTIONS 方法,它允许客户端获取一个服务器支持的方法列表。
- 为了与未来的协议规范兼容,HTTP/1.1 在请求消息中包含了 Upgrade 头域,通过该头域,客户端可以让服务器知道它能够支持的其它备用通信协议,服务器可以据此进行协议切换,使用备用协议与客户端进行通信。
缓存
在 HTTP/1.0 中,使用 Expire 头域来判断资源的 fresh 或 stale,并使用条件请求(conditional request)来判断资源是否仍有效。例如,cache 服务器通过 If-Modified-Since 头域向服务器验证资源的 Last-Modefied 头域是否有更新,源服务器可能返回 304(Not Modified),则表明该对象仍有效;也可能返回 200(OK)替换请求的 Cache 对象。
此外,HTTP/1.0 中还定义了 Pragma:no-cache 头域,客户端使用该头域说明请求资源不能从 cache 中获取,而必须回源获取。
HTTP/1.1 在 1.0 的基础上加入了一些 cache 的新特性,当缓存对象的 Age 超过 Expire 时变为 stale 对象,cache 不需要直接抛弃 stale 对象,而是与源服务器进行重新激活(revalidation)。
HTTP/1.0 中,If-Modified-Since 头域使用的是绝对时间戳,精确到秒,但使用绝对时间会带来不同机器上的时钟同步问题。而 HTTP/1.1 中引入了一个 ETag 头域用于重激活机制,它的值 entity tag 可以用来唯一的描述一个资源。请求消息中可以使用 If-None-Match 头域来匹配资源的 entitytag 是否有变化。
为了使 caching 机制更加灵活,HTTP/1.1 增加了 Cache-Control 头域(请求消息和响应消息都可使用),它支持一个可扩展的指令子集:例如 max-age 指令支持相对时间戳;private 和 no-store 指令禁止对象被缓存;no-transform 阻止 Proxy 进行任何改变响应的行为。
Cache 使用关键字索引在磁盘中缓存的对象,在 HTTP/1.0 中使用资源的 URL 作为关键字。但可能存在不同的资源基于同一个 URL 的情况,要区别它们还需要客户端提供更多的信息,如 Accept-Language 和 Accept-Charset 头域。为了支持这种内容协商机制(content negotiation mechanism),HTTP/1.1 在响应消息中引入了 Vary 头域,该头域列出了请求消息中需要包含哪些头域用于内容协商。
带宽优化
HTTP/1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了。例如,客户端只需要显示一个文档的部分内容,又比如下载大文件时需要支持断点续传功能,而不是在发生断连后不得不重新下载完整的包。
HTTP/1.1 中在请求消息中引入了 range 头域,它允许只请求资源的某个部分。在响应消息中 Content-Range 头域声明了返回的这部分对象的偏移值和长度。如果服务器相应地返回了对象所请求范围的内容,则响应码为 206(Partial Content),它可以防止 Cache 将响应误以为是完整的一个对象。
另外一种情况是请求消息中如果包含比较大的实体内容,但不确定服务器是否能够接收该请求(如是否有权限),此时若贸然发出带实体的请求,如果被拒绝也会浪费带宽。
HTTP/1.1 加入了一个新的状态码 100(Continue)。客户端事先发送一个只带头域的请求,如果服务器因为权限拒绝了请求,就回送响应码 401(Unauthorized);如果服务器接收此请求就回送响应码 100,客户端就可以继续发送带实体的完整请求了。注意,HTTP/1.0 的客户端不支持 100 响应码。但可以让客户端在请求消息中加入 Expect 头域,并将它的值设置为 100-continue。
节省带宽资源的一个非常有效的做法就是压缩要传送的数据。Content-Encoding 是对消息进行端到端(end-to-end)的编码,它可能是资源在服务器上保存的固有格式(如 jpeg 图片格式);在请求消息中加入 Accept-Encoding 头域,它可以告诉服务器客户端能够解码的编码方式。
而 Transfer-Encoding 是逐段式(hop-by-hop)的编码,如 Chunked 编码。在请求消息中加入 TE 头域用来告诉服务器能够接收的 transfer-coding 方式。
长连接
HTTP 1.0 规定浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个 TCP 连接,服务器完成请求处理后立即断开 TCP 连接,服务器不跟踪每个客户也不记录过去的请求。此外,由于大多数网页的流量都比较小,一次 TCP 连接很少能通过 slow-start 区,不利于提高带宽利用率。
HTTP 1.1 支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个 TCP 连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延迟。例如:一个包含有许多图像的网页文件的多个请求和应答可以在一个连接中传输,但每个单独的网页文件的请求和应答仍然需要使用各自的连接。
HTTP 1.1 还允许客户端不用等待上一次请求结果返回,就可以发出下一次请求,但服务器端必须按照接收到客户端请求的先后顺序依次回送响应结果,以保证客户端能够区分出每次请求的响应内容,这样也显著地减少了整个下载过程所需要的时间。
在 HTTP/1.0 中,要建立长连接,可以在请求消息中包含 Connection: Keep-Alive 头域,如果服务器愿意维持这条连接,在响应消息中也会包含一个 Connection: Keep-Alive 的头域。同时,可以加入一些指令描述该长连接的属性,如 max,timeout 等。
事实上,Connection 头域可以携带三种不同类型的符号:
- 一个包含若干个头域名的列表,声明仅限于一次 hop 连接的头域信息;
- 任意值,本次连接的非标准选项,如 Keep-Alive 等;
- close 值,表示消息传送完成之后关闭长连接;
客户端和源服务器之间的消息传递可能要经过很多中间节点的转发,这是一种逐跳传递(hop-by-hop)。HTTP/1.1 相应地引入了 hop-by-hop 头域,这种头域仅作用于一次 hop,而非整个传递路径。每一个中间节点(如 Proxy,Gateway)接收到的消息中如果包含 Connection 头域,会查找 Connection 头域中的一个头域名列表,并在将消息转发给下一个节点之前先删除消息中这些头域。
通常,HTTP/1.0 的 Proxy 不支持 Connection 头域,为了不让它们转发可能误导接收者的头域,协议规定所有出现在 Connection 头域中的头域名都将被忽略。
消息传递
HTTP 消息中可以包含任意长度的实体,通常它们使用 Content-Length 来给出消息结束标志。但是,对于很多动态产生的响应,只能通过缓冲完整的消息来判断消息的大小,但这样做会加大延迟。如果不使用长连接,还可以通过连接关闭的信号来判定一个消息的结束。
HTTP/1.1 中引入了 Chunkedtransfer-coding 来解决上面这个问题,发送方将消息分割成若干个任意大小的数据块,每个数据块在发送时都会附上块的长度,最后用一个零长度的块作为消息结束的标志。这种方法允许发送方只缓冲消息的一个片段,避免缓冲整个消息带来的过载。
在 HTTP/1.0 中,有一个 Content-MD5 的头域,要计算这个头域需要发送方缓冲完整个消息后才能进行。而 HTTP/1.1 中,采用 chunked 分块传递的消息在最后一个块(零长度)结束之后会再传递一个拖尾(trailer),它包含一个或多个头域,这些头域是发送方在传递完所有块之后再计算出值的。发送方会在消息中包含一个 Trailer 头域告诉接收方这个拖尾的存在。
Host头域
在 HTTP1.0 中认为每台服务器都绑定一个唯一的 IP 地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个 IP 地址。
HTTP1.1 的请求消息和响应消息都应支持 Host 头域,且请求消息中如果没有 Host 头域会报告一个错误(400 Bad Request)。此外,服务器应该接受以绝对路径标记的资源请求。
错误提示
HTTP/1.0 中只定义了 16 个状态响应码,对错误或警告的提示不够具体。HTTP/1.1 引入了一个 Warning 头域,增加对错误或警告信息的描述。
此外,在 HTTP/1.1 中新增了 24 个状态响应码,如 409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
内容协商
为了满足互联网使用不同母语和字符集的用户,一些网络资源有不同的语言版本(如中文版、英文版)。HTTP/1.0 定义了内容协商(contentnegotiation)的概念,也就是说客户端可以告诉服务器自己可以接收以何种语言(或字符集)表示的资源。例如如果服务器不能明确客户端需要何种类型的资源,会返回 300(Multiple Choices),并包含一个列表,用来声明该资源的不同可用版本,然后客户端在请求消息中包含 Accept-Language 和 Accept-Charset 头域指定需要的版本。
就像有些人会说几门外语,但每种外语的流利程度并不相同。类似地,网络资源也可以有不同的表达形式,比如有母语版和各种翻译版本。HTTP 引入了一个品质因子(quality values)的概念来表示不同版本的可用性,它的取值从 0.0 到 1.0。例如一个母语是英语的人也能讲法语、甚至还学了点丹麦语,那么他的浏览器可用作如下配置:Accept-Language: en, fr;q=0.5, da;q=0.1。这时,服务器会优先选取品质因子高的值对应的资源版本作为响应。
-
HTTP1与HTTP2区别
什么是HTTP 2.0
HTTP/2(超文本传输协议第 2 版,最初命名为 HTTP 2.0),是 HTTP 协议 的的第二个主要版本,使用于万维网。HTTP/2 是 HTTP 协议自 1999 年 HTTP 1.1 发布后的首个更新,主要基于 SPDY 协议(是 Google 开发的基于 TCP 的应用层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验)。
HTTP2.0与HTTP1.1区别
与 HTTP 1.1 相比,主要区别包括:
- HTTP/2 采用二进制格式而非文本格式。
- HTTP/2 是完全多路复用的,而非有序并阻塞的——只需一个连接即可实现并行。
- 使用报头压缩,HTTP/2 降低了开销。
- HTTP/2 让服务器可以将响应主动 “推送” 到客户端缓存中。
二进制协议
最新的 HTTP 版本在功能和属性(例如从文本协议转换为二进制协议)方面已经有了重大发展。 HTTP1.x 用于处理文本命令以完成请求-响应周期。 HTTP / 2 将使用二进制命令(分别为 1s 和 0s)执行相同的任务。 此属性减轻了框架的复杂性,并简化了由于包含文本和可选空格的命令而导致混淆的命令的实现。
使用 HTTP / 2 实现的浏览器会将相同的文本命令转换为二进制命令,然后再通过网络进行传输。
请求多路复用
HTTP/1.x 有个问题叫线端阻塞(head-of-line blocking), 它是指一个连接(connection)一次只提交一个请求的效率比较高, 多了就会变慢。 HTTP/1.1 试过用流水线(pipelining)来解决这个问题, 但是效果并不理想(数据量较大或者速度较慢的响应, 会阻碍排在他后面的请求)。
此外, 由于网络媒介(intermediary )和服务器不能很好的支持流水线, 导致部署起来困难重重。而多路传输(Multiplexing)能很好的解决这些问题, 因为它能同时处理多个消息的请求和响应; 甚至可以在传输过程中将一个消息跟另外一个掺杂在一起。所以客户端只需要一个连接就能加载一个页面。
这样可以减少额外的往返时间(RTT),使您的网站加载速度更快,而无需进行任何优化,并且不需要域分片。
标头压缩
HTTP / 2压缩大量冗余头帧。 它使用 HPACK 规范作为标头压缩的简单安全方法。 客户端和服务器都维护在先前的客户端-服务器请求中使用的标头列表。
HPACK 在将每个标头传输到服务器之前先压缩每个标头的单独值,然后服务器在先前传输的标头值列表中查找编码信息,以重建完整的标头信息。
HTTP/2服务器推送
此功能使服务器可以向客户端发送其他未缓存的信息,但这些信息会在以后的请求中得到预期。 例如,如果客户端请求资源 X,并且可以理解资源 Y 被请求的文件引用,则服务器可以选择将 Y 与 X 一起推送,而不是等待适当的客户端请求。
-
当我们在浏览器栏输入一个网址,比如
www.ikam.cn
的时候,具体发生了什么呢?这个请求是怎么到达服务器及返回结果的呢?概述
- 浏览器进行 DNS 域名解析,得到对应的 IP 地址
- 根据这个 IP,找到对应的服务器建立连接(三次握手)
- 建立 TCP 连接后发起 HTTP 请求(一个完整的 http 请求报文)
- 服务器响应 HTTP 请求,浏览器得到 html 代码(服务器如何响应)
- 浏览器解析 html 代码,并请求 html 代码中的资源(如js、css、图片等)
- 浏览器对页面进行渲染呈现给用户
- 服务器关闭 TCP 连接(四次挥手)
比如,我们现在使用浏览器访问
www.ikam.cn
,那么具体流程如下:- 对
www.ikam.cn
这个网址进行 DNS 域名解析到 IP - 通过 IP,使用 ARP 地址解析协议,找到对应的服务器,发起 TCP 三次握手
- 建立 TCP 请求后,发起 HTTP 请求(例如 TOMCAT 部署的 springMVC 程序)
- 服务器响应 HTTP 请求,返回 RESPONSE
- 游览器解析 response,并请求其它的资源文件(js、css等)
- 游览器进行渲染界面
注:DNS 域名解析采用的是递归查询的方式,先从本地的 DNS 缓存中查找—>缓存中没有的话就去找根域名服务器—–>根域名服务器找不到继续找下一级,这样递归查找到再返回给游览器。
HTTP请求完整流程详解
域名解析
以 Chrome 浏览器为例:
-
Chrome 浏览器会首先搜索浏览器自身的 DNS 缓存(缓存时间比较短,大概只有 1 分钟,且只能容纳 1000 条缓存),看自身的缓存中是否有
https://www.ikam.cn
对应的条目,而且没有过期,如果有且没有过期则解析到此结束。注:我们怎么查看 Chrome 自身的缓存?可以使用
chrome://com-internals/#dns
来进行查看。 -
如果浏览器自身的缓存里面没有找到对应的条目,那么 Chrome 会搜索操作系统自身的 DNS 缓存,如果找到且没有过期则停止搜索解析到此结束。
注:怎么查看操作系统自身的 DNS 缓存,以 Windows 系统为例,可以在命令行下使用 ipconfig /displaydns 来进行查看。
-
如果在 Windows 系统的 DNS 缓存也没有找到,那么尝试读取 hosts 文件(位于 C:\Windows\System32\drivers\etc),看看这里面有没有该域名对应的 IP 地址,如果有则解析成功。
-
如果在 hosts 文件中也没有找到对应的条目,浏览器就会发起一个 DNS 的系统调用,就会向本地配置的首选 DNS 服务器(一般是电信运营商提供的,也可以使用像 Google 提供的 DNS 服务器)发起域名解析请求(通过的是 UDP 协议向 DNS 的 53 端口发起请求,这个请求是递归的请求,也就是运营商的 DNS 服务器必须得提供给我们该域名的 IP 地址),运营商的 DNS 服务器首先查找自身的缓存,找到对应的条目,且没有过期,则解析成功。如果没有找到对应的条目,则有运营商的 DNS 代我们的浏览器发起迭代 DNS 解析请求,它首先是会找根域的 DNS 的 IP 地址(这个 DNS 服务器都内置 13 台根域的 DNS 的 IP 地址),找打根域的 DNS 地址,就会向其发起请求(请问
www.ikam.cn
这个域名的 IP 地址是多少啊?),根域发现这是一个顶级域 net 域的一个域名,于是就告诉运营商的 DNS 我不知道这个域名的 IP 地址,但是我知道 net 域的 IP 地址,你去找它去,于是运营商的 DNS 就得到了 net 域的 IP 地址,又向 net 域的 IP 地址发起了请求(请问www.ikam.cn
这个域名的 IP 地址是多少?),net 域这台服务器告诉运营商的 DNS 我不知道www.ikam.cn
这个域名的 IP 地址,但是我知道ikam.cn
这个域的 DNS 地址,你去找它去,于是运营商的 DNS 又向ikam.cn
这个域名的 DNS 地址(这个一般就是由域名注册商提供的,像万网,新网等)发起请求(请问www.ikam.cn
这个域名的 IP 地址是多少?),这个时候ikam.cn
域的 DNS 服务器一查,诶,果真在我这里,于是就把找到的结果发送给运营商的 DNS 服务器,这个时候运营商的 DNS 服务器就拿到了www.ikam.cn
这个域名对应的 IP 地址,并返回给 Windows 系统内核,内核又把结果返回给浏览器,终于浏览器拿到了www.ikam.cn
对应的 IP 地址,该进行一步的动作了。注:一般情况下是不会进行以下步骤的:
如果经过以上的 4 个步骤,还没有解析成功,那么会进行如下步骤(以下是针对 Windows 操作系统):
- 操作系统就会查找 NetBIOS name Cache(NetBIOS 名称缓存,就存在客户端电脑中的),那这个缓存有什么东西呢?凡是最近一段时间内和我成功通讯的计算机的计算机名和 Ip 地址,就都会存在这个缓存里面。什么情况下该步能解析成功呢?就是该名称正好是几分钟前和我成功通信过,那么这一步就可以成功解析。
- 如果上一步也没有成功,那会查询 WINS 服务器(是 NETBIOS 名称和 IP 地址对应的服务器)
- 如果上一步也没有查询成功,那么客户端就要进行广播查找
- 如果上一步也没有成功,那么客户端就读取 LMHOSTS 文件(和 HOSTS 文件同一个目录下,写法也一样)
如果第八步还没有解析成功,那么就宣告这次解析失败,那就无法跟目标计算机进行通信。只要这八步中有一步可以解析成功,那就可以成功和目标计算机进行通信。
与服务器建立连接
TCP连接的建立
客户端的请求到达服务器,首先就是建立 TCP 连接:
Client 首先发送一个连接试探,ACK=0 表示确认号无效,SYN = 1 表示这是一个连接请求或连接接受报文,同时表示这个数据报不能携带数据,seq = x 表示 Client 自己的初始序号(seq = 0 就代表这是第0号包),这时候 Client 进入 syn_sent 状态,表示客户端等待服务器的回复。
Server 监听到连接请求报文后,如同意建立连接,则向 Client 发送确认。TCP 报文首部中的 SYN 和 ACK 都置 1 ,ack = x + 1 表示期望收到对方下一个报文段的第一个数据字节序号是 x+1,同时表明 x 为止的所有数据都已正确收到(ack=1 其实是 ack=0+1,也就是期望客户端的第 1 个包),seq = y 表示 Server 自己的初始序号(seq=0 就代表这是服务器这边发出的第 0 号包)。这时服务器进入 syn_rcvd,表示服务器已经收到 Client 的连接请求,等待 client 的确认。
Client 收到确认后还需再次发送确认,同时携带要发送给 Server 的数据。ACK 置 1 表示确认号 ack= y + 1 有效(代表期望收到服务器的第 1 个包),Client 自己的序号 seq= x + 1(表示这就是我的第 1 个包,相对于第 0 个包来说的),一旦收到 Client 的确认之后,这个 TCP 连接就进入 Established 状态,就可以发起 http 请求了。
问题1:TCP 为什么需要 3 次握手?
两个计算机通信是靠协议(目前流行的 TCP/IP 协议)来实现,如果两个计算机使用的协议不一样,那是不能进行通信的,所以这个三次握手就相当于试探一下对方是否遵循 TCP/IP 协议,协商完成后就可以进行通信了,当然这样理解不是那么准确。
问题2:为什么 HTTP 协议要基于 TCP 来实现?
目前在 Internet 中所有的传输都是通过 TCP/IP 进行的,HTTP 协议作为 TCP/IP 模型中应用层的协议也不例外,TCP 是一个端到端的可靠的面向连接的协议,所以 HTTP 基于传输层 TCP 协议不用担心数据的传输的各种问题。
TCP四次挥手
当客户端和服务器通过三次握手建立了 TCP 连接以后,当数据传送完毕,肯定是要断开 TCP 连接的啊。那对于 TCP 的断开连接,这里就有了神秘的 “四次分手”。
第一次分手:主机1(可以使客户端,也可以是服务器端),设置 Sequence Number,向主机 2 发送一个 FIN 报文段;此时,主机 1 进入 FIN_WAIT_1 状态;这表示主机 1 没有数据要发送给主机 2 了;
第二次分手:主机 2 收到了主机 1 发送的 FIN 报文段,向主机 1 回一个 ACK 报文段,Acknowledgment Number 为 Sequence Number 加 1;主机 1 进入 FIN_WAIT_2 状态;主机 2 告诉主机 1,我 “同意” 你的关闭请求;
第三次分手:主机 2 向主机 1 发送 FIN 报文段,请求关闭连接,同时主机 2 进入 LAST_ACK 状态;
第四次分手:主机 1 收到主机 2 发送的 FIN 报文段,向主机 2 发送 ACK 报文段,然后主机 1 进入 TIME_WAIT 状态;主机 2 收到主机 1 的 ACK 报文段以后,就关闭连接;此时,主机 1 等待 2MSL 后依然没有收到回复,则证明 Server 端已正常关闭,那好,主机 1 也可以关闭连接了。
问题1:为什么要四次分手?
TCP 协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP 是全双工模式,这就意味着,当主机 1 发出 FIN 报文段时,只是表示主机 1 已经没有数据要发送了,主机 1 告诉主机 2,它的数据已经全部发送完毕了;但是,这个时候主机 1 还是可以接受来自主机 2 的数据;当主机 2 返回 ACK 报文段时,表示它已经知道主机 1 没有数据发送了,但是主机 2 还是可以发送数据到主机 1 的;当主机 2 也发送了 FIN 报文段时,这个时候就表示主机 2 也没有数据要发送了,就会告诉主机 1,我也没有数据要发送了,之后彼此就会愉快的中断这次 TCP 连接。
发起HTTP请求
HTTP 是一个客户端和服务器端请求和应答的标准(TCP)。客户端是终端用户,服务器端是网站。通过使用 Web 浏览器、网络爬虫或者其它的工具,客户端发起一个到服务器上指定端口(默认端口为 80)的 HTTP 请求。
通俗来讲,他就是计算机通过网络进行通信的规则,是一个基于请求与响应,无状态的,应用层的协议,常基于 TCP/IP 协议传输数据。目前任何终端(手机,笔记本电脑。)之间进行任何一种通信都必须按照 Http 协议进行,否则无法连接。
服务器响应HTTP请求,浏览器得到html代码
接收到 HTTP 请求之后,就轮到负载均衡登场了,它位于网站的最前端,把短时间内较高的访问量分摊到不同机器上处理。负载均衡方案有软件、硬件两种。
浏览器解析html代码,并请求html代码中的资源
浏览器拿到 index.html 文件后,就开始解析其中的 html 代码,遇到 js/css/image 等静态资源时,就向服务器端去请求下载(会使用多线程下载,每个浏览器的线程数不一样),这个时候就用上 keep-alive 特性了,建立一次 HTTP 连接,可以请求多个资源,下载资源的顺序就是按照代码里的顺序,但是由于每个资源大小不一样,而浏览器又多线程请求请求资源,所以请求显示的顺序并不一定是代码里面的顺序。
浏览器在请求静态资源时(在未过期的情况下),向服务器端发起一个 http 请求(询问自从上一次修改时间到现在有没有对资源进行修改),如果服务器端返回 304 状态码(告诉浏览器服务器端没有修改),那么浏览器会直接读取本地的该资源的缓存文件。
浏览器对页面进行渲染呈现给用户
最后,浏览器利用自己内部的工作机制,把请求到的静态资源和 html 代码进行渲染,渲染之后呈现给用户。
-
HTTP优化
HTTP 的相关优化主要包括持久连接 Keep-Alive、修改时间 Last-Modified 以及 If-Modified-Since、版本标记 ETag 以及 If-None-Match、缓存时间 Expires 以及 Cache-Control 和 gzip 压缩等。
持久连接Keep-Alive
HTTP 连接设计之初是请求-响应-关闭,也就是每建立一次 HTTP 连接,只能进行一次资源请求,当需要在同一目标服务器上获取多个资源的时候,就需要多次建立 HTTP 连接,而这个多次建立连接的过程,便降低了网站的性能。
于是,出现了 Connection:Keep-Alive,人称持久连接。Keep-Alive 避免了建立或者说重新建立连接的过程,减少了 HTTP 连接。而与此配套的有 Keep-Alive:timeout=120,max=5
其中,timeout=120 是指这个 TCP 通道保持 120S,max=5 指这个 TCP 通道最多接收 5 个 HTTP 请求,之后便自动关闭该连接。
修改时间Last-Modified和If-Modified-Since
Last-Modified 首部是服务端对客户端的 HTTP 响应所加的一个与缓存有关的 HTTP 首部,该首部标记了所请求资源在服务端的最后修改时间。类似:
Last-Modified : Fri , 12 May 2015 13:10:33 GMT
当客户端发现 HTTP 响应头中有 Last-Modified,会对资源进行缓存,在下次请求资源时,在 HTTP 请求头中添加 If-Modified-Since 首部,首部中将会添加上次成功请求资源时响应头部的 Last-Modified 属性值,即:
If-Modified-Since : Fri , 12 May 2015 13:10:33 GMT
当服务端接收到的 HTTP 请求中,发现有 If-Modified-Since 头部时,会将该属性值与请求资源的最后修改时间进行比对,如果最后修改时间与该属性值一致时,服务端会返回一个 304 Not Modified 响应,该响应中不包括响应实体。浏览器收到 304 的响应后,会进行重定向,获取本地缓存资源。如果最后修改时间与该属性值不一致,则会从服务端重新获取资源,做出 200 响应。
版本标记ETag和If-None-Match
ETag 其实与 Last-Modified 是差不多的方式,但是 ETag 并没有选择以时间作为标记,而是对所请求文件进行某些算法来生成一串唯一的字符串,作为对某一文件的标记。当收到客户端对某一资源的请求时,服务端在响应时,添加 ETag 首部,如下:
ETag:W/"a627ff1c9e65d2dede2efe0dd25efb8c"
当客户端发现 ETag 头部时,同样会对资源进行缓存,并在下次请求时,在请求头部添加 If-None-Match,如:
If-None-Match:W/"a627ff1c9e65d2dede2efe0dd25efb8c"
当服务端收到请求中含有该头部时,会使用同样的 ETag 生成算法对文件 ETag 进行计算,并与 If-None-Match 属性值进行比对,如果一致,则返回一个 304 Not Modified 响应,基本与上一种方式是一致的。
缓存时间Expires和Cache-Control
上述两种方式中,每次请求资源时,虽然在有缓存的情况下,选择缓存进行渲染绘制,但是在这之前还是发起了一次 HTTP 请求,虽然并没有真实的响应实体,但是依然会造成一些资源消耗。而 Expires 与上述两种方式使用了不同的思路。
当服务端希望客户端浏览器对某一资源进行缓存时,为了免去客户端每次都要询问自己:我上次的缓存现在还能用吗?所以,服务端选择了放权。只去告诉浏览器,我这次给你的资源你可以用多长时间,在这个时间段内,你可以一直使用它,无需每次咨询我。而服务端就是通过 Expires 属性来告诉客户端浏览器可以多长时间内不需要询问服务端。如下:
Expires:Thu, 19 Nov 2021 15:00:00 GMT
当客户端在响应首部中发现该属性值时,便会将该资源缓存起来,而缓存的过期时间即是 Expires 中的时间。在这个时间段内,浏览器完全自主。
但是,Expires 有一个不足的地方是,如果服务端时间与客户端本地时间不统一时,可能服务端让客户端可以对该资源缓存一个小时,而客户端本地时间比服务端时间快了两个小时,那就意味着,所有缓存都将不会生效。
于是有了弥补该不足的一个属性,即:Cache-Control。如果服务端在响应首部添加该属性时,客户端将直接使用该属性值来生成本地时间的缓存过期时间,这样便解决了这个问题,如下:
Cache-Control:max-age=3600
如果客户端在 2021 年 5 月 01 日 13 时 00 分 00 秒收到该响应时,便会加上 3600 秒也就是 2021 年 5 月 01 日 14 时 00 分 00 秒作为缓存过期时间。如果响应头部既有 Expires 和 Cache-Control,浏览器会首选 Cache-Control。
压缩gzip
开启 HTTP 的压缩 gzip,可以大大减少传输的数据包的大小:
Accept-Encoding: gzip, deflate, br
通过 Accept-Encoding 指定压缩方式。
-
HTTP协议之chunk编码(分块传输编码)
分块传输编码(Chunked transfer encoding)是超文本传输协议(HTTP)中的一种数据传输机制,允许 HTTP 由应用服务器发送给客户端应用( 通常是网页浏览器)的数据可以分成多个部分。分块传输编码只在 HTTP 协议 1.1 版本(HTTP/1.1)中提供。
通常,HTTP 应答消息中发送的数据是整个发送的,Content-Length 消息头字段表示数据的长度。数据的长度很重要,因为客户端需要知道哪里是应答消息的结束,以及后续应答消息的开始。然而,使用分块传输编码,数据分解成一系列数据块,并以一个或多个块发送,这样服务器可以发送数据而不需要预先知道发送内容的总大小。通常数据块的大小是一致的,但也不总是这种情况。
HTTP分块传输编码好处
HTTP 1.1 引入分块传输编码提供了以下几点好处:
- HTTP 分块传输编码允许服务器为动态生成的内容维持 HTTP 持久连接。通常,持久链接需要服务器在开始发送消息体前发送 Content-Length 消息头字段,但是对于动态生成的内容来说,在内容创建完之前是不可知的。(动态内容,content-length 无法预知)
- 分块传输编码允许服务器在最后发送消息头字段。对于那些头字段值在内容被生成之前无法知道的情形非常重要,例如消息的内容要使用散列进行签名,散列的结果通过 HTTP 消息头字段进行传输。没有分块传输编码时,服务器必须缓冲内容直到完成后计算头字段的值并在发送内容前发送这些头字段的值。(散列签名,需缓冲完成才能计算)
- HTTP 服务器有时使用压缩 (gzip 或 deflate)以缩短传输花费的时间。分块传输编码可以用来分隔压缩对象的多个部分。在这种情况下,块不是分别压缩的,而是整个负载进行压缩,压缩的输出使用本文描述的方案进行分块传输。在压缩的情形中,分块编码有利于一边进行压缩一边发送数据,而不是先完成压缩过程以得知压缩后数据的大小。(gzip压缩,压缩与传输同时进行)
HTTP分块传输详解
分块就是把数据分成一块一块的再发送出去,浏览器收到后再组装起来,这种 “化整为零” 的思路在 HTTP 协议里就是 “chunked” 分块传输编码,在响应报文里用头字段 “Transfer-Encoding: chunked” 来表示,意思是报文里的 body 部分不是一次性发过来的,而是分成了许多的块(chunk)逐个发送。
分块传输也可以用于 “流式数据”,例如由数据库动态生成的表单页面,这种情况下 body 数据的长度是未知的,无法在头字段 “Content-Length” 里给出确切的长度,所以也只能用 chunked 方式分块发送。
“Transfer-Encoding: chunked” 和 “Content-Length” 这两个字段是互斥的,也就是说响应报文里这两个字段不能同时出现,一个响应报文的传输要么是长度已知,要么是长度未知(chunked),这一点你一定要记住。
下面我们来看一下分块传输的编码规则,其实也很简单,同样采用了明文的方式,很类似响应头。
1.每个分块包含两个部分,长度头和数据块;
2.长度头是以 CRLF(回车换行,即\r\n)结尾的一行明文,用 16 进制数字表示长度;
3.数据块紧跟在长度头后,最后也用 CRLF 结尾,但数据不包含 CRLF;
4.最后用一个长度为 0 的块表示结束,即 “0\r\n\r\n”。
范围请求
有了分块传输编码,服务器就可以轻松地收发大文件了,但对于上 G 的超大文件,还有一些问题需要考虑。比如,你在看当下正热播的某穿越剧,想跳过片头,直接看正片,或者有段剧情很无聊,想拖动进度条快进几分钟,这实际上是想获取一个大文件其中的片段数据,而分块传输并没有这个能力。
“范围请求(range requests)的概念,允许客户端在请求头里使用专用字段来表示只获取文件的一部分。服务器会发送专用字段 Accept-Ranges: bytes” 明确告知客户端:“我是支持范围请求的”,范围请求不是 Web 服务器必备的功能,可以实现也可以不实现。
如果不支持的话该怎么办呢?服务器可以发送 “Accept-Ranges: none”,或者干脆不发送 “Accept-Ranges” 字段,这样客户端就认为服务器没有实现范围请求功能,只能老老实实地收发整块文件了。
客户端会发送 Range 请求字段,格式是 “bytes=x-y”,其中 x 和 Y 是以字节为单位的数据范围。Range 的格式也很灵活,起点 x 和终点 y 可以省略,能够很方便地表示正数或者倒数的范围。
假设文件是 100 个字节,那么:
- “0-” 表示从文档起点到文档终点,相当 于 “0-99”,即整个文件;
- “10-” 是从第 10 个字节开始到文档末尾,相当于 “10-99”;
- “-1” 是文档的最后一个字节,相当于 “99-99”;
- “-10” 是从文档末尾倒数 10 个字节,相当于 “90-99”。
例如客户端请求:
GET /16-2 HTTP/1.1 Host: www.ikam.cn Range: bytes=0-31
服务端响应:
HTTP/1.1 206 Partial Content Content-Length: 32 Accept-Ranges: bytes Content-Range: bytes 0-31/96
服务端收到 Range 字段后,要做 4 件事:
- 检查范围是否合法。
- 范围合法的话,计算偏移量,读取文件的片段,返回状态码 “206 Partial Content”,和 200 的意思差不多,但表示 body 只是原数据的一部分。
- 添加响应头字段 Content-Range,告诉片段的实际偏移量和资源的总大小。
- 发送数据。
主题数 180 |
帖子数 91 |
2 精华数 |
注册排名 1 |
- Cursor 搭配神级Prompt(Thinking Claude),无敌编程利器。小白保姆级教程,我奶奶都看得懂的教程!
- 如何更好的使用vscode编写PHP代码呢?下面是一些经验
- 你们会用vim吗?这里整理了比较完整的vim快捷键
- phpstorm快捷键大全使用phpstorm提高开发效率
- 根据时间间隔来计算当前时间为设定的时间间隔的第几次,并返回当前间隔的开始时间
- Redis的8大数据类型用法和使用场景以及每种数据类型低层实现原理
- Ubuntu服务器安装WebRTC 网络中继 Coturn 服务,这里使用apt install coturn安装,配置看文章内容
- 清空Git仓库的所有提交记录,保留最后的版本使其变为“新库”
- git修改历史提交记录的用户名和邮箱
- Hyperf框架封装日志类,可便捷打印日志