HTTP/HTTPS这套东西,说新不新,但真正能把它讲透、用到实战里的人真不多。我抓包三年多,前后端联调常见问题翻来覆去就那几个:请求头没带对、状态码看错、数据包结构理解偏差,尤其是热词里大家搜的“a标签下载视频请求头怎么带token”“burpsuite请求头”“luch-request 配置请求头”,核心其实都指向同一个问题——怎么在合适的层级把HTTP请求头组装对。
这一篇我会把HTTP/HTTPS协议从请求头、响应头、状态码到数据包结构完整过一遍,重点结合实战场景,把我踩过的坑、排查过的线上问题都摊开来讲。不管你是前端、后端、客户端、运维还是刚入行的测试,这篇都能帮你把协议这块从“背字段”提到“会诊断问题”的水平。
1. 内容整体设计与思路拆解
1.1 为什么请求头、响应头总是被单独拎出来说
很多人学HTTP是记列表:Host、User-Agent、Accept、Cookic、Content-Type……背完就忘,因为没搞清楚这些字段为什么存在。
请求头解决的是“客户端想干嘛”的问题,响应头解决的是“服务端怎么回应”的问题,状态码是这个回应的浓缩结论,而数据包结构回答了“这些内容在网络上到底长什么样、怎么传输”。四者是一条完整的链路,不能割裂着看。比如你打开一个网页,浏览器向服务器发起请求,请求头里带了接受的格式、压缩方式、身份凭证,服务器返回时,响应头告诉浏览器内容类型、缓存策略、是否跨域允许,状态码快速表达“这次请求成没成”,底层数据包则决定这些信息能否可靠传输。
我在工作里最常用的一句话就是:看协议,不要只看一行状态码,要把请求头、响应头、数据包连起来读。比如某个接口返回401,如果只盯着状态码,你大概率会去改token逻辑,但如果看一眼响应头里的WWW-Authenticate字段,你会发现是认证方案不匹配,这是完全不同的两类问题。
1.2 HTTPS到底改了什么
先纠正一个常见误区:HTTPS并不是一套新的应用层协议,HTTP的请求行、请求头、请求体结构在HTTPS下完全不变,变的只是传输方式——多了一层TLS/SSL加密隧道。
这里的底层逻辑是:HTTP明文传输时,请求头里的Cookie、Authorization等信息在链路上裸奔,抓包工具能直接看到明文。HTTPS建立连接时先做TLS握手,协商加密算法、交换证书、生成对称密钥,之后所有HTTP报文都通过加密隧道传输。抓包工具看到的是密文,Wireshark里也只是一堆看起来像乱码的TLS记录,除非你配置了SSLKEYLOGFILE把会话密钥交给抓包工具。
对开发者来说,HTTPS带来的实际影响是:以前能在代理层直接改写Header,现在很多场景改不了;以前能看明文包定位问题,现在必须借助浏览器DevTools或者配置代理证书才能看到明文请求头。这些在后面的抓包实操里都会遇到。
1.3 一份协议知识要按什么层级去组织
我的思路是这样:先建立全局认知,再深入细节。全局认知包括请求-响应模型、报文结构、状态码语义;细节包括字段语义、缓存策略、CORS、身份认证。实战时再落到工具上——curl、Chrome DevTools、Burp Suite、Wireshark,每一层都有不同的观察方式。
下面这张路线图,是我给自己团队新人培训时用的,也是这篇博文的骨架:
- 第一层:HTTP报文长什么样(请求行/状态行、请求头/响应头、空行、请求体/响应体)
- 第二层:这些头的语义是什么(重点字段逐个剖析)
- 第三层:状态码背后的服务端逻辑(为什么是4xx,不是5xx,也不是2xx)
- 第四层:从抓到看到排查,工具链怎么配合
- 第五层:真实场景实战(带token、网关加头、代理改包)
2. 协议核心:请求头、响应头、状态码、数据包结构剖析
2.1 请求头:你真正需要掌握的字段和它们的“潜台词”
请求头字段很多,但日常开发真正高频使用的没那么多。我把它们按用途分了四组,这样好记。
身份认证组:Authorization、Cookie、Token类自定义头。这是热词里最受关注的一块。“请求头怎么带token”这个问题,实践中有三种带法:第一种是Authorization: Bearer <token>,这是最规范的做法,RESTful API普遍采用;第二种是放到自定义头里,比如X-Token: <token>,老项目里很常见;第三种是放在Cookie里,由浏览器自动携带。三种方式在安全性、跨域复杂度、服务端解析方式上都不同。用Authorization头的好处是标准、语义清晰,缺点是跨域时会触发CORS预检请求;自定义头灵活但不够规范;Cookie最省事但容易受CSRF攻击影响。我个人的建议是新项目优先用Authorization: Bearer,老项目如果已经在用Cookie就保持一致性,别混用。
内容协商组:Accept、Accept-Encoding、Accept-Language、Content-Type。Accept告诉服务端你希望返回什么格式,Content-Type告诉服务端你发送的请求体是什么格式。这两个是最容易搞混的,我见过太多人把Content-Type写错了导致后端解析不到参数。GET请求一般不需要Content-Type,POST提交JSON要设application/json,表单提交则是application/x-www-form-urlencoded或multipart/form-data。
链路控制组:Host、User-Agent、Referer、Origin。Host在HTTP/1.1里是必需的,表示目标域名和端口;User-Agent标识客户端类型,很多反爬策略就看它;Referer表示来源页面,防盗链也靠它,Origin跟CORS密切相关,表示请求来源域,注意预检请求时它会代替Referer发挥关键作用。有时候前端联调跨域,后端让我们把Origin放行,说的就是这个字段。
传输控制组:Connection、Cache-Control、If-Modified-Since、If-None-Match。Connection: keep-alive在HTTP/1.1里是默认行为,表示复用连接;Cache-Control控制缓存策略,不设或设错了经常导致线上改完代码还是老版本。
2.2 响应头:服务端的“心里话”都写在这里
响应头里藏着大量服务端行为线索。Content-Type是最基础的一个,响应体是JSON还是HTML靠它区分。Content-Length表示响应体长度,用于确认报文完整性。Set-Cookie用于下发Cookie。Cache-Control、Expires、ETag、Last-Modified是一组缓存相关的头,本地调试改了代码不生效,十有八九是它们的问题。
这里要特别讲一下Content-Disposition,因为在文件下载场景里它是主角。响应头里返回Content-Disposition: attachment; filename="xxx.mp4"时,浏览器会把它当作附件下载,而不是直接播放。如果你想直接预览视频,这个头就不能带attachment,或者改成inline。热词里“a标签下载视频”的话题,本质就是请求头(如何带token)和响应头(Content-Disposition如何触发下载)的配合问题。后面实操部分我会给完整方案。
CORS相关响应头也值得单独提一下:Access-Control-Allow-Origin控制允许跨域的源,Access-Control-Allow-Headers控制允许的请求头,Access-Control-Allow-Methods控制允许的方法。前端报跨域错误时,要看的是这几个响应头有没有正确返回。还有个冷门但很关键的:Access-Control-Max-Age可以缓存预检结果,比如设86400秒就一天内不会再触发OPTIONS预检,联调时经常靠它减少无谓请求。
安全性相关响应头是很多后端忽略的:Strict-Transport-Security强制HTTPS,X-Content-Type-Options: nosniff防止MIME嗅探,Content-Security-Policy限制资源加载来源。上过安全扫描系统的基本都见过这三兄弟。
2.3 状态码:别只记数字,要理解背后的语义边界
状态码分为五类,这个谁都知道,但实战中很多问题的根源在于“边界模糊”。我整理了一份高频状态码速查表,按实际问题场景来记,比按数字背效率高得多。
| 状态码 | 含义 | 实际触发场景 | 我的处理建议 |
|---|---|---|---|
| 200 | 请求成功 | 正常返回 | 无 |
| 204 | 无内容 | DELETE操作、接口不返回body | 前端不要尝试解析body |
| 301 | 永久重定向 | 域名迁移、HTTP跳HTTPS | 浏览器会缓存,排查问题时要清缓存 |
| 302 | 临时重定向 | 登录后跳转、未登录跳登录页 | 注意跟随重定向时Authorization头可能丢失 |
| 304 | 未修改 | 协商缓存命中 | 配合ETag/Last-Modified使用,不是报错 |
| 400 | 请求语法错误 | 参数类型不对、JSON格式错误 | 优先看响应体里的错误信息 |
| 401 | 未认证 | token缺失、token过期 | 看响应头WWW-Authenticate和响应体错误码 |
| 403 | 禁止访问 | 权限不足、IP被拉黑、CSRF校验失败 | 和401的区别是“你知道是谁,但不让你进” |
| 404 | 资源不存在 | 路径写错、路由未注册 | 后端要看路由日志,前端要看baseURL是否拼错 |
| 405 | 方法不允许 | GET写了POST、PUT写了PATCH | 检查后端路由定义和前端method参数 |
| 429 | 请求过多 | 触发限流 | 看响应头Retry-After,做指数退避重试 |
| 500 | 服务器内部错误 | 后端代码报未捕获异常 | 让后端看日志,前端能做的只有把参数原样记录 |
| 502 | 网关错误 | Nginx连不上后端进程 | 先查后端服务是否存活,再看Nginx upstream配置 |
| 503 | 服务不可用 | 服务启动中、过载熔断 | 不是代码错了,是暂时处理不了 |
| 504 | 网关超时 | 后端处理超过Nginx超时时间 | 调大proxy_read_timeout或优化接口耗时 |
这里要特别提醒一个容易误判的组合:前端看到502就去怪网关,看到504就去怪网络,我排查过太多这类问题,最后根源都在后端——监听的端口没起来、数据库连接池用完、接口里有个笛卡尔积查询跑了60秒。状态码是结果,不是原因,要顺着链路往前查。
还有一个容易混淆的是401和403。很多团队自定义接口里用403表示“登录过期”,这其实不太规范,从HTTP语义上讲,登录过期应该是401,因为你的凭证无效了。当然,如果团队内部已有约定且前端统一处理了,也不是不能接受,但这种“自定义语义”一定要在接口文档里写清楚,否则新来的同事一定会踩坑。
2.4 数据包结构:HTTP报文在网络上到底长什么样
HTTP报文的结构非常规整,分四部分:起始行、头部字段集合、空行、请求体/响应体。起始行在请求里叫请求行(比如GET /api/users HTTP/1.1),在响应里叫状态行(比如HTTP/1.1 200 OK)。头部字段是键值对集合,每行一个。空行是头和体之间的分隔符,千万别小看这个空行,解析HTTP报文时它就是判断头部结束的边界。请求体或响应体承载实际数据。
用curl发一个最简单的GET请求:
curl -v http://example.com/api/test-v参数会打印完整报文,你会看到类似这样的输出:
> GET /api/test HTTP/1.1 > Host: example.com > User-Agent: curl/8.0.1 > Accept: */* > < HTTP/1.1 200 OK < Content-Type: text/html; charset=UTF-8 < Content-Length: 1256注意大于号开头的是请求报文,小于号开头的是响应报文,那个孤零零的空行就是头体分隔符。
要真正理解数据包,还得把它放到TCP/IP分层模型里看。HTTP是应用层协议,它构建在TCP之上。你在浏览器输入网址回车,发生的是:DNS解析拿IP、TCP三次握手建立连接、发送HTTP请求、TCP把数据切成一个个段、IP层封装成数据报、加上MAC头变成数据帧,经过交换机、路由器最终到达目标服务器。这个过程中,HTTP报文会被层层封装,每经过一层就加一个该层的头部,像套娃一样。所以抓包时Wireshark里看到的内容,其实分为Ethernet层、IP层、TCP层、HTTP层四层,看数据包结构要习惯分层观察。
有人可能会问:HTTP/2和HTTP/3不是早就出来了吗,二进制分帧、头部压缩、多路复用这些概念我是不是也该掌握?我的看法是:日常开发和问题排查,绝大多数场景仍然是在HTTP/1.1的报文结构维度上进行的,因为代理、网关、抓包工具默认呈现的还是这个模型。你只要知道HTTP/2把报文拆成了HEADERS帧和DATA帧,HTTP/3跑在QUIC上,然后按需深入学习即可,不必一上来就被二进制协议劝退。
3. 从看到改:请求头配置与抓包实操全流程
3.1 用Chrome DevTools看透HTTP请求的每一个头
前端调试时最常用的工具就是Chrome DevTools的Network面板。打开开发者工具,切到Network,刷新页面,能看到所有请求。点击任意一条记录,Headers面板会展示该请求的完整链路。
我分享一个小经验:Network面板里默认看的是“友好视图”,也就是把请求头分门别类整理了。但出了问题需要精确复现时,要看原始报文——Headers面板右下角有个“View source”切换按钮,点开就能看到原始请求头和响应头文本。我最常在联调排障时这么干,因为友好的分组视图有时会合并同名头,比如多个Set-Cookie会被折叠,原始视图能看到全部。
实用功能里还有一个特别顺手:右键请求记录,选择“Copy as cURL”,DevTools会生成一条完整的curl命令,包含所有请求头、Cookie、请求体。把它存成一个文件,效果等同“保存现场”。下次用命令行复现问题时直接跑这条命令,再配合curl的-i参数看响应头,基本上能判断问题出在前端还是服务端。
3.2 前端请求头配置实战:fetch、axios、luch-request
先讲标准场景。fetch带Token最简单:
fetch('/api/data', { method: 'GET', headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token'), 'Content-Type': 'application/json' } })axios通常配合拦截器统一处理,这样不用每个请求都手动写:
axios.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, error => { return Promise.reject(error) })这里注意,拦截器方案遇到一个问题:在SSR场景下localStorage不存在,在服务端渲染时跑这段代码会直接报错,需要先判断typeof window。这是我踩过的很实在的坑。
luch-request是Vue3和uni-app生态里用得比较多的请求库,它在配置请求头时的写法和axios类似。如果你在uni-app里用luch-request给GET请求配请求头,常规做法是:
import { http } from '@/utils/request' export function getData(params = {}) { return http.request({ url: '/api/data', method: 'GET', params, header: { Authorization: `Bearer ${uni.getStorageSync('token')}` } }) }还可以在http.setConfig或拦截器里统一设置默认header,这样每个请求都会自动带上。对于小程序场景特别要注意:小程序的网络请求域名必须配置到合法域名列表,本地调试要关掉“校验合法域名”选项,而且请求头里去不掉浏览器默认的Referer,这在某些后端校验来源的接口上会变成坑。
3.3 a标签下载视频时怎么带token
这是热词里的重点场景,也是最容易让人迷惑的一个问题。先说结论:a标签自身的下载行为无法自定义请求头。<a href="xxx.mp4" download>这种写法,浏览器会直接发起GET请求,你没法在a标签上附加任何Header,Token自然带不上去。
那实际项目里怎么解决?我总结过三种方案。
方案一是把Token放到URL查询参数里,/api/file/download?token=xxx。服务端允许从query取token就行,实现最简单。缺点是URL会记录在浏览器历史里,Token有泄露风险,不适合高安全性场景。
方案二是用fetch先请求文件流,拿到blob之后再触发下载。代码大概是这样:
async function downloadFile(url, filename) { const response = await fetch(url, { headers: { 'Authorization': 'Bearer ' + getToken() } }) const blob = await response.blob() const objectUrl = URL.createObjectURL(blob) const a = document.createElement('a') a.href = objectUrl a.download = filename document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(objectUrl) }这个方案通用性好,Token不会暴露到URL里,适合大多数内部系统。注意两点:一是大文件会整个缓存在内存里,下载超过几百兆的文件要谨慎;二是URL.revokeObjectURL要在点击之后异步释放,释放太早会导致下载失败。
方案三是对应后端配合的场景:服务端下发一次性临时URL,比如OSS或CDN的签名URL。浏览器直接打开这个临时链接就能下载,不需要带自定义头。这种方案最适合生产环境的大文件分发。
实际做视频处理时,还有一个细节:如果是<video>标签播放视频而不是下载,Token的处理方式又不一样了。视频播放无法方便地转blob流(大文件内存扛不住),比较推荐的做法是服务端生成带签名或临时Token的播放URL,或者把Token放在Cookie里由浏览器自动携带,后端校验Cookie有效性。
3.4 Burp Suite改请求头:安全测试和接口排查的必备操作
做安全测试或接口调试,Burp Suite是绕不开的工具。最常用的场景是拦截请求后修改Header再转发。它的通用流程是:启动Burp的代理监听(默认127.0.0.1:8080),把浏览器的代理指过去,或者用Burp的浏览器直接访问目标。开启拦截模式,浏览器发请求时会被挂起,你可以在这个界面改任意请求头,比如把Authorization换成另一套Token、修改User-Agent、加自定义头,然后点击Forward放行。
有一点要强调:Burp能抓到HTTPS请求,是因为安装了它自己的CA根证书,浏览器信任这个证书后,Burp就能解密TLS流量看到明文HTTP报文。正规的HTTPS流量拦截都需要先安装证书,不是Burp有什么魔法。
日常做安全测试时,Burp还有个很常用的功能是Repeater,把请求发到Repeater标签页,可以反复修改Header和Body重新发送,非常方便做参数变化对比。比如你想验证接口是否存在越权问题:把A用户的Token换成B用户的Token再发一遍,看返回的请求头有没有把身份正确识别,把数据包响应体对比一下,基本就能判断。
3.5 网关层添加请求头:Nginx与Rainbond
业务系统做微服务化之后,很多公共逻辑下沉到网关层处理,其中很常见的一类就是“统一往请求头里注入信息”。比如用户身份校验通过后,网关把用户ID注入到X-User-Id头里,后端直接读取这个头。
Nginx的proxy_set_header是最基础也最实用的配置,在nginx配置文件的location块里加:
location /api/ { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-User-Id 10001; proxy_set_header Authorization $http_authorization; proxy_pass http://backend_server; }看到没,$http_authorization是Nginx获取请求头的方式,把原始请求里的Authorization透传给后端。在网关层,这种写法很常见。
Rainbond作为云原生应用管理平台,也支持在网关层自定义请求头。在Rainbond的网关管理里编辑HTTP路由规则,找到“请求头”相关配置,可以添加自定义Header键值对,也可以配置从已解析的JWT或其他认证信息里提取的值。具体路径每版本略有差异,但思路一致。我在实操里发现,Rainbond网关默认会注入一些标识头,比如X-Forwarded-Proto、X-Request-Id等,做后端服务时可以直接使用这些头做链路追踪。如果后端拿到请求后始终取不到某些Header,第一件事就是先看一下网关控制台是否把这个头过滤了,这类“网关层丢头”的问题排查成本比较高,日志和抓包要同时看。
3.6 企查查请求头场景与合规观察
热词里出现了企查查请求头,这个具体场景我不展开细节,但可以从这类网站的技术架构引申出一个共性话题:为什么企业信息查询类网站对请求头校验这么严格?因为这类数据涉及企业隐私和商业数据,服务器会从Header里的User-Agent、Referer、Origin、自定义加密字段等多个维度验证请求的合法性。如果请求头不合预期,哪怕路径和参数完全正确,也会返回校验失败。
从技术学习角度,观察这些网站的请求头构成,是学习HTTP头字段组合用法的最好教材。但这里必须提醒一句:任何绕过网站风控策略去爬取数据的行为都可能违反网站服务条款甚至法律法规,做技术研究时请遵守目标网站的规则,优先使用官方API。对企业公开数据,合规获取渠道是政府数据开放平台、官方API接口或有授权的数据服务商。想学习请求头技术,自己搭个后端服务,用浏览器DevTools和Burp观察自己的接口,一样能达到目的。
4. 常见问题与排查技巧实录
4.1 高频故障速查表
根据这几年线上问题排查经验,我把最常见的HTTP相关故障整理成一张速查表,直接按症状查原因。
| 症状 | 可能原因 | 优先排查动作 |
|---|---|---|
| 接口返回401且前端确认Token存在 | Authorization头被网关剥掉 | 抓包或看网关日志,确认请求到达后端时头是否完整 |
| 跨域报错,前端Headers里配置了Authorization不生效 | 预检请求OPTIONS没通过 | 看响应头Access-Control-Allow-Headers是否包含Authorization |
| 上传文件失败,后端拿不到文件参数 | Content-Type写错成application/json | multipart/form-data边界问题,检查formData构造方式 |
| 接口返回200但页面数据不更新 | HTTP缓存命中,走了304 | 排查Cache-Control和ETag,后端加no-cache |
| 下载文件名中文乱码 | Content-Disposition编码问题 | filename加UTF-8编码,比如filename=UTF-8'' |
| 对接第三方接口报403 | 对方校验了User-Agent或来源IP | 确认白名单,设置合规UA |
| 用curl能通、浏览器不行 | 携带了浏览器自动附加的Cookie | 清除该域名Cookie或用隐私模式排查 |
| Token带在URL里被网关/日志系统截断 | URL过长或特殊字符被转义 | 服务端改用Header或Cookie方式接收 |
4.2 排查工具链组合拳
问题排查不能只靠一个工具。我的标准组合是:先看DevTools确认前端发出的请求头和收到的响应头,再用curl -v在命令行复现,如果问题在跨服务调用之间,就需要tcpdump或者Wireshark在出口抓包。
curl-v是排查期的老朋友,建议熟练掌握。它能看到TCP连接建立的细节、发送的请求头、接收的响应头,加-i能显示响应头和正文,加-k跳过证书验证(仅限测试环境),加-x走代理。
服务端排查时,我经常用tcpdump看实时流量:
tcpdump -i eth0 -A -s 0 port 8080-A表示以ASCII格式打印数据包内容,这样直接能看到HTTP文本。但注意:如果服务是HTTPS,这样只能看到加密后的乱码,需要配SSLKEYLOGFILE或者直接在应用层看日志。遇到HTTPS链路问题,最省力气的做法是临时在后端日志里打印请求头,看X-Forwarded-For、Authorization等关键字段是否被正确传递。
4.3 缓存引发的诡异问题与一套复盘实录
有一次线上反馈:后台改了产品图片,前端页面怎么刷新都是老图。我第一反应是CDN缓存,但检查之后发现CDN配置没问题。继续看响应头,发现后端返回了Cache-Control: max-age=86400和ETag,而前端在构建时给静态资源加了带hash的版本号,理论上不会命中旧缓存。最后定位到原因:后端接口本身设置了强缓存,运营改的是图片资源地址,但接口返回的JSON数据在浏览器层被缓存了一天。
这个案例说明:前端页面刷新看到旧数据,不一定代表服务端没改,先看接口响应头里的缓存字段,再决定是全链路刷新还是改代码加no-cache。
另一件值得一提的坑是:所有Header的键值都是大小写不敏感的,authorization和Authorization是一个东西。但Cookie和自定义Header里的值区分大小写,很多对接失败其实是后端把USER_ID和user_id搞混了。遇到这种问题,用抓包工具对比两端日志的原始头大小写,一眼就能看出真相。
4.4 请求头带Token时最容易踩的三个暗坑
第一,Bearer后面一定要有空格。Authorization: Bearer eyJxxx不能写成Authorization: BearereyJxxx,这个看似低级,但各个语言框架解析时行为不同,有的框架会静默忽略非法格式,导致认证失败却不报错。
第二,Session和Token混用的系统,要么统一走Cookie,要么统一走Authorization头,不要同时依赖两套鉴权机制。我就见过一个老系统:登录时给Cookie里种了sessionId,前端又手动在Header里塞了自定义token,后端两个都校验,导致Token过期后Cookie还能用,前端排查了半天不知道是谁放行的。
第三,前端代码里硬编码Token。很多同学为了方便,测试token直接写死在请求拦截器里。一旦上线忘了删,轻则造成安全隐患,重则测试环境Token带到生产,连登录态都能串。正确做法永远是动态读取,从localStorage、pinia/vuex或者secureStore里取。
5. 从数据包视角看HTTP/HTTPS的完整生命周期
前面把请求头、响应头、状态码和数据包结构分块讲完了,这一部分我把它们串成一条线,模拟一次完整请求从浏览器发出到拿到响应的全过程,这样你就知道这些知识背后是怎么配合的。
你在浏览器输入https://api.example.com/login并回车。首先DNS解析域名,浏览器得到服务器的IP,之后与服务器建立TCP连接(三次握手),再因为协议是https,接下来是TLS握手,协商出加密会话和密钥。握手完成后,浏览器组装HTTP数据包:请求行是POST /login HTTP/1.1,请求头里有Host: api.example.com、Content-Type: application/json、Content-Length: 42,如果有登录态还会有Authorization或Cookie。这个报文被TLS加密后交给TCP层,TCP把它分成若干段,每段加上序号;IP层给每段加上源IP和目标IP;最后通过网卡发出。
服务器收到数据后层层拆包,把HTTP报文解出来交给后端的Web框架,框架解析请求行得知路径和方法,解析请求头得知内容格式,读取请求体拿到{"username":"admin"},然后路由到对应的登录接口。登录成功,服务器组装响应:状态行里是HTTP/1.1 200 OK,响应头里Content-Type: application/json,Set-Cookie下发会话ID,Cache-Control: no-store告诉浏览器不要缓存,响应体返回{"token":"xxx","user":{"name":"admin"}}。响应同样经过层层封装和加密回到浏览器,浏览器根据响应头决定如何处理:成功则解析JSON更新页面,失败则按状态码走错误分支。
这套流程里的每一步,都可以用前面讲到的知识点来观测和验证。比如你用DevTools看到某个请求耗时很大,可以继续看时间轴,是DNS查询慢还是TLS握手慢还是TTFB慢,这就是数据包生命周期思维在实战中的应用。
个人觉得,HTTP协议的知识不是背出来的,是靠抓包、排查、复盘“喂”出来的。我见过很多同事一次联调就打开一遍DevTools,看着红的、黄的、绿的请求,一脸茫然不知道该看哪,根源是把知识点碎片化了。如果能把请求头、响应头、状态码、数据包结构这四块在脑海里形成一个完整的链路,大部分问题都能自己在五分钟内定位到七八成。尤其是请求头怎么带Token这种问题,本质就是“客户端-代理-服务端”三层之间如何协商凭证的问题,理解了链路,剩下的就是工具熟练度而已。