
1. 先把 HTTP 数据包拆开看一次请求到底发了什么很多人学接口调试第一步就卡在我看不见它。浏览器里点一下按钮页面变了至于中间到底发了什么、服务器回了什么全靠猜。HTTP 数据包本质上就是一段有固定格式的文本理解了格式后面用 Postman 构造请求、改请求头、看状态码全都顺理成章。这篇笔记围绕HTTP 数据包结构、Postman 构造请求、请求方法、请求头修改、状态码判断这五件事展开算是我自己在做接口调试和日常排障时反复用到的底层知识适合刚接触接口测试、抓包分析或者后端联调的人对照着看也适合已经会点但说不清原理的人补一遍基础。1.1 请求报文的四段式结构一个 HTTP 请求报文从头到尾就是四块内容顺序不能乱请求行、请求头、空行、请求体。请求行只有一行格式是方法 路径 协议版本比如GET /api/user?id1 HTTP/1.1。这里的方法就是 GET、POST 这些路径是资源定位符版本决定了后面很多默认行为HTTP/1.1 和 HTTP/1.0 最大的差别之一就是连接默认是否复用。请求头是若干行Key: Value键值对用来告诉服务器我是谁、我要什么格式、我带了什么凭证。空行是一行什么都没有的换行它的作用是标记头结束了服务器解析到这个空行就开始读请求体。请求体放真正要提交的数据GET 请求通常没有请求体POST 请求通常有。我曾经遇到过一个很典型的坑手写报文时在最后一个请求头后面忘了加空行服务器把请求体当成了请求头继续解析直接返回 400。这个空行肉眼看不见但它是硬性分隔符。1.2 响应报文的结构与状态行响应报文的结构和请求几乎对称状态行、响应头、空行、响应体。状态行的格式是协议版本 状态码 原因短语例如HTTP/1.1 200 OK。原因短语只是给人看的描述程序判断时只看那个三位数字。响应头里常见的有Server、Date、Content-Type、Content-Length、Set-Cookie、Cache-Control等响应体则是真正的资源内容可能是 HTML、JSON、图片二进制流。理解响应结构对排障特别有用。比如你看到响应体是乱码先别怀疑编码去看响应头里的Content-Type有没有带charsetutf-8看到响应体被截断去看Content-Length和实际长度对不对得上。这些线索全在响应头里只是平时浏览器帮你处理了你感觉不到。1.3 抓包看到的和代码写的为什么不一样不少人第一次抓包会懵明明代码里只写了一个 URL抓出来却多了一堆自己没设过的头比如Accept、Accept-Encoding、User-Agent、Connection。原因是 HTTP 客户端浏览器或编程语言的 HTTP 库会自动补默认头。这带来一个很实际的差异——用 Postman 手动构造的请求和真实业务代码发出的请求可能因为默认头不同而结果不同。举个我踩过的例子后端做了一个基于User-Agent的分流移动端 UA 走一套逻辑PC 端 UA 走另一套。我用 Postman 随手发请求Postman 默认 UA 是它自己的标识结果返回了我预期之外的分支数据排查了半天才发现是 UA 在作怪。从那以后我养成习惯调试接口时如果结果和预期不符先看一眼默认头再看一眼请求方法。提示抓包工具看到的请求头里Host、Content-Length、Connection这类字段经常是客户端自动生成的手动构造请求时如果不写可能被自动补上也可能不补取决于工具实现。2. 请求方法不是随便选的GET 和 POST 的真实边界请求方法看起来是最简单的东西但它是 HTTP 语义的核心。方法决定了这个请求是读还是写能不能被缓存能不能被重复发送。很多接口设计问题和线上事故根源都在方法选错了。2.1 常见方法的语义与幂等性HTTP 定义了一组方法日常最常用的是下面这几个方法语义幂等有无请求体GET获取资源是一般无HEAD只取响应头是无POST提交/创建否有PUT整体替换是有PATCH局部修改否有DELETE删除资源是一般无OPTIONS查询支持的方法是无幂等的意思是执行一次和执行 N 次对服务器状态的影响相同。GET 读数据读一次和读十次资源不变所以幂等POST 提交订单提交一次和提交十次结果完全不同所以不幂等。这个性质在实际系统里非常关键网络超时后客户端想重试只有幂等的方法才能安全重试。我见过有人用 GET 去做删除操作结果浏览器预取、爬虫抓取、用户刷新页面都触发了一遍删除这就是方法选错带来的直接损失。PUT 和 PATCH 的区别也值得说一句。PUT 是整体替换你提交什么资源就变成什么没提交的字段会被清空或置默认值PATCH 是局部更新只改你指定的字段。曾经有个同事用 PUT 更新用户信息只传了昵称结果把用户的头像、邮箱全清空了因为后端严格按 PUT 语义做了整体覆盖。这类问题不看文档、不确认语义很容易翻车。2.2 GET 和 POST 的六个实际差别这是面试常问、也是实操中最容易搞混的一组。我把差异归纳成六个维度参数位置GET 参数拼在 URL 的问号后面多个参数用连接POST 参数放在请求体里。长度限制URL 长度受浏览器和服务器双重限制常见 2KB 到 8KB 不等POST 理论上没有硬上限实际由服务器配置决定。可见性GET 参数直接暴露在地址栏、浏览器历史、代理日志、服务器访问日志里POST 参数在请求体中相对不那么显眼。缓存行为GET 请求可以被浏览器、CDN 缓存POST 默认不缓存。书签与回退GET 请求的 URL 可以直接收藏、可以直接粘贴分享POST 不行刷新时浏览器还会弹出是否重新提交表单。编码方式GET 参数要做 URL 编码中文、空格、、都要转义POST 的编码由Content-Type决定可以是表单、JSON、多部分表单等。这里我要重点强调一个常见误解POST 比 GET 安全这个说法是不成立的。两者都是明文传输只要没有 HTTPS用抓包工具都能完整看到请求内容。POST 只是不把参数放在 URL 里看起来没那么直观而已。真正的机密性来自传输层加密和方法没有关系。把用 POST 藏参数当成安全手段是典型的错误认知。2.3 什么时候该用 PUT / PATCH / DELETE在 REST 风格的接口里方法的使用是有约定的查列表和详情用 GET创建用 POST整体更新用 PUT局部更新用 PATCH删除用 DELETE。遵守这个约定最大的好处是接口自解释——别人看到方法就知道这个请求会不会改数据、能不能重试。不过现实项目里并不总是这么理想。有些老旧接口全都用 POST路径里写/getUser、/deleteUser靠路径区分动作。这种设计不是不能用但失去了方法本身携带的语义信息缓存、重试、权限控制都得另外想办法。我在做联调时遇到过一种情况网关的限流策略是按方法配置的GET 放宽、写操作收紧结果对方把所有操作都用 POST 发全部撞到了严格的限流阈值上。这就是方法语义被浪费后的实际代价。3. Postman 怎么把数据包手工拼出来理解了报文结构接下来就是把报文拼出来发出去。Postman 的价值在于它把报文里的每一部分都变成了可视化的输入框你不用手写文本也能精确控制请求行、请求头、请求体的每一个字段。3.1 安装与界面里的四个关键区域Postman 的安装没什么好说的官网下载对应平台安装包一路下一步即可。真正需要熟悉的是它界面里的四个区域对应着报文的四个部分方法下拉框 URL 输入框对应请求行。Params 标签页填 URL 查询参数它会自动帮你拼接到 URL 上并做编码。Headers 标签页对应请求头一行一个键值对可以手动增删改。Body 标签页对应请求体可以选择 none、form-data、x-www-form-urlencoded、raw、binary 等类型。这四个区域从上到下基本就是报文的书写顺序用熟之后你脑子里会自然形成这段内容会落到报文的哪一行。这是我觉得 Postman 比纯命令行工具更直观的地方——它把抽象的报文结构变成了固定的操作面板。3.2 Params、Headers、Body 三处填参的区别新手最容易犯的错误是把参数填错地方。接口文档说参数放在 query 里你填到了 Body文档说放在 header 里你填到了 Params。结果服务器收不到参数返回 400 或参数校验失败你还在怀疑接口挂了。判断参数该放哪其实有一条简单规则看接口文档里参数的标注位置。文档一般会写query、path、header、body四类。path参数直接写在 URL 路径里比如/api/user/123里的 123query参数写在 URL 问号后面对应 Postman 的 Paramsheader参数对应 Postman 的 Headersbody参数对应 Postman 的 Body。填错位置的表现往往是参数明明传了服务端说没收到。还有一个细节是 Body 的类型选择。选x-www-form-urlencodedPostman 会自动设置Content-Type: application/x-www-form-urlencoded并把参数编码成a1b2的形式选raw再选 JSON它会设置Content-Type: application/json你写的内容原样发送。如果手动在 Headers 里写了另一个Content-Type可能和 Body 类型冲突导致服务端解析失败。我自己就遇到过 Body 选了 form-data、Header 里又手写了application/x-www-form-urlencoded后端按后者解析结果一个字段都读不到。3.3 环境变量与集合让请求可复用单个请求调试完就丢了是很浪费的。Postman 提供两个组织工具集合Collection和环境变量Environment。集合用来把一组相关请求放在一起比如一个项目的所有接口环境变量用来抽离那些会变的值比如{{base_url}}、{{token}}。这样做的好处非常实际。测试环境和生产环境的域名不同如果每个请求都硬编码域名切环境时要改几十处用了变量只需要切换环境即可。Token 也是一个道理登录后拿到 token存进环境变量后续请求统一引用{{token}}token 过期时改一处就行。提示环境变量在请求里的引用语法是双大括号比如{{base_url}}/api/user。如果变量名写错了Postman 会原样发送{{...}}字符串服务端自然找不到资源返回 404。3.4 导出 cURL从图形界面回到命令行调试完成后经常需要把请求交给别人复现或者写进脚本。Postman 提供导出为 cURL的功能能把当前请求完整转换成一条命令行。这个转换的价值在于它把方法、URL、请求头、请求体全部固化下来别人复制粘贴就能跑出一样的结果不需要重新对着文档配一遍。导出后你会发现命令里包含了一堆-H参数每一个对应一个请求头。这时候可以顺便检查一下有没有多余的默认头被带进去有没有关键头漏了。我经常用这一步来验证我理解的请求和实际发出的请求是否一致尤其是排查那种Postman 能通、代码不通的问题时把导出的 cURL 和代码里的请求配置逐行对比往往一眼就能看出差异。4. 请求头修改几个真正会改变结果的字段改请求头是接口调试里最有技术含量的部分。有些头改了没影响有些头改了结果完全变样。下面这几个是我实际工作中改得最多、也最需要搞清楚的。4.1 Content-Type 决定后端怎么解析 BodyContent-Type是请求体格式的声明后端框架靠它来选择解析器。常见的取值有这么几种application/x-www-form-urlencoded传统表单格式键值对用连接值做 URL 编码。multipart/form-data文件上传用的格式每段数据有独立的分隔符和头部。application/json现在最主流的 API 格式请求体是一段合法 JSON。text/xml老系统的常见格式请求体是 XML 字符串。这个头不匹配的后果很直接。后端如果是 Spring MVCRequestBody接收 JSON你发 form 格式它会报 415 不支持的媒体类型反过来后端用RequestParam接收表单你发 JSON参数全是 null。很多人遇到参数传了但后端读不到八成就是这里对不上。实测下来最省事的做法是优先信任接口文档的 Content-Type不要凭感觉选。文档没写清楚时就去问后端或者抓一次正常业务的包看真实请求用的是什么。4.2 User-Agent / Referer / Origin 的作用User-Agent标识客户端类型和版本服务器用它做适配、统计、风控。有些接口会根据 UA 返回不同内容比如返回移动端精简版还是 PC 端完整版。调试时如果结果和浏览器不一致可以试着把 UA 换成浏览器的 UA 再试。Referer表示当前请求是从哪个页面发起的常用于防盗链和来源统计。一个典型的例子图片服务器配置了防盗链只允许来自本站页面的请求访问图片直接粘贴图片地址打开会被拒绝。这时在 Postman 里补一个正确的 Referer请求就能通过。Origin和跨域相关浏览器在跨域请求时会自动带上它服务器根据它决定是否返回允许跨域的响应头。用 Postman 发请求不受同源策略限制所以经常出现Postman 能通、浏览器不通的现象原因就是浏览器会在跨域时先发一个 OPTIONS 预检请求服务器没正确响应预检真正的请求就不会发出。遇到这类问题别急着改代码先用 Postman 发一个 OPTIONS 请求看看服务端回了什么。4.3 Cookie 与 Authorization 的取舍身份凭证的传递方式主要有两种Cookie和Authorization。Cookie 是传统方式浏览器自动携带和管理服务端用Set-Cookie下发Authorization 是令牌方式客户端手动在请求头里带上Bearer xxx。调试接口时如果用 Cookie 认证Postman 可以开启 Cookie 管理把登录后的 Cookie 自动存下来供后续请求使用如果用 Token 认证就手动加一个 Authorization 头。这里有个容易忽略的点Token 通常有有效期过期后会返回 401而不是 403。看到 401 时第一反应应该是凭证无效或过期了而不是权限不够。4.4 Connection 与 HTTP 连接复用Connection头控制连接的复用行为。HTTP/1.0 默认是close每次请求都要重新建立 TCP 连接HTTP/1.1 默认是keep-alive一个 TCP 连接上可以连续发多个请求这就是连接复用。复用的价值在于省掉了反复三次握手和慢启动的开销对高并发场景提升明显。不过连接复用也有它的边界。HTTP/1.1 的复用是串行的同一个连接上请求要排队前一个没回完后一个就得等这就是所谓的队头阻塞。HTTP/2 引入了多路复用在一个连接上并行传输多个请求缓解了这个问题。另外连接不是永久保持的服务端和客户端都有空闲超时超时后会主动关闭。如果你在调试时看到连接被对端关闭这类报错很可能就是空闲太久、连接已被回收客户端却还在往旧连接上发数据。注意Connection属于逐跳头代理服务器可能会把它剥掉或改写你手动设置的值不一定能原样传到最终服务端。5. 状态码判断从三位数字定位问题在哪一层状态码是服务器对本次请求的一句话结论。三位数字第一位代表大类读懂它能让你在排查问题时少走很多弯路。5.1 五类状态码的分工类别含义责任方典型值1xx信息性服务端100、1012xx成功正常200、201、2043xx重定向客户端需跟进301、302、3044xx客户端错误请求方400、401、403、404、405、4295xx服务端错误服务端500、502、503、504这个表的实用价值在于责任划分。看到 4xx先检查自己的请求参数对不对、凭证带没带、路径拼错没有。看到 5xx基本可以确定问题在服务端或者中间链路上你把客户端代码翻个底朝天也没用。我以前有个习惯看到请求失败就先去改代码结果折腾半天发现是服务端 500白白浪费了时间。后来学会先看状态码定责效率高了很多。5.2 401、403、404、405 容易混的几个这几个都是 4xx但含义差别很大400 Bad Request请求本身格式有问题比如 JSON 语法错误、必填参数缺失。401 Unauthorized没提供凭证或凭证无效。名字有误导性它其实表示未认证。403 Forbidden凭证有效但没权限访问这个资源。404 Not Found资源不存在或者路径拼错了。405 Method Not Allowed路径存在但不支持你用的这个方法比如接口只允许 POST你发了 GET。429 Too Many Requests触发限流了请求太频繁。401 和 403 的区别我用一句话记401 是你是谁我不知道403 是我知道你是谁但你不能进。404 和 405 的区别也很实用404 说明连资源都没找到405 说明资源找到了但方法不对。遇到 405 时最简单的验证办法是把方法换成 OPTIONS 发一次响应头里的Allow字段会列出这个接口支持哪些方法。5.3 502 / 503 / 504 的排查顺序这三个 5xx 在网关场景下特别常见尤其是 502几乎所有做过后端的人都遇到过。它们的区别502 Bad Gateway网关从上游服务收到了一条无效响应。可能是上游进程挂了、端口没人监听、上游返回了非法内容、连接被上游重置。503 Service Unavailable服务暂时不可用通常是过载、正在重启、或者主动限流。504 Gateway Timeout网关等上游响应超时了。排查 502 的顺序我总结成四步看上游服务是否活着直接curl上游地址看有没有响应。如果没有先确认进程和端口。看网关的错误日志日志里通常会说清楚是连接被拒、读取超时还是响应格式非法。检查上游响应是否合法上游如果返回了非 HTTP 格式的内容网关会判定为无效响应并报 502。检查超时和缓冲区配置上游处理慢、响应体大超过网关的超时或缓冲区限制也会报错。提示502 和 504 经常被混淆。简单说502 是上游给了个坏答案504 是上游根本没给答案等太久了。定位方向不同别一概而论。6. 常见问题与排查实录前面讲的是原理和结构这一节记录几个我在实际操作中反复遇到的具体问题和解法都是些文档里不太会写、但实际很耗时间的东西。6.1 问题速查表现象可能原因快速验证方法参数传了但服务端读不到Content-Type 与 Body 格式不匹配对照接口文档确认 Content-TypePostman 通、代码不通默认请求头不同或代理配置不同导出 cURL 与代码配置逐行对比返回 401 但 token 看着没过期时钟偏差、环境变量没切、Token 前缀缺失确认Bearer前缀和当前环境返回 404 但路径确认没错变量没替换、路径多了或少了一层关闭变量直接写死真实地址再试上传文件失败Content-Type 不是 multipart/form-data用 Postman 的 form-data 类型请求偶发失败连接被复用后服务端已关闭关掉 keep-alive 或用新连接重试响应中文乱码响应头 charset 缺失或与实际编码不符检查 Content-Type 里的 charset6.2 几个踩过的坑坑一以为改了 URL 就是新连接。有一次调试一个长连接接口改完参数重新发送结果返回的还是上一次的旧数据。排查后发现客户端复用了同一个连接而服务端对同一连接上的请求做了缓存处理。后来在请求头里临时加上Connection: close强制每次新建连接问题立刻消失。这让我意识到连接复用虽然是性能优化但在调试阶段反而会掩盖问题必要时得主动关掉。坑二把 302 当成成功。有些工具默认会自动跟随重定向你看到的是最终页面的 200中间的 302 被隐藏了。如果接口在重定向过程中丢失了请求体很多客户端在 301/302 时会改方法或丢 body最终结果就会出错。后来我养成习惯在 Postman 里把自动跟随重定向关掉先看第一跳的状态码和 Location 头再决定要不要手动跟进。坑三JSON 里多了一个逗号。这个坑说出来有点丢人但确实常见。手写 JSON Body 时最后一个字段后面多加了一个逗号或者用了单引号服务端解析失败返回 400。Postman 对 raw JSON 的语法校验不是强制的写错了它照发不误。后来我改成先在编辑器里格式化好、确认合法再粘贴进 Postman这类低级错误就基本没有了。坑四环境变量和请求头同名。Postman 里的变量引用如果写错名字不会报错会原样发出去。我遇到过一次请求头里要带一个叫X-Token的值我把环境变量名写成了{{token}}而实际变量叫{{access_token}}结果请求头里发的就是字面量{{token}}服务端当然认不出来。这类问题不报错、只报业务失败最难查所以变量名一定要和定义严格一致。坑五忽略了响应时间和响应体大小。排查问题时只看状态码是不够的响应时间异常长、响应体异常大往往意味着服务端有问题即使状态码是 200。比如一个列表接口突然返回了几兆的数据可能是分页参数没生效查的是全量数据。这种问题状态码看不出来得靠观察响应体的实际内容和耗时来判断。我现在调试接口会习惯性看一眼 Postman 右下角的耗时和大小两个数字有异常就顺藤摸瓜查下去。说到底HTTP 这套东西的学习曲线不在于概念多难而在于细节多、默认行为多、工具会自动帮你做很多事导致你看不清真实发生了什么。我的经验是调试时尽量把自动化关掉让工具笨一点关闭自动重定向、关闭自动携带 Cookie、手动指定 Content-Type这样每一次请求都清清楚楚出了问题也能快速定位到底是哪一环变了。等把裸的请求搞明白了再打开那些便利功能心里就有底了。这个顺序反过来做往往会在出问题时一头雾水越查越乱。