一文带你深入了解HTTP
点个关注👆跟腾讯工程师学技术
http的发展史
在学习网络之前,了解它的历史能够帮助我明白为何它会发展为如今这个样子,能让我有探究它的兴趣。下面的这张图片就展示了“互联网”诞生至今的发展历程
http是什么?
HyperTextTransferProtocol 直译为“超文本传输协议”。
2.传输:http是一个“双向协议”,传输的是请求方和响应方之间的数据,不限制请求方和响应方之间的角色,传递的过程中可以存在任意“中间人”。
所以对于以上的问题可以有这样的总结:http是一个在计算机世界里专门在两点之间传递文字、图片、音频、视频等超文本数据的约定和规范。
与http相关的一些概念
浏览器(web Browser):浏览器的本质是http中的请求方,使用http协议获得网络上的各种资源。在HTTP协议里,浏览器的角色被称为"User Agent"即用户代理,意思是作为访问者的”代理来发起HTTP请求。下图是一些主流浏览器及其内核。
服务器(web Server):硬件含义就是物理形式或“云”形式的机器。软件含义的 Web 服务器就是提供 Web 服务的应用程序,通常会运行在硬件含义的服务器上。它利用强大的硬件能力响应海量的客户端 HTTP 请求,返回动态的信息。常见的web服务器有Apache、Nginx。
CDN(Content Delivery Network):CDN是为了解决长距离网络访问速度慢的问题而诞生的一种网络应用服务,全称为“内容分发网络”。CDN最核心的原则是“就近访问”,使用HTTP协议里的代理和缓存技术,用户在上网的时候不直接访问原网站,而是访问离他最近的一个CDN节点,节省了访问过程中的时间成本。(负载均衡,安全防护,边缘计算)。
爬虫(Crawler):“机器人”形式的用户代理,是一种可以自动访问Web资源的应用程序。
HTML(Hyper Text Markup Language):超文本标记语言,用于描述超文本页面,用标签定义图片、文字、排版布局,最终由浏览器渲染。
web Service:由W3C定义的应用服务开发规范,使用client-server主从架构。是一个基于Web(HTTP)的服务架构技术。
WAF:网络应用防火墙,位于Web服务器之前,专门检测http流量,是防护web应用安全的技术。可以阻止SQL注入,跨站脚本攻击,可以完全集成进Apache或Nginx。
TCP/IP:一系列网络通信协议的统称,其中最核心的是TCP和IP协议。其他的还有UDP,ICMP,ARP等,共同构成一个复杂但有层次的协议栈。IP(Internet Protocol)协议主要解决寻址和路由问题,以及如何在两点之间传输数据包。TCP(Transmission Control Protoco)协议位于IP协议之上,意思是“传输控制协议”,基于IP协议提供可靠的、字节流形式的通信,是HTTP协议实现的基础。互联网上的 HTTP 协议运行在 TCP/IP 上,HTTP 也就可以更准确地称为“HTTP over TCP/IP”。
DNS(Domain Name System): 域名系统,用有意义的名字来作为 IP 地址的等价替代。在 DNS 中,“域名”(Domain Name)又称为“主机名”(Host)。域名用“.”分隔成多个单词,级别从左到右逐级升高,最右边的被称为“顶级域名”。但想要使用 TCP/IP 协议来通信仍然要使用 IP 地址,所以需要把域名做一个转换,“映射”到它的真实 IP,这就是所谓的“域名解析”。
URI/URL:URI(Uniform Resource Identifier)中文名称是统一资源标识符。DNS 和 IP 地址只是标记了互联网上的主机,URI能够唯一地标记互联网上资源。URI 另一个更常用的表现形式是 URL(Uniform Resource Locator), 统一资源定位符,也就是我们俗称的“网址”,它实际上是 URI 的一个子集,通常不会做严格的区分。
URI 主要有三个基本的部分构成:
1.协议名:即访问该资源应当使用的协议
2.主机名:即互联网上主机的标记,可以是域名或 IP 地址
3.路径:即资源在主机上的位置,使用“/”分隔多级目录
HTTPS:全称是“HTTP over SSL/TLS”,也就是运行在 SSL/TLS 协议上的 HTTP。它是一个负责加密通信的安全协议,建立在 TCP/IP 之上,所以也是个可靠的传输协议,可以被用作 HTTP 的下层,相当于“HTTP+SSL/TLS+TCP/IP”。
代理(Proxy): 是 HTTP 协议中请求方和应答方中间的一个环节,作为“中转站”,既可以转发客户端的请求,也可以转发服务器的应答。
代理有很多的种类,常见的有:
4.反向代理:靠近服务器端,代表服务器响应客户端的请求;
网络分层模型层级是从下往上数的,一般我们比较常接触到的是TCP/IP四层模型,也是比较早出现的分层模型。
利用TCP/IP协议族进行网络通信时,会通过分层顺序与对方进行通信(发送端从应用层往下走,接收端从应用层往上走)。
域名是一个有层次的结构,是一串用“.”分隔的多个单词,最右边的被称为“顶级域名”,然后是“二级域名”,层级关系向左依次降低。最左边的是主机名,通常用来表明主机的用途,比如“www”表示提供万维网服务、“mail”表示提供邮件服务,不过这也不是绝对的。
可以通过下面的例子了解一下协议 主机 域名之间的层次关系。域名就像人的名字一样,名字的关键是要让我们容易记忆。除了标识身份之外,域名还可以代替ip地址。
我们经常会使用域名访问网站,但其实在网络查找的工程当中是使用ip定位资源的,域名必须解析为ip地址才可以正确的拿到资源。DNS就是用来将域名变为ip的协议。
DNS 的核心系统是一个三层的树状、分布式服务,基本对应域名的结构:
虽然DNS的服务,遍布全球,服务能力也很厉害,但是全世界的网民都在使用这个服务,也会对服务器造成很大的压力。在核心 DNS 系统之外,还有两种手段用来减轻域名解析的压力,并且能够更快地获取结果,基本思路就是“缓存”。
DNS的解析结果可以保存在大公司自己的DNS服务器里,或者操作系统缓存、hosts 文件当中,很多域名解析的工作就都不用请求根DNS服务器了,直接在本地或本机就能解决,不仅方便了用户,也减轻了各级 DNS 服务器的压力,效率就大大提升了。
基于域名和DNS服务器,我们可以实现重定向。因为域名代替了ip地址,所以可以对外域名不变,而主机IP可以任意变动。当主机有情况需要下线、迁移时,可以更改 DNS 记录,让域名指向其他的机器。
我们应该都听说过负载均衡吧,DNS在域名解析阶段就可以进行负载均衡的操作。
HTTP/1.X
(一)HTTP报文
HTTP 协议的请求报文和响应报文的结构基本相同,由三大部分组成:
请求行
状态行
状态行一般用来描述服务端对于客户端的请求回复的状态,一般也是由三个部分组成。
头部字段
1.字段名不区分大小写,例如“Host”也可以写成“host”,但首字母大写的可读性更好。
2.字段名里不允许出现空格,可以使用连字符“-”,但不能使用下划线“_”。例如,“test-name”是合法的字段名,而“test name”“test_name”是不正确的字段名。
3.字段名后面必须紧接着“:”,不能有空格,而“:”后的字段值前可以有多个空格。
4.字段的顺序是没有意义的,可以任意排列不影响语义。
5.字段原则上不能重复,除非这个字段本身的语义允许,例如 Set-Cookie。
(二)HTTP请求方法
这几个是我们比较常用的方法,有必要好好学习一下。
GET和HEAD
GET适用于向服务器请求资源,一般将数据携带于url上。
HEAD类似于简化版的GET请求,服务端收到HEAD请求时只返回响应头并且响应头与GET完全一致。
POST和PUT
POST 适用于向服务端发送数据,将数据携带在body当中,通常表示的是“create”的含义。
PUT 类似于POST方法,也可以向服务器提交数据,是“update”的含义。
GET和POST的区别
在这里特别容易被问到的问题是GET和POST的区别,我也想在这块详细的写一下。以下是基于我个人的理解
1. 大小:GET通常将数据带在URL当中而POST将数据放在body里(是RFC在语义上的要求,语法上GET也可以使用body传输数据而POST同样可以把参数放在URL里),因此由于浏览器对于URL长度的限制,GET请求能携带的数据大小一般不超过2KB。值得一提Chrome浏览器对URL的长度限制已经增加到2MB,但是我们考虑到兼容性,URL的长度应该以最大限制的最小标准为主(IE浏览器限制为2KB),除了浏览器的限制,还应该考虑到服务端的限制。
2. 安全:安全是指请求的方法是否会对服务器当中的资源造成影响,因为GET方法是只读的,只要服务器没有“曲解”客户端的请求,服务端上的数据就是安全的。而POST会对服务端的数据进行“增删改”的操作,因此是不安全的。
3. 幂等:幂等的意思是说多次重复执行操作,产生的效果是否相同。显然因为GET方法只对服务器上的资源做只读操作,因此是幂等的。POST在RFC中的定义是“新增或提交数据”,多次提交数据会创建多个资源,所以不是幂等的(而 PUT 是“替换或更新数据”,多次更新一个资源,所以是幂等的)。
4. 缓存: 就是说这个方法的可缓存性,绝大多数的浏览器的实现里仅仅支持GET缓存。因为GET因为是读取,就可以对GET请求的数据做缓存。而POST不幂等也就意味着不能随意多次执行。因此也就不能缓存。
(三)URI是什么
URI,也就是统一资源标识符(Uniform Resource Identifier)。因为它经常出现在浏览器的地址栏里,所以俗称为“网络地址”,简称“网址”。URI 不完全等同于网址,它包含有 URL 和 URN 两个部分,在 HTTP 世界里用的网址实际上是 URL——统一资源定位符(Uniform Resource Locator)。但因为 URL 实在是太普及了,所以常常把这两者简单地视为相等。
scheme 协议名,表示资源应该使用哪种协议来访问。最常见的当然就是“http”了,表示使用 HTTP 协议。另外还有“https”,表示使用经过加密、安全的 HTTPS 协议。此外还有其他不是很常见的 scheme,例如 ftp、ldap、file、news 等。
:// 分隔符,在 scheme 之后,必须是三个特定的字符“://”,它把 scheme 和后面的部分分离开。没有特定的意义。
user:passwd@ 身份信息,表示登录主机时的用户名和密码,但现在已经不推荐使用这种形式了,因为它把敏感信息以明文形式暴露出来,存在严重的安全隐患。
host:port主机名,表示资源所在的主机名,通常的形式是“host:port”,即主机名加端口号。
path 路径,表示资源所在位置,采用了类似文件系统“目录”的表示方式,通常以‘/’开始
query 查询参数,用一个“?”开始,但不包含“?”,表示对资源附加的额外要求。path是多个“key=value”的字符串,这些字符串用字符“&”连接,浏览器和服务器都可以按照这个格式把长串的查询参数解析成可理解的字典或关联数组形式。
在 URI 里只能使用 ASCII 码,对于 ASCII 码以外的字符集和特殊字符做一个特殊的操作,把它们转换成与 URI 语义不冲突的形式。这在 RFC 规范里称为“escape”和“unescape”,俗称“转义”。URI 转义的规则有点“简单粗暴”,直接把非 ASCII 码或特殊字符转换成十六进制字节值,然后前面再加上一个“%”。
(四)状态码
在HTTP报文部分我们说了HTTP的状态行,我们在这个部分就来看看状态行中的状态码。
状态码是一个十进制的数字,RFC 标准把状态码分成了五类,用数字的第一位表示分类,而 0 ~ 99 不用,这样状态码的实际可用范围就变成了 100~599。这五类的具体含义是:
1××:提示信息,表示目前是协议处理的中间状态,还需要后续的操作。
2××:成功,报文已经收到并被正确处理。
3××:重定向,资源位置发生变动,需要客户端重新发送请求。
4××:客户端错误,请求报文有误,服务器无法处理。
5××:服务器错误,服务器在处理请求时内部发生了错误。
1××
1×× 类状态码属于提示信息,是协议处理的中间状态,实际能够用到的时候很少。
2××
2×× 类状态码表示服务器收到并成功处理了客户端的请求,这也是客户端最愿意看到的状态码。
“200 OK”是最常见的成功状态码,表示一切正常,服务器如客户端所期望的那样返回了处理结果,如果是非 HEAD 请求,通常在响应头后都会有 body 数据。
“204 No Content”是另一个很常见的成功状态码,它的含义与“200 OK”基本相同,但响应头后没有 body 数据。所以对于 Web 服务器来说,正确地区分 200 和 204 是很必要的。
“206 Partial Content”是 HTTP 分块下载或断点续传的基础,在客户端发送“范围请求”、要求获取资源的部分数据时出现,它与 200 一样,也是服务器成功处理了请求,但 body 里的数据不是资源的全部,而是其中的一部分。状态码 206 通常还会伴随着头字段Content-Range,表示响应报文里 body 数据的具体范围,供客户端确认,例如“Content-Range: bytes 0-99/2000”,意思是此次获取的是总计 2000 个字节的前 100 个字节。
3××
3××类状态码表示客户端请求的资源发生了变动,客户端必须用新的 URI 重新发送请求获取资源,也就是通常所说的“重定向”,包括著名的 301、302 跳转。
“301 Moved Permanently” 俗称“永久重定向”,含义是此次请求的资源已经不存在了,需要改用改用新的 URI 再次访问。
“302 Found”,曾经的描述短语是“Moved Temporarily”,俗称“临时重定向”,意思是请求的资源还在,但需要暂时用另一个 URI 来访问。
“304 Not Modified” 是一个比较有意思的状态码,它用于 If-Modified-Since 等条件请求,表示资源未修改,用于缓存控制。它不具有通常的跳转含义,但可以理解成“重定向已到缓存的文件”(即“缓存重定向”)。
4××
4××类状态码表示客户端发送的请求报文有误,服务器无法处理,它就是真正的“错误码”含义了。
“400 Bad Request” 是一个通用的错误码,表示请求报文有错误,只是一个笼统的错误,没有明确含义的状态码。
“403 Forbidden” 实际上不是客户端的请求出错,而是表示服务器禁止访问资源。
“404 Not Found” 原意是资源在本服务器上未找到,所以无法提供给客户端。但现在已经被“用滥了”,只要服务器“不高兴”就可以给出个 404,而我们也无从得知后面到底是真的未找到,还是有什么别的原因,某种程度上它比 403 还要令人讨厌。
5××
5××类状态码表示客户端请求报文正确,但服务器在处理时内部发生了错误,无法返回应有的响应数据,是服务器端的“错误码”。
“500 Internal Server Error” 与 400 类似,也是一个通用的错误码,服务器究竟发生了什么错误我们是不知道的。不过对于服务器来说这应该算是好事,通常不应该把服务器内部的详细信息,例如出错的函数调用栈告诉外界。虽然不利于调试,但能够防止黑客的窥探或者分析。
“501 Not Implemented” 表示客户端请求的功能还不支持,这个错误码比 500 要“温和”一些,和“即将开业,敬请期待”的意思差不多,不过具体什么时候“开业”就不好说了。
“502 Bad Gateway” 通常是服务器作为网关或者代理时返回的错误码,表示服务器自身工作正常,访问后端服务器时发生了错误,但具体的错误原因也是不知道的。
“503 Service Unavailable” 表示服务器当前很忙,暂时无法响应服务,我们上网时有时候遇到的“网络服务正忙,请稍后重试”的提示信息就是状态码 503。503 是一个“临时”的状态,很可能过几秒钟后服务器就不那么忙了,可以继续提供服务,所以 503 响应报文里通常还会有一个“Retry-After”字段,指示客户端可以在多久以后再次尝试发送请求。
(五)HTTP的特点
(六)HTTP的实体数据
数据类型
Accept
在TCP/IP协议栈里,数据的传输都是Header+body的形式。在传输层协议中,不需要关心数据是什么,但在应用层必须要告诉上层数据的类型,否则上层就不知该如何处理。最早的HTTP协议中,并没有附加的数据类型信息,所有传送的数据都被客户程序解释为HTML文档,而为了支持多媒体数据类型,HTTP协议中就使用了附加在文档之前的MIME(Multipurpose Internet Mail Extensions 多用途互联网邮件扩展类型)指定的数据类型信息来标识数据类型。MINE将数据分为七大类(video、image、application、text、audio、multipart、message),再以type/subtype的格式细分出其下的子类。例如我们常用到的text/html 、text/css 、image/jpeg 、 applaction/json等。
Accept-encoding
此外HTTP协议还制定了数据的压缩格式:
Accept-Language
数据类型在请求头中的表现
在 HTTP 协议里用 Accept、Accept-Encoding、Accept-Language 等请求头字段进行内容协商的时候,还可以用一种特殊的“q”参数表示权重来设定优先级,这里的“q”是“quality factor”的意思。权重的最大值是 1,最小值是 0.01,默认值是 1,如果值是 0 就表示拒绝。具体的形式是在数据类型或语言代码后面加一个“;”,然后是“q=value”。服务器会在响应头里多加一个 Vary 字段,记录服务器在内容协商时参考的请求头字段。
(七)HTTP如何传输大文件
在HTTP头部表示为Transfer-Encoding: chunked,指报文里的body部分不是一次性发过来的,而是分为许多chunked分块发送。Transfer-Encoding: chunked和Content-Length这两个字段是互斥的,也就是说响应报文里这两个字段不能同时出现,一个响应报文的传输要么是长度已知,要么是长度未知(chunked),这一点你一定要记住。
如果想获取某个大文件其中的片段,分块传输就没办法满足这样的需求。HTTP协议提出了范围请求这样的概念,允许客户端只获取文件的某一部分。客户端先发个HEAD请求看看服务器是否支持范围请求,服务器必须在Accept-Ranges响应头中告知客户端是否具有范围请求的能力。请求头Ranges是HTTP范围请求的专用字段,值的格式是bytes=x-y表示x ~ y之间的范围。服务端在收到 Ranges请求头时,首先验证x-y的范围是否合法(x和y可以省略,省略x则表示从后往前,省略y则表示从前往后),其次计算读取偏移量,返回206状态码和所读取的文件 ,最后在响应头加上Content-Range表示实际返回的偏移量和总数,格式为bytes x-y/length。
范围请求还支持在一个头里定义多个x-y,这种情况需要一种特殊的MIME类型multipart/byteranges,表示报文是有多段组成。
(八)HTTP连接管理
http的通信过程采取请求/应答模式,在http0.9/1.0时期,每次发起请求都需要建立连接->发送数据->断开连接,由于整个请求的过程非常短暂,早起的http也称为短链接无链接的协议。由于TCP简历连接要经过三次握手四次挥手,整个过程需要3个RTT,而HTTP的一次简单请求通常只需要2个RTT,那么被浪费掉的时间有60%。
Connection:keep-alive
HTTP1.1提出了长连接的概念,也就是Keep-alive。在长连接上建立一次TCP连接可以发送多个HTTP请求。但因为连接是alive的,如果一直不关闭,就会占用大量的服务器资源,导致服务无法及时响应真正的请求,所以我们也需要及时关闭连接。可以通过在客户端请求头添加Connection: close字段主动关闭连接。服务端通常不会主动关闭连接,但我们也可以通过设置时长、请求数等方式约定断开连接的条件。
队头阻塞
基于请求-应答模式的http协议,形成了串行的请求队列(http1.1还提出了管道机制,即在同一个TCP连接上不用等待上一个请求的响应即可发出下个请求,不过客户端还是按照正常顺序接受响应,这种做法并没带来任何性能上的改善,所以默认保持关闭),如果队首的请求处于阻塞状态,那么后面的请求也无法正常响应结果就是更长时间的性能浪费。
并发连接和域名分片是对队头阻塞的针对性优化策略,浏览器限制每个客户端可以并发建立6~8个连接,又可以将多个域名指向同一个服务器,这样实际的连接数量就更多了,是一种用数量解决质量的思路。
重定向
状态码
在前面了解过HTTP状态码,3XX即表示为重定向。下面详细介绍下各个状态码的含义。
301指永久重定向,可能是域名下线,域名迁移等原因,原地址不再维护。此时浏览器在重定向的同时记录重定向后的地址,下次访问该域名就自动访问新的URI了。
302指临时重定向,可能是服务器维护、临时关闭等原因,临时跳转到新的地址上,此时浏览器不会记录重定向的地址,认为原地址还是有效的,下次访问时还是优先访问原地址。
303类似 302,但要求重定向后的请求改为 GET 方法,访问一个结果页面,避免 POST/PUT 重复操作。
307类似 302,但重定向后请求里的方法和实体不允许变动,含义比 302 更明确。
308类似 307,不允许重定向后的请求变动,但它是 301“永久重定向”的含义。
可以在地址栏输入bing.com,浏览器控制台中的状态如下图所示:
客户端是如何处理重定向的
在浏览器地址栏输入bing.con我们可以看到,状态码如下图所示:
我们浏览器收到响应之后根据响应头中的Location字段判断重定向的地址,然后进行被动跳转。
虽然重定向的用途很广,但是随之而来的也有更多问题。
(九)cookie
HTTP 是“无状态”的,这既是优点也是缺点。优点是服务器没有状态差异,可以很容易地组成集群,而缺点就是无法支持需要记录状态的事务操作。好在 HTTP 协议是可扩展的,后来发明的 Cookie 技术,给 HTTP 增加了“记忆能力”。
Cookie 主要用于以下三个方面:
相关属性
生存周期
Expires俗称“过期时间”,用的是绝对时间点,可以理解为“截止日期”(deadline)。
Max-Age用的是相对时间,单位是秒,浏览器用收到报文的时间点再加上 Max-Age,就可以得到失效的绝对时间。
Expires 和 Max-Age 可以同时出现,两者的失效时间不一致时浏览器会优先采用Max-Age计算失效期。如果服务器不设置Max-Age、Expries或者字段值为0指不能缓存cookie,但在会话期间是可用的,浏览器会话关闭之前可以用cookie记录用户的信息。
作用域
Domain和Path指定了 Cookie 所属的域名和路径,浏览器在发送 Cookie 前会从 URI 中提取出 host 和 path 部分,对比 Cookie 的属性。如果不满足条件,就不会在请求头里发送 Cookie。通常 Path 就用一个“/”或者直接省略,表示域名下的任意路径都允许使用 Cookie。
安全性
HttpOnly表示此 Cookie 只能通过浏览器 HTTP 协议传输,禁止其他方式访问。这也是预防“跨站脚本”(XSS)攻击的有效手段。
SameSite可以防范“跨站请求伪造”(XSRF)攻击,SameSite = strict表示禁止cookie在跳转链接时跨域传输。SameSite = lax稍微宽松一点,允许在GET、HEAD等安全请求方式中跨域携带。默认值为none,表示不限制cookie的携带和传输。
Secure表示这个cookie仅能用HTTPS协议加密传输,明文的HTTP协议会禁止发送。但Cookie本身不是加密的,浏览器里还是以明文的形式存在。
服务器的缓存控制
浏览器在访问页面资源时首先会查找缓存数据,如果没有再发送请求,向服务器获取资源;服务器响应请求,返回资源,同时标记资源的有效期;浏览器缓存资源,等待下次重用。这就是客户端缓存。服务器标记资源有效期使用的头字段是Cache-Control,里面的值max-age=xxx就是资源的有效时间(与cookie的max-age不同,这里的max-age时间的计算起点是响应报文的创建时刻)。
此外在响应报文里还可以用其他的值来更精确地指示浏览器应该如何使用缓存:
no-store: 不允许缓存,用于某些变化非常频繁的数据,例如秒杀页面。
no-cache: 可以缓存,但在使用之前必须要去服务器验证是否过期。
must-revalidate: 如果缓存不过期就可以继续使用,但过期了就必须去服务器验证。
客户端的缓存控制
条件请求
浏览器在刷新页面时相当于在请求头中添加了Cache-Control:no-cache,这样在刷新页面时,还是向服务端发送了请求,并没有很好的利用到缓存。所以HTTP协议又定义了一系列“If”开头的“条件请求”字段,专门用来检查验证资源是否过期。
条件请求一共有 5 个头字段,我们最常用的是if-Modified-Since和If-None-Match这两个。需要第一次的响应报文预先提供Last-modified(最后修改时间)和ETag(资源唯一标识),然后第二次请求时就可以带上缓存里的原值,验证资源是否是最新的。如果资源没有变,服务器就回应一个“304 Not Modified”,表示缓存依然有效,浏览器就可以更新一下有效期,然后放心大胆地使用缓存了。
代理缓存
代理服务器
代理服务器就是客户端和服务端之间的中间商,在中间的位置转发上游的请求和下游的响应。代理服务器在计算机领域有非常重要的功能:
缓存代理
HTTP的服务端缓存主要由代理服务器来实现,代理服务器收到源服务器的响应之后将报文转发给客户端的同时也存入自己的cache里,下次再有相同的请求就可以直接发送304或者缓存数据,节省源服务器的成本。
因为代理服务器既是服务端,又是客户端的特性,有一些特殊的cache-control属性:
private: 表示只能客户端缓存,不允许代理服务器上缓存。
punlic:表示完全公开,客户端和代理服务器都可以缓存。
proxy-revalidate:要求代理服务器缓存过期后必须回源验证。
s-maxage: 代理服务器缓存的有效期
no-transform: 不允许代理服务器转换数据格式。
2.客户端
max-stale: 如果代理上的缓存过期了也可以接受,但不能过期太多,超过 x 秒也会不要。
min-flash: 表示缓存少于x有效期就不要了。
only-if-cached:表示只接受代理缓存的数据,不接受源服务器的响应。如果代理上没有缓存或者缓存过期,就应该给客户端返回一个 504。
HTTPS实际上就指的是HTTP over TLS/SSl。是在原本的HTTP协议上加了一层TLS/SSL协议。
(一)SSL/TLS
SSL 即安全套接层(Secure Sockets Layer),在 OSI 模型中处于第 5 层(会话层),由网景公司于 1994 年发明。SSL 发展到 v3 时已经证明了它自身是一个非常好的安全通信协议,于是在 1999 年它改名为 TLS(传输层安全, Transport Layer Security),目前应用的最广泛的 TLS 是 1.2,而之前的协议(TLS1.1/1.0、SSLv3/v2)都已经被认为是不安全的。
机密性(基于TLS1.2)
SSL/TLS通过加密(encrypt)来传输密文(cipher text)保证数据传输的安全性,只有拥有密钥(key)的人才能够通过解密(decrypt)获得明文(plain text/clear text),加密解密的操作过程就是加密算法。所以“密钥”是一长串的数字,约定俗成的度量单位是“位”(bit)。比如,说密钥长度是 128,就是 16 字节的二进制串,密钥长度 1024,就是 128 字节的二进制串。按照密钥的使用方式,加密可以分为两大类:对称加密和非对称加密。
对称加密
顾名思义,加密解密都使用相同的密钥就叫做对称加密。TLS里目前常用的有 AES 和 ChaCha20。
AES 的意思是“高级加密标准”(Advanced Encryption Standard),密钥长度可以是 128、192 或 256。它是 DES 算法的替代者,安全强度很高,性能也很好,而且有的硬件还会做特殊优化,所以非常流行,是应用最广泛的对称加密算法。
ChaCha20 是 Google 设计的另一种加密算法,密钥长度固定为 256 位,纯软件运行性能要超过 AES,曾经在移动客户端上比较流行,但 ARMv8 之后也加入了 AES 硬件优化,所以现在不再具有明显的优势。
非对称加密
对称加密看上去很好的实现了机密性,但是还有一个问题就是如何安全的传输密钥。因为在加密算法中,只要拥有密钥就可以解密,如果密钥在传输过程中被窃取,也就无机密性可言。为了解决这个问题,又有了非对称加密算法。他拥有两个密钥,分别是公钥(public key)和私钥(private key),公钥是公开的,而私钥是严格保密的。公钥和私钥有个特别的“单向”性,虽然都可以用来加密解密,但公钥加密后只能用私钥解密,反过来,私钥加密后也只能用公钥解密。非对称加密可以解决密钥交换的问题。网站秘密保管私钥,在网上任意分发公钥,你想要登录网站只要用公钥加密就行了,密文只能由私钥持有者才能解密。而黑客因为没有私钥,所以就无法破解密文。
非对称加密算法的设计要比对称算法难得多,在 TLS 里只有很少的几种,比如 DH、DSA、RSA、ECC 等。
RSA 可能是其中最著名的一个,几乎可以说是非对称加密的代名词,它的安全性基于“整数分解”的数学难题,使用两个超大素数的乘积作为生成密钥的材料,想要从公钥推算出私钥是非常困难的。
ECC是非对称加密里的“后起之秀”,它基于“椭圆曲线离散对数”的数学难题,使用特定的曲线方程和基点生成公钥和私钥,子算法 ECDHE 用于密钥交换,ECDSA 用于数字签名。相对RSA,ECC在安全和性能上都有更明显的优势,160位的ECC相当于1024位的RSA,260位的ECC相当于2048位的RSA。
混合加密
虽然非对称加密没有密钥交换的难题,但因为它们都是基于复杂的数学难题,运算速度很慢,即使是 ECC 也要比 AES 差上好几个数量级。所以目前TLS使用混合加密,使二者取长补短,既能高效加密解密,又能安全的进行数据传输。
在建立连接之初先使用非对称加密的形式传递密钥,然后用随机数产生对称算法使用的“会话密钥”(session key),再用公钥加密。因为会话密钥很短,通常只有 16 字节或 32 字节,所以慢一点也无所谓。对方拿到密文后用私钥解密,取出会话密钥。这样,双方就实现了对称密钥的安全交换,后续就不再使用非对称加密,全都使用对称加密。
完整性
摘要算法
实现完整性的手段主要是摘要算法(Digest Algorithm),也就是常说的散列函数、哈希函数(Hash Function)。可以把摘要算法近似地理解成一种特殊的加密算法,它能够把任意长度的数据加密成固定长度、而且独一无二的“摘要”字符串,且不能从压缩后的密文中推导出原文。MD5(Message-Digest 5)、SHA-1(Secure Hash Algorithm 1就是最常用的两个摘要算法,能够生成 16 字节和 20 字节长度的数字摘要。但这两个算法的安全强度比较低,不够安全,在 TLS 里已经被禁止使用了。目前TLS使用的是SLA-2。摘要算法保证了“数字摘要”和原文是完全等价的。所以,我们只要在原文后附上它的摘要,就能够保证数据的完整性。不过摘要算法不具有机密性,所以真正的完整性还是需要建立在机密性之上。
身份认证&不可否认
数字签名
数字签名的原理其实很简单,就是把公钥私钥的用法反过来,之前是公钥加密、私钥解密,现在是私钥加密、公钥解密。但又因为非对称加密效率太低,所以私钥只加密原文的摘要,这样运算量就小的多,而且得到的数字签名也很小,方便保管和传输。
数字证书和CA
因为公钥是任何人都可以发布的,所以我们需要引入第三方来保证公钥的可信度,这个“第三方”就是我们常说的 CA(Certificate Authority,证书认证机构),CA 对公钥的签名认证也是有格式的,要包含公钥的序列号、用途、颁发者、有效时间等等,把这些打成一个包再签名,完整地证明公钥关联的各种信息,形成“数字证书”(Certificate)。小一点的 CA 可以让大 CA 签名认证,但链条的最后,也就是 Root CA,就只能自己证明自己了,这个就叫“自签名证书”(Self-Signed Certificate)或者“根证书”(Root Certificate)。你必须相信,否则整个证书信任链就走不下去了。
(二)TLS1.2建立连接的过程
TLS协议的组成
记录协议(Record Protocol): 规定了 TLS 收发数据的基本单位:记录(record)。所有的其他子协议都需要通过记录协议发出,但多个记录数据可以在一个 TCP 包里一次性发出。
警报协议(Alert Protocol): 的职责是向对方发出警报信息,有点像是 HTTP 协议里的状态码。比如,protocol_version 就是不支持旧版本,bad_certificate 就是证书有问题,收到警报后另一方可以选择继续,也可以立即终止连接。
握手协议(Handshake Protocol): 是 TLS 里最复杂的子协议,要比 TCP 的 SYN/ACK 复杂的多,浏览器和服务器会在握手过程中协商 TLS 版本号、随机数、密码套件等信息,然后交换证书和密钥参数,最终双方协商得到会话密钥,用于后续的混合加密系统。
变更密码规范协议(Change Cipher Spec Protocol): 是一个“通知”,告诉对方,后续的数据都将使用加密保护。那么反过来,在它之前,数据都是明文的。
基于ECDHE 的 TLS1.2 握手
(三)TLS1.3
HTTP/2
HTTP1.X引入了Cookie解决了无状态的问题、通过引入TLS/SSL解决了明文传输不安全的问题。那接下来HTTP2的发力点就放在性能层面了。Google首先发明了SPDY协议,随后互联网标准化组织IETF以SPDY 为基础发布了HTTP2。HTTP2对于性能上的优化主要由以下几点出发:1. 包头过大 2. 队头阻塞 。
(一)性能优化
包头过大(头部压缩)
在HTTP1.x时期,很多请求请求体和响应体的大小远远小于头部字段的大小,比如GET请求,301/302/204响应。而且很多头部字段是重复的,HTTP/1.x浪费了大量的带宽在传输重复的头字段上,所以,HTTP/2 把“头部压缩”作为性能改进的一个重点。
HPACK算法是专门为压缩 HTTP 头部定制的算法,与 gzip、zlib 等压缩算法不同,它是一个“有状态”的算法,需要客户端和服务器各自维护一份“索引表”,压缩和解压缩就是查表和更新表的操作。为了方便管理和压缩,HTTP/2 废除了原有的起始行概念,把起始行里面的请求方法、URI、状态码等统一转换成了头字段的形式, 为了与“真头字段”区分开来,这些“伪头字段”会在名字前加一个“:”,比如“:authority” “:method” “:status”,分别表示的是域名、请求方法和状态码。废除了起始行里的版本号和错误原因短语。用索引号表示重复的字符串,还釆用哈夫曼编码来压缩整数和字符串,可以达到 50%~90% 的高压缩率。
队头阻塞(二进制分帧、流式传输)
二进制分帧
HTTP/2把原来的Header+Body的消息“打散”为数个小片的二进制“帧”(Frame),用HEADER帧存放头数据、DATA帧存放实体数据。
流式传输
HTTP/2还定义了一个“流”(Stream)的概念,它是二进制帧的双向传输序列,同一个消息往返的帧会分配一个唯一的流 ID。你可以把它想象成是一个虚拟的“数据流”,在里面流动的是一串有先后顺序的数据帧,这些数据帧按照次序组装起来就是HTTP/1里的请求报文和响应报文。HTTP/2 可以在一个 TCP 连接上用“流”同时发送多个“碎片化”的消息,这就是常说的“多路复用”( Multiplexing),多个往返通信都复用一个连接来处理。在“流”的层面上看,消息是一些有序的“帧”序列,而在“连接”的层面上看,消息却是乱序收发的“帧”。多个请求 / 响应之间没有了顺序关系,不需要排队等待,也就不会再出现“队头阻塞”问题,降低了延迟,大幅度提高了连接的利用率。
帧开头是帧长度(不包含报文头的9个字节),默认上限是2^14,最大是2^24,也就是说 HTTP/2的帧通常不超过16K,最大是 16M。
后面的一个字节是帧类型,大致可以分成数据帧和控制帧两类,HEADERS帧和DATA帧属于数据帧,存放的是 HTTP 报文,而 SETTINGS、PING、PRIORITY 等则是用来管理流的控制帧。
第 5 个字节是非常重要的帧标志信息,可以保存 8 个标志位,携带简单的控制信息。常用的标志位有 END_HEADERS 表示头数据结束,END_STREAM 表示单方向数据发送结束(即 EOS,End of Stream)。
报文头里最后 4 个字节是流标识符,也就是帧所属的“流”,接收方使用它就可以从乱序的帧里识别出具有相同流 ID 的帧序列(在 HTTP/2 连接上,虽然帧是乱序收发的,但只要它们都拥有相同的流 ID,就都属于一个流,而且在这个流里帧不是无序的,而是有着严格的先后顺序。),按顺序组装起来就实现了虚拟的“流”。流标识符虽然有 4 个字节,但最高位被保留不用,所以只有 31 位可以使用,也就是说,流标识符的上限是 2^31,大约是 21 亿。
流的特点
协议栈
HTTP/3
QUIC 基于 UDP,而 UDP 是“无连接”的,不需要“握手”和“挥手”,所以天生就要比 TCP 快。QUIC 全面采用加密通信,它使用自己的帧“接管”了 TLS 里的“记录”,握手消息、警报消息都不使用 TLS 记录,直接封装成 QUIC 的帧发送,省掉了一次开销。QUIC 的基本数据传输单位是包(packet)和帧(frame),一个包由多个帧组成,包面向的是“连接”,帧面向的是“流”。
QUIC 使用不透明的“连接 ID”来标记通信的两个端点,客户端和服务器可以自行选择一组 ID 来标记自己,这样就解除了 TCP 里连接对“IP 地址 + 端口”(即常说的四元组)的强绑定,支持“连接迁移”(Connection Migration)。
(二)HTTP/3
推荐阅读
点击下方空白 ▼ 查看明日开发者黄历