http 是什么?
HyperTextTransferProtocol 直译为 “超文本传输协议”。
1. 超文本:指文字、图片、视频、音频等的混合体,比如最熟悉的 html。
2. 传输:http 是一个 “双向协议”,传输的是请求方和响应方之间的数据,不限制请求方和响应方之间的角色,传递的过程中可以存在任意 “中间人”。
3. 协议:协是两个或多个参与者之间的交流,议是指对参与者之间的约定和规范。所以,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 协议中请求方和应答方中间的一个环节,作为 “中转站”,既可以转发客户端的请求,也可以转发服务器的应答。
代理有很多的种类,常见的有:1. 匿名代理:完全 “隐匿” 了被代理的机器,外界看到的只是代理服务器;
2. 透明代理:顾名思义,它在传输过程中是 “透明开放” 的,外界既知道代理,也知道客户端;
3. 正向代理:靠近客户端,代表客户端向服务器发送请求;
4. 反向代理:靠近服务器端,代表服务器响应客户端的请求;
网络的分层模型
网络分层模型层级是从下往上数的,一般我们比较常接触到的是 TCP/IP 四层模型,也是比较早出现的分层模型。
第一层是链路层 (link layer), 负责在底层网络上发送原始数据包,工作在网卡这个层次,使用 MAC 地址来标记网络上的设备,所以有时候也叫 MAC 层。对应的是 ISO 模型的 "数据链路层"。
第二层叫网络层 (internet layer),IP 协议就处在这一层。因为 IP 协议定义了 "IP 地址" 的概念,所以就可以在 "链路层" 的基础上,用 IP 地址取代 MAC 地址,在这个网络里找设备时只要把 IP 地址再翻译成 MAC 地址就可以了。对应的是 ISO 模型的 "网络层"。
第三层叫 "传输层"(transport layer),这个层次协议的职责是保证数据在 IP 地址标记的两点之间可靠地传输,是 TCP 协议和 UDP 协议工作的层次。对应的是 ISO 模型的 "传输层"。
第四层叫 "应用层"(application layer), 由于下面的三层把基础打得非常好,所以在这一层就 "百花齐放" 了,有各种面向具体应用的协议。例如 Telnet、SSH、FTP、SMTP 等等,当然还有我们的 HTTP。 对应的是 ISO 模型的 "会话层","表示层","应用层"。
利用 TCP/IP 协议族进行网络通信时,会通过分层顺序与对方进行通信(发送端从应用层往下走,接收端从应用层往上走)。
域名
域名是一个有层次的结构,是一串用 “.” 分隔的多个单词,最右边的被称为 “顶级域名”,然后是 “二级域名”,层级关系向左依次降低。最左边的是主机名,通常用来表明主机的用途,比如 “www” 表示提供万维网服务、“mail” 表示提供邮件服务,不过这也不是绝对的。可以通过下面的例子了解一下协议 主机 域名之间的层次关系。域名就像人的名字一样,名字的关键是要让我们容易记忆。除了标识身份之外,域名还可以代替 ip 地址。
DNS
我们经常会使用域名访问网站,但其实在网络查找的工程当中是使用 ip 定位资源的,域名必须解析为 ip 地址才可以正确的拿到资源。DNS 就是用来将域名变为 ip 的协议。
DNS 的核心系统是一个三层的树状、分布式服务,基本对应域名的结构:
1. 根域名服务器(Root DNS Server):管理顶级域名服务器,返回 "com","net","cn" 等顶级域名服务器的 IP 地址。
2. 顶级域名服务器(Top-level DNS Server):管理各自域名下的权威域名服务器,比如 cn 顶级域名服务器可以返回 123.cn 域名服务器的 IP 地址。
3. 权威域名服务器(Authoritative DNS Server):管理自己域名下主机的 IP 地址,比如 123.cn 权威域名服务器可以返回 www.123.cn 的 IP 地址。
虽然 DNS 的服务,遍布全球,服务能力也很厉害,但是全世界的网民都在使用这个服务,也会对服务器造成很大的压力。在核心 DNS 系统之外,还有两种手段用来减轻域名解析的压力,并且能够更快地获取结果,基本思路就是 “缓存”。
DNS 的解析结果可以保存在大公司自己的 DNS 服务器里,或者操作系统缓存、hosts 文件当中,很多域名解析的工作就都不用请求根 DNS 服务器了,直接在本地或本机就能解决,不仅方便了用户,也减轻了各级 DNS 服务器的压力,效率就大大提升了。
基于域名和 DNS 服务器,我们可以实现重定向。因为域名代替了 ip 地址,所以可以对外域名不变,而主机 IP 可以任意变动。当主机有情况需要下线、迁移时,可以更改 DNS 记录,让域名指向其他的机器。
我们应该都听说过负载均衡吧,DNS 在域名解析阶段就可以进行负载均衡的操作。
第一种方式,因为域名解析可以返回多个 IP 地址,所以一个域名可以对应多台主机,客户端收到多个 IP 地址后,就可以自己使用轮询算法依次向服务器发起请求,实现负载均衡。
第二种方式,域名解析可以配置内部的策略,返回离客户端最近的主机,或者返回当前服务质量最好的主机,这样在 DNS 端把请求分发到不同的服务器,实现负载均衡。
HTTP/1.X
前面我们说了 HTTP 就是 “超文本传输协议”,是一个在计算机世界里专门在两点之间传递文字、图片、音频、视频等超文本数据的约定和规范。在学习过网络的层次模型之后我们又了解了 HTTP 是一个应用层的协议。在这个环节我们开始正式深入 HTTP 的世界(基于 http/1.1)。
(一)HTTP 报文 HTTP 协议的请求报文和响应报文的结构基本相同,由三大部分组成:
1. 起始行(start line):描述请求或响应的基本信息;
2. 头部字段集合(header):使用 key-value 形式更详细地说明报文;
3. 消息正文(entity):实际传输的数据,它不一定是纯文本,可以是图片、视频等二进制数据。
- 请求行
- 状态行
- 头部字段
1. 字段名不区分大小写,例如 “Host” 也可以写成 “host”,但首字母大写的可读性更好。
2. 字段名里不允许出现空格,可以使用连字符 “-”,但不能使用下划线 “_”。例如,“test-name” 是合法的字段名,而 “test name”“test_name” 是不正确的字段名。
3. 字段名后面必须紧接着 “:”,不能有空格,而 “:” 后的字段值前可以有多个空格。
4. 字段的顺序是没有意义的,可以任意排列不影响语义。
5. 字段原则上不能重复,除非这个字段本身的语义允许,例如 Set-Cookie。
(二)HTTP 请求方法 目前 HTTP/1.1 规定了八种方法,单词都必须是大写的形式,下面就来看看这些方法:
1.GET:获取资源,可以理解为读取或者下载数据。
2.HEAD:获取资源的元信息。
3.POST:向资源提交数据,相当于写入或上传数据。
4.PUT:类似 POST。
5.DELETE:删除资源。
6.CONNECT:建立特殊的连接隧道。
7.OPTIONS:列出可对资源实行的方法。
8.TRACE:追踪请求 - 响应的传输路径。
这几个是我们比较常用的方法,有必要好好学习一下。
GET 和 HEAD
- GET 适用于向服务器请求资源,一般将数据携带于 url 上。
- HEAD 类似于简化版的 GET 请求,服务端收到 HEAD 请求时只返回响应头并且响应头与 GET 完全一致。
- POST 适用于向服务端发送数据,将数据携带在 body 当中,通常表示的是 “create” 的含义。
- PUT 类似于 POST 方法,也可以向服务器提交数据,是 “update” 的含义。
在这里特别容易被问到的问题是 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 实在是太普及了,所以常常把这两者简单地视为相等。 URI 本质上是一个字符串,这个字符串的作用是唯一地标记资源的位置或者名字。
上面这个图片就是一个完整的 URI,下面详细拆解一下它的结构。
scheme 协议名,表示资源应该使用哪种协议来访问。最常见的当然就是 “http” 了,表示使用 HTTP 协议。另外还有 “https”,表示使用经过加密、安全的 HTTPS 协议。此外还有其他不是很常见的 scheme,例如 ftp、ldap、file、news 等。
:// 分隔符,在 scheme 之后,必须是三个特定的字符 “://”,它把 scheme 和后面的部分分离开。没有特定的意义。
user:passwd@ 身份信息,表示登录主机时的用户名和密码,但现在已经不推荐使用这种形式了,因为它把敏感信息以明文形式暴露出来,存在严重的安全隐患。
host:port 主机名,表示资源所在的主机名,通常的形式是 “host:port”,即主机名加端口号。
path 路径,表示资源所在位置,采用了类似文件系统 “目录” 的表示方式,通常以‘/’开始
query 查询参数,用一个 “?” 开始,但不包含 “?”,表示对资源附加的额外要求。path 是多个 “key=value” 的字符串,这些字符串用字符 “&” 连接,浏览器和服务器都可以按照这个格式把长串的查询参数解析成可理解的字典或关联数组形式。
#fragment 片段标识符,它是 URI 所定位的资源内部的一个 “锚点”,浏览器可以在获取资源后直接跳转到它指示的位置。但片段标识符仅能由浏览器这样的客户端使用,服务器是看不到的。
在 URI 里只能使用 ASCII 码,对于 ASCII 码以外的字符集和特殊字符做一个特殊的操作,把它们转换成与 URI 语义不冲突的形式。这在 RFC 规范里称为 “escape” 和 “unescape”,俗称 “转义”。URI 转义的规则有点 “简单粗暴”,直接把非 ASCII 码或特殊字符转换成十六进制字节值,然后前面再加上一个 “%”。(四)状态码 在 HTTP 报文部分我们说了 HTTP 的状态行,我们在这个部分就来看看状态行中的状态码。 状态码是一个十进制的数字,RFC 标准把状态码分成了五类,用数字的第一位表示分类,而 0 ~ 99 不用,这样状态码的实际可用范围就变成了 100~599。这五类的具体含义是:
- 1××:提示信息,表示目前是协议处理的中间状态,还需要后续的操作。
- 2××:成功,报文已经收到并被正确处理。
- 3××:重定向,资源位置发生变动,需要客户端重新发送请求。
- 4××:客户端错误,请求报文有误,服务器无法处理。
- 5××:服务器错误,服务器在处理请求时内部发生了错误。
1×× 类状态码属于提示信息,是协议处理的中间状态,实际能够用到的时候很少。
"100 Continue" 应该是比较常接触到的,会在 POST 请求发送大文件给服务器时询问服务器是否能够接受时使用,需要带上请求头 Expect: 100-continue。这个过程也就是我们常说的 POST 发送两个 TCP 包给服务器的说法的来源,不过客户端不需要一直等待服务端的回应,在一定时间内没有收到否定的回答还是会将数据主体发送给服务器。
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 的特点 1. 灵活可扩展:HTTP 在诞生之初只规定了报文的基本格式,比如用空格分隔单词,用换行分隔字段,“header+body” 等,报文里的各个组成部分都没有做严格的语法语义限制,可以由开发者任意定制。而那些 RFC 文档,实际上也可以理解为是对已有扩展的 “承认和标准化”,实现了 “从实践中来,到实践中去” 的良性循环。
2. 可靠传输:因为 HTTP 协议是基于 TCP/IP 的,而 TCP 本身是一个 “可靠” 的传输协议,所以 HTTP 自然也就继承了这个特性,能够在请求方和应答方之间 “可靠” 地传输数据。
3. 应用层的协议: HTTP 凭借着可携带任意头字段和实体数据的报文结构,以及连接控制、缓存代理等方便易用的特性,只要不太苛求性能,HTTP 几乎可以传递一切东西,满足各种需求,称得上是一个 “万能” 的协议。
4. 请求 - 应答:请求 - 应答模式是 HTTP 协议最根本的通信模型,通俗来讲就是 “一发一收”。请求 - 应答模式也明确了 HTTP 协议里通信双方的定位,永远是请求方先发起连接和请求,是主动的,而应答方只有在收到请求后才能答复,是被动的,如果没有请求时不会有任何动作。
5. 无状态: “状态” 其实就是客户端或者服务器里保存的一些数据或者标志,记录了通信过程中的一些变化信息。HTTP 在整个协议里没有规定任何的 “状态”,但不要忘了 HTTP 是 “灵活可扩展” 的,虽然标准里没有规定 “状态”,但完全能够在协议的框架里给它 “打个补丁”,增加这个特性 (cookie)。
6. 明文传输: “明文” 意思就是协议里的报文(准确地说是 header 部分)不使用二进制数据,而是用简单可阅读的文本形式。
7. 不安全:安全有很多的方面,明文只是 “机密” 方面的一个缺点,在 “身份认证” 和 “完整性校验” 这两方面 HTTP 也是欠缺的。
(六)HTTP 的实体数据
- 数据类型
gzip:GNU zip 压缩格式,也是互联网上最流行的压缩格式;
deflate:zlib(deflate)压缩格式,流行程度仅次于 gzip;
br:一种专门为 HTTP 优化的新压缩算法(Brotli)。
Accept-Language
标记了客户端可理解的自然语言,也允许用 “,” 做分隔符列出多个类型,例如:Accept-Language: zh-CN, zh, en
- 数据类型在请求头中的表现
(七)HTTP 如何传输大文件
1. 数据压缩
前面提到的 accept-encoding 请求头可以算是是一种传输大文件的解决方式,服务器可以选择一种浏览器支持的数据压缩方式放进 content-encoding 响应头里,再把原数据压缩后返回给客户端。缺点是这种方式只对文本有较好地压缩率,对于图片音频等本身就已经高度压缩的多媒体数据束手无策。
2. 分块传输
在 HTTP 头部表示为 Transfer-Encoding: chunked, 指报文里的 body 部分不是一次性发过来的,而是分为许多 chunked 分块发送。Transfer-Encoding: chunked 和 Content-Length 这两个字段是互斥的,也就是说响应报文里这两个字段不能同时出现,一个响应报文的传输要么是长度已知,要么是长度未知(chunked),这一点你一定要记住。
3. 范围请求
如果想获取某个大文件其中的片段,分块传输就没办法满足这样的需求。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
- 队头阻塞
并发连接和域名分片是对队头阻塞的针对性优化策略,浏览器限制每个客户端可以并发建立 6~8 个连接,又可以将多个域名指向同一个服务器,这样实际的连接数量就更多了,是一种用数量解决质量的思路。
- 重定向
状态码 在前面了解过 HTTP 状态码,3XX 即表示为重定向。下面详细介绍下各个状态码的含义。
301 指永久重定向,可能是域名下线,域名迁移等原因,原地址不再维护。此时浏览器在重定向的同时记录重定向后的地址,下次访问该域名就自动访问新的 URI 了。
302 指临时重定向,可能是服务器维护、临时关闭等原因,临时跳转到新的地址上,此时浏览器不会记录重定向的地址,认为原地址还是有效的,下次访问时还是优先访问原地址。
303 类似 302,但要求重定向后的请求改为 GET 方法,访问一个结果页面,避免 POST/PUT 重复操作。
307 类似 302,但重定向后请求里的方法和实体不允许变动,含义比 302 更明确。
308 类似 307,不允许重定向后的请求变动,但它是 301 “永久重定向” 的含义。
可以在地址栏输入 bing.com, 浏览器控制台中的状态如下图所示:
客户端是如何处理重定向的 在浏览器地址栏输入 bing.con 我们可以看到,状态码如下图所示: 我们浏览器收到响应之后根据响应头中的 Location 字段判断重定向的地址,然后进行被动跳转。 虽然重定向的用途很广,但是随之而来的也有更多问题。 第一个问题是 “性能损耗”。很明显,重定向的机制决定了一个跳转会有两次请求 - 应答,比正常的访问多了一次。虽然 301/302 报文很小,但大量的跳转对服务器的影响也是不可忽视的。站内重定向可以长连接复用,站外重定向就要开两个连接。
第二的问题是循环重定向,比如 A->B->C->A, 当我们访问 A 时就会发生无限跳转。所以 HTTP 协议特别规定,浏览器必须具有检测 “循环跳转” 的能力,在发现这种情况时应当停止发送请求并给出错误提示。
(九)cookie HTTP 是 “无状态” 的,这既是优点也是缺点。优点是服务器没有状态差异,可以很容易地组成集群,而缺点就是无法支持需要记录状态的事务操作。好在 HTTP 协议是可扩展的,后来发明的 Cookie 技术,给 HTTP 增加了 “记忆能力”。
cookie 同样存在于 HTTP 头部字段里。服务端可以使用 set-cookie 标识客户端身份,客户端则在请求时携带 cookie 告诉服务端自己的信息。cookie 字段以 key=value 的格式保存,浏览器在一个 cookie 字段里可以存放多对数据,用;分割。
Cookie 主要用于以下三个方面:
1. 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
2. 个性化设置(如用户自定义设置、主题等)
3. 浏览器行为跟踪(如跟踪分析用户行为等)
- 相关属性
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 本身不是加密的,浏览器里还是以明文的形式存在。
(十)HTTP 缓存控制
- 服务器的缓存控制
此外在响应报文里还可以用其他的值来更精确地指示浏览器应该如何使用缓存:
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”,表示缓存依然有效,浏览器就可以更新一下有效期,然后放心大胆地使用缓存了。
- 代理缓存
2. 健康检查:使用‘心跳’等机制监控服务器,保证服务器的可用性。
3. 安全防护:保护被代理服务端的 IP 和流量,防止网络攻击或负载问题。
4. 加密卸载:对外和对内使用不同的加密策略,节省加密成本
5. 内容缓存:暂存 / 复位服务器的响应。
缓存代理 HTTP 的服务端缓存主要由代理服务器来实现,代理服务器收到源服务器的响应之后将报文转发给客户端的同时也存入自己的 cache 里,下次再有相同的请求就可以直接发送 304 或者缓存数据,节省源服务器的成本。
因为代理服务器既是服务端,又是客户端的特性,有一些特殊的 cache-control 属性:
1. 服务端
private: 表示只能客户端缓存,不允许代理服务器上缓存。
punlic: 表示完全公开,客户端和代理服务器都可以缓存。
proxy-revalidate: 要求代理服务器缓存过期后必须回源验证。
s-maxage: 代理服务器缓存的有效期
no-transform: 不允许代理服务器转换数据格式。
2. 客户端
max-stale: 如果代理上的缓存过期了也可以接受,但不能过期太多,超过 x 秒也会不要。
min-flash: 表示缓存少于 x 有效期就不要了。
only-if-cached: 表示只接受代理缓存的数据,不接受源服务器的响应。如果代理上没有缓存或者缓存过期,就应该给客户端返回一个 504。
HTTPS
由于 HTTP 天生 “明文” 的特点,整个传输过程完全透明,任何人都能够在链路中截获、修改或者伪造请求 / 响应报文,数据不具有可信性。只有具有机密性、完整性、身份认证和不可否认性,我们才认为这个请求是安全的。HTTPS 为 HTTP 增加了以上四个特性。
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)
对称加密 顾名思义,加密解密都使用相同的密钥就叫做对称加密。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 字节,所以慢一点也无所谓。对方拿到密文后用私钥解密,取出会话密钥。这样,双方就实现了对称密钥的安全交换,后续就不再使用非对称加密,全都使用对称加密。
- 完整性
- 身份认证 & 不可否认
(二)TLS1.2 建立连接的过程
- TLS 协议的组成
警报协议(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. 队头阻塞 。 (一)性能优化
- 包头过大 (头部压缩)
HPACK 算法是专门为压缩 HTTP 头部定制的算法,与 gzip、zlib 等压缩算法不同,它是一个 “有状态” 的算法,需要客户端和服务器各自维护一份 “索引表”,压缩和解压缩就是查表和更新表的操作。为了方便管理和压缩,HTTP/2 废除了原有的起始行概念,把起始行里面的请求方法、URI、状态码等统一转换成了头字段的形式,为了与 “真头字段” 区分开来,这些 “伪头字段” 会在名字前加一个 “:”,比如 “:authority” “:method” “:status”,分别表示的是域名、请求方法和状态码。废除了起始行里的版本号和错误原因短语。用索引号表示重复的字符串,还釆用哈夫曼编码来压缩整数和字符串,可以达到 50%~90% 的高压缩率。
下面的这个表格列出了 “静态表” 的一部分,这样只要查表就可以知道字段名和对应的值,比如数字 “2” 代表 “GET”,数字 “8” 代表状态码 200。
新增的头字段或者值保存在动态表(Dynamic Table)里,它添加在静态表后面,结构相同,但会在编码解码的时候随时更新。比如说,第一次发送请求时的 “user-agent” 字段长是一百多个字节,用哈夫曼压缩编码发送之后,客户端和服务器都更新自己的动态表,添加一个新的索引号 “65”。那么下一次发送的时候就不用再重复发那么多字节了,只要用一个字节发送编号就好。
- 队头阻塞 (二进制分帧、流式传输)
二进制分帧 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 亿。
流的特点 1. 流是可并发的,一个 HTTP/2 连接上可以同时发出多个流传输数据,也就是并发多请求,实现 “多路复用”。
2. 客户端和服务器都可以创建流,双方互不干扰。
3. 流是双向的,一个流里面客户端和服务器都可以发送或接收数据帧,也就是一个 “请求 - 应答” 来回。
4. 流之间没有固定关系,彼此独立,但流内部的帧是有严格顺序的。
5. 流可以设置优先级,让服务器优先处理,比如先传 HTML/CSS,后传图片,优化用户体验。
6. 流 ID 不能重用,只能顺序递增,客户端发起的 ID 是奇数,服务器端发起的 ID 是偶数。
7. 在流上发送 “RST_STREAM” 帧可以随时终止流,取消接收或发送。
8. 第 0 号流比较特殊,不能关闭,也不能发送数据帧,只能发送控制帧,用于流量控制。
- 协议栈
HTTP/3
HTTP/2 虽然使用 “帧”、“流”、“多路复用”,没有了 “队头阻塞”,但这些手段都是在应用层里,而在 TCP 协议里,还是会发生 “队头阻塞”。Google 在推 SPDY 的时候就已经意识到了这个问题,于是就又发明了一个新的 QUIC 协议,让 HTTP 跑在 QUIC 上而不是 TCP 上。而这个 HTTP over QUIC 就是 HTTP 协议的下一个大版本,HTTP/3。它在 HTTP/2 的基础上又实现了质的飞跃,真正完美地解决了队头阻塞问题。
(一)QUICK
QUIC 基于 UDP,而 UDP 是 “无连接” 的,不需要 “握手” 和 “挥手”,所以天生就要比 TCP 快。QUIC 全面采用加密通信,它使用自己的帧 “接管” 了 TLS 里的 “记录”,握手消息、警报消息都不使用 TLS 记录,直接封装成 QUIC 的帧发送,省掉了一次开销。QUIC 的基本数据传输单位是包(packet)和帧(frame),一个包由多个帧组成,包面向的是 “连接”,帧面向的是 “流”。
QUIC 使用不透明的 “连接 ID” 来标记通信的两个端点,客户端和服务器可以自行选择一组 ID 来标记自己,这样就解除了 TCP 里连接对 “IP 地址 + 端口”(即常说的四元组)的强绑定,支持 “连接迁移”(Connection Migration)。
(二)HTTP/3
因为 QUIC 本身就已经支持了加密、流和多路复用,所以 HTTP/3 不需要定义流,而是直接使用 QUIC 的流。由于流管理被 “下放” 到了 QUIC,所以 HTTP/3 里帧的结构也变简单了。帧头只有两个字段:类型和长度,而且同样都采用变长编码,最小只需要两个字节。
本文转载自:https://mp.weixin.qq.com/s/pDEkcYN31jNg7LuFXUwn4w