转:Http详解-上

转自:https://juejin.cn/post/7149549470961631262

本文为极客时间《透视HTTP协议》学习笔记,如有需要请支持原文

1. 概述

1.1 概述

HTTP是超文本传输协议(英文:HyperText Transfer Protocol)。HTTP 是构建互联网的重要基础技术,它没有实体,依赖许多其他的技术来实现,但同时许多技术也都依赖于它。

  • 协议:HTTP 是一个用在计算机世界里的协议。它使用计算机能够理解的语言确立了一种计算机之间交流通信的规范,以及相关的各种控制和错误处理方式。
  • 传输:HTTP 专门用来在两点之间传输数据,不能用于广播、寻址或路由。
  • 超文本:HTTP 传输的是文字、图片、音频、视频等超文本数据。

1.2 历史

1.2.1 HTTP诞生

1989 年,任职于欧洲核子研究中心(CERN)的蒂姆·伯纳斯 - 李(Tim Berners-Lee)发表了一篇论文,提出了在互联网上构建超链接文档系统的构想。这篇论文中他确立了三项关键技术。

  • URI:即统一资源标识符,作为互联网上资源的唯一身份;
  • HTML:即超文本标记语言,描述超文本文档;
  • HTTP:即超文本传输协议,用来传输超文本。

1.2.2 HTTP/0.9

最早版本是1991年发布的0.9版。该版本极其简单,只有一个命令GET。
TCP 连接(connection)建立后,客户端向服务器请求(request)网页,协议规定,服务器只能回应HTML格式的字符串,不能回应别的格式。服务器发送完毕,就关闭TCP连接。

1.2.3 HTTP/1.0

1996年5月,HTTP/1.0 版本发布,内容大大增加。但 HTTP/1.0 并不是一个“标准”,只是记录已有实践和模式的一份参考文档,不具有实际的约束力,相当于一个“备忘录”。

  • 增加了 HEAD、POST 等新方法;
  • 增加了响应状态码,标记可能的错误原因;
  • 引入了协议版本号概念;
  • 引入了 HTTP Header(头部)的概念,让 HTTP 处理请求和响应更加灵活;
  • 传输的数据不再仅限于文本。

1.2.4 HTTP/1.1

1997年1月,HTTP/1.1 版本发布,只比 1.0 版本晚了半年。它进一步完善了 HTTP 协议,一直用到了20年后的今天,直到现在还是最流行的版本。HTTP/1.1 是对 HTTP/1.0 的小幅度修正,但它是一个“正式的标准”,而不是一份可有可无的“参考文档”。

  • 增加了 PUT、DELETE 等新的方法;
  • 增加了缓存管理和控制;
  • 明确了连接管理,允许持久连接;
  • 允许响应数据分块(chunked),利于传输大文件;
  • 强制要求 Host 头,让互联网主机托管成为可能。

1.2.5 SPDY 协议

2009年,谷歌公开了自行研发的 SPDY 协议,主要解决 HTTP/1.1 效率不高的问题。
这个协议在Chrome浏览器上证明可行以后,就被当作 HTTP/2 的基础,主要特性都在 HTTP/2 之中得到继承。

1.2.6 HTTP/2

2015年,HTTP/2 发布。它不叫 HTTP/2.0,是因为标准委员会不打算再发布子版本了,下一个新版本将是 HTTP/3。HTTP/2 的制定充分考虑了现今互联网的现状:宽带、移动、不安全,在高度兼容HTTP/1.1 的同时在性能改善方面做了很大努力,主要的特点有:

  • 二进制协议,不再是纯文本;
  • 可发起多个请求,废弃了 1.1 里的管道;
  • 使用专用算法压缩头部,减少数据传输量;
  • 允许服务器主动向客户端推送数据;
  • 增强了安全性,“事实上”要求加密通信。

虽然 HTTP/2 已经发布好几年,也衍生出了 gRPC 等新协议,但由于 HTTP/1.1 实在是太过经典和强势,目前它的普及率还比较低,大多数网站使用的仍然还是 20 年前的HTTP/1.1。

1.2.7 HTTP/3

2022年6月6日,IETF (互联网工程任务小组) 正式发布了 HTTP/3 的 RFC。

在 HTTP/2 还处于草案之时,Google 又发明了一个新的协议,叫做 QUIC,而且还是相同的“套路”,继续在 Chrome 和自家服务器里试验着“玩”,依托它的庞大用户量和数据量,持续地推动 QUIC 协议成为互联网上的“既成事实”。

也就是 2018 年,互联网标准化组织 IETF 提议将“HTTP over QUIC”更名 为“HTTP/3”并获得批准,HTTP/3 正式进入了标准化制订阶段。

1.2.8 总结

  1. HTTP 协议始于三十年前蒂姆·伯纳斯 - 李的一篇论文;
  2. HTTP/0.9 是个简单的文本协议,只能获取文本资源;
  3. HTTP/1.0 确立了大部分现在使用的技术,但它不是正式标准;
  4. HTTP/1.1 是目前互联网上使用最广泛的协议,功能也非常完善;
  5. HTTP/2 基于 Google 的 SPDY 协议,注重性能改善,但还未普及;
  6. HTTP/3 基于 Google 的 QUIC 协议,是将来的发展方向。

1.3 关联概念

1.3.1 网络世界

互联网的正式名称是 Internet,里面存储着无穷无尽的信息资源,我们通常所说的“上 网”实际上访问的只是互联网的一个子集“万维网”(World Wide Web),它基于 HTTP 协议,传输 HTML 等超文本资源,能力也就被限制在 HTTP 协议之内。现在的互联网 90% 以上的部分都被万维网,也就是 HTTP 所覆盖。

1.3.2 浏览器

浏览器的正式名字叫“Web Browser”,顾名思义,就是检索、查看互联网上网页资源的 应用程序,名字里的 Web,实际上指的就是“World Wide Web”,也就是万维网。
浏览器本质上是一个 HTTP 协议中的请求方,使用 HTTP 协议获取网络上的各种资源。

1.3.3 Web 服务器

Web 服务器是一个很大也很重要的概念,它是 HTTP 协议里响应请求的主体,通常有软件和硬件两层含义,硬件就是提供web服务的硬件机器,软件就是提供 Web 服务的应用程序,常用的有Apache和Nginx。

1.3.4 CDN

CDN,全称是“Content Delivery Network”,翻译过来就是“内容分发网络”。它应用 了 HTTP 协议里的缓存和代理技术,代替源站响应客户端的请求。它可以缓存源站的数据,大幅度缩短响应时间。
除了基本的网络加速外,还提供负载均衡、 安全防护、边缘计算、跨运营商网络等功能,能够成倍地“放大”源站服务器的服务能力。

1.3.5 爬虫

爬虫实际上是一种可以自动访问Web资源的应用程序。绝大多数是由各大搜索引擎“放”出来的,抓取网页存入庞大的数据库,再建立关键字索 引,这样我们才能够在搜索引擎中快速地搜索到互联网角落里的页面。
爬虫也有不好的一面,它会过度消耗网络资源,占用服务器和带宽,影响网站对真实数据的 分析,甚至导致敏感信息泄漏。所以,又出现了“反爬虫”技术,通过各种手段来限制爬 虫。其中一项就是“君子协定”robots.txt,约定哪些该爬,哪些不该爬。

1.3.6 DNS

在 TCP/IP 协议中使用 IP 地址来标识计算机,数字形式的地址对于计算机来说是方便了, 但对于人类来说却既难以记忆又难以输入。 于是“域名系统”(Domain Name System)出现了,用有意义的名字来作为 IP 地址的 等价替代。
域名用“.”分隔成多个单词,级别从左到右逐级升高,最右边的被称为“顶级域名”。
但想要使用 TCP/IP 协议来通信仍然要使用 IP 地址,所以需要把域名做一个转换,“映 射”到它的真实 IP,这就是所谓的“域名解析”。

1.3.7 URI/URL

URI(Uniform Resource Identifier),中文名称是统一资源标识符,使用它就能够唯一地标记互联网上资源。
URI 另一个更常用的表现形式是 URL(Uniform Resource Locator), 统一资源定位符,也就是我们俗称的“网址”,它实际上是 URI 的一个子集,不过因为这两者几乎是相同的,差异不大,所以通常不会做严格的区分。
URN:Uniform Resource Name,统一资源名称,也是URI的一个子集。
URI 主要有三个基本的部分构成:协议名,即访问该资源应当使用的协议;主机名,即互联网上主机的标记,可以是域名或 IP 地址;路径,即资源在主机上的位置,使用“/”分隔多级目录。
image

1.3.8 HTTPS

HTTPS全称是“HTTP over SSL/TLS”,也就是 运行在 SSL/TLS 协议上的 HTTP,它是一个负责加密通信的安全协议,建 立在 TCP/IP 之上,所以也是个可靠的传输协议,可以被用作 HTTP 的下层。
SSL 的全称是“Secure Socket Layer”,由网景公司发明,当发展到 3.0 时被标准化,改 名为 TLS,即“Transport Layer Security”,但由于历史的原因还是有很多人称之为 SSL/TLS,或者直接简称为 SSL。
SSL 使用了许多密码学最先进的研究成果,综合了对称加密、非对称加密、摘要算法、数字 签名、数字证书等技术,能够在不安全的环境中为通信的双方创建出一个秘密的、安全的传输通道。

1.3.9 代理

代理(Proxy)是 HTTP 协议中请求方和应答方中间的一个环节,作为“中转站”,既可以 转发客户端的请求,也可以转发服务器的应答。
代理有很多的种类,常见的有:

  1. 匿名代理:完全“隐匿”了被代理的机器,外界看到的只是代理服务器;
  2. 透明代理:顾名思义,它在传输过程中是“透明开放”的,外界既知道代理,也知道客 户端;
  3. 正向代理:靠近客户端,代表客户端向服务器发送请求;
  4. 反向代理:靠近服务器端,代表服务器响应客户端的请求;

由于代理在传输过程中插入了一个“中间层”,所以可以在这个环节做很多有意思的事情, 比如:

  1. 负载均衡:把访问请求均匀分散到多台机器,实现访问集群化;
  2. 内容缓存:暂存上下行的数据,减轻后端的压力;
  3. 安全防护:隐匿 IP, 使用 WAF 等工具抵御网络攻击,保护被代理的机器;
  4. 数据处理:提供压缩、加密等额外的功能。

1.4 访问Web服务器

1.4.1 IP地址访问Web服务器

image
简要叙述最简单的浏览器 HTTP 请求过程:

  1. 浏览器从地址栏的输入中获得服务器的 IP 地址和端口号;
  2. 浏览器用 TCP 的三次握手与服务器建立连接;
  3. 浏览器向服务器发送拼好的报文;
  4. 服务器收到报文后处理请求,同样拼好报文再发给浏览器;
  5. 浏览器解析报文,渲染输出页面。

1.4.2 使用域名访问Web服务器

在浏览器地址栏里直接输入 IP 地址可以访问服务器,但绝大多数情况下,我们是不知道服务器IP 地址的,使用的是域名。浏览器看到了网址里的域名,发起域名解析动作,把这个域名翻译成 TCP/IP 协议里的 IP 地址。

不过因为域名解析的全过程实在是太复杂了,如果每一个域名都要大费周折地去网上查一下,那我们上网肯定会慢得受不了。所以,在域名解析的过程中会有多级的缓存,浏览器首先看一下自己的缓存里有没有,如果没有就向操作系统的缓存要,还没有就检查本机域名解析文件 hosts,也就是上一讲中我们修改的 C:\WINDOWS\system32\drivers\etc\hosts

刚好,里面有一行映射关系 127.0.0.1 www.chrono.com,于是浏览器就知道了域名对应的 IP 地址,就可以愉快地建立 TCP 连接发送 HTTP 请求了。
image

1.4.3 真实的网络世界

image
如果你用的是电脑台式机,那么你可能会使用带水晶头的双绞线连上网口,由交换机接入固定网络。如果你用的是手机、平板电脑,那么你可能会通过蜂窝网络、WiFi,由电信基站、无线热点接入移动网络。接入网络的同时,网络运行商会给你的设备分配一个 IP 地址,这个地址可能是静态分配的,也可能是动态分配的。静态 IP 就始终不变,而动态 IP 可能你下次上网就变了。

假设你要访问的是 Apple 网站,显然你是不知道它的真实 IP 地址的,在浏览器里只能使用域名 www.apple.com 访问,那么接下来要做的必然是域名解析。这就要用 DNS 协议开始从操作系统、本地 DNS、根 DNS、顶级 DNS、权威 DNS 的层层解析,当然这中间有缓存,可能不会费太多时间就能拿到结果。

DNS 解析可能会给出 CDN 服务器的 IP 地址,这样你拿到的就会是 CDN 服务器而不是目标网站的实际地址。因为 CDN 会缓存网站的大部分资源,比如图片、CSS 样式表,所以有的 HTTP 请求就不需要再发到 Apple,CDN 就可以直接响应你的请求,把数据发给你。

由 PHP、Java 等后台服务动态生成的页面属于“动态资源”,CDN 无法缓存,只能从目标网站获取。于是你发出的 HTTP 请求就要开始在互联网上的“漫长跋涉”,经过无数的路由器、网关、代理,最后到达目的地。

目标网站的服务器对外表现的是一个 IP 地址,但为了能够扛住高并发,在内部也是一套复杂的架构。通常在入口是负载均衡设备,例如四层的 LVS 或者七层的 Nginx,在后面是许多的服务器,构成一个更强更稳定的集群。

负载均衡设备会先访问系统里的缓存服务器,通常有 memory 级缓存 Redis 和 disk 级缓存 Varnish,它们的作用与 CDN 类似,不过是工作在内部网络里,把最频繁访问的数据缓存几秒钟或几分钟,减轻后端应用服务器的压力。

如果缓存服务器里也没有,那么负载均衡设备就要把请求转发给应用服务器了。这里就是各种开发框架大显神通的地方了,例如 Java 的 Tomcat/Netty/Jetty,Python 的 Django,还有 PHP、Node.js、Golang 等等。它们又会再访问后面的 MySQL、PostgreSQL、MongoDB 等数据库服务,实现用户登录、商品查询、购物下单、扣款支付等业务操作,然后把执行的结果返回给负载均衡设备,同时也可能给缓存服务器里也放一份。

应用服务器的输出到了负载均衡设备这里,请求的处理就算是完成了,就要按照原路再走回去,还是要经过许多的路由器、网关、代理。如果这个资源允许缓存,那么经过 CDN 的时候它也会做缓存,这样下次同样的请求就不会到达源站了。

最后网站的响应数据回到了你的设备,它可能是 HTML、JSON、图片或者其他格式的数据,需要由浏览器解析处理才能显示出来,如果数据里面还有超链接,指向别的资源,那么就又要重走一遍整个流程,直到所有的资源都下载完。

2. 基础

2.1 报文结构

HTTP 协议的请求报文和响应报文的结构基本相同,由三大部分组成:

  • 起始行(start line):描述请求或响应的基本信息;
  • 头部字段集合(header):使用 key-value 形式更详细地说明报文;
  • 消息正文(entity):实际传输的数据,它不一定是纯文本,可以是图片、视频等二进制数据。

这其中前两部分起始行和头部字段经常又合称为“请求头”或“响应头”,消息正文又称为“实体”,但与“header”对应,很多时候就直接称为“body”。

HTTP 协议规定报文必须有 header,但可以没有 body,而且在 header 之后必须要有一个“空行”,也就是“CRLF”,十六进制的“0D0A”。
image

如下是一个http报文实例
image
在这个浏览器发出的请求报文里,第一行“GET /HTTP/1.1”就是请求行,而后面的“Host”“Connection”等等都属于 header,报文的最后是一个空白行结束,没有 body。很多时候,特别是浏览器发送 GET 请求的时候都是这样,HTTP 报文经常是只有 header 而没 body。

2.2 请求行

请求行,也就是请求报文里的起始行,它简要地描述了客户端想要如何操作服务器端的资源。请求行由三部分构成:

  • 请求方法:是一个动词,如 GET/POST,表示对资源的操作;
  • 请求目标:通常是一个 URI,标记了请求方法要操作的资源;
  • 版本号:表示报文使用的 HTTP 协议版本。

这三个部分通常使用空格(space)来分隔,最后要用CRLF 换行表示结束。
image
在如下的请求行里,“GET”是请求方法,“/”是请求目标,“HTTP/1.1”是版本号。

GET / HTTP/1.1

2.4 状态行

状态行,也就是响应报文里的起始行,意思是服务器响应的状态。状态行也有三部分构成:

  • 版本号:表示报文使用的 HTTP 协议版本;
  • 状态码:一个三位数,用代码的形式表示处理的结果,比如 200 是成功,500 是服务器错误;
  • 原因:作为数字状态码补充,是更详细的解释文字,帮助人理解原因。
    image
    在如下的状态行里,这个报文使用的协议版本号是 1.1,状态码是 200,一切OK。
HTTP/1.1 200 OK

2.5 头部字段

请求行或状态行再加上头部字段集合就构成了 HTTP 报文里完整的请求头或响应头,如下:
image

image
头部字段是 key-value 的形式,key 和 value 之间 用“:”分隔,最后用 CRLF 换行表示字段结束。HTTP 头字段非常灵活,不仅可以使用标准里的 Host、 Connection 等已有头,也可以任意添加自定义头,这就给 HTTP 协议带来了无限的扩展可能。不过使用头字段需要注意下面几点:

  1. 字段名不区分大小写,例如“Host”也可以写 成“host”,但首字母大写的可读性更好;
  2. 字段名里不允许出现空格,可以使用连字符“-”,但不 能使用下划线“_”。例如,“test-name”是合法的字 段名,而“test name”“test_name”是不正确的字段名;
  3. 字段名后面必须紧接着“:”,不能有空格,而“:”后的 字段值前可以有多个空格;
  4. 字段的顺序是没有意义的,可以任意排列不影响语义;
  5. 字段原则上不能重复,除非这个字段本身的语义允许,例 如 Set-Cookie。

2.6 常用头字段

HTTP 协议规定了非常多的头部字段,实现各种各样的功能,但基本上可以分为四大类:

  1. 通用字段:在请求头和响应头里都可以出现;
  2. 请求字段:仅能出现在请求头里,进一步说明请求信息或者额外的附加条件;
  3. 响应字段:仅能出现在响应头里,补充说明响应报文的信 息;
  4. 实体字段:它实际上属于通用字段,但专门描述 body 的 额外信息。

2.6.1 Host(请求字段)(必填字段)

请求字段,只能出现在请求头里,它同时也是唯一一个 HTTP/1.1 规范里要求必须出现的字段,也就是说,如果请求头里没有 Host,那这就是 一个错误的报文。

Host 字段告诉服务器这个请求应该由哪个主机来处理,当 一台计算机上托管了多个虚拟主机的时候,服务器端就需要 用 Host 字段来选择,有点像是一个简单的“路由重定 向”。

例如在 127.0.0.1 上有三个虚拟主机,那么当使用域名的方式访问时,就必须要用 Host 字段来区分这三个 IP 相同但域名不同的网站,否则服务器 就会找不到合适的虚拟主机,无法处理。

2.6.2 User-Agent(请求字段)

请求字段,只出现在请求头里。它使用一个字符串来描述发起 HTTP 请求的客户端,服务器可以依据它来返回最合适此浏览器显示的页面。

由于历史的原因,User-Agent 非常混乱,每个浏览器都自称是“Mozilla”“Chrome”“Safari”,企图使用这个 字段来互相“伪装”,导致 User-Agent变得越来越长,最终变得毫无意义。

不过有的比较“诚实”的爬虫会在 User-Agent 里用“spider”标明自己是爬虫,所以可以利用这个字段实现简单的反爬虫策略。

2.6.3 Date(通用字段)

通用字段,但通常出现在响应头里,表示 HTTP 报文创建的时间,客户端可以使用这个时间再搭配其 他字段决定缓存策略。

2.6.4 Server(响应字段)

响应字段,只能出现在响应头里。它告诉客户 端当前正在提供 Web 服务的软件名称和版本号,例如“Server: openresty/1.15.8.1”, 即使用的是 OpenResty 1.15.8.1。

Server 字段也不是必须要出现的,因为这会暴露服务器信息,如果这个版本恰好存在bug,那么黑客就有可能利用bug攻陷服务器。所以,有的网站响应头里要么没有这个字段,要么就给出一个完全无关的描述信息。比如 GitHub,它的 Server 字段里只是显示为“GitHub.com”。
image

2.6.5 Content-Length(实体字段)

它表示报文里body 的长度,也就是请求头或响应头空行后面数据的长度。服务器看到这个字段,就知道了后续有多少数据,可以直接接收。如果没有这个字段,那么 body 就是不定长的,需要使用 chunked 方式分段传输。

2.7 请求方法

2.7.1 标准请求方法

URI只能定位资源,但是怎么操作资源,需要有某种动作指令,所以,就这么出现了“请求方法”。它的实际含义就 是客户端发出了一个“动作指令”,要求服务器端对 URI 定 位的资源执行这个动作。

目前 HTTP/1.1 规定了八种方法,单词都必须是大写的形 式,我先简单地列把它们列出来,后面再详细讲解。

  1. GET:获取资源,可以理解为读取或者下载数据;
  2. HEAD:获取资源的元信息;
  3. POST:向资源提交数据,相当于写入或上传数据;
  4. PUT:类似 POST;
  5. DELETE:删除资源;
  6. CONNECT:建立特殊的连接隧道;
  7. OPTIONS:列出可对资源实行的方法;
  8. TRACE:追踪请求 - 响应的传输路径。

虽然客户端发出了这些明确的操作指令,但是执行的最终还是服务器,服务器不一定按照客户端的要求进行操作资源,比如,DELETE不一定删除资源,也可以提交数据。
image

2.7.2 常用--Get(从服务器获取资源)

它的含义是请求从服务器获取资源,这个资源既可以是静态 的文本、页面、图片、视频,也可以是由 PHP、Java 动态 生成的页面或者其他格式的数据。

GET 方法虽然基本动作比较简单,但搭配 URI 和其他头字 段就能实现对资源更精细的操作。例如,在 URI 后使用“#”,就可以在获取页面后直接定位 到某个标签所在的位置;使用 If-Modified-Since 字段就变 成了“有条件的请求”,仅当资源被修改时才会执行获取动作;使用 Range 字段就是“范围请求”,只获取资源的一部分数据。

2.7.3 常用--HEAD(从服务器获取资源)

HEAD方法与 GET 方法类似,也是请求从服务器获取资源,服务器的处理机制也是一样的,但服务器不会返回请求 的实体数据,只会传回响应头,也就是资源的“元信息”。

HEAD 方法可以看做是 GET 方法的一个“简化版”或 者“轻量版”。因为它的响应头与 GET 完全相同,所以可以用在很多并不真正需要资源的场合,避免传输body数据的浪费。

比如,想要检查一个文件是否存在,只要发个 HEAD 请求 就可以了,没有必要用 GET 把整个文件都取下来。再比 如,要检查文件是否有最新版本,同样也应该用 HEAD,服 务器会在响应头里把文件的修改时间传回来。

2.7.4 常用--POST(以向服务器提交数据)

向 URI 指定的资源提交数据,数据就放在报文的 body 里。POST应用的场景也非常多,只要向服务器发送数据,用的大多数都是 POST。

比如,你上论坛灌水,敲了一堆字后点击“发帖”按钮,浏览器就执行了一次 POST 请求,把你的文字放进报文的 body 里,然后拼好 POST 请求头,通过 TCP 协议发给服 务器。

2.7.5 常用--PUT(以向服务器提交数据)

PUT 的作用与 POST 类似,也可以向服务器提交数据,但 与 POST 存在微妙的不同,通常 POST 表示的是“新建”“create”的含义,而 PUT 则是“修 改”“update”的含义。

在实际应用中,PUT 用到的比较少。而且,因为它与 POST 的语义、功能太过近似,有的服务器甚至就直接禁止使用 PUT 方法,只用 POST 方法上传数据。

2.7.6 非常用--DELETE(删除资源)

DELETE方法指示服务器删除资源,因为这个动作危险性太 大,所以通常服务器不会执行真正的删除操作,而是对资源 做一个删除标记。当然,更多的时候服务器就直接不处理 DELETE 请求。

2.7.7 非常用--CONNECT(建立特殊的连接隧道)

CONNECT是一个比较特殊的方法,要求服务器为客户端和 另一台远程服务器建立一条特殊的连接隧道,这时 Web 服 务器在中间充当了代理的角色。

2.7.8 非常用--OPTIONS(列出可对资源实行的方法)

OPTIONS方法要求服务器列出可对资源实行的操作方法, 在响应头的 Allow 字段里返回。它的功能很有限,用处也不 大,有的服务器(例如 Nginx)干脆就没有实现对它的支持。

2.7.9 非常用--TRACE(追踪请求 - 响应的传输路径)

TRACE方法多用于对 HTTP 链路的测试或诊断,可以显示 出请求 - 响应的传输路径。它的本意是好的,但存在漏洞, 会泄漏网站的信息,所以 Web 服务器通常也是禁止使用。

2.7.10 扩展方法

虽然 HTTP/1.1 里规定了八种请求方法,但它并没有限制我 们只能用这八种方法,这也体现了 HTTP 协议良好的扩展 性,我们可以任意添加请求动作,只要请求方和响应方都能 理解就行。

有一些得到了实际应用的请求方法 (WebDAV),例如 MKCOL、COPY、MOVE、LOCK、 UNLOCK、PATCH 等。如果有合适的场景,你也可以把它 们应用到自己的系统里,比如用 LOCK 方法锁定资源暂时不 允许修改,或者使用 PATCH 方法给资源打个小补丁,部分更新数据。但因为这些方法是非标准的,所以需要为客户端 和服务器编写额外的代码才能添加支持。你也完全可以根据实际需求,自己发明新的方法。

2.7.11 安全与幂等

所谓的“安全”是指请求方法不会“破 坏”服务器上的资源,即不会对服务器上的资源造成实质的修改。按照这个定义,只有 GET 和 HEAD 方法是“安全”的, POST/PUT/DELETE 操作会修改服务器上的资源,增加 或删除数据,所以是“不安全”的。

所谓的“幂等”实际上是一个数学用语,意思是多次执行相同的操作,结果也都是相同的, 即多次“幂”后结果“相等”。GET 和 HEAD 既是安全的也是幂等的,DELETE 可以多次删除同一个资源,效果都是“资源不存在”,所以 也是幂等的。POST 是“新增或提交数据”,多次 提交数据会创建多个资源,所以不是幂等的;而 PUT 是“替换或更新数据”,多次更新一个资源,资源还是会第 一次更新的状态,所以是幂等的。

2.8 URI

2.8.1 URI的格式

URI 本质上是一个字符串,这个字符串的作用是唯一地标记资源的位置或者名字。

下面的这张图显示了 URI 最常用的形式,由 scheme、 host:port、path 和 query 四个部分组成,但有的部分可以 视情况省略。
image

2.8.2 URI的基本组成

  • scheme: 翻译成中文叫“方案名”或者“协议名”,表示资源应该使用哪种协议来访问。最常见是“http”,另外还有“https”,表示经过加密、安全的 HTTPS协议。 此外还有其他的,例如 ftp、ldap、 file、news 等。如果一个 URI 没有提供 scheme,是无法处理的。
  • :// 在 scheme 之后,必须是三个特定的字符://,它把 scheme 和后面的部分分离开。
  • authority: 表示资源 所在的主机名,通常的形式是“host:port”,即主机名加端口号。主机名可以是 IP 地址或者域名的形式,必须要有,否则浏览器就会找不到服务器。但端口号有时可以省略,浏览器等客户端会依据 scheme 使用默认的端口号,例如 HTTP 的 默认端口号是 80,HTTPS 的默认端口号是 443。
  • path: 标记资源所在位置,有了协议名和主机地址、端口号再加上path,浏览器就可以连接服务器访问资源了。URI 里 path 采用了类似文件系统“目录”“路径”的表示 方式,因为早期互联网上的计算机多是 UNIX 系统,所以采用了 UNIX 的“/”风格。
  • query: 表示对资源附加的额外要求,它在 path 之后,用一个“?”开始,但不包含“?”。查询参数 query 有一套自己的格式,是多 个“key=value”的字符串,这些 KV 值用字符“&”连接,浏览器和客户端都可以按照这个格式把长串的查询参数 解析成可理解的字典或关联数组形式。

如下是几个实例:

http://nginx.org
http://www.chrono.com:8080/11-1
https://tools.ietf.org/html/rfc7230
file:///D:/http_study/www/

最后一个 URI 要注意了,它的协议名不是“http”,而是“file”,表示这是本地文件,而后面居然有三个斜杠,三个斜杠里的前两个属于 URI 特殊分隔符“😕/”,然后后面的“/D:/http_study/www/”是路径,而中间的主机名被“省略”了。这实际上是 file 类型 URI 的“特例”,它允许省略主机名,默认是本机 localhost。

2.8.3 URI 的完整格式

URI 还有一个“真正”的完整形态,如下图所示,这个“真正”形态比基本形态多了两部分。
image

  • user:passwd@: 表示登录主机时的用户名和密码, 但现在已经不推荐使用这种形式了(RFC7230),因为它把 敏感信息以明文形式暴露出来,存在严重的安全隐患。
  • #fragment: 它是 URI 所定位的资源内部的一 个“锚点”或者说是“标签”,浏览器可以在获取资源后直接跳转到它指示的位置。

2.8.4 URI 的编码

在 URI 里只能使用 ASCII 码,为了在URI中表示ASCII 码以外的字符集和“@&/”等特殊字符,URI 引入了编码机制,把它们转换成与 URI 语义不冲 突的形式。这在 RFC 规范里称 为“escape”和“unescape”,俗称“转义”。

URI 转义的规则有点“简单粗暴”,直接把非 ASCII 码或特殊字符转换成十六进制字节值,然后前面再加上一 个“%”。

例如,空格被转义成“%20”,“?”被转义成“%3F”。 而中文、日文等则通常使用 UTF-8 编码后再转义,例如“银河”会被转义成“%E9%93%B6%E6%B2%B3”。 有了这个编码规则后,URI 就更加完美了,可以支持任意的 字符集用任何语言来标记资源。

2.9 响应状态码

2.9.1 状态码的位置

服务器收到请求报文,解析后需要进行处理,具体的业务逻辑多种多样,但最后必定是拼出一个响应报文发回客户端。响应报文由响应头加响应体数据组成,响应头又由状态行和头字段构成。状态行的结构如下:
image

  • Version: 是 HTTP 协议的版本号,通常是HTTP/1.1,用处不是很大。
  • Reason: 是原因短语,是状态码的简短文字描述,例如“OK”“Not Found”等等,也可以自定义。但它只是为了兼容早期的文本客户端而存在,提供的信息很有限,目前的大多数客户端都会忽略它。
  • Status Code: 状态码,它是一个十进制数字,以代码的形式表示服务器对请求的处理结果,就像我们通常编写程序时函数返回的错误码一样。意义在于表达HTTP 数据处理的“状态”,客户端可以依据代码适时转换处理状态,例如继续发送请求、切换协议,重定向跳转等。

2.9.2 状态码

目前 RFC 标准里规定的状态码是三位数,所以取值范围就是从 000 到 999。RFC 标准把状态码分成了五类,用数字的第一位表示分类,而 099 不用,这样状态码的实际可用范围就大大缩小了,由000999 变成了 100~599。这五类的具体含义是:

  • 1××: 提示信息,表示目前是协议处理的中间状态,还需要后续的操作;偶尔能够见到的是101;
  • 2××: 成功,报文已经收到并被正确处理,常用的有 200、204、206;
  • 3××: 重定向,资源位置发生变动,需要客户端重新发送请求,常用的有 301、302、304;
  • 4××: 客户端错误,请求报文有误,服务器无法处理,常用的有 400、403、 404;
  • 5××: 服务器错误,服务器在处理请求时内部发生了错误,常用的有 500、501、 502、503;

目前 RFC 标准里总共有 41 个状态码,但状态码的定义是开放的,允许自行扩展。所以 Apache、Nginx 等 Web 服务器都定义了一些专有的状态码。如果你自己开发 Web 应用,也完全可以在不冲突的前提下定义新的代码。

状态码 说明
100 Continue 服务器仅接收到部分请求,但是服务器并没有拒绝该请求,客户端应该继续发送其余的请求。
101 Switching Protocols 它的意思是客户端使用 Upgrade 头字段,要求在 HTTP 协议的基础上改成其他的协议继续通信,比如 WebSocket。而如果服务器也同意变更协议,就会发送状态码 101,但这之后的数据传输就不会再使用 HTTP 了。
103 Checkpoint 用于 PUT 或者 POST 请求恢复失败时的恢复请求建议。
200 OK 是最常见的成功状态码,表示一切正常,服务器如客户端所期望的那样返回了处理结果,如果是非 HEAD请求,通常在响应头后都会有 body 数据。
201 Created 请求被创建完成,同时新的资源被创建。
202 Accepted 供处理的请求已被接受,但是处理未完成。
203 Non-Authoritative Information 请求已经被成功处理,但是一些应答头可能不正确,因为使用的是其他文档的拷贝。
204 No Content 请求已经被成功处理,但是没有返回新文档。浏览器应该继续显示原来的文档。如果用户定期地刷新页面,而Servlet可以确定用户文档足够新,这个状态代码是很有用的。
205 Reset Content 请求已经被成功处理,但是没有返回新文档。但浏览器应该重置它所显示的内容。用来强制浏览器清除表单输入内容。
206 Partial Content 是 HTTP 分块下载或断点续传的基础,在客户端发送“范围请求”、要求获取资源的部分数据时出现,它与 200 一样,也是服务器成功处理了请求,但 body 里的数据不是资源的全部,而是其中的一部分。状态码 206 通常还会伴随着头字段“Content-Range”,表示响应报文里 body 数据的具体范围,供客户端确认,例如“Content-Range: bytes 0-99/2000”,意思是此次获取的是总计 2000 个字节的前 100 个字节。
300 Multiple Choices 多重选择。链接列表。用户可以选择某链接到达目的地。最多允许五个地址。
301 Moved Permanently 俗称“永久重定向”,含义是此次请求的资源已经不存在了,需要改用改用新的 URI再次访问。
302 Found 俗称“临时重定向”,意思是请求的资源还在,但需要暂时用另一个 URI 来访问。301 和 302 都会在响应头里使用字段Location指明后续要跳转的 URI,最终的效果很相似,浏览器都会重定向到新的URI。两者的根本区别在于语义,一个是“永久”,一个是“临时”,所以在场景、用法上差距很大。比如,你的网站升级到了 HTTPS,原来的 HTTP 不打算用了,这就是“永久”的,所以要配置 301 跳转,把所有的HTTP 流量都切换到 HTTPS。再比如,今天夜里网站后台要系统维护,服务暂时不可用,这就属于“临时”的,可以配置成 302 跳转,把流量临时切换到一个静态通知页面,浏览器看到这个 302 就知道这只是暂时的情况,不会做缓存优化,第二天还会访问原来的地址。
303 See Other 所请求的页面可在别的 URL 下被找到。
304 Not Modified 未按预期修改文档。客户端有缓冲的文档并发出了一个条件性的请求(一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用。
305 Use Proxy 客户请求的文档应该通过Location头所指明的代理服务器提取。
306 Switch Proxy 目前已不再使用,但是代码依然被保留。
307 Temporary Redirect 被请求的页面已经临时移至新的 URL 。
308 Resume Incomplete 用于 PUT 或者 POST 请求恢复失败时的恢复请求建议。
400 Bad Request 是一个通用的错误码,表示请求报文有错误,但具体是数据格式错误、缺少请求头还是 URI 超长它没有明确说,只是一个笼统的错误,客户端看到 400只会是“一头雾水”“不知所措”。所以,在开发 Web 应用时应当尽量避免给客户端返回 400,而是要用其他更有明确含义的状态码。
401 Unauthorized 合法请求,但对被请求页面的访问被禁止。因为被请求的页面需要身份验证,客户端没有提供或者身份验证失败。
402 Payment Required 此代码尚无法使用。
403 Forbidden 实际上不是客户端的请求出错,而是表示服务器禁止访问资源。原因可能多种多样,例如信息敏感、法律禁止等,如果服务器友好一点,可以在 body 里详细说明拒绝请求的原因,不过现实中通常都是直接给一个“闭门羹”。
404 Not Found 可能是我们最常看见也是最不愿意看到的一个状态码,它的原意是资源在本服务器上未找到,所以无法提供给客户端。但现在已经被“用滥了”,只要服务器“不高兴”就可以给出个 404,而我们也无从得知后面到底是真的未找到,还是有什么别的原因,某种程度上它比403 还要令人讨厌。
405 Method Not Allowed 不允许使用某些方法操作资源,例如不允许 POST 只能 GET;
406 Not Acceptable 资源无法满足客户端请求的条件,例如请求中文但只有英文;
407 Proxy Authentication Required 用户必须首先使用代理服务器进行验证,这样请求才会被处理。
408 Request Timeout 请求超出了服务器的等待时间。
409 Conflict 多个请求发生了冲突,可以理解为多线程并发时的竞态;
410 Gone 被请求的页面不可用。
411 Length Required "Content-Length" 未被定义。如果无此内容,服务器不会接受请求。
412 Precondition Failed 请求中的前提条件被服务器评估为失败。
413 Request Entity Too Large 由于所请求的实体太大,服务器不会接受请求。
414 Request-URI Too Long 由于 URL 太长,服务器不会接受请求。当 POST 请求被转换为带有很长的查询信息的 GET 请求时,就会发生这种情况。
415 Unsupported Media Type 由于媒介类型不被支持,服务器不会接受请求。
416 Requested Range Not Satisfiable 客户端请求部分文档,但是服务器不能提供被请求的部分。
417 Expectation Failed 服务器不能满足客户在请求中指定的请求头。
429 Too Many Requests 客户端发送了太多的请求,通常是由于服务器的限连策略;
431 Request Header Fields Too Large 请求头某个字段或总体太大;
500 Internal Server Error 与 400 类似,也是一个通用的错误码,服务器究竟发生了什么错误我们是不知道的。不过对于服务器来说这应该算是好事,通常不应该把服务器内部的详细信息,例如出错的函数调用栈告诉外界。虽然不利于调试,但能够防止黑客的窥探或者分析。
501 Not Implemented 表示客户端请求的功能还不支持,这个错误码比 500 要“温和”一些,和“即将开业,敬请期待”的意思差不多,不过具体什么时候“开业”就不好说了。
502 Bad Gateway 通常是服务器作为网关或者代理时返回的错误码,表示服务器自身工作正常,访问后端服务器时发生了错误,但具体的错误原因也是不知道的。
503 Service Unavailable 表示服务器当前很忙,暂时无法响应服务,我们上网时有时候遇到的“网络服务正忙,请稍后重试”的提示信息就是状态码 503。503 是一个“临时”的状态,很可能过几秒钟后服务器就不那么忙了,可以继续提供服务,所以 503 响应报文里通常还会有一个“Retry-After”字段,指示客户端可以在多久以后再次尝试发送请求。
504 Gateway Timeout 网关超时。服务器充当网关或者代理的角色时,未能从上游服务器收到一个及时的响应。
505 HTTP Version Not Supported 服务器不支持请求中指明的HTTP协议版本。
511 Network Authentication Required 用户需要提供身份验证来获取网络访问入口。

2.10 特点总结

  1. HTTP 是灵活可扩展的,可以任意添加头字段实现任意功能;
  2. HTTP 是可靠传输协议,基于 TCP/IP 协议“尽量”保证数据的送达;
  3. HTTP 是应用层协议,比 FTP、SSH 等更通用功能更多,能够传输任意数据;
  4. HTTP 使用了请求 - 应答模式,客户端主动发起请求,服务器被动回复请求;
  5. HTTP 本质上是无状态的,每个请求都是互相独立、毫无关联的,协议不要求客户端或 服务器记录请求相关的信息。

2.11 优缺点总结

  1. HTTP 最大的优点是简单、灵活和易于扩展;
  2. HTTP 拥有成熟的软硬件环境,应用的非常广泛,是互联网的基础设施;
  3. HTTP 是无状态的,可以轻松实现集群化,扩展性能,但有时也需要用 Cookie 技术来实现“有状态”;
  4. HTTP 是明文传输,数据完全肉眼可见,能够方便地研究分析,但也容易被窃听;
  5. HTTP 是不安全的,无法验证通信双方的身份,也不能判断报文是否被窜改;
  6. HTTP 的性能不算差,但不完全适应现在的互联网,还有很大的提升空间。
posted @ 2023-06-07 10:34  MyMemo  阅读(41)  评论(0编辑  收藏  举报