参考:
(建议精读)HTTP灵魂之问,巩固你的 HTTP 知识体系
《透视 HTTP 协议》——chrono
目录:
1、说说你对HTTP的了解吧。
1. HTTP状态码。
2. HTTP请求头和响应头,其中包括cookie、跨域响应头、Accept相关字段、鉴权token等等。
3. HTTP缓存。
4. HTTP跨域。
5. HTTPS。
2、输入网址后到页面显示,浏览器发生了什么
3、HTTP的优缺点
4、HTTP1.1到2的一些版本更新。
5、HTTP代理 / 缓存代理(具体看博客了里 20220817-CDN相关和HTTP代理 的另一篇文章)
这个我还真遇到过。。。那时候回答的乱七八糟的,就吭哧吭哧开始背看的那些文章,背到一半忘记了,面试官说:好可以了,下个问题吧。。。
哈哈哈哈哈现在回忆起来还是那么尴尬!虽然现在也还是没有特别理解,但是这道题好像可以比当年答得好了。
如果是现在我应该会这么回答:
HTTP协议是应用层协议,通常建立在传输层协议TCP之上,现在常用的HTTPS是HTTP在TCP协议建立后再进行SSL/TSL安全套接层连接的基础上建立的。
它有几个特点:包括灵活可拓展 可以自定义头字段实现功能、建立在TCP的基础上可靠传输、采用请求-应答的通信模式、无状态连接等。
http在我们的实际使用过程中主要需要注意以下几个方面:
状态码大概分为五类,从1-5开头的三位数表示。
1xx: 表示目前是协议处理的中间状态,还需要后续操作。
1××类状态码属于提示信息,是协议处理的中间状态,实际能够用到的时候很少。
偶尔能够见到的是“101 Switching Protocols”。它的意思是客户端使用 Upgrade 头字段,要求在 HTTP 协议的基础上改成其他的协议继续通信,比如 WebSocket。而如果服务器也同意变更协议,就会发送状态码 101,但这之后的数据传输就不会再使用 HTTP 了。2xx: 表示成功状态。
2××类状态码表示成功,常用的有 200、204、206;3xx: 重定向状态,资源位置发生变动,需要重新请求。
3××类状态码表示重定向,常用的有 301、302、304;4xx: 请求报文有误。
4××类状态码表示客户端错误,常用的有 400、403、404;5xx: 服务器端发生错误。
5××类状态码表示服务器错误,常用的有 500、501、502、503。
200 OK是最常见的成功状态码,表示一切正常,服务器如客户端所期望的那样返回了处理结果,如果是非 HEAD 请求,通常在响应头后都会有 body 数据。
204 No Content是另一个很常见的成功状态码,它的含义与“200 OK”基本相同,但响应头后没有 body 数据。所以对于 Web 服务器来说,正确地区分 200 和 204 是很必要的。
206 Partial Content是 HTTP 分块下载或断点续传的基础,表示 body 里的数据不是资源的全部,而是其中的一部分。
状态码 206 通常还会伴随着头字段“Content-Range”,表示响应报文里 body 数据的具体范围,供客户端确认,例如“Content-Range: bytes 0-99/2000”,意思是此次获取的是总计 2000 个字节的前 100 个字节。
301 Moved Permanently俗称“永久重定向”,含义是此次请求的资源已经不存在了,需要改用新的 URI 再次访问。
与它类似的是“302 Found”,曾经的描述短语是“Moved Temporarily”,俗称“临时重定向”,意思是请求的资源还在,但需要暂时用另一个 URI 来访问。
301 和 302 都会在响应头里使用字段 Location 指明后续要跳转的 URI,最终的效果很相似,浏览器都会重定向到新的 URI。两者的根本区别在于语义,一个是“永久”,一个是“临时”,所以在场景、用法上差距很大。
比如,你的网站升级到了 HTTPS,原来的 HTTP 不打算用了,这就是“永久”的,所以要配置 301 跳转,把所有的 HTTP 流量都切换到 HTTPS。
再比如,今天夜里网站后台要系统维护,服务暂时不可用,这就属于“临时”的,可以配置成 302 跳转,把流量临时切换到一个静态通知页面,浏览器看到这个 302 就知道这只是暂时的情况,不会做缓存优化,第二天还会访问原来的地址。
304 Not Modified是一个比较有意思的状态码,它用于 If-Modified-Since 等条件请求,表示资源未修改,用于缓存控制。它不具有通常的跳转含义,但可以理解成“重定向已到缓存的文件”(即“缓存重定向”)。
400 Bad Request是一个通用的错误码,表示请求报文有错误,但具体是数据格式错误、缺少请求头还是 URI 超长它没有明确说,只是一个笼统的错误,客户端看到 400 只会是“一头雾水”“不知所措”。所以,在开发 Web 应用时应当尽量避免给客户端返回 400,而是要用其他更有明确含义的状态码。
403 Forbidden实际上不是客户端的请求出错,而是表示服务器禁止访问资源。原因可能多种多样,例如信息敏感、法律禁止等,如果服务器友好一点,可以在 body 里详细说明拒绝请求的原因,不过现实中通常都是直接给一个“闭门羹”。
404 Not Found可能是我们最常看见也是最不愿意看到的一个状态码,它的原意是资源在本服务器上未找到,所以无法提供给客户端。但现在已经被“用滥了”,只要服务器“不高兴”就可以给出个 404,而我们也无从得知后面到底是真的未找到,还是有什么别的原因,某种程度上它比 403 还要令人讨厌。
405 Method Not Allowed:不允许使用某些方法操作资源,例如不允许 POST 只能 GET;
500 Internal Server Error与 400 类似,也是一个通用的错误码,服务器究竟发生了什么错误我们是不知道的。不过对于服务器来说这应该算是好事,通常不应该把服务器内部的详细信息,例如出错的函数调用栈告诉外界。虽然不利于调试,但能够防止黑客的窥探或者分析。
501 Not Implemented表示客户端请求的功能还不支持,这个错误码比 500 要“温和”一些,和“即将开业,敬请期待”的意思差不多,不过具体什么时候“开业”就不好说了。“502 Bad Gateway”通常是服务器作为网关或者代理时返回的错误码,表示服务器自身工作正常,访问后端服务器时发生了错误,但具体的错误原因也是不知道的。
503 Service Unavailable表示服务器当前很忙,暂时无法响应服务,我们上网时有时候遇到的“网络服务正忙,请稍后重试”的提示信息就是状态码 503。
503 是一个“临时”的状态,很可能过几秒钟后服务器就不那么忙了,可以继续提供服务,所以 503 响应报文里通常还会有一个“Retry-After”字段,指示客户端可以在多久以后再次尝试发送请求。
一个个说哈。
Accept: text/html,application/xml,image/webp,image/png
Accept-Encoding: gzip, deflate, br (如果没有表示不支持压缩
Content-Type: text/html; charset=utf-8 (返回字符集编码)
Content-Type: image/pngContent-Encoding: gzip (如果没有表示没有压缩
客户端用 Accept 头告诉服务器希望接收什么样的数据,同时在POST请求中也会用 Content 头说明发送了什么样的数据,服务端也会在响应头中添加Content响应头告诉客户端实际发送了什么数据。
Accept-Language
Content-Language: zh-CNAccept-Language: zh-CN,zh;q=0.9,en;q=0.8
“q”是权重的意思。最大值是 1,最小值是 0.01,默认值是 1,如果值是 0 就表示拒绝。具体的形式是在数据类型或语言代码后面加一个“;”,然后是“q=value”。
这里要提醒的是“;”的用法,在大多数编程语言里“;”的断句语气要强于“,”,而在 HTTP 的内容协商里却恰好反了过来,“;”的意义是小于“,”的。
Vary: Accept-Encoding,User-Agent,Accept
分块传输
body数据长度未知的情况下可以使用chunked方式发送
响应头:Transfer-Encoding: chunked,和Content-Length响应头互斥。
注:流式下载的时候,服务端采用动态压缩的方式传输数据,所以采用分块编码,压缩一部分就发一部分,发送数据是动态生成的,总长度未知。
范围请求
比如视频播放拖动进度条实时获取文件的某一范围数据,就可以使用范围请求。它允许客户端在请求头里使用专用字段来表示只获取文件的一部分。
服务器必须在响应头里使用字段“Accept-Ranges: bytes”
明确告知客户端它支持范围请求.
请求头 Range 是 HTTP 范围请求的专用字段,格式是“bytes=x-y”,其中的 x 和 y 是以字节为单位的数据范围。
x、y 表示的是“偏移量”,范围必须从 0 计数,例如前 10 个字节表示为“0-9”,第二个 10 字节表示为“10-19”,而“0-10”实际上是前 11 个字节。
请求头
GET /16-2 HTTP/1.1
Host: www.chrono.com
Range: bytes=0-31
响应头
HTTP/1.1 206 Partial Content
Content-Length: 32
Accept-Ranges: bytes
Content-Range: bytes 0-31/96
服务器收到 Range 字段后,需要做四件事。
第一,它必须检查范围是否合法,比如文件只有 100 个字节,但请求“200-300”,这就是范围越界了。服务器就会返回状态码 416,意思是“你的范围请求有误,我无法处理,请再检查一下”。
第二,如果范围正确,服务器就可以根据 Range 头计算偏移量,读取文件的片段了,返回状态码“206 Partial Content”,和 200 的意思差不多,但表示 body 只是原数据的一部分。
第三,服务器要添加一个响应头字段Content-Range,告诉片段的实际偏移量和资源的总大小,格式是“bytes x-y/length”,与 Range 头区别在没有“=”,范围后多了总长度。例如,对于“0-10”的范围请求,值就是“bytes 0-10/100”。
最后剩下的就是发送数据了,直接把片段用 TCP 发给客户端,一个范围请求就算是处理完了。
注:range针对的是原数据的范围字段,但是服务器是会压缩了菜发送过来。
如果服务器支持长连接,它总会在响应报文里放一个Connection: keep-alive
字段,告诉客户端它支持长连接的,接下来只要向服务器发送了第一次请求,后续的请求都会重复利用第一次打开的 TCP 连接,也就是长连接,在这个连接上收发数据。
如果长时间不关闭长连接也会有挤兑服务器资源的可能,因此服务端往往会自行设置自动关闭连接的策略。
请求头里发送Connection: close
字段可以自动关闭连接。
响应头里传回Connection: close
也代表自动关闭连接了。
拿 Nginx 来举例,它有两种方式:
使用“keepalive_timeout”指令,设置长连接的超时时间,如果在一段时间内连接上没有任何数据收发就主动断开连接,避免空闲连接占用系统资源。
使用“keepalive_requests”指令,设置长连接上可发送的最大请求次数。比如设置成 1000,那么当 Nginx 在这个连接上处理了 1000 个请求后,也会主动断开连接。
301:永久重定向
302:暂时重定向
“重定向”实际上发送了两次 HTTP 请求,第一个请求返回了 302,然后第二个请求就被重定向到了“/index.html”
响应头Location: /index.html
只有配合 301/302 状态码才有意义,它标记了服务器要求重定向的 URI。
浏览器收到 301/302 报文,会检查响应头里有没有“Location”。如果有,就从字段值里提取出 URI,发出新的 HTTP 请求,相当于自动替我们点击了这个链接。
聊一聊Cookie
CSRF/XSRF攻击与防御
Cookie 就是服务器委托浏览器存储在客户端里的一些数据,而这些数据通常都会记录用户的关键识别信息.
主要涉及到响应头字段 Set-Cookie 和请求头字段 Cookie。
当用户通过浏览器第一次访问服务器的时候,服务器肯定是不知道他的身份的。所以,就要创建一个独特的身份标识数据,格式是“key=value”,然后放进 Set-Cookie 字段里,随着响应报文一同发给浏览器。set-cookie可以设置多个,关键字使用;隔开。
浏览器收到响应报文,看到里面有 Set-Cookie,知道这是服务器给的身份标识,于是就保存起来,下次再请求的时候就自动把这个值放进 Cookie 字段里发给服务器。
因为第二次请求里面有了 Cookie 字段,服务器就知道这个用户不是新人,之前来过,就可以拿出 Cookie 里的值,识别出用户的身份,然后提供个性化的服务。
你对cookie了解多少?
过期时间
Cookie 的有效期可以使用 Expires 和 Max-Age 两个属性来设置。
“Expires”俗称“过期时间”,用的是绝对时间点,可以理解为“截止日期”(deadline)。
“Max-Age”用的是相对时间,单位是秒,浏览器用收到报文的时间点再加上 Max-Age,就可以得到失效的绝对时间。
Expires 和 Max-Age 可以同时出现,两者的失效时间可以一致,也可以不一致,但浏览器会优先采用 Max-Age 计算失效期。
作用域
cookie的domain属性
让浏览器仅发送给特定的服务器和 URI,避免被其他网站盗用。
“Domain”和“Path”指定了 Cookie 所属的【服务器】域名和路径,浏览器在发送 Cookie 前会从请求服务器的 URI (URI 可以是js/html/img/css资源请求)中提取出 host 和 path 部分,对比 Cookie 的属性。如果不满足条件,就不会在请求头里发送 Cookie。
如果一个域名’aaa.com/bbb’的客户端向域名’ccc.com/ddd’的服务器请求数据,响应中setcookie的cookie中默认属性domain=‘ccc.com’,path=‘/ddd’。以后客户端再向’ccc.com/'的服务器发送请求时,path符合就会自动带上这个cookie。
domain可以用于同一个域名下的多网站做单点登陆。
安全性
SameSite是什么
SameSite cookie 的说明
在 JS 脚本里可以用 document.cookie 来读写 Cookie 数据,这就带来了安全隐患,有可能会导致“跨站脚本”(XSS)攻击窃取数据。
-HttpOnly会告诉浏览器,此 Cookie 只能通过浏览器 HTTP 协议传输,禁止其他方式访问,浏览器的 JS 引擎就会禁用 document.cookie 等一切相关的 API,脚本攻击也就无从谈起了。
-SameSite可以防范“跨站请求伪造”(XSRF)攻击。
涉及到第一方cookie和第三方cookie。
- 第一方cookie:由相同站点发送的 cookie。
- 第三方cookie:由跨站请求发送的cookie。
拿 “请求的目标URL(或者cookie的domain)” 和 “当前网站URL(也就是浏览器地址栏中的网址)” 这两者来进行比较从而判断是否形成跨站的。
两者的ORIGIN的注册域相同则为相同站点,不同则构成跨站。所谓注册域,是指您可以购买或租用的域名,即公共后缀(public suffix)之下的一级,也称为顶级域名。=
设置成“SameSite=Strict”可以严格限定 Cookie 只能允许第一方cookie发送。这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 cookie,跳转过去总是未登陆状态。
“SameSite=Lax”则略宽松一点,允许 第三方网站导航回第一方网站的时候发送请求携带cookie,允许GET/HEAD 等安全方法,但禁止 POST 跨站发送。
“SameSite=None”则不限制cookie的发送,只要domain/path符合就可以携带,但是为了安全性谷歌要求必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。
一开始我没搞懂sameSite和domain的区别。
samesite:校验发送请求的客户端域名,domain校验服务器的域名
如果不指定samesite,可能会出现:用户进入安全网站获得cookie,再进入恶意网站,恶意网站获取到cookie后(携带第三方cookie)向cookie设置的domain域名发送恶意请求的情况(csrf)。
在旧版浏览器,如果SameSite属性没有设置,或者没有得到运行浏览器的支持,那么它的行为等同于None,Cookies会被包含在任何请求中——包括跨站请求。
但是,在Chrome 80+版本中,SameSite的默认属性是SameSite=Lax。换句话说,当 Cookie 没有设置 SameSite 属性时,将会视作 SameSite 属性被设置为Lax。如果想要指定 Cookies 在同站、跨站请求都被发送,那么需要明确指定 SameSite 为 None。具有 SameSite=None 的 Cookie 也必须标记为secure并通过HTTPS传送。
Chrome 也宣布,将在下个版本也就是Chrome 83版本,在访客模式下禁用第三方 Cookie,在2022年全面禁用第三方 Cookie,到时候,即使你能指定 SameSite 为 None 也没有意义,因为你已经无法写入第三方 Cookie 了。
-Secure,表示这个 Cookie 仅能用 HTTPS 协议加密传输,明文的 HTTP 协议会禁止发送。但 Cookie 本身不是加密的,浏览器里还是以明文的形式存在。
HTTP缓存机制
cache-control
请求头/响应头有关,注意就算响应头设置了MAx-age但是请求头设置no-store的时候,浏览器仍然不会缓存,这是一个协商缓存字段,是双方的协商缓存策略一致时生效。
浏览器中“无缓存刷新”、控制台中“disable cache”的请求机制就是如此,在请求头中加上cache-control: no-cache
。大家可以自己观察一下,当勾选了控制台上的disable cache,即使响应头中存在etag,仍然不会走缓存,而取消勾选之后才能看到304状态码。max-age:0
也可以达到同样的效果,刷新缓存。
字段值 | 作用 |
---|---|
max-age | 在指定时间内,缓存服务器不再对资源的有效性进行确认,可以使用,以服务端发出文件的时间为基准时间 |
no-cache | 防止从缓存中返回过期的资源,所以使用之前,需要和源服务器发起请求比对过期时间 |
no-store | 不进行缓存,暗示请求报文中可能含有机密信息,不可缓存 |
must-revalidate | 和 no-cache 相似的词,如果缓存不过期就可以继续使用,但过期了如果还想用就必须去服务器验证。 |
private | 只有某个在通过缓存服务器的时候,得到缓存资源 |
public | 所有的用户在通过缓存服务器的时候,都可以缓存这个资源。 |
在精确度上,Etag要优于Last-Modified,Last-Modified的时间单位是秒,如果某个文件在1秒内变了多次,那么他们的Last-Modified其实并没有体现出>来修改,但是Etag每次都会改变确保了精度
在性能上,Etag要逊于Last-Modified,毕竟> Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值。
在优先级上,服务器校验优先考虑Etag。
所以,两者互补
[先看]跨源资源共享(CORS)
所有人都应该知道的跨域及CORS
什么是跨域请求以及实现跨域的方案
HTTP 跨域
跨域问题是由浏览器规定的同源策略引起的,所谓的同源是指,域名、协议、端口均为相同。
同源策略限制了一下行为:
因此现在的前后分离开发往往都是跨域通信的。
分为简单请求和复杂请求两种,简单请求是方法为:GET / HEAD / OPTION的请求,而且规定了可携带的安全请求头和内容类型(content-type),其余都是复杂请求,而且复杂请求在发送前必须发出预检请求检测服务器是否支持。
简单请求中,浏览器会自动为请求头加上Origin字段,而服务器也会根据Origin判断是否是自己支持的域名,返回的响应头带上Access-Control-Allow-Origin。
复杂请求中,预检请求是Option方法,请求头中会带上Origin指明发送主机域名、Access-Control-Request-Method指明即将发送的方法、Access-Control-Request-Headers指明方法中额外带上的请求头字段。
浏览器收到后会返回Access-Control-Allow-Origin说明可接受的域名、Access-Control-Allow-Methods说明服务器可接受方法、Access-Control-Allow-Header说明可接收的请求头、Access-Control-Max-Age说明预检请求的有效时间,在这时间内可以不用重复发起预检请求。
OPTIONS /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-TypeHTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
JSONP
JSONP仅能发起GET请求,它主要利用img / script 等标签可以跨域发起请求的特性。
浏览器解析HTML代码时,原生具有src属性的标签,浏览器都赋予其HTTP请求的能力,而且不受跨域限制,使用src发送HTTP请求,服务器直接返回一段JS代码的函数调用,将服务器数据放在函数实参中,前端提前写好响应的函数准备回调,接收数据,实现跨域数据交互。
服务器发送请求
使用nodejs代理服务器或者nginx反向代理。
服务器响应头:access-control-allow-credentials
客户端请求头设置:withCredentials。
- 相关响应头
如果服务端指定了具体的域名而非“*”,那么响应首部中的Vary 字段的值必须包含 Origin。这将告诉客户端:服务器对不同的源站返回不同的内容。
Access-Control-expose-headers
Access-Control-Max-Age
Access-Control-Allow-Methods
Access-Control-Allow-Headers
access-control-allow-origin : 指定了允许访问该资源的外域 URI。允许来自哪个URL的请求,参考请求头中的Origin。
例如,下面的字段值将允许来自 https://mozilla.org 的请求:Access-Control-Allow-Origin: https://mozilla.org Vary: Origin
- 相关请求头
Origin
Access-Control-Request-Method
Access-Control-Request-Headers
HTTPS把 HTTP 下层的传输协议由 TCP/IP 换成了 SSL/TLS,由“HTTP over TCP/IP”变成了“HTTP over SSL/TLS”,让 HTTP 运行在了安全的 SSL/TLS 协议上,收发报文不再使用 Socket API,而是调用专门的安全接口。
浏览器和服务器在使用 TLS 建立连接时需要选择一组恰当的加密算法来实现安全通信,这些算法的组合被称为“密码套件”(cipher suite,也叫加密套件)。
HTTPS中使用到的是混合加密方式,非对称加密连接,对称加密会话。
非对称加密中涉及到公钥和私钥,公钥加密后只能用私钥解密,私钥加密后也只能用公钥解密。一般非对称加密的密钥很长,加解密非常耗时,因此客户端、服务端建立了连接后就会使用仅有十几位的会话密钥进行对称加密通话了.
而光有会话机密性仍然不够,还缺少完整性、身份验证。
黑客虽然拿不到会话密钥,无法破解密文,但可以通过窃听收集到足够多的密文,再尝试着修改、重组后发给网站。因为没有完整性保证,服务器只能“照单全收”,然后他就可以通过服务器的响应获取进一步的线索,最终就会破解出明文。
比如黑客截取到一段密文:AABBCCDD,然后他尝试修改:AABBCCD,AABBCC,AABBC,多试n遍之后意识到:这是一个{AA:BB,CC:DD}的对象,接下来,哪怕他并不知道BB是什么值,也可以把BB/DD换成自己的加密数据,再假如AABBCCDD代表着把id为BB的用户的钱转给id为DD的用户,这就出事故了。
另外,黑客也可以伪造身份发布公钥。如果你拿到了假的公钥,混合加密就完全失效了。你以为自己是在和“某宝”通信,实际上网线的另一端却是黑客,银行卡号、密码等敏感信息就在“安全”的通信过程中被窃取了。
身份验证引入了第三方的安全性证书颁发机构CA,用户向它申请一个安全证书,证书包含序列号、用途、颁发者、有效时间等等,把这些打成一个包再签名,完整地证明公钥关联的各种信息,形成“数字证书”。
同时浏览器也会支持向证书机构查询验证签名。
签名和验签操作和通信加密相反,把公钥私钥的用法反过来,之前是公钥加密、私钥解密,现在是私钥加密、公钥解密。
签名和公钥一样完全公开,任何人都可以获取。但这个签名只有用私钥对应的公钥才能解开,拿到摘要后,再比对原文验证完整性,就可以像签署文件一样证明消息确实是你发的。\
TLS的连接流程
TSL包含几个子协议,比如握手协议(Handshake Protocol)、变更密码规范协议(Change Cipher Spec Protocol)等。
TCP连接后,浏览器会先发送一个“Client Hello”的消息,里面有客户端的版本号、支持的密码套件,还有一个随机数(Client Random),用于后续生成会话密钥。
Handshake Protocol: Client Hello Version: TLS 1.2 (0x0303) Random: 1cbf803321fd2623408dfe… Cipher Suites (17 suites) Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f) Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
服务器会返回一个“Server Hello”消息。把版本号对一下,也给出一个随机数(Server Random),然后从客户端的列表里选一个作为本次通信使用的密码套件,再用密码套件规定的加密方式和私钥加密生成数字签名,把这几样东西和证书一起发给浏览器。
浏览器获取到证书后从自己的证书信任链验证合法性,验证成功后使用公钥后解密数字签名,如果信息和证书上的一致,则连接成功。(因为私钥只存在于拥有证书的服务器手上,因此其他人若篡改签名,公钥绝对无法解密成功)
然后开始协商生成会话密钥,浏览器根据约定好的加密套件,生成一个会话密钥,然后使用公钥进行非对称算法加密(比如 RSA、ECDHE)给服务器发回去。
服务器拿到密文后用私钥解密,取出会话密钥。这样,双方就都拥有了“服务器随机数、浏览器随机数、会话密钥”三个随机数,然后再使用约定好的加密算法一阵加密,最终获得了最终的会话密钥。就实现了对称密钥的安全交换,后续就不再使用非对称加密,全都使用对称加密。
为了防止账号、密码被盗,有的时候(比如网上银行)还会使用 U 盾给用户颁发客户端证书,实现“双向认证”,这样会更加安全。
双向认证的流程也没有太多变化,只是在“Server Hello Done”之后,“Client Key Exchange”之前,客户端要发送“Client Certificate”消息,服务器收到后也把证书链走一遍,验证客户端的身份。
TLS连接耗时怎么优化
- 硬件加速,现有使用 SSL 加速服务器,用专门的服务器集群来彻底“卸载”TLS 握手时的加密解密计算。
- TLS1.3升级后大幅简化了握手过程,只需要1RTT。
- 服务器直接将证书链圈发给客户端,然后客户端收到后再验证,节省了客户端确认的时间。
- 会话复用:只要第一次连接成功后就使用一个sessionID记录下,就是客户端和服务器首次连接后各自保存一个会话的 ID 号,内存里存储主密钥和其他相关的信息。当客户端再次连接时发一个 ID 过来,服务器就在内存里找,找到就直接用主密钥恢复会话状态,跳过证书验证和密钥交换,只用一个消息往返就可以建立安全通信。
优点:
HTTP 以文本方式传输,简单、灵活和易于扩展;
HTTP的基本报文格式就是’header+body’,头部信息用的也都是常见单词,降低了学习门槛。而且http里中的请求方法、状态码、请求头等等都可以根据开发者需要定制扩写。Http的灵活还体现在没有限制下层传输协议,除了TCP,还可以使用 SSL/TLS,甚至是基于 UDP 的 QUIC.
HTTP 拥有成熟的软硬件环境,应用的非常广泛,是互联网的基础设施;
HTTP 是无状态的,既是缺点也是优点:实现上会简单一些,减轻了服务器状态处理的负担。同时可以轻松实现集群化,扩展性能。缺点在于一些连续行为上可能会造成不便:比如购物 下单 结算 支付的一系列行为,服务器无法知道这是同一个用户的连续关联行为,因此如果需要也可以用 Cookie 技术来实现“有状态”;
“无状态”表示服务器都是相同的,没有“状态”的差异,所以可以很容易地组成集群,让负载均衡把请求转发到任意一台服务器,不会因为状态不一致导致处理出错,使用“堆机器”的“笨办法”轻松实现高并发高可用。
这个所谓的“状态”应该怎么理解呢?“状态”其实就是客户端或者服务器里保存的一些数据或者标志,记录了通信过程中的一些变化信息。
你一定知道,TCP 协议是有状态的,一开始处于 CLOSED 状态,连接成功后是 ESTABLISHED 状态,断开连接后是 FIN-WAIT 状态,最后又是 CLOSED 状态。这些“状态”就需要 TCP 在内部用一些数据结构去维护,可以简单地想象成是个标志量,标记当前所处的状态,例如 0 是 CLOSED,2 是 ESTABLISHED 等等。
再来看 HTTP,那么对比一下 TCP 就看出来了,在整个协议里没有规定任何的“状态”,客户端和服务器永远是处在一种“无知”的状态。
建立连接前两者互不知情,每次收发的报文也都是互相独立的,没有任何的联系。收发报文也不会对客户端或服务器产生任何影响,连接后也不会要求保存任何信息。
队头阻塞可以使用并发连接(每个客户端最多6个长连接)和域名分片(请求不同的域名,但其实不同的域名都指向同一个服务器,长连接的数量就上去了)来解决。
HTTP2的特性解析
HTTP2的优化点主要在性能方面。
浏览器和服务器将共同维护一份 常见的头部名称与值 编成的字典,然后http采用特殊的算法压缩时,用索引号去替代字符串,http传送到目的地后再解压。
数据流:用于传输二进制帧提出的概念,它是二进制帧的双向传输序列,同一个消息往返的帧会分配一个唯一的流 ID。你可以把它想象成是一个虚拟的“数据流”,在里面流动的是一串有先后顺序的数据帧,这些数据帧按照次序组装起来就是 HTTP/1 里的请求报文和响应报文。
流(stream):已建立连接上的双向字节流。每个流都有一个唯一的整数标识符(1、2…N);
消息:与逻辑消息对应的完整的一系列数据帧。
帧(frame):HTTP2.0通信的最小单位,每个帧包含帧头部,至少也会标识出当前帧所属的流(stream id)。
与 HTTP/1“并发多个连接”不同,HTTP/2 的“多路复用”特性要求对一个域名(或者 IP)只用一个 TCP 连接,所有的数据都在这一个连接上传输,这样不仅节约了客户端、服务器和网络的资源,还可以把带宽跑满,让 TCP 充分“吃饱”。
HTTP/2 虽然使用“帧”“流”“多路复用”,没有了“队头阻塞”,但这些手段都是在应用层里,而在下层,也就是 TCP 协议里,还是会发生“队头阻塞”。
这是怎么回事呢?让我们从协议栈的角度来仔细看一下。在 HTTP/2 把多个“请求 - 响应”分解成流,交给 TCP 后,TCP 会再拆成更小的包依次发送(其实在 TCP 里应该叫 segment,也就是“段”)。
在网络良好的情况下,包可以很快送达目的地。但如果网络质量比较差,像手机上网的时候,就有可能会丢包。
而 TCP 为了保证可靠传输,有个特别的“丢包重传”机制,丢失的包必须要等待重新传输确认,其他的包即使已经收到了,也只能放在缓冲区里,上层的应用拿不出来,只能“干着急”。
于是Google就又发明了一个新的“QUIC”协议,让 HTTP 跑在 QUIC 上而不是 TCP 上。而这个“HTTP over QUIC”就是 HTTP 协议的下一个大版本,HTTP/3。它在 HTTP/2 的基础上又实现了质的飞跃,真正“完美”地解决了“队头阻塞”问题。
QUIC 选定了 UDP,在它之上把 TCP 的那一套连接管理、拥塞窗口、流量控制等“搬”了过来,“去其糟粕,取其精华”,打造出了一个全新的可靠传输协议,可以认为是“新时代的 TCP”。