HTTP 报文是浏览器和服务器之间沟通的语言。每次我们访问一个网页,背后其实都在进行一次信息交换,而这个交换的过程就是通过 HTTP 报文来实现的。HTTP 报文负责将我们的请求传达给服务器,然后再把服务器的回应带回给我们。无论是打开一张图片,还是提交一段评论,所有的操作都离不开 HTTP 报文的支持。
本文涉及的技术名词: HTTP 报文请求头响应头Content-Lengthcurl
大家好,我是 HQ。国庆小长假刚刚结束,大家是不是还沉浸在假期的悠闲中呢?不过,假期归假期,技术学习可不能落下。最近,总想重温一下 HTTP 的相关知识,查漏补缺,更好地夯实基础。这样不仅能帮助自己深入了解 HTTP 报文的结构和作用,也更有利于前端工作的开展。接下来的内容,我主要想深入探讨下HTTP报文相关的结束点. 好, 就从什么事报文开始吧 .
什么是 HTTP 报文?
“HTTP 报文是浏览器和服务器之间交流信息的载体。报文主要分为两种类型:请求报文(由客户端发送给服务器,请求资源或执行操作)和 响应报文(由服务器返回给客户端,回应请求的结果)。两种报文的结构基本相同,都由三大部分组成:
- 起始行(start line) :这是报文的开场白,用来描述请求或响应的基本信息。对于请求报文,起始行会说明要执行的操作(例如
GET
请求一个网页或者POST
提交数据)以及目标资源的地址;而在响应报文中,起始行则表明服务器的状态(例如200 OK
表示请求成功,404 Not Found
表示资源不存在)。 - 头部字段集合(header) :这部分类似于附带的说明信息,采用
key-value
形式,提供关于报文的更多细节。比如,头部字段可以告诉接收方传输的数据类型、长度,或者指定服务器支持的特性。头部字段的作用就像给数据贴上标签,方便浏览器或服务器正确处理后续内容。 - 消息正文(entity) :也称为‘实体’,这里放置的是实际传输的数据,可以是文本、图片、视频等多种类型。虽然消息正文并非每次都需要,比如一些请求只需要头部信息即可完成操作,但在大多数情况下,数据传输都会使用这一部分。
值得注意的是,起始行和头部字段经常合称为‘请求头’或‘响应头’,而消息正文通常被称为‘body’。HTTP 协议规定,报文中必须有头部字段(header),但可以没有消息正文(body)。此外,在头部字段之后必须包含一个‘空行’,即回车换行符(CRLF),其十六进制表示为‘0D0A’,这标志着头部字段的结束。”
可以把 HTTP 报文想象成一封信:
- 起始行就像信的标题,概述这封信的主题(比如“请求商品目录”)。
- header则是信的详细说明,例如发送人地址、优先级等附加信息。
- 空白行就好比信封和信纸之间的空白区域,用来区分内容部分。
- body(消息正文)则是信的主要内容部分,如果有的话。
接下来,我们通过一个简单的登录接口来演示 HTTP 报文的实际样子,并使用 curl
命令进行操作。curl
是一个命令行工具,可以方便地向服务器发送请求,并查看返回的响应,非常适合用于理解 HTTP 报文的结构和内容。
curl -v
参数启用详细模式(verbose mode),它会输出请求和响应的详细信息,包括请求头、响应头、SSL 握手等。这对于调试请求
curl-i
参数用于 包含响应头(include response headers)在输出中,这意味着它会同时显示服务器的响应头和响应体内容。
我们以百度为例子:
curl www.baidu.com -v -i
GET / HTTP/1.1
Host: www.baidu.com
User-Agent: curl/8.0.1
Accept: */*
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Connection: keep-alive
Content-Length: 2381
Content-Type: text/html
Etag: "588604dc-94d"
Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
在请求www.baidu.com请求报文中,第一行 "GET / HTTP/1.1"
就是请求行,用于描述客户端要做的操作。在这个例子中,GET
是请求方法,表示客户端希望获取资源;/
是资源路径,代表服务器的根目录;HTTP/1.1
则表示使用的协议版本。
紧接着的 "Host"
、"Connection"
等字段都是属于header(头部字段)的部分,它们提供了请求的额外信息,比如服务器的主机名、连接的管理方式等。报文的最后有一个空白行来标志头部字段的结束。
需要注意的是,在很多情况下,特别是当浏览器发送 GET
请求时,HTTP 报文通常只有 header 而没有 body。这是因为 GET
请求的主要目的是从服务器获取数据,而不需要上传额外的内容,所以报文的结构就变得比较简洁。
请求行
了解了 HTTP 报文的基本结构后,我们来深入看看请求报文中的起始行,也就是请求行(request line) 。这个部分就像是报文的标题,告诉服务器客户端想要做什么。它包含了三个关键的元素:请求方法、资源路径和 HTTP 版本号,分别解释客户端的意图、目标资源和通信标准。
请求行由三部分组成:
- 请求方法:用来说明客户端希望执行的操作,就像不同的动词表示不同的动作。例如,
GET
表示获取资源,POST
表示提交数据,PUT
用于更新资源,而DELETE
则用来删除资源。每种方法都有特定的用途,就像我们日常生活中说“拿”、“放”、“改”、“删”一样,操作不尽相同。 - 资源路径:描述客户端希望访问的服务器资源位置。例如,
/
表示一个请求资源路径 - HTTP 版本号:指示使用的 HTTP 协议版本,例如 HTTP/1.1 或 HTTP/2.0。这个版本号确保客户端和服务器之间可以按照统一的标准进行对话,就像讲同一种语言一样,避免误解。
这三个部分通常使用空格(space)来分隔,最后要用 CRLF 换行表示结束。
GET / HTTP/1.1
状态行
看完了请求行,我们再来看看响应报文中的起始行。在响应报文中,它不叫“响应行”,而是被称为状态行(status line) ,用于描述服务器的响应状态,告诉客户端请求的结果如何。
与请求行相比,状态行的结构更简单,同样由三部分组成:
- HTTP 版本号:指明服务器使用的 HTTP 协议版本,例如 HTTP/1.1 或 HTTP/2.0。这就像是告诉客户端,双方在使用同一套“语言”进行交流。
- 状态码:一个三位数字,表示请求的结果状态。例如,
200
表示“请求成功”,404
表示“资源未找到”,500
则代表“服务器内部错误”。这些状态码可以看作是服务器给出的“分数”,用来评价请求的处理情况。 - 原因短语:对状态码的简要描述,例如 “OK” 对应
200
,表示成功;“Not Found” 对应404
,表示资源找不到。这就像是给“分数”加上一段注解,让客户端更容易理解具体的响应情况。
状态行的格式通常为:<HTTP版本号> <状态码> <原因短语>
。例如,我们请求的www.baidu.com的状态行如下:
HTTP/1.1 200 OK
这意味着服务器使用 HTTP/1.1 版本的协议,响应状态是 200 OK
,表示请求处理成功。
头部字段
在请求报文或响应报文中,请求行(或状态行)加上头部字段集合,就构成了完整的请求头或响应头。头部字段是 HTTP 报文的重要组成部分,用于携带各种附加信息,帮助浏览器和服务器更好地处理请求和响应。
头部字段通常以 key-value
形式表示,每个字段都代表一个特定的信息,比如请求的主机名、内容类型、数据长度等。可以把它们想象成一组“标签”,为报文提供了详细的说明,就像在包裹上贴上的快递标签,标注了收件人、寄件人、重量和注意事项。
这里有两个示意图,可以帮助大家更直观地理解请求头和响应头的结构,图中分别展示了请求头和响应头的基本组成部分:
请求头和响应头的结构基本相同,唯一的区别在于起始行的不同。因此,我们可以把请求头和响应头的字段放在一起讲解。头部字段以 key-value
形式表示,键(key)和值(value)之间用冒号(:
)分隔,每个字段用 CRLF(回车换行符)表示结束。例如,"Host: www.baidu.com"
这一行中,"Host"
是键,"www.baidu.com"
是值。
HTTP 头字段具有很高的灵活性,除了使用标准的头字段(如 Host
、Connection
等),还可以添加自定义的头字段,从而为 HTTP 协议带来无限的扩展可能性。想象一下,就像在信件里添加附加信息,无论是收件人的公司部门,还是发送的优先级,都可以通过添加合适的“标签”来实现。
在使用头字段时需要注意以下几点规则:
- 字段名不区分大小写:例如,
"Host"
和"host"
是等价的,不过为了保持可读性,建议采用首字母大写的形式。 - 字段名不能包含空格:字段名可以使用连字符(
-
),但不能使用下划线(_
)。例如,"test-name"
是合法的字段名,而"test name"
和"test_name"
是不正确的。 - 字段名后面紧跟冒号(
:
) :字段名和冒号之间不能有空格,但冒号后面的字段值前可以有多个空格。例如,"Host: 127.0.0.1"
是正确的,而"Host : 127.0.0.1"
则是不正确的。 - 字段的顺序没有影响:头字段的排列顺序是没有固定要求的,可以任意排列,不会影响报文的语义。
- 字段原则上不应重复:除非某些字段允许多次出现,比如
Set-Cookie
,可以用于设置多个 Cookie 值。
常用头字段
Host 是一个请求头字段,只能出现在请求头中,而且是唯一一个在 HTTP/1.1 规范中被要求必须出现的字段。这意味着,如果请求头里没有 Host 字段,那这个请求报文就是不符合规范的错误报文。
在实际应用中,Host 字段的作用是告诉服务器请求应该由哪个主机来处理。以 Nginx 为例,当一台服务器上配置了多个虚拟主机时,不同的网站可能使用同一个 IP 地址,但根据不同的域名提供服务。Nginx 会根据请求中的 Host 字段来区分这些网站,决定将请求分发给哪个虚拟主机进行处理。
举个例子,假设同一台服务器上托管了 example.com
和 test.com
两个网站。Nginx 会通过 Host 字段来判断当前请求的目标是 example.com
还是 test.com
,然后将请求路由到对应的网站目录。这就像一个门卫查看访客的预约信息,然后指引他们前往正确的目的地一样。其配置示例如下:
server {
listen 80;
server_name example.com;
location / {
root /var/www/example;
index index.html;
}
}
server {
listen 80;
server_name test.com;
location / {
root /var/www/test;
index index.html;
}
}
当 Nginx 收到请求时,会检查请求头中的 Host
字段,根据配置的 server_name
匹配到对应的 server
块,从而将请求转发到相应的网站目录。
User-Agent 是 HTTP 请求头中的一个常用字段,用来标识请求的客户端信息。它告诉服务器发起请求的客户端类型,比如浏览器的名称、版本、操作系统等。这就像是请求者的“身份证”,帮助服务器了解是谁在访问; 我们来看看上面用curl访问百度的User-Agent是什么样的:
User-Agent: curl/8.0.1
如果用Chrome浏览器访问 **www.baidu.com
我们看到User-Agent
大概是这样Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36
值得注意的是, User-Agent
字段的内容是可以伪造的,客户端完全可以手动修改该字段,所以不能完全依赖它来判断客户端的真实身份。但它依然是服务器了解客户端基本信息的重要参考。
Date 字段 是 HTTP 报文中的一个通用字段,尽管理论上可以出现在请求头中,但它通常出现在响应头里,表示服务器生成响应报文的时间。这个时间是按照格林威治标准时间(GMT)表示的,格式类似于:
Date: Thu, 10 Oct 2024 10:00:00 GMT
Date字段的电影应用场景有:
- 缓存策略的决策:客户端可以根据 Date 字段的时间信息,结合其他与缓存相关的字段(如
Cache-Control
和Expires
),决定是否需要从服务器获取最新的资源,还是可以使用本地缓存的副本。例如,如果本地缓存的资源比 Date 字段标记的时间更新,那么可以直接使用缓存数据。 - 时钟同步:虽然不是主要用途,但客户端可以参考 Date 字段来同步本地时钟,特别是在分布式系统中,确保时间的一致性很重要。
- 调试和日志记录:开发者可以通过 Date 字段来分析服务器生成响应的时间,帮助排查延迟或性能问题。
Date 字段仅表示服务器生成响应的时间,而不是客户端接收到响应的时间。因此,网络延迟可能导致客户端实际接收数据的时间有所不同。
Server 字段 是 HTTP 响应头中的一个字段,用于告诉客户端当前提供 Web 服务的软件名称和版本号。它只能出现在响应头里,表示服务器的身份信息,让客户端知道请求是由什么样的服务器处理的; 例如,当我们访问百度时, 响应头的Server是: Server: bfe/1.0.8.18``Server
,其中 bfe
代表百度自研的前端负载均衡服务器(Baidu Front End),1.0.8.18
则是该软件的版本号。这个字段表明,百度使用 BFE
服务器来处理请求,并且提供了具体的版本信息。
需要注意的是,Server 字段并不是必须要出现的。虽然它可以提供服务器软件的名称和版本信息,但这也会带来一定的安全风险。因为当服务器的具体版本被暴露出来时,如果这个版本恰好存在已知的漏洞或 bug,就可能被黑客利用,从而对服务器进行攻击。
正因为如此,一些网站在响应头中要么不包含 Server
字段,要么返回一个完全无关的描述信息。例如,服务器可能会设置 Server
字段为一个通用的名称,如 "Server: webserver"
,或者故意模糊处理成 "Server: unknown"
,来隐藏真实的服务器信息。
这种做法相当于给服务器戴上“面具”,避免让外界轻易得知它的真实身份。虽然不能完全防止攻击,但至少可以减少因版本信息泄露而带来的安全风险。
出于安全性考量,很多公司会在服务器配置中隐藏 Server
字段或将其值设置为通用描述,防止黑客通过简单的头信息获取到关于服务器的详细信息,从而减少潜在的攻击面。
Content-Length 字段 是 HTTP 报文中的一个重要头字段,用来表示消息正文(body)的字节长度。如Content-Length: 2381
它告诉接收方接下来要读取的数据有多大,使得客户端或服务器知道传输的数据量。这在 HTTP 报文中扮演着“数据计量器”的角色,帮助通信双方准确处理数据的接收和发送。
Content-Length字段的主要作用
- 数据完整性:客户端或服务器通过
Content-Length
字段,能够判断数据传输是否完成。例如,如果实际接收的数据少于Content-Length
指定的长度,接收方就会认为数据不完整,可能会触发重试机制。 - 持久连接的管理:在使用 HTTP/1.1 持久连接时,服务器可以通过
Content-Length
来判断本次响应的结束位置,然后保持连接以供下一个请求使用。这有助于提高网络效率,减少频繁建立和关闭连接的开销。 - 适应各种传输场景:当需要分块或多段传输数据时(如文件上传),
Content-Length
字段可以用于预先告知数据总量,让接收方提前做好准备。此外,某些需要计算带宽使用率的场景,也会利用这个字段来进行流量统计。
总结
- HTTP 报文是网络通信中的核心组件,负责在客户端和服务器之间传递信息。通过请求报文和响应报文的交互,我们能够实现从网页访问到数据传输的各种操作。理解报文的结构和常见的头字段有助于我们更好地掌握 HTTP 协议的工作原理。
- 请求报文和响应报文的基本结构相似,都是由起始行(请求行或状态行)、头部字段(header)、消息正文(body)三大部分组成。头部字段起到了丰富报文信息、指导数据处理的作用。
- 常见的头字段,如
Host
、User-Agent
、Date
、Server
和Content-Length
,在实际应用中各自发挥着不同的功能:
-
Host
字段用于确定请求的目标服务器,特别适用于多虚拟主机的场景。User-Agent
字段帮助服务器了解客户端的设备和软件环境,有助于内容定制和兼容性处理。Date
字段表示响应生成的时间,常用于缓存策略和调试分析。Server
字段告诉客户端当前处理请求的服务器类型及版本,但需要注意安全性问题。Content-Length
字段用于标识消息正文的长度,以确保数据传输的完整性。
- 头字段不仅可以使用标准定义的内容,还可以根据需求自定义,极大地增强了 HTTP 协议的灵活性。然而,正确地使用头字段也需要遵循一些规则,以保证通信的稳定和安全。