了解HTTP协议
HTTP 简介
HTTP 是 Hyper Text Transfer Protocol,超文本传输协议,HTTP是万维网的数据通信的基础,是互联网的基石。
因为 HTTP 定义了客户端(如浏览器)与服务器之间如何通信,以传输超文本(如 HTML、图片、视频等)。作为应用层协议,HTTP 基于 TCP 协议(默认端口 80),并通过请求 - 响应模型实现数据交换。
HTTP的发展是由 蒂姆·伯纳斯·李 于1989年在欧洲核子研究组织(CERN)所发起,最初版本为HTTP/0.9,仅支持 GET 方法和纯文本响应。HTTP 在 1.0 实现了允许非持久连接。
HTTP的标准制定由万维网协会(World Wide Web Consortium,W3C)和互联网工程任务组(Internet Engineering Task Force,IETF)进行协调,最终发布了一系列的RFC,其中最著名的是1999年6月公布的 RFC 2616,定义了HTTP协议中现今依旧广泛使用的一个版本——HTTP 1.1。而 HTTP 2.0 标准于 2015年5月以 RFC 7540 正式发表,取代 HTTP 1.1 成为 HTTP 的实现标准。但是,现在 HTTP 1.1 依然以易用性在广泛使用。实际上,在 2022 年,发布了 HTTP 3.0,但是用的不多,很多浏览器默认关闭 HTTP 3,因为HTTP3.0彻底放弃TCP,感兴趣的可以去看这篇文章,https://www.cnblogs.com/beatle-go/p/17957267
HTTP是一个客户端终端(用户)和服务器端(网站)请求和应答的标准(TCP)。它规定了双方如何发起请求、传递数据、返回结果,确保信息能被正确理解和处理。
通常,由HTTP客户端发起一个请求,创建一个到服务器指定端口(默认是80端口)的TCP连接。HTTP服务器则在那个端口监听客户端的请求。一旦收到请求,服务器会向客户端返回一个状态,比如”HTTP/1.1 200 OK”,以及返回的内容,如请求的文件、错误消息、或者其它信息。
而通过使用网页浏览器或者其它的工具,客户端发起一个HTTP请求到服务器上指定端口(默认端口为80)。我们称这个客户端为用户代理程序(user agent)。应答的服务器上存储着一些资源,比如HTML文件和图像。我们称这个应答服务器为源服务器(origin server)。在用户代理和源服务器中间可能存在多个“中间层”,比如代理服务器、网关或者隧道(tunnel)。而 HTTP 协议中,请求是由接受方(通常是浏览器)发起的。
设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法,通过 HTTP 或者 HTTPS 协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识。
HTTP是应用层协议
HTTP 本身是一个 应用层协议,它只关心 要做什么(比如获取网页、提交表单),但不关心 怎么传输,而 TCP 传输控制协议是互联网的传输层协议,负责确保数据可靠、有序地到达对方。IP 是计算机之间的通信协议,TCP 是应用程序之间的通信协议。
尽管TCP/IP协议是互联网上最流行的应用,但 HTTP 协议中,并没有规定必须使用它或它支持的层。事实上,HTTP可以在任何互联网协议上,或其他网络上实现。HTTP假定其下层协议提供可靠的传输。因此,任何能够提供这种保证的协议都可以被其使用。因此也就是其在TCP/IP协议族使用TCP作为其传输层。
那么,一个网络连接是由传输层来控制的,因此从根本上说不属于 HTTP 的范畴。HTTP 协议并不需要下面的传输层协议是面向连接的,仅仅需要它是可靠的。所以说,虽然HTTP在3.0换成了可靠的 UDP,但是我们依旧认为,HTTP 是一个基于 TCP/IP 通信协议来传递数据的应用层协议。
在客户端与服务端能够传递请求、响应之前,这两者间必须建立一个 TCP
链接,这个过程需要多次往返交互。为了解决为每一对请求响应都需要打开单独的TCP连接的性能问题,HTTP
1.1 引入了持久化链接,可以通过 Connection
标头来部分控制底层的 TCP 连接。HTTP/2
则更进一步,通过在一个连接中复合多个消息,让这个连接始终平缓并更加高效。
HTTP 的核心特点
HTTP 的设计简洁、灵活,也存在一些固有特性
无状态(Stateless)
HTTP 协议本身不存储客户端的任何状态信息。也就是说在 HTTP 这级别的协议,对于发送过的请求或响应都不做持久化处理。例如,当用户访问多个页面时,服务器无法区分是否为同一用户,需通过Cookie或Session等机制手动维护状态。
注意,无状态指的是在同一个连接中,两个执行成功的请求之间是没有关系的。但是并非无绘会话,借助 HTTP Cookie 就可使用有状态的会话
无连接(早期版本)
客户机(Web浏览器)和服务器之间默认不需要建立持久的连接,HTTP/1.0 默认每次请求后关闭 TCP 连接,导致频繁握手开销。HTTP/1.1 通过持久连接(Keep-Alive)允许同一连接处理多个请求 / 响应,大幅减少延迟。
媒体独立
HTTP 不限制传输的数据类型,支持 HTML、图片、视频、音频、文件等,只需通过Content-Type头指定数据格式。
HTTP 是简约的
大体上看,HTTP 被设计得简单且易读,HTTP 报文能够被人读懂并理解,向开发者提供了更简单的测试方式
HTTP是可扩展的
在 HTTP/1.0 中引入的 HTTP 标头(header)让协议扩展变得非常容易。只要服务端客户端之间对新标头的语义经过简单协商,新功能就可以被加入进来。
HTTP的工作流程
HTTP协议定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。
首先,HTTP采用了请求-响应模型。客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据。
HTTP由请求和响应构成,是一个标准的客户端服务器模型(BS架构)。HTTP协议永远都是客户端发起请求,服务器回送响应。
HTTP 请求/响应的步骤是这样的:
客户端连接到 Web 服务器,建立TCP连接
一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接。这部分涉及到了地址解析的相关内容(浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址),解析出了地址信息才能开始建立连接
该连接是通过TCP来完成的,该协议与IP协议共同构建Internet,即著名的TCP/IP协议族,因此Internet又被称作是TCP/IP网络。HTTP是比TCP更高层次的应用层协议,根据规则,只有低层协议建立之后才能,才能进行更层协议的连接,因此,首先要建立TCP连接
发送 HTTP 请求
通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据4部分组成。在这部分,客户端会把自己的请求封装成一个HTTP请求数据包,然后再封装成TCP包
服务器接受请求并返回HTTP响应
Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据4部分组成。
释放连接TCP连接
若 connection 模式为 close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若 connection 模式为 keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求
这部分涉及到了 HTTP 的连接管理
Connection: keep-alive:在HTTP/1.1中,默认情况下,连接是保持活动的,即多个请求和响应可以共享同一个TCP连接,从而避免频繁建立和关闭连接的开销。这个特性称为”持久连接”。
Connection: close:客户端或服务器可以通过
Connection: close来关闭连接,表示该连接只处理当前请求和响应,之后就关闭连接。HTTP2.0 的多路复用下面说
客户端浏览器解析HTML内容,渲染页面
客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示。
所以说,在客户端请求的时候,数据在各层协议的整体组织如下
Nginx 服务器就是我们最常用的 Web 服务器
而经过上面的内容,可以总结出来 HTTP 协议由以下四部分构成
这个结构放到下面 HTTP 消息结构再详细讲解
HTTP的请求方法
HTTP 请求方法定义了客户端与服务器之间的交互方式,不同方法对应不同的语义和行为
注意:
- 方法名称是区分大小写的。当某个请求所针对的资源不支持对应的请求方法的时候,服务器应当返回状态码405(Method Not Allowed),当服务器不认识或者不支持对应的请求方法的时候,应当返回状态码501(Not Implemented)。
- HTTP服务器至少应该实现GET和HEAD方法,其他方法都是可选的。当然,所有的方法支持的实现都应当匹配下述的方法各自的语义定义。此外,除了上述方法,特定的HTTP服务器还能够扩展自定义的方法。例如PATCH(由 RFC 5789 指定的方法)用于将局部修改应用到资源。
GET
请求获取指定资源方法,更准确的说,是向指定的资源发出“显示”请求。
使用GET方法应该只用在读取数据,而不应当被用于产生“副作用”的操作中
- 安全:不会修改服务器数据
- 幂等:多次请求同一资源的结果相同。
- 无请求体:请求参数通过 URL 的查询字符串(Query
String)传递(如
?id=123&name=test)。虽然也可以发送带请求体的 GET 请求,但是我们认为 GET 是无请求体的
GET提交的数据会放在 URL 之后,也就是请求行里面,以 ?
分割URL和传输数据,参数之间以&相连
GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制
HEAD
与GET方法一样,都是向服务器发出指定资源的请求。只不过服务器将不传回资源的本文部分。也就是,仅返回响应头,不返回响应体
它的好处在于,使用这个方法可以在不必传输全部内容的情况下,就可以获取其中“关于该资源的信息”(元信息或称元数据)。
- 安全:不修改服务器数据。
- 幂等:多次请求结果相同。
- 无请求体。
一般在检查资源是否存在、获取资源大小等时候,使用 HEAD
POST
向服务器提交数据,创建新资源,用于创建新资源或执行具有 “副作用” 的操作
- 非安全:可能修改服务器数据。
- 非幂等:多次请求可能导致创建多个资源。
- 有请求体:数据通过请求体(Request Body)传递
PUT
更新整个指定资源(若资源不存在则创建),一般在全量更新资源时候使用,我们更多认为,在向指定资源位置上传其最新内容时候,使用 PUT 方法
- 幂等:多次请求同一资源的结果相同(例如 PUT
/users/123无论调用多少次,用户 123 的信息都会被更新为最新值)。 - 有请求体:包含完整的资源数据(如更新用户 123 的所有信息)。
DELETE
请求服务器删除 Request-URI 所标识的资源。也就是,删除指定资源。
- 幂等:多次删除同一资源的结果相同(例如 DELETE
/users/123调用两次,第二次会返回 “资源不存在”,但不会导致错误)。 - 无请求体:无需传递数据。
误用非幂等方法(如 POST 删除),可能导致重复删除引起问题
PATCH
更新部分指定资源,若资源不存在则创建
- 幂等:多次请求同一资源的结果相同(例如 PATCH
/users/123每次更新部分字段,最终资源状态一致)。 - 有请求体:包含需要更新的字段(如仅更新用户的邮箱)。
OPTIONS
查询服务器支持的请求方法、响应头、跨域设置等,一般用于预检请求,CORS 用的最多
- 安全:不修改服务器数据。
- 幂等:多次请求结果相同。
- 无请求体(或可选)。听说的,确实没见过 OPTIONS 带请求体的
TRACE
回显服务器收到的请求,一般调试时候使用,就是测试服务器是否正确处理请求
- 安全:不修改服务器数据。
- 幂等:多次请求结果相同。
- 无请求体。
CONNECT
HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接(经由非加密的HTTP代理服务器)。
HTTP状态码
当浏览者访问一个网页时,浏览者的浏览器会向网页所在服务器发出请求。当浏览器接收并显示网页前,此网页所在的服务器会返回一个包含 HTTP 状态码的信息头(server header)用以响应浏览器的请求。
所有HTTP响应的第一行都是状态行,依次是当前HTTP版本号,3位数字组成的状态代码,以及描述状态的短语,彼此由空格分隔。
状态代码的第一个数字代表当前响应的类型:
- 1xx消息——请求已被服务器接收,继续处理
- 2xx成功——请求已成功被服务器接收、理解、并接受
- 3xx重定向——需要后续操作才能完成这一请求
- 4xx请求错误——请求含有词法错误或者无法被执行
- 5xx服务器错误——服务器在处理某个正确请求时发生错误
虽然 RFC 2616 中已经推荐了描述状态的短语,例如”200 OK”,“404 Not Found”,但是开发者仍然能够自行决定采用何种短语,用以显示本地化的状态描述或者自定义信息。说实话,一般是加而不是改
HTTP状态码列表:
| 状态码 | 状态码英文名称 | 中文描述 |
|---|---|---|
| 100 | Continue | 继续。客户端应继续其请求 |
| 101 | Switching Protocols | 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议 |
| 200 | OK | 请求成功。一般用于GET与POST请求 |
| 201 | Created | 已创建。成功请求并创建了新的资源 |
| 202 | Accepted | 已接受。已经接受请求,但未处理完成 |
| 203 | Non-Authoritative Information | 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本 |
| 204 | No Content | 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档 |
| 205 | Reset Content | 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域 |
| 206 | Partial Content | 部分内容。服务器成功处理了部分GET请求 |
| 300 | Multiple Choices | 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择 |
| 301 | Moved Permanently | 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替 |
| 302 | Found | 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI |
| 303 | See Other | 查看其它地址。与301类似。使用GET和POST请求查看 |
| 304 | Not Modified | 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 |
| 305 | Use Proxy | 使用代理。所请求的资源必须通过代理访问 |
| 306 | Unused | 已经被废弃的HTTP状态码 |
| 307 | Temporary Redirect | 临时重定向。与302类似。使用GET请求重定向 |
| 400 | Bad Request | 客户端请求的语法错误,服务器无法理解 |
| 401 | Unauthorized | 请求要求用户的身份认证 |
| 402 | Payment Required | 保留,将来使用 |
| 403 | Forbidden | 服务器理解请求客户端的请求,但是拒绝执行此请求 |
| 404 | Not Found | 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置”您所请求的资源无法找到”的个性页面 |
| 405 | Method Not Allowed | 客户端请求中的方法被禁止 |
| 406 | Not Acceptable | 服务器无法根据客户端请求的内容特性完成请求 |
| 407 | Proxy Authentication Required | 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权 |
| 408 | Request Time-out | 服务器等待客户端发送的请求时间过长,超时 |
| 409 | Conflict | 服务器完成客户端的 PUT 请求时可能返回此代码,服务器处理请求时发生了冲突 |
| 410 | Gone | 客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置 |
| 411 | Length Required | 服务器无法处理客户端发送的不带Content-Length的请求信息 |
| 412 | Precondition Failed | 客户端请求信息的先决条件错误 |
| 413 | Request Entity Too Large | 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息 |
| 414 | Request-URI Too Large | 请求的URI过长(URI通常为网址),服务器无法处理 |
| 415 | Unsupported Media Type | 服务器无法处理请求附带的媒体格式 |
| 416 | Requested range not satisfiable | 客户端请求的范围无效 |
| 417 | Expectation Failed(预期失败) | 服务器无法满足请求头中 Expect 字段指定的预期行为。 |
| 418 | I’m a teapot | 状态码 418 实际上是一个愚人节玩笑。它在 RFC 2324 中定义,该 RFC 是一个关于超文本咖啡壶控制协议(HTCPCP)的笑话文件。在这个笑话中,418 状态码是作为一个玩笑加入到 HTTP 协议中的。 |
| 500 | Internal Server Error | 服务器内部错误,无法完成请求 |
| 501 | Not Implemented | 服务器不支持请求的功能,无法完成请求 |
| 502 | Bad Gateway | 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应 |
| 503 | Service Unavailable | 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中 |
| 504 | Gateway Time-out | 充当网关或代理的服务器,未及时从远端服务器获取请求 |
| 505 | HTTP Version not supported | 服务器不支持请求的HTTP协议的版本,无法完成处理 |
URL
所谓的网址,就是 URL,统一资源定位符,就是标记资源在互联网上的位置,它不仅是浏览器加载资源的地址,也是 HTTP 请求的目的地。
URL 是一种结构化的字符串,用于唯一标识和定位互联网上的资源
- 告诉 HTTP 客户端(如浏览器):从哪个服务器获取资源,以及获取哪个资源。
- 包含资源的访问方式(协议)、服务器地址(主机)、资源路径(路径)、参数(查询字符串)等关键信息。
例如
1 | https://www.example.com:8080/api/users?id=123&page=1#section1 |
这个 URL 表示:通过 HTTPS
协议,访问www.example.com服务器的 8080
端口,获取/api/users路径下的资源,并传递id=123和page=1两个参数,最后定位到页面的section1部分。(#不是hash路由)
URL 的结构遵循 RFC 3986 标准,由6 个核心部分组成,按顺序排列如下:
1 | [协议]://[用户信息]@[主机]:[端口]/[路径]?[查询参数]#[片段标识符] |
其中
协议就是客户端与服务端的通信协议,不止 HTTP
协议,ftp:,file:,mailto:等也经常出现。
而用户信息是用于身份验证的用户名和密码,格式是用户名:密码,现代
URL 几乎不使用此部分,已被 HTTP
的Authorization请求头替代。
主机就是指定资源所在服务器的域名或IP 地址,若使用域名,需通过 DNS 解析为 IP 地址。
端口就是指定服务器上的服务端口号,区分同一主机上的不同服务。
路径就是指定资源在服务器上的具体位置
查询参数就是向服务器传递额外参数,以?开头,参数之间用&分隔,格式为key=value。
片段标识符是用于定位资源内部的特定部分,以#开头,后面跟片段名称,你去油管复制个视频,就是
#
标注的资源,而且片段标识符不会被发送到服务器,仅在客户端浏览器中使用,若资源是
API 接口,片段通常无效。
URL 是有编码规则的,URL
中不允许包含特殊字符(如空格、中文、&、?、#等),这些字符需要进行URL
编码,转换为%XX格式,XX为字符的 ASCII
码的两位十六进制表示。
- 保留字符:
! * ' ( ) ; : @ & = + $ , / ? # [ ](这些字符有特殊含义,若需作为普通字符使用,需编码)。 - 非保留字符:
a-z, A-Z, 0-9, - _ . ~(这些字符可直接使用,无需编码)。 - 空格:空格在 URL
中需编码为
%20(注意:+号也可表示空格,但推荐使用%20)。 - 中文 / 特殊字符:中文等非 ASCII 字符需编码为 UTF-8
格式的
%XX(如 “你好” 编码为%E4%BD%A0%E5%A5%BD)。
JavaScript
中可使用encodeURIComponent()编码查询参数,解码使用decodeURIComponent()
HTTP消息结构
HTTP消息分为两种类型:请求消息和响应消息。不同的消息,大体格式都是一样的,核心还是四个部分,请求和响应的不太一样,而且顺序固定
1 | GET /api/users?id=123 HTTP/1.1 # 请求行 |
HTTP请求格式
客户端发送一个 HTTP 请求到服务器的请求消息,包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分
下图给出了请求报文的一般格式
来看我往 osu 浏览器发送了一个下载的请求
其中,请求行非常显眼而且清晰,请求行是 HTTP 请求的第一行,也是最关键的一行,必须存在,格式为
1 | 请求方法 + 空格 + 请求目标 + 空格 + HTTP版本 + 回车换行 |
请求目标就是请求 URL,是请求的资源路径,注意,请求目标必须以 / 开头,否则会400
例如GET /api/users?page=1&size=10 HTTP/1.1,POST /api/users HTTP/1.1等都是请求行
上述的请求头就是这样的
请求头是请求行之后、空行之前的多行文本,每一行都是一个 “键值对”,作用是向服务器传递元信息,如请求的主机、数据格式、认证信息等,格式为:
1 | 头字段名: 头字段值 + 回车换行(\r\n) |
- 虽然头字段名大小写不敏感,规范推荐首字母大写,如
Host而非host - 冒号
:后必须跟一个空格,如Host: example.com,无空格会解析失败,而且每行末尾必须是\r\n - 请求头分为通用头(如
Connection)、请求头(如Host)、实体头(如Content-Type)三类 - HTTP1.1 必选 Host 请求头,因为它指定请求的服务器主机名 + 端口,其他头均为可选
那么,常用的请求头如下
| 头字段名 | 作用 | 示例 |
|---|---|---|
| User-Agent | 标识客户端类型(浏览器 / 手机 / 爬虫),服务器可据此返回适配内容 | User-Agent: Mozilla/5.0 (Windows NT 10.0; Chrome/120.0.0.0) |
| Content-Type | 声明请求体的格式(仅 POST/PUT/PATCH 有请求体时需要) | Content-Type: application/json(JSON 格式)Content-Type: application/x-www-form-urlencoded(表单格式)Content-Type: multipart/form-data(文件上传) |
| Content-Length | 声明请求体的字节长度(服务器据此判断请求体是否完整) | Content-Length: 27(表示请求体有 27 个字节) |
| Authorization | 传递认证信息(如 Token、账号密码) | Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9… |
| Accept | 声明客户端能接受的响应格式(如 JSON/XML/HTML) | Accept: application/json, text/html |
| Cookie | 传递客户端的 Cookie 信息(维持会话) | Cookie: sessionid=123456; username=zhangsan |
| Connection | 声明连接类型(keep-alive 表示长连接,close 表示短连接) | Connection: keep-alive |
下属是一个完整的请求头示例
空行是单个回车换行(),作用是告诉服务器:请求头已经结束,接下来是请求体(如果有的话),而且空行必须存在,即使没有请求体,注意空行不能有任何字符(包括空格、制表符),否则服务器会把后续内容当成请求头解析,导致错误;
请求体是可选部分,仅当请求需要向服务器传递数据时存在, GET/HEAD/DELETE 等方法通常无请求体(HTTP 规范允许 GET 带请求体,但这并不好)。
- 请求体的格式由
Content-Type请求头指定,请求体的长度由Content-Length指定 - 请求体是纯文本 / 二进制数据,如 JSON 字符串、表单数据、文件二进制,这是 HTTP 协议可视化的重要因素
常见请求体格式
1 | # 请求头声明格式 |
1 | # 请求头声明格式 |
1 | # 请求头声明格式(boundary是分隔符,随机生成) |
HTTP响应格式
HTTP 响应格式是服务器向客户端传递 处理结果 的核心,与请求格式相对应,也整体遵循文本格式,所以 HTTP 响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。例如
1 | 200 OK # 状态行 |
状态行是 HTTP 响应的第一行,也是最关键的一行,必须存在,格式为:
1 | HTTP版本 + 空格 + 状态码 + 空格 + 状态描述 + 回车换行(\r\n) |
HTTP 版本就是要声明一下使用的 HTTP
协议版本,状态码是响应体返回的,状态描述就是对状态码的解释,回车换行还是\r\n
响应头是状态行之后、空行之前的多行文本,每一行都是一个 “键值对”,作用是向客户端传递元信息(如响应的内容格式、长度、缓存策略、认证信息等),格式和请求头一样:
1 | 头字段名: 头字段值 + 回车换行(\r\n) |
- 头字段名大小写不敏感,规范推荐首字母大写
- 一样,冒号
:后必须跟一个空格,而且每行末尾必须是\r\n; - 响应头可以有任意多行,分为通用头(如
Date)、响应头(如Content-Type)、实体头(如Content-Length)三类。
那么常用的响应头如下
| 头字段名 | 作用 | 示例 |
|---|---|---|
| Date | 声明响应的发送时间(通常为 GMT 格式) | Date: Mon, 10 Jul 2023 12:00:00 GMT |
| Content-Type | 声明响应体的格式(服务器据此告诉客户端如何解析数据) | Content-Type: application/json(JSON 格式)Content-Type: text/html(HTML 格式)Content-Type: image/jpeg(图片格式) |
| Content-Length | 声明响应体的字节长度(服务器据此告诉客户端需要接收多少数据) | Content-Length: 120(表示响应体有 120 个字节) |
| Content-Encoding | 声明响应体的压缩格式(如 gzip、br) | Content-Encoding: gzip(表示响应体已用 gzip 压缩) |
| Cache-Control | 声明客户端的缓存策略(控制浏览器是否缓存响应体) | Cache-Control: max-age=31536000(缓存 1 年)Cache-Control: no-cache(不缓存) |
| Expires | 声明响应体的过期时间(与 Cache-Control 类似,优先级低于 Cache-Control) | Expires: Mon, 10 Jul 2024 12:00:00 GMT |
| Location | 声明重定向的目标 URL(仅 3xx 状态码使用) | Location: https://www.example.com |
| Set-Cookie | 向客户端设置 Cookie 信息(维持会话,如 sessionid) | Set-Cookie: sessionid=123456; Path=/ |
| WWW-Authenticate | 声明认证方式(仅 401 状态码使用,如 Basic Auth) | WWW-Authenticate: Basic realm=“example.com” |
| Server | 声明服务器软件(如 Nginx、Apache、Tomcat) | Server: Nginx/1.21.6 |
| X-Frame-Options | 声明是否允许在 frame/iframe 中嵌入页面(防止点击劫持) | X-Frame-Options: DENY(禁止嵌入) |
| X-XSS-Protection | 声明 XSS 防护等级(防止跨站脚本攻击) | X-XSS-Protection: 1; mode=block |
例如,我加载 osu 的主页图
空行是单个回车换行(),作用是告诉客户端:“响应头已经结束,接下来是响应体(如果有的话)”。
- 一样,空行必须存在,即使没有响应体,而且空行不能有任何字符
响应体是可选部分,仅当服务器需要向客户端传递数据时存在(如成功返回资源、错误返回错误信息),204 No Content、304 Not Modified 等状态码通常无响应体。
- 响应体的格式由
Content-Type响应头指定,响应体的长度由Content-Length指定 - 响应体是纯文本 / 二进制数据,请求体响应体可读几乎是HTTP可读的核心
常见响应体格式示例
1 | # 响应头声明格式 |
1 | # 响应头声明格式 |
1 | # 响应头声明格式 |
掌握这些规则后,你可以手动构造 HTTP 响应 (Telnet),来测测爆
HTTP content-type
Content-Type,如图字面含义,就是内容的类型,一般是指网页中存在的Content-Type,用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件,明确告诉服务器如何解析请求体或客户端如何解析响应体
Content-Type
的核心功能是声明数据的媒体类型(MIME Type),至于什么是
MIME 类型,下面说
在请求中(Request)
- 作用:告诉服务器 我发送的请求体数据是什么格式,服务器据此解析数据。
- 位置:位于请求头中,仅当请求有请求体时(如 POST/PUT/PATCH)需要声明。
在响应中(Response)
- 作用:告诉客户端 “我返回的响应体数据是什么格式”,客户端据此解析数据。
- 位置:位于响应头中,无论响应是否有响应体(如 204 无响应体时通常省略)。
Content-Type 的分类与常见类型
Text 文本类型
声明数据为纯文本格式,可直接阅读
类型 说明 示例场景 text/plain纯文本(无格式,如 TXT 文件) 发送普通文本 text/htmlHTML 格式 服务器返回 HTML 页面 text/jsonJSON 格式 API 接口返回 JSON 数据 text/xmlXML 格式 旧版 API 接口 text/cssCSS 格式 服务器返回 CSS 样式文件 text/javascriptJavaScript 格式 服务器返回 JS 脚本文件 text/markdownMarkdown 格式 文档展示 Application 应用类型
声明数据为 应用程序专用格式,通常是结构化数据
类型 说明 示例场景 application/jsonJSON 格式(最常用,API 接口首选) RESTful API 返回 JSON 数据 application/xmlXML 格式(用于数据交换,如 SOAP、RSS) 旧版企业级 API application/x-www-form-urlencodedURL 编码格式(键值对拼接,如 key1=value1&key2=value2)网页表单提交(默认格式) application/json-patch+jsonJSON Patch 格式(用于部分更新 JSON 资源) 增量更新 API application/octet-stream二进制流格式(通用二进制数据,无特定格式) 文件上传(需结合 filename)application/pdfPDF 格式 下载 PDF 文件 application/zipZIP 格式 下载压缩包 application/x-www-form-urlencoded表单数据格式(键值对,如 key=value&key=value)传统表单提交 Multipart 多部分类型
声明数据由多个部分组成,如表单中的文本字段 + 文件上传,需通过
boundary(分隔符)分割不同部分。类型 说明 示例场景 multipart/form-data表单数据格式(支持文本字段和文件上传) 网页文件上传(如头像上传) multipart/mixed混合格式(包含多个不同类型的部分,如文本 + 图片) 邮件附件(文本 + 文件) multipart/related关联格式(部分之间有引用关系,如 HTML + 图片) 富文本邮件(HTML + 附件) Image 图片类型,Video 视频类型,Audio 音频类型
不多说了
Content-Type
除了主类型和子类型,还可以包含参数(键值对),用于补充说明数据的细节。常见参数如下:
| 参数名 | 作用 | 示例 |
|---|---|---|
charset |
声明数据的字符编码(如 UTF-8、GBK) | Content-Type: text/html; charset=UTF-8 |
boundary |
声明多部分数据的分隔符(仅multipart类型需要) |
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW |
boundary |
声明多部分数据的分隔符(仅multipart类型需要) |
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW |
filename |
声明文件的名称(仅multipart类型的文件字段需要) |
Content-Disposition: form-data; name="avatar"; filename="avatar.jpg" |
type |
声明 MIME 类型(如multipart/mixed的子类型) |
Content-Type: multipart/mixed; type="text/plain" |
MIME 类型
MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的标准,用来表示文档、文件或字节流的性质和格式。
MIME 消息能包含文本、图像、音频、视频以及其他应用程序专用的数据。
浏览器通常使用 MIME 类型,而不是文件扩展名,因为文件扩展名不够可靠,来确定如何处理URL,因此 Web 服务器在响应头中添加正确的 MIME 类型非常重要。如果配置不正确,浏览器可能会无法解析文件内容,网站将无法正常工作,并且下载的文件也会被错误处理。
MIME 类型通用结构:
1 | type/subtype |
MIME 的组成结构非常简单,由类型与子类型两个字符串中间用
/分隔而组成,不允许有空格。
type
表示可以被分多个子类的独立类别,subtype
表示细分后的每个类型。
MIME类型对大小写不敏感,但是传统写法都是小写。
两种主要的 MIME 类型在默认类型中扮演了重要的角色:
text/plain表示文本文件的默认值。application/octet-stream表示所有其他情况的默认值。
常见的 MIME 类型如下:
HTTPS
还记得 HTTP 协议有一个重要特征就是 HTTP 是简约的,也就是可读的,因为 HTTP 最初设计时数据是明文在网络上传输的,也就是任何人只要拦截了网络,就可以不费吹灰之力获取到HTTP请求/响应内容,从而非法获取信息。
HTTPS 协议是 HyperText Transfer Protocol Secure(超文本传输安全协议)的缩写,是一种通过计算机网络进行安全通信的传输协议。它相比 HTTP 更加安全,因为 HTTP 本身是不安全的,因为传输的数据未经加密,可能会被窃听或篡改,它解决了 HTTP 的三大安全问题:
- 窃听:数据在传输过程中被第三方拦截并查看内容(如监听网络流量)。
- 篡改:数据在传输过程中被第三方修改(如修改网页内容、注入恶意代码)。
- 伪造:第三方伪装成服务器或客户端,与对方建立虚假连接。
HTTPS 在 HTTP 上加入 SSL/TLS 协议,为数据传输提供了加密和身份验证。HTTPS 经由 HTTP 进行通信,但利用 SSL/TLS 来加密数据包,HTTPS 开发的主要目的,是提供对网站服务器的身份认证,保护交换资料的隐私与完整性。
可以看到,HTTPS 的主要作用是在不安全的网络上创建一个安全信道,并可在使用适当的加密包和服务器证书可被验证且可被信任时,对窃听和中间人攻击提供合理的防护。
HTTP 的 URL 是由 http:// 起始与默认使用端口 80,而 HTTPS 的 URL 则是由 https:// 起始与默认使用端口443。
那么,加密是如何实现的,SSL/TLS协议的基本思路是采用 非对称加密+对称加密的综合模式。
首先,HTTPS 的通信开始于握手阶段,客户端与服务器通过一系列步骤建立安全连接。
客户端发起请求
客户端(浏览器)向服务器发送 HTTPS 请求,请求包含:
- 支持的 SSL/TLS 版本(如 TLS 1.2、TLS 1.3)。
- 支持的加密套件(如 AES-256-GCM、RSA-2048)。
- 支持的哈希算法(如 SHA-256)。
服务器收到请求后,返回:
服务器证书:由权威 CA(证书颁发机构)颁发,包含服务器的公钥、域名、有效期等信息。
加密套件列表:从客户端的列表中选择一个双方都支持的加密套件。
服务器公钥:用于加密后续的会话密钥。
客户端验证服务器证书
客户端验证服务器证书的有效性,如果合法,就会生成一个随机码,然后用服务器的公钥加密这个随机码,发送给服务器。
客户端生成会话密钥
客户端生成一个随机的会话密钥(如 AES-256 的密钥),并使用服务器公钥加密该密钥,发送给服务器。
服务器端解密会话密钥
服务器收到加密的会话密钥后,使用自己的私钥解密,得到会话密钥。
开始建立安全通信
客户端和服务器使用会话密钥进行对称加密通信,此后的所有数据都通过会话密钥加密传输,第三方即使拦截到数据包,也无法解密内容。
所以,HTTPS 使用非对称加密和对称加密结合的方式,既保证密钥的安全传输,又保证数据的高效加密。非对称加密就是使用一对密钥(公钥和私钥),公钥加密的数据只能用私钥解密,私钥加密的数据只能用公钥解密。而对称加密就是使用同一密钥进行加密和解密
HTTPS 的信任基于预先安装在操作系统中的证书颁发机构(CA)。HTTPS 通过证书验证和域名匹配机制,确保客户端与服务器的身份真实可靠。
因为服务器证书由权威 CA 颁发,客户端内置了根 CA 的公钥,通过根 CA 验证服务器证书的签名,确保证书未被篡改且由可信 CA 颁发。而证书中包含服务器的域名,客户端验证证书中的域名与当前访问的域名一致就可以达到匹配域名。
HTTP2一些值得注意的特点
二进制分帧
HTTP/2 最核心的底层优化是引入了二进制分帧层,它将所有传输的内容(请求头、请求体、响应头、响应体)都拆分为更小的帧(Frame),每个帧包含类型、标志、流 ID、长度等元数据。
所以说,HTTP2 从 HTTP1 的文本协议,变成了二进制协议
那么解析方式也从逐行解析变成了按帧解析,因为二进制格式比文本格式更紧凑,解析效率自然高。而且二进制格式减少了传输过程中的开销,降低了网络延迟。好处不言而喻
多路复用
简单的说,HTTP/2 允许同时发送多个请求和响应,而不是像 HTTP/1.1 一样只能一个一个地处理。
HTTP/2 的多路复用是基于 流(Stream)的,每个流是一个独立的双向字节流。流的创建和管理由 HTTP/2 的分帧层处理。
这样可以减少延迟,提高效率,提高网络吞吐量。这解决了 HTTP/1.1 的 队头阻塞(Head-of-line Blocking)问题。
什么是队头阻塞
HTTP/1.1 中,在同一个 TCP 连接上,一个请求的响应未完成时,后续请求必须等待,即使其他请求已准备好。例如:
- 客户端发送请求 1(请求一个大图片)。
- 服务器开始处理请求 1,但由于图片较大,响应需要一段时间。
- 客户端发送请求 2(请求一个小 CSS 文件)。
- 由于请求 1 的响应未完成,请求 2 被阻塞,直到请求 1 的响应完成。
这种情况下,请求 2 必须等待请求 1 完成,即使请求 2 的数据量很小,这会导致整体性能下降。
HTTP/2 通过为每个请求 / 响应分配一个唯一的流 ID(Stream ID),不同流的帧可以在同一个 TCP 连接上并行传输。
- 客户端发送请求 1(流 ID=1)。
- 客户端发送请求 2(流 ID=2)。
- 服务器处理请求 1 和请求 2,同时发送响应 1 和响应 2。
- 客户端根据流 ID 将响应 1 和响应 2 分配给对应的请求。
这样,即使请求 1 的响应未完成,请求 2 也可以继续处理,不会被阻塞。
头部压缩
HTTP/2 使用 HPACK 算法对 HTTP 头部进行压缩,减少了头部传输的数据量,从而减少了网络延迟。
HPACK 算法的核心思想是维护一个动态字典,用于存储之前交换过的头部字段。当发送一个头部字段时,如果该字段已经在字典中,则只发送字段的索引,而不是完整的字段名和值。
HTTP/2 的头部压缩是在分帧层实现的,每个请求和响应的头部都会被压缩后传输。
服务器推送
HTTP/2 支持服务器推送,允许服务器在客户端请求之前推送资源,以提高性能。
服务器推送是指服务器在响应一个请求时,主动推送客户端可能需要的其他资源(如图片、CSS、JS 等)。
服务器主动推送资源,那么就减少了客户端的请求次数。那么客户端可以更快地获取所需的资源,提高了页面加载速度。
实际上,服务器推送是在分帧层实现的,服务器可以通过发送 推送帧(Push Frame) 来推送资源。
默认长连接
HTTP/2 默认使用长连接,一个连接可以处理多个流(请求 / 响应),而不是像 HTTP/1.1 一样需要频繁地建立和关闭连接。HTTP/2 的长连接是在分帧层实现的,一个连接可以处理多个流
HTTP/2 的长连接管理由 流(Stream) 的创建和关闭来控制。当一个流完成后,该流的资源会被释放,连接可以继续处理其他流。
必须使用 HTTPS
- HTTP1.1:可通过 HTTPS(HTTP+SSL/TLS)加密传输,但无特殊优化。
- HTTP2.0:必须使用 HTTPS(因二进制分帧层依赖 TLS 加密),且支持 TLS 的所有扩展(如 TLS 1.3),安全性更高。
HTTP/2 是在 HTTPS 的基础上实现的,它使用 HTTPS 的加密功能来保护数据传输,同时通过二进制分帧层提高了传输效率。
HTTP/2 支持 TLS 的所有扩展(如 TLS 1.3),提供了更高的安全性。TLS 1.3 的握手过程更快,加密效率更高
兼容性
HTTP/2 可以与 HTTP/1.1 共存,服务器可以同时支持 HTTP/1.1 和 HTTP/2。如果客户端不支持 HTTP/2,服务器可以回退到 HTTP/1.1。







