HTTP 协议是当前 web 开发中最核心的协议,只要使用网站,就会用到 HTTP 协议。
HTTP 是一种 一问一答式的协议:客户端发送一个请求,服务器就返回一个请求,请求和服务一一对应。
接下来,我们来学习 HTTP 的报文格式(借助 fiddler 抓包工具进行学习)
一. HTTP 报文格式
首先,我们先打开 fiddler 抓包工具,再打开 b站,让 fiddler 对我们这个打开 b站 的请求与对应的响应进行抓包
1.1 请求的抓包数据如下
GET https://api.bilibili.com/x/internal/gaia-gateway/ExGetAxe HTTP/1.1
Host: api.bilibili.com
Connection: keep-alive
sec-ch-ua-platform: "Windows"
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36 Edg/150.0.0.0
sec-ch-ua: "Not;A=Brand";v="8", "Chromium";v="150", "Microsoft Edge";v="150"
sec-ch-ua-mobile: ?0
Accept: */*
Origin: https://www.bilibili.com
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://www.bilibili.com/
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: buvid3=5F29E282-1A87-40C1-C34F-83219087B07270003infoc;b_nut=1758374970; _uuid=1010E98CF1-23AB-B342-C28A-EA55B64675DA70423infoc; CURRENT_FNVAL=4048; CURRENT_QUALITY=0; buvid_fp=c4c4375bad36dab40af5ea4f5a85361c; buvid4=5BB8DB59-54F8-5895-158E-B4A4ADFF714371006-025092021-MhEwiVsH2OzS453Vvq0lkQ%3D%3D; rpdid=|(u)~mkRJ)ml0J'u~l)J~Rm~l; bsource=search_bing; home_feed_column=4; browser_resolution=1014-983; bili_ticket=eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3ODQ0MzIyNjgsImlhdCI6MTc4NDE3MzAwOCwicGx0IjotMX0.M2stIKcgjQDYB5IasQqgjZSqED3E_ksqromyYs4iYSg; bili_ticket_expires=1784432208; b_lsid=D3AAAC83_19F69008134
请求共有四个部分组成:1.首行 2.请求头 3.空行 4.正文
以上述的抓包结果展示,请求的各个部分如下
1.首行(请求方法 + URL + HTTP协议版本号)
GET https://api.bilibili.com/x/internal/gaia-gateway/ExGetAxe HTTP/1.1
2.请求头(header,键值对结构,键与值之间用 :分割)
Host: api.bilibili.com
Connection: keep-alive
sec-ch-ua-platform: "Windows"
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36 Edg/150.0.0.0
sec-ch-ua: "Not;A=Brand";v="8", "Chromium";v="150", "Microsoft Edge";v="150"
sec-ch-ua-mobile: ?0
Accept: */*
Origin: https://www.bilibili.com
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://www.bilibili.com/
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: buvid3=5F29E282-1A87-40C1-C34F-83219087B07270003infoc;b_nut=1758374970; _uuid=1010E98CF1-23AB-B342-C28A-EA55B64675DA70423infoc; CURRENT_FNVAL=4048; CURRENT_QUALITY=0; buvid_fp=c4c4375bad36dab40af5ea4f5a85361c; buvid4=5BB8DB59-54F8-5895-158E-B4A4ADFF714371006-025092021-MhEwiVsH2OzS453Vvq0lkQ%3D%3D; rpdid=|(u)~mkRJ)ml0J'u~l)J~Rm~l; bsource=search_bing; home_feed_column=4; browser_resolution=1014-983; bili_ticket=eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3ODQ0MzIyNjgsImlhdCI6MTc4NDE3MzAwOCwicGx0IjotMX0.M2stIKcgjQDYB5IasQqgjZSqED3E_ksqromyYs4iYSg; bili_ticket_expires=1784432208; b_lsid=D3AAAC83_19F69008134
3.空行(标识 header 部分的结束)
4.正文(GET 请求的 正文部分一般是空白的)
1.2 响应的抓包数据如下
HTTP/1.1 200 OK
Date: Thu, 16 Jul 2026 03:37:49 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH
Access-Control-Allow-Origin: https://www.bilibili.com
Access-Control-Expose-Headers: x-bili-gaia-vvoucher,x-bili-trace-id,x-bili-gaia-param
Bili-Status-Code: 0
Bili-Trace-Id: 66f3c388d56a5852
Cpu_usage: 181
Vary: Origin
X-Bili-Trace-Id: 0cc4abeaa06ebde666f3c388d56a5852
Cross-Origin-Resource-Policy: cross-origin
Access-Control-Allow-Headers: origin,no-cache,x-requested-with,if-modified-since,pragma,last-modified,cache-control,expires,content-type,access-control-allow-credentials,dnt,x-customheader,keep-alive,user-agent,x-cache-webcdn,x-bilibili-key-real-ip,x-backend-bili-real-ip,x-risk-header,x-event-traceid
Expires: Thu, 16 Jul 2026 03:37:48 GMT
Cache-Control: no-cache
X-Cache-Webcdn: BYPASS from blzone05
Content-Length: 914{"code":0,"message":"0","ttl":1,"data":{"version":"v1","public_key":"-----BEGIN rsa public key-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1SomDTbicibEZdRNTFIf\nG0MZI9Vm+VLvXvoS6YGtHkMexd+O8ImALMxiMkydZ0h5XPPfiUXGiyfWawVW1Q6Q\nL8E9HV8tXgI82zMc/2ZnAyAp5dACWfcsqF1lH4hh63W424c9EJ/Ryqk4ZFss+vDr\nn33+FN1LOyJtg8nPqCt9DN9PFaRvJTEXT0Bt2ZQTiSznHPIpalgHUNsg1OPV3ou9\n5ahf5CpRl89QeIptrbObEqsmRqC0rDwsMUhY1NnvPKGYnCqOWl006q6OBP77qHa9\nHRLZS0EuCEuLUnKRQq2vbbpqKrgIQln+HjMy4oIjV7Bdv9e6SJSxwausegaCQ+pt\nIzy6O41iIQSISCRf+2iywvKGxvi3Jhhc39GxvhBPl9W3QF4knJmWHHbqAb94mDNE\nT0GzjbU6c3j7FJcC2JjK7PeC44+VSyiY7N+9Dc0ulw7OIoigPl+R6zyWEPCdKems\n9Cd8vn/njM+o7BRaBzyqJSYSOjQQiQHJYzcMRhhue/2W1wSx2S9Ry/zNvpP/Qapw\n4Z6IlDt+wL9omIwcFMndUruAaRKQEDgDRKZHspUIRkXhCiaV4MwUH1rF6EFq8BRc\nIsoS5O0o8ew5mzW81q2I1Mkvx2wDRyX9+zlVBMkKAvv3xMzU5l7sUM1eP3tw3mhg\n6D6utwZfuFFb7Zj1oGJq4VMCAwEAAQ==\n-----END rsa public key-----","deadline":2524608000}}
响应也是共有四个部分组成:1.首行 2.响应头 3.空行 4.正文
以上述的抓包结果展示,请求的各个部分如下
1.首行(版本号 + 状态码 + 状态码描述)
HTTP/1.1 200 OK
2.响应头(header,也是键值对结构,和请求头一致)
Date: Thu, 16 Jul 2026 03:37:49 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH
Access-Control-Allow-Origin: https://www.bilibili.com
Access-Control-Expose-Headers: x-bili-gaia-vvoucher,x-bili-trace-id,x-bili-gaia-param
Bili-Status-Code: 0
Bili-Trace-Id: 66f3c388d56a5852
Cpu_usage: 181
Vary: Origin
X-Bili-Trace-Id: 0cc4abeaa06ebde666f3c388d56a5852
Cross-Origin-Resource-Policy: cross-origin
Access-Control-Allow-Headers: origin,no-cache,x-requested-with,if-modified-since,pragma,last-modified,cache-control,expires,content-type,access-control-allow-credentials,dnt,x-customheader,keep-alive,user-agent,x-cache-webcdn,x-bilibili-key-real-ip,x-backend-bili-real-ip,x-risk-header,x-event-traceid
Expires: Thu, 16 Jul 2026 03:37:48 GMT
Cache-Control: no-cache
X-Cache-Webcdn: BYPASS from blzone05
Content-Length: 914
3.空行(标识 header 部分的结束)
4.正文
{"code":0,"message":"0","ttl":1,"data":{"version":"v1","public_key":"-----BEGIN rsa public key-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1SomDTbicibEZdRNTFIf\nG0MZI9Vm+VLvXvoS6YGtHkMexd+O8ImALMxiMkydZ0h5XPPfiUXGiyfWawVW1Q6Q\nL8E9HV8tXgI82zMc/2ZnAyAp5dACWfcsqF1lH4hh63W424c9EJ/Ryqk4ZFss+vDr\nn33+FN1LOyJtg8nPqCt9DN9PFaRvJTEXT0Bt2ZQTiSznHPIpalgHUNsg1OPV3ou9\n5ahf5CpRl89QeIptrbObEqsmRqC0rDwsMUhY1NnvPKGYnCqOWl006q6OBP77qHa9\nHRLZS0EuCEuLUnKRQq2vbbpqKrgIQln+HjMy4oIjV7Bdv9e6SJSxwausegaCQ+pt\nIzy6O41iIQSISCRf+2iywvKGxvi3Jhhc39GxvhBPl9W3QF4knJmWHHbqAb94mDNE\nT0GzjbU6c3j7FJcC2JjK7PeC44+VSyiY7N+9Dc0ulw7OIoigPl+R6zyWEPCdKems\n9Cd8vn/njM+o7BRaBzyqJSYSOjQQiQHJYzcMRhhue/2W1wSx2S9Ry/zNvpP/Qapw\n4Z6IlDt+wL9omIwcFMndUruAaRKQEDgDRKZHspUIRkXhCiaV4MwUH1rF6EFq8BRc\nIsoS5O0o8ew5mzW81q2I1Mkvx2wDRyX9+zlVBMkKAvv3xMzU5l7sUM1eP3tw3mhg\n6D6utwZfuFFb7Zj1oGJq4VMCAwEAAQ==\n-----END rsa public key-----","deadline":2524608000}}
二. HTTP 报文格式 各个部分 详解
2.1 URL
URL 全称 统一资源定位符(Uniform Resource Locator),是互联网上用来唯一标识、定位网络资源的标准地址
下面,我们以豆包的 URL 为例来一一解析 URL 的结构
https://www.doubao.com/chat/38433957980594946?channel=browser_landing_page
URL 共有 5 个部分组成,分别是:协议名称、IP地址或域名、端口号、带有层次结构的路径和查询字符串等
以上述 UPL 为例,这各个部分对应的内容分别是
1. 协议名称(URL 不是 HTTP 专属的概念,它可以给各种协议提供支持)
https
2.IP地址或域名(IP地址和域名本质上是同一个东西,域名其实是给 IP地址 起的一个中文名,可以用来替代那些难记的 IP地址。IP地址和域名可以通过 DNS 域名解析系统来相互转化)
www.doubao.com
3.端口号(区分不同的应用程序)
该 URL 中没有显示端口号,采用的是 http 默认的端口号80,https 默认的端口号则为 443
4.带有层次结构的路径(和电脑中的文件夹类似,用来区分网站里不同页面、内容)
/chat/38433957980594946
5.查询字符串(键值对结构,在 ?后面的语句,键与值之间用 “ = ” 分割,对访问的的资源进行补充说明)
channel=browser_landing_page
urlencode 转义操作
我们知道,查询字符串(query string)是程序员自定义的,那么,万一在 查询字符串 中也出现了特殊含义的符号,比如说 url 各个部分之间的分割符等,服务器又应该怎么去识别哪个才是真正的分割符呢?
为了解决这个问题,我们提出了 urlencode 的方法。urlencode 是 一种转义操作,不仅仅是标点符号,对于其它非英语的文字,都是需要转义的。在这些字符经过转义后,就不会影响到服务器的数据识别
2.2 方法
在 HTTP 中,方法共分以下几种:GET、POST、PUT、HEAD、DELETE、OPTIONS、TRECE、CONNECT、LINK 和 UNLINE 等
其中,我们主要学习 GET 和 POST 这两个方法
GET 方法
GET 是 HTTP 标准里用于向服务器获取数据的请求方法,设计的初衷是只用来从服务器获取资源,不修改、不新增、不删除服务端数据
GET 请求一般都是没有 正文body 部分 的,如果要通过 GET 方法 向服务器发送一些数据,就需要通过 查询字符串(query string)来传递过去的
POST 方法
POST 是 HTTP 标准里用于向服务器提交数据的请求方法,设计目的是把客户端数据发送给服务端,用于新增、修改、提交业务数据,会改变服务器资源状态
在 POST 方法 中可以将二进制数据通过 base64 编码 转码为文本形式再通过正文部分发送到服务器上(其实 正文 body 部分也可以直接发送二进制数据)
注意:在 Base64 编码后,体积会膨胀 1/3,相比直接传原始二进制,传输流量更大,性能更差
GET 和 POST 的区别
1.语义上的区别
GET 在语义上就是获取数据,POST 在语义上就是上传数据,HTTP 标准强制区分二者语义,设计上禁止随意混用。但是在实际开发中,GET 和 POST 并没有本质上的区别,这两个方法的语义常常是可以混用的,主要还是看程序员自己编写的代码逻辑
2.携带数据的方式
GET 的数据拼接在 URL 查询字符串(query string)中,而 POST 的数据存放在 HTTP 正文(body) 中。
上述的情况只是大多数情况,这并不是绝对的,在 POST 请求也可以带有 查询字符串(query string),GET 请求理论上也可以带有正文(body)部分
3.GET 请求通常设置为幂等的,POST则没有该要求
幂等的定义 :当请求一定时,得到的响应都是一定的
4.当 GET 被设置为幂等时,就可以允许 GET 请求的结果被缓存,而 POST 由于并不要求幂等,所以经常是不幂等的,所以一般 POST 请求的结果是不能被缓存的
因为幂等在请求一定时,得到的响应一定是一致的,所以我们就可以将 GET 得到的结果缓存下来,在客户端再次发出一样的请求时,就会直接返回已缓存的数据而不用去真的访问服务器,加快了执行效率
对于 GET 和 POST 常见的错误说法
1. POST 比 GET 更安全
由于 GET 参数暴露在 URL 地址栏中,POST 数据隐藏在请求 正文(body)部分 里,所以就有人认为 POST 比 GET 更安全。不过这是一个错误的想法,保证安全的关键是 “加密传输” 而不是数据显示的位置。如果 POST 在传输时不进行加密,那么只要对这个 POST 请求随便抓个包就可以完整地看到传输的数据,真正的安全靠的是 加密的方式,而不是请求方法
2. GET 传输数据有长度限制
这是在好久之前浏览器对于 URL 长度的限制,在那时 URL 传输的数据太多可能会被截断。而到了现在的主流浏览器/服务器早已没有了对 URL 长度的限制
3. GET 只能传输文本而 POST 可以传输二进制数据
GET 确实是只能放文本数据,但是可以通过 base64 将 二进制数据 转码为 文本数据 进行传输
而且 GET 在理论上也可以带有正文(body)部分。
2.3 报头(header)
报头(header)是键值对结构。由很多行构成,每一行都是一个键值对,键与值之间用 “ :”分割。其 键 和 值 都是由 RFC标准文档规定好的。
报头中几个关键的 键(key)
1. HOST
HOST 表示的是 域名和端口号,以上文抓包到的数据为例
Host: api.bilibili.com
其中,api.bilibili.com 就是报头的域名,端口号采用了默认的端口号(HTTP用 80,HTTPS用 443),不显式展示
也许,有的同学就会有疑问了?哎,在上文讲的 URL 当中不就已经保存了一遍 域名和端口号 吗?为什么在报头这里又保存一遍呢?原因其实是这样的,HTTP 协议 在传输数据时可能会涉及到 “加密” (HTTPS),而 URL部分是不会被加密的,加密的只是 报头(header)和 正文(body)部分。所以,在服务器收到请求之后就可以做一个校验,验证 URL 中的 域名和端口号 是否和 报头中加密的 域名和端口号 一致,保证网络传输安全
2. Content-Length
表示 正文(body)部分中的数据长度,单位为字节
作用:对于 TCP 来说,一个连接可以发送多个请求,所以,在服务器接收到数据时就得区分从哪到哪是一个完整的 http 请求数据(DUP 则没有这个方面的顾虑,因为 UDP 是 面向数据报的,读写的基本单位为一个 UDP 数据报,一个 UDP 数据报 就对应这一个完整的应用层数据包)。
那么,我们要怎么对这些 请求数据 进行区分呢?
这要先对这些请求分为两类进行讨论:
1.对于没有 正文 body 部分的 http 请求,当读到空行时,就可以认为这个请求就结束了
2.而对于有 正文 body 部分的 http 请求,则是先读取 首行 和 报头header,在读到空行后,通过解析 报头header 中的 Content-Length 得到要往下读取的字节的长度,并根据该长度接着往下读取相应的字节,读完后就可以认为这个请求就结束了
在之前对打开 B站的请求的响应的抓包数据中,Content-Length 的字段内容如下
Content-Length: 914
这就说明该响应的正文部分的长度为 914 个字节
3. Content-Type
描述了 请求中的 正文body 部分的数据格式,提示了接收方应该如何解析请求中 正文body 中的数据
在之前对打开 B站的请求的响应的抓包数据中,Content-Type 的字段内容如下
Content-Type: application/json; charset=utf-8
这就说明该响应的正文部分是由 UTF-8 编码的 JSON 格式数据
注:Content-Length 和 Content-Type 是共存的,在一个请求中要不两个都有,要不两个都没有,不会存在一个有另一个没有的情况
4. User-Agent(简称 UA)
User-Agent 是用来自我介绍的一个键,里面表示了用户使用的这台设备的浏览器和操作系统的情况,用来告诉服务器自己是什么设备、系统、浏览器、版本。服务器就可以在接收到这些信息后,返回不同版本的页面(手机端/电脑端),或根据不同浏览器的版本来返回不同的代码等(有的浏览器的版本比较旧,支持的功能就比较少;有的浏览器版本比较新,支持的功能就比较多)。
就比如之前对打开 B站 请求的抓包数据中,User-Agent 字段 如下
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36 Edg/150.0.0.0
这条 UA 就代表的是 运行在 64 位 Win10/Win11 系统上,150 版本的 Microsoft Edge 浏览器
5. Referer
描述了当前页面的来源,即记录了这个页面是从哪个页面跳转过来的
就比如之前对打开 B站 请求的抓包数据中,Referer 字段 如下
Referer: https://www.bilibili.com/
这就说明当前页面就是从 B站主页点击跳转过来的
6. Cookie
由于浏览器有安全沙箱限制,网页里的 JavaScript 代码没有权限直接读写你电脑本地文件、硬盘文件夹(担心 JavaScript 随意读取用户的文件系统,造成安全泄露问题),但是,在实际的开发过程的,有的时候我们又希望能够将网页中的某些数据保存到本地硬盘当中,这就出现了一个到底应不应该让 JavaScript 代码 操作用户文件系统的矛盾(无论是开不开放这个权限,其所带来的对应的问题都是我们不想看到的)。
为了解决这个问题,浏览器引入了 Cookie 机制,cookie 就是浏览器允许网页在本地硬盘存储数据的一种机制,这并不是让网页的 JavaScript 代码直接访问文件系统,而是做了一种抽象隔离,仅单独提供了一套受管控的键值对小型本地存储,网页只能操作 Cookie 这一块隔离存储空间,不能访问电脑其他文件。
浏览器保存了这些 Cookie 之后,就会在后续给服务器发送请求的时候,把这些 cookie 携带到请求的报头header 中的 Cookie 字段里面传输给服务器。
Cookie 里的数据是程序员自定义的,是和业务息息相关的。以下是两种 Cookie 的典型业务使用场景:
1. 登录认证
Cookie 里存有你的登录凭证,服务器拿到后就不用你重新输账号密码,直接认出你是谁,保持登录状态,如果没有 Cookie 中保存的登录凭证,就会被服务器判定为还未登录,就需要输入账号密码
2. 读取偏好设置
像 清晰度、页面布局、窗口分辨率、语言 偏好 都保存在 Cookie 当中,当服务端收到 Cookie 中的 设置偏好数据 之后,就会适配对应的页面效果,不用每次进网页重新设置
7. 状态码
HTTP 响应状态码是服务器返回给客户端的 3位数字编码,用来告知客户端本次请求的处理结果(响应结果是成功还是失败,如果失败了是什么原因)
HTTP 中的状态码主要分为以下五大类(每个大类当中又会细分为多种具体的状态码)
接下来,我们就来学习几个常见的状态码
1. 200 OK
这是最常见的状态码,表示该请求处理成功。打开 fiddler,我们可以发现大部分请求的响应状态码都是 200 OK

2. 404 Not Found
这个状态码表示服务器没有找到要访问的资源,显示可能如下所示

3. 403 Forbidden
访问被拒绝(没有权限访问该内容)

4. 405 Method Not Allowed
请求方法不被允许,接口后端只写了接收 POST请求的逻辑,你却用 GET去访问,就会出现该状态码
5. 500 Internal Server Error
服务器出现错误,客户端请求本身没问题,但服务端代码运行时出现异常崩溃。当代码逻辑中存在 bug、空指针、数据库查询报错 或 服务器程序抛出未捕获的异常时,就会出现该状态码
6. 504 Gateway Timout
网关超时,即网关不能及时的从后端服务中获取响应。
网关:网络的入口,负责向后端接口服务器转发请求,大部分的请求都要通过网关进行中转
7. 302 Move temporarily
临时重定向,服务器告诉浏览器,你要访问的资源临时换到了另一个地址,让浏览器自动跳转到新 URL(因为是 临时的,所以服务器并不会记住这个跳转关系,当我们下次再访问原地址时,还会再次发生跳转)
具体流程如下图所示;
客户端先访问服务器A,服务器A会返回给客户端 302 状态码并在响应头携带服务器 B 的地址,客户端拿到地址后再跳转访问服务器B

三. 小结
HTTP 以“一问一答”的简单模型支撑起了整个 Web 世界,但它有一个致命短板:数据以明文传输。一旦请求在网络上被截获,内容将一览无余。为了解决这个问题,我们又引入了 HTTPS 协议,那么,HTTPS 是如何通过加密机制解决这个安全问题的?在下一篇博客,我们将深入 HTTPS 的对称加密、非对称加密、中间人攻击与数字证书

1070



被折叠的 条评论
为什么被折叠?



