前言
力求以最简单和高效的语言说明问题,让大家快速掌握知识点。资料参考参考《图解HTTP》、维基百科和 MDN 等,可作为精华学习笔记使用。
本人能力有限,如有不正确之处请批评指正。
概述
HTTP 全称 Hypertext Transfer Protocol ,直译为“超文本转移协议”,但更多时候俗称“超文本传输协议”。
HTTP 字面意义上就是为了 HTML 的传输而发明的网络协议,但进过不断的完善、改进和发展后,它已经不再局限于此,比如现在css、js、图片也是通过这个协议传输的。因此 HTTP 已经成为了 Web 领域一种通用的传输协议。
我们通常使用的网络(包括互联网)是在 TCP/IP 协议族的基础上运作的。而 HTTP 属于它内部的一个子集。可以说 HTTP 是基于 TCP/IP 的协议,其在 TCP/IP 的四层结构中属于应用层。
HTTP 与 TCP/IP 族的关系
TCP/IP 协议族(簇)是可分为四层,从上到下分别是:应用层、传输层、网络层、链路层。客户端和服务端都是这样的结构,只是他们的数据流方向相反。每一层都会有不同的协议对经过的数据包进行封装(或解包)。
本文重点不在讨论 TCP/IP 协议族,我们这里只选取与 HTTP 关联性较强的三个进行介绍:IP、TCP 和 DNS 。
不想看过多理论性论述的朋友,理解下面的图就够了,可完美解答“输入一个网址经历了什么”这个常见问题:
IP
全称 Internet Protocol,即互联网协议。在 TCP/IP 的四层结构中位于网络层,作用是把各种数据包传送给对方。
实际上很少有传输双方都在同一局域网中的情况,因而一般需要经过多次路由转发。
IP 间的通信依赖 MAC(Media Access Control Address)地址。 IP 地址可变换,但 MAC 地址基本上不会更改。
会采用 ARP(Address Resolution Protocol)协议对 IP 和 MAC 进行转换。 ARP 是一种用以解析地址的协议,根据通信方的 IP 地址就可以反查出对应的 MAC 地址。
TCP
全称 Transmission Control Protocol,即传输控制协议。在 TCP/IP 的四层结构中位于传输层,提供可靠的字节流服务。
所谓的字节流服务是指,为了方便传输, 将大块数据分割成以报文段为单位的数据包进行管理。而可靠的传输服务是指,能够把数据准确可靠地传给对方。
为了准确无误地将数据送达目标处,TCP 协议采用了三次握手(three-way handshaking)策略,它是在 HTTP 数据发送前完成的,所以这里就不展开了。
DNS
全称 Domain Name System,即域名系统。在 TCP/IP 的四层结构中位于应用层,提供域名到 IP 地址之间的解析服务 。
这是我们可以通过域名访问网站的基础。DNS 协议提供通过域名查找 IP 地址,或逆向从 IP 地址反查域名的服务。
发展简史
- 1990年10月
万维网之父 Tim Berners-Lee 最早提出了 HTTP 协议 - 1991年
HTTP/0.9 诞生(Tim 的文章) - 1994年
成立 W3C 组织 - 1996年5月
HTTP/1.0 发布(RFC1945) - 1997年1月
HTTP/1.1 发布(第一版 RFC2068,第二版 RFC2616) - 2000年5月
HTTPS 发布(RFC2818) - 2015年5月
HTTP/2.0(取代SPDY协议)发布(RFC7540) - 未来
QUIC 协议,或 HTTP/3.0
特点
支持客户/服务器模式
由客户端向服务器发出请求,服务器端响应请求,并进行相应服务。能够明确区分哪端是客户端,哪端是服务器端。 一般服务器不可以主动对客户端发起请求(扩展的 WebSocket 协议可以实现双工)。
简单快速
客户向服务器请求服务时,只需传送请求方法和路径。由于 HTTP 协议简单、使得 HTTP 服务器的程序规模小,因而通信速度很快。
灵活
HTTP 允许传输任意类型的数据类型,这得益于有 Content-Type 这个报文头的设计,发送方可以告知接收方实体主体的媒体类型,接收方能以此正确解析数据。HTTP 也允许自定义报文头,这使得客户端和服务器建立某种扩展约定很方便。
无连接
限制每次 TCP 连接只处理一个请求(限 HTTP/1.0 之前)。服务器处理完客户的请求,并应答后,即断开连接。采用这种方式可以节省服务器资源,在早期只有简单文本传输的时代是适用的。但随着网页的复杂度增大,这一限制反而降低了性能,HTTP/1.0 及之后的版本加入的 keep-alive 机制一定程度上打破了这一限制。
无状态
协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。随着 Web 的不断发展,因无状态而导致业务处理变得棘手的情况增多了,为了实现期望的保持状态功能,于是引入了 cookie 技术。
报文结构
请求部分
- 请求行(Request line)
位与第一行;分为 Method(请求方法)、Path-to-resource(请求URI)、Http/Version-number(HTTP协议及版本)三部分。 - 请求报文头(Request headers)
从第二行开始至第一个空行结束;向服务器传递附加信息,形式是<key>:<value>。 - 请求报文体(Request body)
从第一个空行之后的都是正文;可选;可以自定义格式的文本,比如json格式、表单格式、二进制数据。
响应部份
- 响应行(Response line)
位与第一行;分为 Http/Version-number(HTTP 协议及版本)、Statuscode(状态码)、message(状态描述)三部分; - 响应报文头(Response headers)
从第二行开始至第一个空行结束;向客户端传递附加信息,形式是<key>:<value>。 - 响应报文体(Response body)
从第一个空行之后的都是正文;可选;可以自定义格式的文本,比如 json 格式、表单格式、二进制数据。
报文头(首部字段)
概述
HTTP 的报文头大体可以分为四类:通用报文头、请求报文头、响应报文头和实体报文头(描述报文体)。
在 HTTP/1.1 里一共规范了47种报文头,除此之外还有 Cookie、Set-Cookie 和 Content-Disposition 等非正式的报文头,它们的使用频率也很高,这些被归纳在 RFC4229 中。报文头是可以自定义的,也就是说只要客户端和服务器约定好就可以使用,这类自定义的报文头旧时会加 X 前缀来区分,但 RFC6648 已经停止这种做法。
各种报文定义参见:报文列表
格式
HTTP 的报文头是由首部字段名和字段值构成的,中间用冒号“:”分隔;字段值可以是多值,用“,”分隔。
形如:
<headerName>: <key1> = <value1>, <key2> = <value2>
“端到端”和“逐跳”
根据在代理服务器的行为不同,可将报文头分为两种:
端到端报文头(End-to-end)
分在此类别中的报文头会转发给请求 / 响应对应的最终接收目标,且必须保存在由缓存生成的响应中,另外规定它必须被转发。
逐跳报文头(Hop-by-hop)
分在此类别中的报文头只对单次转发有效,会因通过缓存或代理而不再转发。HTTP/1.1 和之后版本中,如果要使用 Hop-by-hop 首部,需提供 Connection 首部字段。
下面列举了 HTTP/1.1 中的逐跳首部字段。除这8个首部字段之外,其他所有字段都属于端到端首部。
- Connection
- Keep-Alive
- Proxy-Authenticate
- Proxy-Authorization
- Trailer
- TE
- Transfer-Encoding
- Upgrade
比如:
GET / HTTP/1.1
Upgrade: HTTP/1.1
Connection: Upgrade
=> 代理服务器移除 Connection 指定的字段,再转发 =>
GET / HTTP/1.1
请求方法
请求方法使用在请求行中,是客户端告诉服务器该执行什么样的数据操作的标记,但也仅仅只是标记作用,并没有严格意义上限制服务器的行为。
能够严格遵循这套标准的服务,比如 RESTful 架构,有利于语义化并提供客户端一定的自主性,但在非标准实现的服务器上,你甚至可以用一个 POST 方法涵盖 GET、POST、PUT、DELETE 操作。
注:下面括号内是支持的版本;省略了 LINK 和 UNLINK 这两个已经废弃的方法。
- GET(1.0、1.1)
用来请求访问已被 URI 标识的资源,会把请求的数据挂在 URL 中;对用户隐私不友好;请求的字符长度有限制(IE 最短,只支持2083)。 - POST(1.0、1.1)
一般用来传输实体的主体,目的不是获取响应主体内容;把数据放在报文体里传送。 - PUT(1.0、1.1)
和 POST 一样,用来提交数据,不同的是,PUT 是幂等的,POST 不是幂等的。 - HEAD(1.0、1.1)
和 GET 差不多,只不过是用于获取报头的,可以用来验证超链接的有效性。 - DELETE(1.0、1.1)
请求服务器删除指定资源,和 PUT 一样没有验证机制,存在安全隐患。 - TRACE(1.1)
回显服务器收到请求,用于测试或诊断。 - CONNECT(1.1)
开启一个 C 端和所请求资源之间的双向沟通的通道,比如代理服务器 proxy 来访问网站。 - OPTION(1.1)
用来查询针对请求 URI 指定的资源支持的方法,常见于发起复杂 CORS 请求的情况。
等幂性:如果一个方法或功能执行一次或者多次,结果是一样的,那么就说这个方法或功能是等幂的。
例如,设置某个用户的性别为男性,这个方法无论执行一次还是多次,它的结果都是相同的。所以,该方法具有幂等性。
例如,某个账户充值100元,这个方法执行一次和执行多次的结果是不相同的。所以,该方法不具有幂等性。
响应状态码
用以表示网页服务器超文本传输协议响应状态的3位数字代码。按首字母可分为以下五大类:
- 1xx:表示消息。代表请求已被接受,需要继续处理;只包含状态行,几乎不用。
- 2xx:表示成功。代表请求已被服务器接收、理解、接受。
- 3xx:重定向。代表需要客户端采取进一步操作才能完成请求,后续的请求地址在本次的响应 location 域中指明。
- 4xx:请求错误。代表客户端看起来可能发生了错误。
- 5xx:表示服务器错误。
完整列表请参考:状态码列表
统一资源标识符
概述
虽然统一资源标识符不是本文 HTTP 协议专有的概念,但其就是我们日常访问网站必须的东西,也就是输入在浏览器地址栏中的那个字符串。它可以是 http 开头,也可以是 ftp、mailto、telnet、file 等,通通都是“统一资源标识符”。
统一资源标识符,即 Uniform Resource Identifier,简称 URI。URI 就是由某个协议方案表示的资源的定位标识符。协议方案是指访问资源所使用的协议类型名称。
下面这些例子都是 URI:
ftp://ftp.is.co.za/rfc/rfc1808.txt
http://www.ietf.org/rfc/rfc2396.txt
ldap://[2001:db8::7]/c=GB?objectClass?one
mailto:John.Doe@example.com
news:comp.infosystems.www.servers.unix
tel:+1-816-555-1212
telnet://192.0.2.16:80/
urn:oasis:names:specification:docbook:dtd:xml:4.1.2
格式
URI = scheme:[//authority]path[?query][#fragment]
其中 authority = [userinfo@]host[:port]
图片来自维基百科:
scheme(协议名)
必选,指定使用的协议。由一系列字符组成,这些字符序列以字母开头,后跟字母,数字,加号(+),句点(.)或连字符(-)的任意组合。不区分字母大小写,最后附一个冒号。
authority(授权)
可选,其前面带有两个斜杠(//)。包含 userinfo,host,port 三部分。
- userinfo(用户信息)
可选,指定用户名和密码作为从服务器端获取资源时必要的登录信息。用分号(:)分割用户名和密码,以 @ 为结束。 - host(服务器地址 )
必选,指定服务器的 IP 地址或域名,如果是 IPv6 的地址需要加中括号([ ])。 - port(服务器端口号 )
可选,指定要访问的服务器端口。以冒号(:)为开头。http 协议默认为 80 端口。
path(带层次的文件路径 )
必选,定义比较广泛,电话号码和email也归类在此,但一般是指定服务器上的文件路径来定位特指的资源,与 UNIX 系统的文件目录结构相似。作为路径时必须以斜杠(/)开头,用若干的斜杆(/)对层级进行分割。
query(查询字符串 )
可选,针对已指定的文件路径内的资源,可以使用查询字符串传入任意参数。以问号(?)为开头,以 & 符号作为分隔(非标准规定)键值对形式的参数。
fragment(片段标识符 )
可选,使用片段标识符通常可标记出已获取资源中的子资源,一般作为客户端辅助定位用,服务器不会使用该值。以井号(#)为开头。
编码
RFC3986 文档规定,Uri/Url 中只允许包含英文字母(a-zA-Z)、数字(0-9)、- _ . ~ 4个特殊字符以及所有保留字符。除了上述字符,其他字符都应进行百分号编码。编码的意义是避免歧义的产生,并且扩大可表示的字符集范围。
百分号编码
对保留字需要取其 ASCⅡ 内码,然后加上“%”前缀进行编码;对于非ASCⅡ字符需要取其 Unicode 内码,然后加上“%”前缀进行编码。比如问号(?)会被编码为“%3F”。
使用 js 原生的 encodeURIComponent 方法可以编码大部分字符,更加全面的编码可用 qs 包的 stringify 方法。
主保留字
: / ? # [ ] @
用于分隔不同组件,如果不是作为分隔符,则需要进行百分号编码。比如冒号(:)用于分隔 scheme 和其他组件;斜杠(/)用于分割 authority 和其他组件。
副保留字
! $ & ' ( ) * + , ; =
用于在每个组件中起到分隔作用的,如果不是作为分隔符,则需要进行百分号编码。如等于号(=)用于表示 query 中的键值对,& 符号用于分隔 query 中多个键值对。
关于空格的处理
RFC1738 中会将空格编码为加号(+),但这在 RFC3986 中百分号编码为 %20,一般 %20 可向下兼容。
URI、URL与URN
- URI(Uniform Resource Identifier)
统一资源标志符,一个紧凑的字符串用来标示抽象或物理资源。 - URL(Uniform Resource Locator)
统一资源定位符,URI的子集,表示资源的地点(互联网上所处的位置)。 - URN(Uniform Resource Name)
统一资源名称,URI的子集,定义某事物的身份,不关心其访问方式与位置。
示意图:(来自维基百科)
例子:辨析https://segmentfault.com/a/1190000022295229.html#intro
- https是访问方式;
segmentfault.com/a/1190000022295229.html
是存放位置;#intro是资源 - URL即
https://segmentfault.com/a/1190000022295229.html
- URN即
segmentfault.com/a/1190000022295229.html#intro
- 两者都是URI
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。