# HTTP 的诞生
# 创世纪
1989 年,任职于欧洲核子研究中心(CERN)的蒂姆·伯纳斯 - 李(Tim Berners-Lee)发表了一篇论文,提出了在互联网上构建超链接文档系统的构想。这篇论文中他确立了三项关键技术。
- URI:即统一资源标识符,作为互联网上资源的唯一身份;
- HTML:即超文本标记语言,描述超文本文档;
- HTTP:即超文本传输协议,用来传输超文本。
这三项技术在如今的我们看来已经是稀松平常,但在当时却是了不得的大发明。基于它们,就可以把超文本系统完美地运行在互联网上,让各地的人们能够自由地共享信息,蒂姆把这个系统称为“万维网”(World Wide Web),也就是我们现在所熟知的 Web。
# HTTP/0.9
20 世纪 90 年代初期的互联网世界非常简陋,计算机处理能力低,存储容量小,网速很慢,还是一片“信息荒漠”。网络上绝大多数的资源都是纯文本,很多通信协议也都使用纯文本,所以 HTTP 的设计也不可避免地受到了时代的限制。 这一时期的 HTTP 被定义为 0.9 版,结构比较简单,为了便于服务器和客户端处理,它也采用了纯文本格式。蒂姆·伯纳斯 - 李最初设想的系统里的文档都是只读的,所以只允许用“GET”动作从服务器上获取 HTML 文档,并且在响应请求之后立即关闭连接,功能非 常有限。 HTTP/0.9 虽然很简单,但它作为一个“原型”,充分验证了 Web 服务的可行性,而“简单”也正是它的优点,蕴含了进化和扩展的可能性,因为:把简单的系统变复杂,要比把复杂的系统变简单容易得多。
# HTTP/1.0
HTTP/1.0 版本在 1996 年正式发布。 它在多方面增强了 0.9 版,形式上已经和我们现在的 HTTP 差别不大了,例如:
- 增加了 HEAD、POST 等新方法;
- 增加了响应状态码,标记可能的错误原因;
- 引入了协议版本号概念;
- 引入了 HTTP Header(头部)的概念,让 HTTP 处理请求和响应更加灵活;
- 传输的数据不再仅限于文本。
但 HTTP/1.0 并不是一个“标准”,只是记录已有实践和模式的一份参考文档,不具有实际的约束力,相当于一个“备忘录”。 所以 HTTP/1.0 的发布对于当时正在蓬勃发展的互联网来说并没有太大的实际意义,各方势力仍然按照自己的意图继续在市场上奋力拼杀。
# HTTP/1.1
1999 年,HTTP/1.1 发布了 RFC 文档,编号为 2616,正式确立了延续十余年的传奇。 HTTP/1.1是一个“正式的标准”,而不是一份可有可无的“参考文档”。这意味着今后互联网上所有的浏览器、服务器、网关、代理等等,只要用到 HTTP 协议,就必须严格遵守这个标准,相当于是互联网世界的一个“立法”。 HTTP/1.1 主要的变更点有:
- 增加了 PUT、DELETE 等新的方法;
- 增加了缓存管理和控制;
- 明确了连接管理,允许持久连接;
- 允许响应数据分块(chunked),利于传输大文件;
- 强制要求 Host 头,让互联网主机托管成为可能。
由于 HTTP/1.1 太过庞大和复杂,所以在 2014 年又做了一次修订,原来的一个大文档被拆分成了六份较小的文档,编号为 7230-7235,优化了一些细节,但此外没有任何实质性的改动。
# HTTP/2
HTTP/1.1 发布之后,整个互联网世界呈现出了爆发式的增长,慢慢发现它的一些缺陷,主要就是连接慢,无法跟上迅猛发展的互联网。
2015 年发布了 HTTP/2, RFC 编号 7540。
HTTP/2 的制定充分考虑了现今互联网的现状:宽带、移动、不安全,在高度兼容HTTP/1.1 的同时在性能改善方面做了很大努力,主要的特点有:
- 二进制协议,不再是纯文本;
- 可发起多个请求,废弃了 1.1 里的管道;
- 使用专用算法压缩头部,减少数据传输量;
- 允许服务器主动向客户端推送数据;
- 增强了安全性,“事实上”要求加密通信。
由于 HTTP/1.1 实在是太过经典和强势,目前它的普及率还比较低,大多数网站使用的仍然还是 20 年前的HTTP/1.1。
# HTTP/3
在 HTTP/2 还处于草案之时,Google 又发明了一个新的协议,叫做 QUIC,先在 Chrome 和自家服务器里试验,依托它的庞大用户量和数据量,持续地推动 QUIC 协议。2018 年,互联网标准化组织 IETF 提议将“HTTP over QUIC”更名为“HTTP/3”并获得批准,HTTP/3 正式进入了标准化制订阶段,也许两三年后就会正式发布,到时候我们很可能会跳过 HTTP/2 直接进入 HTTP/3。
# 小结
- HTTP 协议始于三十年前蒂姆·伯纳斯 - 李的一篇论文;
- HTTP/0.9 是个简单的文本协议,只能获取文本资源;
- HTTP/1.0 确立了大部分现在使用的技术,但它不是正式标准;
- HTTP/1.1 是目前互联网上使用最广泛的协议,功能也非常完;
- HTTP/2 基于 Google 的 SPDY 协议,注重性能改善,但还未普及;
- HTTP/3 基于 Google 的 QUIC 协议,是将来的发展方向。
# 什么是 HTTP?
HTTP---HyperText Transfer Protocol 超文本传输协议
# 协议
HTTP 是一个用在计算机世界里的协议。它使用计算机能够理解的语言确立了一种计算机之间交流通信的规范,以及相关的各种控制和错误处理方式。
# 传输
HTTP 是一个在计算机世界里专门用来在两点之间传输数据的约定和规范。
# 超文本
超文本---超越了普通文本的文本,它是文字、图片、音频和视频等的混合体,最关键的是含有“超链接”,能够从一个“超文本”跳跃到另一个“超文本”,形成复杂的非线性、网状的结构关系。
HTTP 是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范。
# HTTP 为什么会如此设计
# 网络为什么要分层
# 分层
- 把复杂的网络通信划分出多个层次,再给每一个层次分配不同的职责,层次内只专心做自己的事情就好,用“分而治之”的思想把一个“大麻烦”拆分成了数个“小麻烦”,从而解决了网络通信的难题。
- 实现了很好的封装,应用层只用关心自己和传输层,传输层只用关心自己和网络层
- 可以很好地实现升级
# 网络分层原理
# OSI 网络分层模型
OSI(Open System Interconnection Reference Model)概念模型
- 第一层:物理层,网络的物理形式,例如电缆、光纤、网卡、集线器等等;
- 第二层:数据链路层,它基本相当于 TCP/IP 的链接层;
- 第三层:网络层,相当于 TCP/IP 里的网际层;
- 第四层:传输层,相当于 TCP/IP 里的传输层;
- 第五层:会话层,维护网络中的连接状态,即保持会话和同步;
- 第六层:表示层,把数据转换为合适、可理解的语法和语义;
- 第七层:应用层,面向具体的应用传输数据。
OSI
- OSI欲成为全球计算机都遵循的标准
- OSI在市场化过程中困难重重,TCP/IP在全球范围成功运行
- OSI最终并没有成为广为使用的标准模型
- OSI的专家缺乏实际经验
- OSI标准指定周期过长,按OSI标准生产的设备无法及时进入市场
- OSI模型设计并不合理,一些功能在多层中重复出现
# TCP/IP 网络分层模型
- 从下到上,第一层叫“链接层”(link layer)或者网络接口层,负责在以太网、WiFi 这样的底层网络上发送原始数据包,工作在网卡这个层次,使用 MAC 地址来标记网络上的设备,所以有时候也叫 MAC层。
- 第二层叫“网际层”或者“网络互连层”(internet layer),IP 协议就处在这一层。因为IP 协议定义了“IP 地址”的概念,所以就可以在“链接层”的基础上,用 IP 地址取代MAC 地址,把许许多多的局域网、广域网连接成一个虚拟的巨大网络,在这个网络里找设备时只要把 IP 地址再“翻译”成 MAC 地址就可以了。
- 第三层叫“传输层”(transport layer),这个层次协议的职责是保证数据在 IP 地址标记的两点之间“可靠”地传输,是 TCP 协议工作的层次,另外还有它的一个“小伙伴”UDP。
- TCP 是一个有状态的协议,需要先与对方建立连接然后才能发送数据,而且保证数据不丢失不重复。而 UDP 则比较简单,它无状态,不用事先建立连接就可以任意发送数据,但不保证数据一定会发到对方。两个协议的另一个重要区别在于数据的形式。
- TCP 的数据是连续的“字节流”,有先后顺序,而 UDP 则是分散的小数据包,是顺序发,乱序收。关于 TCP 和 UDP 可以展开讨论的话题还有很多,比如最经典的“三次握手”和“四次挥手”,一时半会很难说完,好在与 HTTP 的关系不是太大,以后遇到了再详细讲解。
- 第四层叫“应用层”(application layer),由于下面的三层把基础打得非常好,所以在这一层就“百花齐放”了,有各种面向具体应用的协议。例如 Telnet、SSH、FTP、SMTP 等等,当然还有我们的 HTTP。
- MAC 层的传输单位是帧(frame),IP 层的传输单位是包(packet),TCP 层的传输单位是段(segment),HTTP 的传输单位则是消息或报文(message)。但这些名词并没有什么本质的区分,可以统称为数据包。
# OSI 模型与 TCP/IP 模型对照
OSI 的分层模型在四层以上分的太细,而 TCP/IP 实际应用时的会话管理、编码转换、压缩等和具体应用经常联系的很紧密,很难分开。例如,HTTP 就同时包含了连接管理和数据格式定义。
# HTTP 的特点
# 灵活可扩展
HTTP 最初诞生的时候就比较简单,本着开放的精神只规定了报文的基本格式,比如用空格分隔单词,用换行分隔字段,“header+body”等,报文里的各个组成部分都没有做严格的语法语义限制,可以由开发者任意定制。
所以,HTTP 协议就随着互联网的发展一同成长起来了。在这个过程中,HTTP 协议逐渐增加了请求方法、版本号、状态码、头字段等特性。而 body 也不再限于文本形式的 TXT 或 HTML,而是能够传输图片、音频视频等任意数据,这些都是源于它的“灵活可扩展”的特 点。
# 可靠传输
- 这个特点显而易见,因为 HTTP 协议是基于 TCP/IP 的,而 TCP 本身是一个“可靠”的传输协议,所以 HTTP 自然也就继承了这个特性,能够在请求方和应答方之间“可靠”地传输数据。
- 它的具体做法与 TCP/UDP 差不多,都是对实际传输的数据(entity)做了一层包装,加上一个头,然后调用 Socket API,通过 TCP/IP 协议栈发送或者接收。
- 不过我们必须正确地理解“可靠”的含义,HTTP 并不能 100% 保证数据一定能够发送到另一端,在网络繁忙、连接质量差等恶劣的环境下,也有可能收发失败。“可靠”只是向使用者提供了一个“承诺”,会在下层用多种手段“尽量”保证数据的完整送达。
- 当然,如果遇到光纤被意外挖断这样的极端情况,即使是神仙也不能发送成功。所以,“可靠”传输是指在网络基本正常的情况下数据收发必定成功。
# 应用层协议
- 在 TCP/IP 诞生后的几十年里,虽然出现了许多的应用层协议,但它们都仅关注很小的应用领域,局限在很少的应用场景。例如 FTP 只能传输文件、SMTP 只能发送邮件、SSH 只能远程登录等,在通用的数据传输方面“完全不能打”。
- 所以 HTTP 凭借着可携带任意头字段和实体数据的报文结构,以及连接控制、缓存代理等方便易用的特性,一出现就“技压群雄”,迅速成为了应用层里的“明星”协议。只要不太苛求性能,HTTP 几乎可以传递一切东西,满足各种需求,称得上是一个“万能”的协议。
# 请求 - 应答
- 第四个特点,HTTP 协议使用的是请求 - 应答通信模式。
- HTTP 协议规定,请求是从客户端发出,服务器端响应该请求并返回。
# 无状态
- 无状态---协议自身不对请求和响应之间的通讯状态进行保存。
# 其他特点
- 除了以上的五大特点,其实 HTTP 协议还可以列出非常多的特点,例如传输的实体数据可缓存可压缩、可分段获取数据、支持身份认证、支持国际化语言等。但这些并不能算是 HTTP 的基本特点,因为这都是由第一个“灵活可扩展”的特点所衍生出来的。
# 小结
- HTTP 是灵活可扩展的,可以任意添加头字段实现任意功能;
- HTTP 是可靠传输协议,基于 TCP/IP 协议“尽量”保证数据的送达;
- HTTP 是应用层协议,比 FTP、SSH 等更通用功能更多,能够传输任意数据;
- HTTP 使用了请求 - 应答模式,客户端主动发起请求,服务器被动回复请求;
- HTTP 本质上是无状态的,每个请求都是互相独立、毫无关联的,协议不要求客户端或服务器记录请求相关的信息。
# 优缺点
这一部分介绍 HTTP 协议的优点和缺点。其实这些也应该算是 HTTP 的特点,但这一讲会更侧重于评价它们的优劣和好坏。
# 优点
# 简单、灵活、易于扩展
简单
- 首先,HTTP 最重要也是最突出的优点是“简单、灵活、易于扩展”。
- 初次接触 HTTP 的人都会认为,HTTP 协议是很“简单”的,基本的报文格式就是“header+body”,头部信息也是简单的文本格式,用的也都是常见的英文单词。
- 降低了学习和使用的门槛,能够让更多的人研究和开发 HTTP 应用。
灵活和易于扩展
灵活和易于扩展实际上是一体的,它们互为表里、相互促进,因为“灵活”所以才会“易于扩展”,而“易于扩展”又反过来让 HTTP 更加灵活,拥有更强的表现能力。
HTTP 协议里的请求方法、URI、状态码、原因短语、头字段等每一个核心组成要素都没有被“写死”,允许开发者任意定制、扩充或解释,给予了浏览器和服务器最大程度的信任和自由,也正好符合了互联网“自由与平等”的精神——缺什么功能自己加个字段或者错误码什么的补上就是了。
“灵活、易于扩展”的特性还表现在 HTTP 对“可靠传输”的定义上,它不限制具体的下层协议,不仅可以使用 TCP、UNIX Domain Socket,还可以使用 SSL/TLS,甚至是基于 UDP 的 QUIC,下层可以随意变化,而上层的语义则始终保持稳定。
# 应用广泛、环境成熟
- HTTP 协议的另一大优点是“应用广泛”,软硬件环境都非常成熟。
- 随着互联网特别是移动互联网的普及,HTTP 的触角已经延伸到了世界的每一个角落:从简单的 Web 页面到复杂的 JSON、XML 数据,从台式机上的浏览器到手机上的各种 APP,从看新闻、泡论坛到购物、理财、“吃鸡”,你很难找到一个没有使用 HTTP 的地方。
- 不仅在应用领域,在开发领域 HTTP 协议也得到了广泛的支持。它并不限定某种编程语言或者操作系统,所以天然具有“跨语言、跨平台”的优越性。而且,因为本身的简单特性很容易实现,所以几乎所有的编程语言都有 HTTP 调用库和外围的开发测试工具。
- HTTP 广泛应用的背后还有许多硬件基础设施支持,各个互联网公司和传统行业公司都不遗余力地“触网”,购买服务器开办网站,建设数据中心、CDN 和高速光纤,持续地优化上网体验,让 HTTP 运行的越来越顺畅。
- “应用广泛”的这个优点也就决定了:无论是创业者还是求职者,无论是做网站服务器还是写应用客户端,HTTP 协议都是必须要掌握的基本技能。
# 双刃剑
# 无状态
“无状态”的利
- 因为服务器没有“记忆能力”,所以就不需要额外的资源来记录状态信息,不仅实现上会简单一些,而且还能减轻服务器的负担,能够把更多的 CPU 和内存用来对外提供服务。
- 而且,“无状态”也表示服务器都是相同的,没有“状态”的差异,所以可以很容易地组成集群,让负载均衡把请求转发到任意一台服务器,不会因为状态不一致导致处理出错,使用“堆机器”的“笨办法”轻松实现高并发高可用。
“无状态”的弊
- 既然服务器没有“记忆能力”,它就无法支持需要连续多个步骤的“事务”操作。
- 例如电商购物,首先要登录,然后添加购物车,再下单、结算、支付,这一系列操作都需要知道用户的身份才行,但“无状态”服务器是不知道这些请求是相互关联的,每次都得问一遍身份信息,不仅麻烦,而且还增加了不必要的数据传输量。
# 明文
“明文”意思就是协议里的报文(准确地说是 header 部分)不使用二进制数据,而是用简单可阅读的文本形式。
明文的利
- 对比 TCP、UDP 这样的二进制协议,它的优点显而易见,不需要借助任何外部工具,用浏览器、Wireshark 或者 tcpdump 抓包后,直接用肉眼就可以很容易地查看或者修改,为我们的开发调试工作带来极大的便利。
明文的弊
- 当然,明文的缺点也是一样显而易见,HTTP 报文的所有信息都会暴露。
你有没有听说过“免费 WiFi 陷阱”之类的新闻呢?
- 黑客就是利用了 HTTP 明文传输的缺点,在公共场所架设一个 WiFi 热点开始“钓鱼”,诱骗网民上网。一旦你连上了这个 WiFi 热点,所有的流量都会被截获保存,里面如果有银行卡号、网站密码等敏感信息的话那就危险了,黑客拿到了这些数据就可以冒充你为所欲为。
# 缺点:不安全
安全有很多的方面,明文只是“机密”方面的一个缺点,在“身份认证”和“完整性校验”这两方面 HTTP 也是欠缺的。
“身份认证”简单来说就是“怎么证明你就是你”。HTTP 没有提供有效的手段来确认通信双方的真实身份。虽然协议里有一个基本的认证机制,但因为刚才所说的明文传输缺点,这个机制非常容易被攻破。如果仅使用 HTTP 协议,很可能你会连到一个页面一模一样但却是个假冒的网站,然后再被“钓”走各种私人信息。
HTTP 协议也不支持“完整性校验”,数据在传输过程中容易被窜改而无法验证真伪。
- 比如,你收到了一条银行用 HTTP 发来的消息:“小明向你转账一百元”,你无法知道小明是否真的就只转了一百元,也许他转了一千元或者五十元,但被黑客窜改成了一百元,真实情况到底是什么样子 HTTP 协议没有办法给你答案。
- 虽然银行可以用 MD5、SHA1 等算法给报文加上数字摘要,但还是因为“明文”这个致命缺点,黑客可以连同摘要一同修改,最终还是判断不出报文是否被窜改。
# 性能
- HTTP 的性能,可以用六个字来概括:“不算差,不够好”。
- HTTP 协议基于 TCP/IP,并且使用了“请求 - 应答”的通信模式,所以性能的关键就在这两点上。
- 必须要说的是,TCP 的性能是不差的,否则也不会纵横互联网江湖四十余载了,而且它已经被研究的很透,集成在操作系统内核里经过了细致的优化,足以应付大多数的场景。
- 只可惜如今的江湖已经不是从前的江湖,现在互联网的特点是移动和高并发,不能保证稳定的连接质量,所以在 TCP 层面上 HTTP 协议有时候就会表现的不够好。
- 而“请求 - 应答”模式则加剧了 HTTP 的性能问题,这就是著名的“队头阻塞”(Headof-line blocking),当顺序发送的请求序列中的一个请求因为某种原因被阻塞时,在后面排队的所有请求也一并被阻塞,会导致客户端迟迟收不到数据。
- 为了解决这个问题,就诞生出了一个专门的研究课题“Web 性能优化”,HTTP 官方标准里就有“缓存”一章(RFC7234),非官方的“花招”就更多了,例如切图、数据内嵌与合并,域名分片、JavaScript“黑科技”等等。 *
- 不过现在已经有了终极解决方案:HTTP/2 和 HTTP/3。
# 小结
- HTTP 最大的优点是简单、灵活和易于扩展;
- HTTP 拥有成熟的软硬件环境,应用的非常广泛,是互联网的基础设施;
- HTTP 是无状态的,可以轻松实现集群化,扩展性能,但有时也需要用 Cookie 技术来实 现“有状态”;
- HTTP 是明文传输,数据完全肉眼可见,能够方便地研究分析,但也容易被窃听;
- HTTP 是不安全的,无法验证通信双方的身份,也不能判断报文是否被窜改;
- HTTP 的性能不算差,但不完全适应现在的互联网,还有很大的提升空间。
虽然 HTTP 免不了这样那样的缺点,但你也不要怕,别忘了它有一个最重要的“灵活可扩展”的优点,所有的缺点都可以在这个基础上想办法解决。
# HTTP 解决了什么问题
解决 WWW 信息交互必须面对的需求:
- 低门槛
- 可扩展性:巨大的用户群体,超长的寿命
- 分布式系统下的 Hypermedia:大粒度数据的网络传输
- Internet 规模
- 无法控制的 scalability
- 不可预测的负载、非法格式的数据、恶意消息
- 客户端不能保持所有服务器信息,服务器不能保持多个请求间的状态信息
- 独立的组件部署:新老组件并存
# URI
# 当没有 URI 时
- 站长 A 欲分享一部电影 Forrest Gump 给 B,需要告诉:
- 请使用 FTP 协议访问 mysite.net,端口是 8502
- 登录用户名是user,密码pass
- 进入到 /shablue/movie 目录下
- 转换为二进制模式
- 下载名为 Forrest Gump.mkv 格式的文件
# 什么是 URI
- URL: RFC1738 (1994.12),Uniform Resource Locator,表示资源的位置,期望提供查找资源的方法
- URN:RFC2141 (1997.5),Uniform Resource Name,期望为资源提供持久的、位置无关的标识方式,并允许简单地将多个命名空间映射到单个 URN 命名空间
- 例如磁力链接 magnet:?xt=urn:sha1:YNCKHTQC5C
- URI:RFC1630 (1994.6)、RFC3986 (2005.1,取代 RFC2396 和 RFC2732 ),Uniform Resource Identifier,用以区分资源,是 URL 和 URN 的超集,用以取代 URL 和 URN 概念 Uniform Resource Identifier 统一资源标识符
Resource 资源
- 可以是图片、文档、今天杭州的温度等,也可以是不能通过互联网访问的实体,例如人、公司、实体书,也可以是抽象的概念,例如亲属关系或者数字符号
- 一个资源可以有多个 URI
Identifier 标识符
- 将当前资源与其他资源区分开的名称
Uniform 统一
- 允许不同种类的资源在同一上下文中出现
- 对不同种类的资源标识符可以使用同一种语义进行解读
- 引入新标识符时,不会对已有标识符产生影响
# 合法的 URI
- http://www.ietf.org/rfc/rfc2396.txt
- telnet://192.0.2.16:80/
- http://nginx.org
- https://tools.ietf.org/html/rfc7230
- file:///D:/http_study/www/
- mailto:John.Doe@example.com
- news:comp.infosystems.www.servers.unix
- tel:+1-816-555-1212
# URI 格式
# URI 基本格式
- scheme---方案名/协议名,表示资源应该使用哪种协议来访问。
- 最常见的当然就是“http”了,表示使用 HTTP 协议。另外还有“https”,表示使用经过加密、安全的 HTTPS 协议。此外还有其他不是很常见的 scheme,例如 ftp、ldap、file、news 等。
- 浏览器或者你的应用程序看到 URI 里的 scheme,就知道下一步该怎么走了,会调用相应的 HTTP 或者 HTTPS 下层 API。显然,如果一个 URI 没有提供 scheme,即使后面的地址再完善,也是无法处理的。
- 😕/---它把scheme 和后面的部分分离开
- authority---表示资源所在的主机名,通常的形式“host:port”,即主机名加端口号。
- 主机名可以是 IP 地址或者域名的形式,必须要有,否则浏览器就会找不到服务器。
- 但端口号有时可以省略,浏览器等客户端会依据 scheme 使用默认的端口号,例如 HTTP 的默认端口号是 80,HTTPS 的默认端口号是 443。
- path
- 有了协议名和主机地址、端口号,再加上后面标记资源所在位置的path,浏览器就可以连接服务器访问资源了。
- URI 里 path 采用了类似文件系统“目录”“路径”的表示方式,因为早期互联网上的计算机多是 UNIX 系统,所以采用了 UNIX 的“/”风格。其实也比较好理解,它与scheme 后面的“😕/”是一致的。
- ?query
- 使用“协议名 + 主机名 + 路径”的方式,已经可以精确定位网络上的任何资源了。但这还不够,很多时候我们还想在操作资源的时候附加一些额外的修饰参数。
- 查询参数 query 的格式,多个“key=value”的字符串,这些 KV 值用字符“&”连接,浏览器和客户端都可以按照这个格式把长串的查询* 解析成可理解的字典或关联数组形式。
# URI 完整格式
- user:passwd@---身份信息,表示登录主机时的用户名和密码,但现在已经不推荐使用这种形式了(RFC7230),因为它把敏感信息以明文形式暴露出来,存在严重的安全隐患。
- #fragment---片段标识符,它是 URI 所定位的资源内部的一个“锚点”或者说是“标签”,浏览器可以在获取资源后直接跳转到它指示的位置。但片段标识符仅能由浏览器这样的客户端使用,服务器是看不到的。也就是说,浏览器永远不会把带“#fragment”的URI 发送给服务器,服务器也永远不会用这种方式去处理资源的片段。
# URI 编码
# 为什么要进行 URI 编码
- 传递数据中,如果存在用作分隔符的保留字符怎么办?
- 对可能产生歧义性的数据编码
- 不在 ASCII 码范围内的字符
- ASCII 码中不可显示的字符
- URI 中规定的保留字符
- 不安全字符(传输环节中可能会被不正确处理),如空格、引号、尖括号等
示例:
https://www.baidu.com/s?wd=?#!
https://www.baidu.com/s?wd=极客 时间
https://www.baidu.com/s?wd=极客 ‘>时 间
# 保留字符与非保留字符
保留字符
- reserved = gen-delims / sub-delims
- gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
- sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
非保留字符
- unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
- ALPHA: %41-%5A and %61-%7A
- DIGIT: %30-%39
- -: %2D .: %2E _: %5F
- ~: %7E
# URI 编码
- URI 转义的规则是,直接把非 ASCII 码或特殊字符转换成十六进制字节值,然后前面再加上一个“%”。
- 例如,空格被转义成“%20”,“?”被转义成“%3F”。
- 而中文、日文等则通常使用 UTF-8 编码后再转义,例如“银河”会被转义成“%E9%93%B6%E6%B2%B3”。
- 有了这个编码规则后,URI 就更加完美了,可以支持任意的字符集用任何语言来标记资源。
- 不过我们在浏览器的地址栏里通常是不会看到这些转义后的“乱码”的,这实际上是浏览器一种“友好”表现,隐藏了 URI 编码后的样子,例如
https://www.baidu.com/s?wd=极客 ‘>时 间
实际编码后为https://www.baidu.com/s?wd=%E6%9E%81%E5%AE%A2%20%E2%80%98%3E%E6%97%B6%20%E9%97%B4
# 小结
- URI 通常由 scheme、host:port、path 和 query 四个部分组成,有的可以省略;
- scheme---方案名/协议名,表示资源应该使用哪种协议来访问;
- host:port---表示资源所在的主机名和端口号;
- path---标记资源所在的位置;
- query---表示对资源附加的额外要求;
- 在 URI 里对“@&/”等特殊字符和汉字必须要做编码,否则服务器收到 HTTP 报文后会无法正确处理。
# HTTP 报文
用于 HTTP 协议交互的信息被称为 HTTP 报文。请求端(客户端)的 HTTP 报文叫做请求报文,响应端(服务器端)的叫做响应报文。 HTTP 报文本身是由多行(用 CR+LF 作换行符)数据构成的字符串文 本。
HTTP 报文大致可分为报文首部和报文主体两块。两者由最初出现的 空行(CR+LF)来划分。通常,并不一定要有报文主体。
# 请求报文及响应报文的结构
请求报文
响应报文
# 请求行
# 请求方法
- GET:主要的获取信息方法,大量的性能优化都针对该方法,幂等方法
- HEAD:类似 GET 方法,但服务器不发送 BODY,用以获取 HEAD 元数据,幂等方法
- POST:常用于提交 HTML FORM 表单、新增资源等
- PUT:更新资源,带条件时是幂等方法
- DELETE:删除资源,幂等方法
- CONNECT:建立 tunnel 隧道
- OPTIONS:显示服务器对访问资源支持的方法,幂等方法
- TRACE:回显服务器收到的请求,用于定位问题。有安全风险
# 响应行
# 响应码
响应码规范:RFC6585 (2012.4)、RFC7231 (2014.6)
# 1xx
- 1xx:请求已接收到,需要进一步处理才能完成,HTTP1.0 不支持
- 100 Continue:上传大文件前使用
- 由客户端发起请求中携带 Expect: 100-continue 头部触发
- 101 Switch Protocols:协议升级使用
- 由客户端发起请求中携带 Upgrade: 头部触发,如升级 websocket 或者 http/2.0
- 102 Processing:WebDAV 请求可能包含许多涉及文件操作的子请求,需要很长时间 才能完成请求。该代码表示服务器已经收到并正在处理请求,但无响应可用。这样可 以防止客户端超时,并假设请求丢失
# 2xx
- 2xx:成功处理请求
- 200 OK: 成功返回响应。
- 201 Created: 有新资源在服务器端被成功创建。
- 202 Accepted: 服务器接收并开始处理请求,但请求未处理完成。这样一个模 糊的概念是有意如此设计,可以覆盖更多的场景。例如异步、需要长时间处理 的任务。
- 203 Non-Authoritative Information:当代理服务器修改了 origin server 的 原始响应包体时(例如更换了HTML中的元素值),代理服务器可以通过修改 200为203的方式告知客户端这一事实,方便客户端为这一行为作出相应的处理。 203响应可以被缓存。
- 204 No Content:成功执行了请求且不携带响应包体,并暗示客户端无需 更新当前的页面视图。
- 205 Reset Content:成功执行了请求且不携带响应包体,同时指明客户端 需要更新当前页面视图。
- 206 Partial Content:使用 range 协议时返回部分响应内容时的响应码
- 207 Multi-Status:RFC4918 ,在 WEBDAV 协议中以 XML 返回多个资源 的状态。
- 208 Already Reported:RFC5842 ,为避免相同集合下资源在207响应码 下重复上报,使用 208 可以使用父集合的响应码。
# 3xx
- 3xx:重定向使用 Location 指向的资源或者缓存中的资源。在 RFC2068 中规定客户端重定向次数不应超过 5 次,以防止死循环。
- 300 Multiple Choices:资源有多种表述,通过 300 返回给客户端后由其 自行选择访问哪一种表述。由于缺乏明确的细节,300 很少使用。
- 301 Moved Permanently:资源永久性的重定向到另一个 URI 中。
- 302 Found:资源临时的重定向到另一个 URI 中。
- 303 See Other:重定向到其他资源,常用于 POST/PUT 等方法的响应中。
- 304 Not Modified:当客户端拥有可能过期的缓存时,会携带缓存的标识 etag、时间等信息询问服务器缓存是否仍可复用,而304是告诉客户端可以 复用缓存。
- 307 Temporary blueirect:类似302,但明确重定向后请求方法必须与原请求方法相同,不得改变。
- 308 Permanent blueirect:类似301,但明确重定向后请求方法必须与原请求方法相同,不得改变。
# 4xx
- 4xx:客户端出现错误
- 400 Bad Request:服务器认为客户端出现了错误,但不能明确判断为以下哪种错误 时使用此错误码。例如HTTP请求格式错误。
- 401 Unauthorized:用户认证信息缺失或者不正确,导致服务器无法处理请求。
- 403 Forbidden:服务器理解请求的含义,但没有权限执行此请求
- 404 Not Found:服务器没有找到对应的资源
- 405 Method Not Allowed:服务器不支持请求行中的 method 方法
- 406 Not Acceptable:对客户端指定的资源表述不存在(例如对语言或者编码有要 求),服务器返回表述列表供客户端选择。
- 407 Proxy Authentication Requiblue:对需要经由代理的请求,认证信息未通过代理 服务器的验证
- 408 Request Timeout:服务器接收请求超时
- 409 Conflict:资源冲突,例如上传文件时目标位置已经存在版本更新的资源
- 410 Gone:服务器没有找到对应的资源,且明确的知道该位置永久性找不到该资源
- 411 Length Requiblue:如果请求含有包体且未携带 Content-Length 头部,且不属于 chunk类请求时,返回 411
- 412 Precondition Failed:复用缓存时传递的 If-Unmodified-Since 或 If- None-Match 头部不被满足
- 413 Payload Too Large/Request Entity Too Large:请求的包体超出服 务器能处理的最大长度
- 414 URI Too Long:请求的 URI 超出服务器能接受的最大长度
- 415 Unsupported Media Type:上传的文件类型不被服务器支持
- 416 Range Not Satisfiable:无法提供 Range 请求中指定的那段包体
- 417 Expectation Failed:对于 Expect 请求头部期待的情况无法满足时的 响应码
- 421 Misdirected Request:服务器认为这个请求不该发给它,因为它没有能力 处理。
- 426 Upgrade Requiblue:服务器拒绝基于当前 HTTP 协议提供服务,通过 Upgrade 头部告知客户端必须升级协议才能继续处理。
- 428 Precondition Requiblue:用户请求中缺失了条件类头部,例如 If-Match
- 429 Too Many Requests:客户端发送请求的速率过快
- 431 Request Header Fields Too Large:请求的 HEADER 头部大小超过限制
- 451 Unavailable For Legal Reasons:RFC7725 ,由于法律原因资源不可访问
# 5xx
- 5xx:服务器端出现错误
- 500 Internal Server Error:服务器内部错误,且不属于以下错误类型
- 501 Not Implemented:服务器不支持实现请求所需要的功能
- 502 Bad Gateway:代理服务器无法获取到合法响应
- 503 Service Unavailable:服务器资源尚未准备好处理当前请求
- 504 Gateway Timeout:代理服务器无法及时的从上游获得响应
- 505 HTTP Version Not Supported:请求使用的 HTTP 协议版本不支持
- 507 Insufficient Storage:服务器没有足够的空间处理请求
- 508 Loop Detected:访问资源时检测到循环
- 511 Network Authentication Requiblue:代理服务器发现客户端需要进 行身份验证才能获得网络访问权限
# 内容协商与资源表述
# 内容协商的两种方式
Proactive 主动式内容协商:
- 指由客户端先在请求头部中提出需要的表述形式,而服务器根据这些请求 头部提供特定的 representation 表述
Reactive 响应式内容协商:
- 指服务器返回 300 Multiple Choices 或者 406 Not Acceptable,由客户端 选择一种表述 URI 使用
# 常见的协商要素
质量因子 q:内容的质量、可接受类型的优先级
- “q”参数表示权重来设定优先级,这里的“q”是“quality factor”的意思。
- 权重的最大值是 1,最小值是 0.01,默认值是 1,如果值是 0 就表示拒绝。具体的形式是在数据类型或语言代码后面加一个“;”,然后是“q=value”。 *【注】这里要提醒的是“;”的用法,在大多数编程语言里“;”的断句语气要强于“,”,而在 HTTP 的内容协商里却恰好反了过来,“;”的意义是小于“,”的。
媒体资源的 MIME 类型及质量因子
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp, image/apng,\*/\*;q=0.8,application/signed-exchange;v=b3
Accept: text/html,application/xml;q=0.9,*/*;q=0.8
- 它表示浏览器最希望使用的是 HTML 文件,权重是 1,其次是 XML 文件,权重是 0.9,最后是任意数据类型,权重是 0.8。服务器收到请求头后,就会计算权重,再根据自己的实际情况优先输出 HTML 或者 XML。
字符编码:由于 UTF-8 格式广为使用, Accept-Charset 已被废弃
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
内容编码:主要指压缩算法
Accept-Encoding: gzip, deflate, br
表述语言
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
# 资源表述的元数据头部
- 媒体类型、编码
content-type: text/html; charset=utf-8
- 内容编码
content-encoding: gzip
- 语言
Content-Language: de-DE, en-CA
# HTTP 传输大文件的方法
# 数据压缩
压缩是把大文件整体变小
Accept Encoding:gzip、deflate、br
- 表示浏览器支持的压缩格式列表,这样服务器就可以从中选择一种压缩算法,放进
Content-Encoding
响应头里,再把原数据压缩后发给浏览器。 - 如果压缩率能有 50%,也就是说 100K 的数据能够压缩成 50K 的大小,那么就相当于在带宽不变的情况下网速提升了一倍,加速的效果是非常明显的。
- 不过这个解决方法也有个缺点,gzip 等压缩算法通常只对文本文件有较好的压缩率,而图片、音频视频等多媒体数据本身就已经是高度压缩的,再用 gzip 处理也不会变小(甚至还有可能会增大一点),所以它就失效了。
- 不过数据压缩在处理文本的时候效果还是很好的,所以各大网站的服务器都会使用这个手段作为“保底”。例如,在Nginx 里就会使用“gzip on”指令,启用对“text/html”的压缩。
# 分块压缩
如果大文件整体不能变小,那就把它“拆开”,分解成多个小块,把这些小块分批发给浏览器,浏览器收到后再组装复原。这样浏览器和服务器都不用在内存里保存文件的全部,每次只收发一小部分,网络也不会被大文件长时间占用,内存、带宽等资源也就节省下来了。
在响应报文里用头字段Transfer-Encoding: chunked
来表示,意思是报文里的 body 部分不是一次性发过来的,而是分成了许多的块(chunk)逐个发送。
分块传输也可以用于“流式数据”,例如由数据库动态生成的表单页面,这种情况下 body 数据的长度是未知的,无法在头字段“Content-Length”里给出确切的长度,所以也只能用 chunked 方式分块发送。
“Transfer-Encoding: chunked”和“Content Length”这两个字段是互斥的,也就是说响应报文里这两个字段不能同时出现,一个响应报文的传输要么是长度已知,要么是长度未知(chunked)。
下面来看一下分块传输的编码规则,其实也很简单,同样采用了明文的方式,很类似响应头。
- 每个分块包含两个部分,长度头和数据块;
- 长度头是以 CRLF(回车换行,即\r\n)结尾的一行明文,用 16 进制数字表示长度;
- 数据块紧跟在长度头后,最后也用 CRLF 结尾,但数据不包含 CRLF;
- 最后用一个长度为 0 的块表示结束,即“0\r\n\r\n”。
# 范围请求
- 响应头里使用字段
Accept-Ranges: bytes
表示服务器告知客户端:支持范围请求 Accept-Ranges: none
或者不发送Accept-Ranges
字段,客户端就认为服务器没有实现范围请求功能,只能收发整块文件。- 请求头Range是 HTTP 范围请求的专用字段,格式是“bytes=x-y”,其中的 x 和 y 是以字节为单位的数据范围。
- 要注意 x、y 表示的是“偏移量”,范围必须从 0 计数,例如前 10 个字节表示为“0-9”,第二个 10 字节表示为“10-19”,而“0-10”实际上是前 11 个字节。
服务器收到 Range 字段后,需要做四件事。
- 它必须检查范围是否合法,比如文件只有 100 个字节,但请求“200-300”,这就是范围越界了。服务器就会返回状态码416,意思是“你的范围请求有误,我无法处理,请再检查一下”。
- 如果范围正确,服务器就可以根据 Range 头计算偏移量,读取文件的片段了,返回状态码“206 Partial Content”,表示 body 只是原数据的一部分。
- 服务器要添加一个响应头字段Content-Range,告诉片段的实际偏移量和资源的总大小,格式是“bytes x-y/length”,与 Range 头区别在没有“=”,范围后多了总长度。例如,对于“0-10”的范围请求,值就是“bytes 0-10/100”。
- 发送数据。直接把片段用 TCP 发给客户端,一个范围请求就算是处理完了。
不仅看视频的拖拽进度需要范围请求,常用的下载工具里的多段下载、断点续传也是基于它实现的,要点是:
- 先发个 HEAD,看服务器是否支持范围请求,同时获取文件的大小;
- 开 N 个线程,每个线程使用 Range 字段划分出各自负责下载的片段,发请求传输数据;
- 下载意外中断也不怕,不必重头再来一遍,只要根据上次的下载记录,用 Range 请求剩下的那一部分就可以了。
# 多段数据
- 刚才说的范围请求一次只获取一个片段,其实它还支持在 Range 头里使用多个“x-y”,一次性获取多个片段数据。
- 这种情况需要使用一种特殊的 MIME 类型:“multipart/byteranges”,表示报文的 body 是由多段字节序列组成的,并且还要用一个参数“boundary=xxx”给出段之间的分隔标记。
- 多段数据的格式与分块传输也比较类似,但它需要用分隔标记 boundary 来区分不同的片段,可以通过图来对比一下。
每一个分段必须以“- -boundary”开始(前面加两个“-”),之后要用“Content-Type”和“Content-Range”标记这段数据的类型和所在范围,然后就像普通的响应头一样以回车换行结束,再加上分段数据,最后用一个“- -boundary- -”(前后各有两个“-”)表示所有的分段结束。
例如,我们在实验环境里用 Telnet 发出有两个范围的请求:
GET /16-2 HTTP/1.1
Host: www.chrono.com
Range: bytes=0-9, 20-29
2
3
得到的就会是下面这样:
HTTP/1.1 206 Partial Content
Content-Type: multipart/byteranges; boundary=0000000000
Content-Length: 189
Connection: keep-alive
Accept-Ranges: bytes
--00000000001
Content-Type: text/plain
Content-Range: bytes 0-9/96
// this is
--00000000001
Content-Type: text/plain
Content-Range: bytes 20-29/96
ext json d
--00000000001--
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
报文里的--00000000001
就是多段的分隔符,使用它客户端就可以很容易地区分出多段 Range 数据。
# 小结
- 压缩 HTML 等文本文件是传输大文件最基本的方法;
- 分块传输可以流式收发数据,节约内存和带宽,使用响应头字段“Transfer-Encoding: chunked”来表示,分块的格式是 16 进制长度头 + 数据块;
- 范围请求可以只获取部分数据,即“分块请求”,实现视频拖拽或者断点续传,使用请求头字段“Range”和响应头字段“Content-Range”,响应状态码必须是 206;
- 也可以一次请求多个范围,这时候响应报文的数据类型是“multipart/byteranges”,body 里的多个部分会用 boundary 字符串分隔。
要注意这四种方法不是互斥的,而是可以混合起来使用。
# HTTP 的连接管理
# 短连接与长连接
- 在 HTTP/1.1中的连接都会默认启用长连接。
- 如果服务器支持长连接,它总会在响应报文里放一个“Connection: keep-alive”字段,告诉客户端:支持长连接。
- 长连接也需要在恰当的时间关闭,不能永远保持与服务器的连接,这在客户端或者服务器都可以做到。
- 在客户端,可以在请求头里加上“Connection: close”字段,告诉服务器:“这次通信后就关闭连接”。服务器看到这个字段,就知道客户端要主动关闭连接,于是在响应报文里也加上这个字段,发送之后就调用 Socket API 关闭 TCP 连接。
服务器端通常不会主动关闭连接,但也可以使用一些策略。拿 Nginx 来举例,它有两种方式:
- 使用“keepalive_timeout”指令,设置长连接的超时时间,如果在一段时间内连接上没有任何数据收发就主动断开连接,避免空闲连接占用系统资源。
- 使用“keepalive_requests”指令,设置长连接上可发送的最大请求次数。比如设置成 1000,那么当 Nginx 在这个连接上处理了 1000 个请求后,也会主动断开连接。
- 另外,客户端和服务器都可以在报文里附加通用头字段“Keep-Alive: timeout=value”,限定长连接的超时时间。但这个字段的约束力并不强,通信的双方可能并不会遵守,所以不太常见。
# 队头阻塞
- “队头阻塞” Head-of-line blocking,也叫“队首阻塞”。
- “队头阻塞”与短连接和长连接无关,而是由 HTTP 基本的“请求 - 应答”模型所导致的。
- 因为 HTTP 规定报文必须是“一发一收”,这就形成了一个先进先出的“串行”队列。队列里的请求没有轻重缓急的优先级,只有入队的先后顺序,排在最前面的请求被最优先处理。
- 如果队首的请求因为处理的太慢耽误了时间,那么队列里后面的所有请求也不得不跟着一起等待,结果就是其他的请求承担了不应有的时间成本。
# 解决
- 并发连接(concurrent connections),同时对一个域名发起多个长连接,用数量来解决质量的问题。
- 域名分片(domain sharding)技术
- 比如:shard1.chrono.com、shard2.chrono.com,而这些域名都指向同一台服务器 www.chrono.com
# 小结
- 早期的 HTTP 协议使用短连接,收到响应后就立即关闭连接,效率很低;
- HTTP/1.1 默认启用长连接,在一个连接上收发多个请求响应,提高了传输效率;
- 服务器会发送“Connection: keep-alive”字段表示启用了长连接;
- 报文头里如果有“Connection: close”就意味着长连接即将关闭;
- 过多的长连接会占用服务器资源,所以服务器会用一些策略有选择地关闭长连接;
- “队头阻塞”问题会导致性能下降,可以用“并发连接”和“域名分片”技术缓解。
# HTTP 的 Cookie 机制
# Cookie 是什么
RFC6265, HTTP State Management Mechanism HTTP的状态管理机制
保存在客户端、由浏览器维护、表示应用状态的 HTTP 头部
- 存放在内存或者磁盘中
- 服务器端生成 Cookie 在响应中通过 Set-Cookie 头部告知客户端(允许多个 Set-Cookie 头部传递多个值)
- 客户端得到 Cookie 后,后续请求都会 自动将 Cookie 头部携带至请求中
为 Cookie 服务的首部字段
首部字段名 | 说明 | 首部类型 |
---|---|---|
Set-Cookie | 开始状态管理所使用的 Cookie 信息 | 响应首部字段 |
Cookie | 服务器接收到的 Cookie 信息 | 请求首部字段 |
# Set-Cookie
Set-Cookie 字段的属性
属性 | 说明 | |
---|---|---|
NAME=VALUE | 赋予 Cookie 的名称和其值 | 必需项 |
expires=DATE | Cookie 的有效期 | 若不明确指定则默认为浏览器关闭前为止 |
path=PATH | 将服务器上的文件目录作为 Cookie 的适用对象 | 若不指定则默认为文档所在的文件目录 |
domain=域名 | 作为 Cookie 适用对象的域名 | 若不指定则默认为创建 Cookie 的服务器的域名 |
Secure | 仅在 HTTPS 安全通信时才会发送 Cookie | |
HttpOnly | 加以限制,使 Cookie 不能被 JavaScript 脚本访问 | 防止跨站脚本攻击-XSS 对 Cookie 信息盗取 |
# HTTP 缓存
# 补充学习
# HTTP 的代理服务
← 首页