1. HTTP到底是什么:先把它当“点餐流程”来理解
1.1 为什么所有Web服务都离不开HTTP
做开发和运维这些年,我见过太多同事被报错信息绕得团团转,回头一看根因就是对HTTP协议的理解停留在“发请求、收响应”这一步。HTTP,全称HyperText Transfer Protocol,超文本传输协议,是Web服务里最基础的通信语言。
你可以把它想象成在餐厅点餐:你(客户端)举手示意要菜单和点菜,服务员(协议)把你的需求记下来,传给后厨(服务器),后厨做完后端上来,你收到菜再决定要不要继续加菜。整个过程就是一次HTTP交互。没有这套约定的流程,客户端和服务器各说各话,Web世界根本跑不起来。
理解了这一点,你就能明白HTTP解决的核心问题:让两个完全不同的系统,按照一套通用规则安全、可靠地交换信息。这套规则和语言无关,和操作系统无关,和硬件平台也无关。所以不管是浏览器访问网页、手机App请求后端接口、还是两台服务器之间同步数据,底层走的都是同一套HTTP逻辑。这篇文章适合所有刚接触Web开发、运维排查、接口联调的人,读完你能把HTTP的基础知识和Web服务的运行链路串起来,遇到报错时能快速定位问题方向。
1.2 一次请求的完整结构:请求行、请求头、请求体
我们常说的“HTTP报文”,其实分请求报文和响应报文两部分。很多人只会在代码里调接口,从没认真看过报文长什么样,这是很可惜的。我强烈建议你打开终端跑一条命令:
bash复制curl -v http://example.com
-v参数会打印出完整的请求和响应过程。你可以看到类似这样的输出:
text复制> GET / 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
< Server: ECS (dcb/7F83)
>开头的是请求报文,<开头的是响应报文。请求报文由三部分组成:
- 请求行:第一行,包含请求方法(GET)、请求路径(/)、协议版本(HTTP/1.1)。
- 请求头:从第二行到空行之前,包含Host、User-Agent、Accept等,用来告诉服务器“我是谁、我想要什么格式的内容”。
- 请求体:空行之后的内容,GET请求通常没有,POST/PUT这类方法会把数据放在这里。
响应报文结构类似,第一行变成了状态行,包含协议版本、状态码(200)、状态描述(OK),后面是响应头和响应体。
有个细节值得留意:请求头和请求体之间那个空行不能省略,它是协议的分隔符。曾经有开发同学用字符串拼接报文时忘了加空行,服务端一直解析不出来,排查半天才发现是少了\r\n\r\n。HTTP报文的换行规范是CRLF,也就是\r\n,不是单纯的\n,这里很容易踩坑。
1.3 HTTP方法:不只是GET和POST
日常开发中大家用得最多的就是GET和POST,但HTTP协议里定义的方法远不止这两个。我见过不少人在设计接口时只用POST,理由是“GET和POST都能传数据,干脆统一用POST”,这种想法会带来语义混乱,也给后续维护埋雷。
下表是常用HTTP方法的语义和特点:
| 方法 | 主要用途 | 是否携带请求体 | 是否幂等 |
|---|---|---|---|
| GET | 获取资源 | 通常不携带 | 是 |
| POST | 创建资源或触发操作 | 是 | 否 |
| PUT | 整体更新资源 | 是 | 是 |
| PATCH | 部分更新资源 | 是 | 否 |
| DELETE | 删除资源 | 通常不携带 | 是 |
| HEAD | 只获取响应头,不获取响应体 | 通常不携带 | 是 |
| OPTIONS | 询问服务器支持的请求方法,或CORS预检 | 通常不携带 | 是 |
解释一下“幂等”这个概念:同一个请求执行一次和执行多次,对服务器资源产生的效果是一样的。比如GET请求重复发十次,不影响服务器数据。但POST请求重复发十次,可能会创建十条记录。这个特性在选择请求方法时非常重要。
OPTIONS方法在Web服务里有特殊地位。浏览器发起跨域请求时,如果请求方式较复杂,例如携带了自定义Header或者使用PUT/DELETE,会先发一个OPTIONS预检请求,询问服务器允不允许。有些安全加固要求禁用或限制OPTIONS方法,因为攻击者可能利用它探测服务器支持的HTTP方法和Header信息。在Nginx里可以这样配置,把OPTIONS请求直接返回204:
nginx复制if ($request_method = OPTIONS) {
return 204;
}
或者更精细地限制允许的方法:
nginx复制limit_except GET POST OPTIONS {
deny all;
}
1.4 状态码:一眼定位问题
状态码是服务端返回给客户端的“处理结果通知”,三位的数字编码,按首位数字分成五大类。学会看状态码,排查问题至少快一半,因为你不用再看几十行日志才能猜到发生了什么。
| 状态码范围 | 类别 | 典型场景 |
|---|---|---|
| 1xx | 信息提示 | 101 Switching Protocols,WebSocket升级时使用 |
| 2xx | 成功 | 200正常返回,201创建成功,204无内容 |
| 3xx | 重定向 | 301永久移动,302临时跳转,304未修改 |
| 4xx | 客户端错误 | 400请求格式错误,401未认证,403无权限,404找不到 |
| 5xx | 服务器错误 | 500服务器内部异常,502网关错误,503服务不可用 |
这几个状态码里,最容易混的是401和403。401表示“你是谁我没搞清楚”,需要提供有效凭证;403表示“我知道你是谁,但你没权限访问这个资源”。可以这样记:401是没带身份证,403是带了身份证但没资格进这个房间。
还有一点容易忽略:3xx重定向不是错误。后端接口返回301或者302,代表资源地址变了,客户端需要跟着Location头去新地址。这里有个坑,跨域请求遇到重定向时,部分浏览器会直接报错,因为Location指向的域名和原域名不一致,被CORS拦住了。排查此类问题要重点看响应头里的Location字段。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Web服务是怎样跑起来的:从地址栏到页面渲染
2.1 URL到浏览器页面:中间到底发生了什么
在地址栏输入网址,敲回车,到页面显示出来,这个过程看似一瞬间,实际经历了好几个阶段:
- DNS解析:把域名翻译成IP地址。浏览器会先查本地缓存,再查系统配置的DNS服务器。
- 建立TCP连接:浏览器和服务器IP建立TCP连接,经历三次握手。这一步要确认双方收发能力都没问题。
- 发送HTTP请求:TCP连接建立后,浏览器把HTTP请求报文发送给服务器。
- 服务器处理并返回:服务器收到报文,根据路径和参数处理后返回HTTP响应。
- 浏览器解析渲染:浏览器拿到HTML后,发现里面还引用了CSS、JS、图片等资源,会再发起多次HTTP请求去获取。
- 关闭连接:HTTP/1.1默认开启Keep-Alive,TCP连接不立即关闭,多个请求可以复用。
这里最值得关注的是DNS解析阶段。之前有同事反馈“网站时好时坏”,排查了一圈,发现是本地电脑的DNS被配置成了某个不稳定的地址,解析时快时慢。建议遇到此类问题先用nslookup或dig查一下域名解析结果,再ping一下目标IP排除网络问题。
另外要注意HTTP/1.1和HTTP/2的区别。HTTP/1.1一个TCP连接同一时间只能发一个请求,浏览器为了提速通常会开6个左右的并行连接。HTTP/2引入了多路复用,多个请求可以共享一个TCP连接,性能提升明显。
2.2 HTTP和HTTPS:差的那一层不是小事
“HTTP和HTTPS的区别”是面试高频题,也是实际部署中必须搞懂的点。简单说,HTTPS = HTTP + TLS加密层,默认端口从80变成443。
为什么必须上HTTPS?因为HTTP报文是明文传输的。你连了一个公共WiFi,用HTTP访问网站,中间任何设备都能看到你的请求内容和服务器返回的内容,包括密码、Cookie、聊天记录。HTTPS通过TLS协议做了三件事:
- 加密传输数据,防止被窃听。可以理解成点餐时用只有你和后厨能懂的暗号交流,旁边的人听不懂。
- 身份认证,防止被冒充。服务器会出示数字证书,客户端验证证书由可信CA签发,才确认对方是真正的服务器。
- 完整性校验,防止数据被篡改。传输过程中任何一个字节被改动,接收方都能检测出来。
现在部署Web服务,基本没有理由再裸奔HTTP。Nginx里配置HTTPS其实不复杂,核心是证书文件和私钥文件:
nginx复制server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/nginx/certs/example.com.pem;
ssl_certificate_key /etc/nginx/certs/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://127.0.0.1:8080;
}
}
一个容易忽略的点:换成HTTPS后,代码里如果硬编码了http://的资源地址,页面虽然能打开,但浏览器会提示“不安全”或“混合内容”警告。排查类似问题的方法很简单,打开浏览器的开发者工具,看Console面板里报错的资源地址,把http://改成https://或直接用相对路径。
2.3 服务端选型:静态、动态、反向代理怎么选
Web服务这个词范围很广,从最简单的静态文件服务器到复杂的微服务网关都算。实际项目中,我们需要根据业务场景选择合适的技术组合。
我总结的选型思路是这样的:
- 只是托管静态页面、图片、JS/CSS文件,Nginx或者Caddy足够,配置量小,性能高。
- 需要处理业务逻辑、读写数据库,就要有应用服务,比如Java的Spring Boot、Python的Flask/Django、Node.js的Express等。
- 应用服务不要直接暴露公网,前面挂一层反向代理。Nginx是最常见的反向代理选择,负责接收外部请求、转发给内部应用服务,再把结果返回给客户端。
- 请求量大了,反向代理再加个负载均衡功能,把流量分发给多台后端服务器。
为了演示真实场景,我通常会在一台服务器上装Nginx,然后本地起一个Python服务作为后端,配置Nginx的proxy_pass把请求转发过去。这样既模拟了生产架构,又方便观察HTTP报文的流转。
为什么非要加一层反向代理?直接让应用服务监听80端口不就行了吗?这里有三个现实原因:
- 应用服务直接暴露公网,容易把框架的报错信息、内部IP透传出去,安全隐患大。
- 一个服务器上可能跑好几个应用,端口各不同,需要根据域名或路径分流。
- 应用服务自身处理静态文件的能力通常不如Nginx,由Nginx挡在前面处理静态资源,能减轻应用压力。
2.4 HTTP和RPC:什么时候用哪个
热词里有人问“HTTP与RPC之间的区别”,这也是个值得展开的话题。很多团队在微服务内部通信选型时纠结,是继续用HTTP还是上RPC框架。
HTTP是应用层协议,RPC是一种远程调用思想,中文叫远程过程调用。两者不是同一个层面的东西,所以不能简单说谁替代谁。
我给你的使用建议:
- 对外提供API接口,用HTTP/HTTPS。因为HTTP是通用协议,任何语言的客户端都能方便调用。RESTful风格的接口在Web场景里是事实标准。
- 内部服务间高频调用,可以用RPC框架,比如gRPC、Dubbo。RPC通常用二进制序列化,传输效率高,还内置了服务发现、负载均衡、链路追踪等能力。
- 如果团队规模不大,内部服务间直接用HTTP也完全没问题,少引入一套RPC框架,运维会更简单。
gRPC虽然底层走的是HTTP/2,但它有自己的接口定义语言,生成的客户端代码调用起来就像调用本地方法一样。这里面的取舍本质是:通用性优先选HTTP,性能和服务治理能力优先选RPC。
3. 动手实操:搭建服务、发起请求、抓包分析
3.1 5分钟起一个HTTP服务
很多初学者以为搭建Web服务要装一大堆软件,其实Python自带的模块就能在5分钟内起一个HTTP服务,特别适合本地测试和临时文件共享。
在命令行里进入某个目录,执行:
bash复制python3 -m http.server 8000
然后浏览器访问http://127.0.0.1:8000,就能看到目录文件列表。这个服务支持文件下载,日志会打印在终端里,每收到一个请求就输出一行记录,格式大概是:
text复制127.0.0.1 - - [12/Aug/2025 10:00:00] "GET / HTTP/1.1" 200 -
这功能虽然简单,但用来演示HTTP请求过程已经够了。注意它只支持GET和HEAD,不支持POST,也没有框架的路由逻辑。
如果想看更接近生产环境的服务,用Flask写一个最简单的带POST接口的服务:
python复制from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/hello', methods=['GET', 'POST'])
def hello():
if request.method == 'POST':
data = request.get_json()
return jsonify({"received": data, "method": "POST"})
return jsonify({"message": "hello, GET"})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)
启动后服务监听在8080端口,接下来就能用各种工具去调试了。
3.2 curl——HTTP调试中最顺手的工具
curl是后端开发和运维必会的工具,没有之一。它支持几乎所有的HTTP方法、自定义Header、携带请求体、处理Cookie,用起来非常顺手。
之前热词里有人问“IDEA HTTP怎么测试POST携带data”,这其实涉及接口调试工具的用法。在命令行里用curl发送POST请求,写法是这样的:
bash复制curl -X POST http://127.0.0.1:8080/hello \
-H "Content-Type: application/json" \
-d '{"name": "张三", "age": 18}'
-X POST指定方法,-H指定请求头,-d指定请求体。curl默认会把响应内容打印到终端,如果你只想看状态码和响应头,可以加-o /dev/null -w "%{http_code}"参数。
调试接口时我经常推荐几个参数组合:
bash复制curl -v http://127.0.0.1:8080/hello
-v打印详细报文,适合分析协议层问题。
bash复制curl -i http://127.0.0.1:8080/hello
-i在输出中显示响应头,适合查看Content-Type、Set-Cookie等信息。
bash复制curl -L http://example.com
-L跟随重定向,遇到301/302时自动跳转到Location指定的地址。
还有一个实用场景:模拟慢请求或者指定超时时间,用--max-time参数,例如curl --max-time 5 http://example.com,5秒内没响应就断开。这对排查“接口卡住”的问题很有用。
3.3 用Nginx配置反向代理
生产环境中Nginx承担的角色很重要。我们用前面那个Flask服务作为后端,然后用Nginx把/api/路径的请求转发给它。
先安装Nginx,不同系统命令不一样,我这里以Ubuntu为例:
bash复制sudo apt update
sudo apt install nginx
然后修改配置文件,通常路径是/etc/nginx/sites-available/default:
nginx复制server {
listen 80;
server_name _;
location /static/ {
alias /var/www/static/;
expires 7d;
}
location /api/ {
proxy_pass http://127.0.0.1:8080;
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-Forwarded-Proto $scheme;
}
}
最后sudo nginx -t检查配置语法,正确后sudo systemctl reload nginx生效。
这段配置做了几件事:
/static/开头的请求,直接从本机/var/www/static/目录返回文件,不经过后端,静态资源响应速度更快。/api/开头的请求,转发到127.0.0.1:8080,也就是Flask服务。注意转发时带上了Host、客户端真实IP等头,后端才能记录真实访问来源。
验证时,访问http://服务器IP/api/hello,如果能返回Flask的JSON响应,说明反向代理配置成功。
这里要特别说明proxy_pass的路径拼接规则,这是最容易出错的点。location /api/配proxy_pass http://127.0.0.1:8080;(结尾没有路径),会把/api/hello整个拼到后端地址后面,变成http://127.0.0.1:8080/api/hello。如果proxy_pass http://127.0.0.1:8080/;(结尾带斜杠),会把/api前缀去掉,变成http://127.0.0.1:8080/hello。很多同学在这两个写法之间来回踩坑,记住这个差异即可。
3.4 浏览器开发者工具看请求
调试Web接口,浏览器开发者工具的Network面板是利器。按F12打开,切到Network标签,刷新页面,就能看到所有HTTP请求的列表。
点开任意一个请求,有这几个关键区域要会看:
- Headers:请求URL、请求方法、状态码、请求头、响应头。注意看
General部分的Request Method和Status Code,Response Headers里重点关注Content-Type和Cache-Control。 - Payload:POST请求发送的请求体数据,能看到表单字段或JSON内容。
- Preview / Response:响应内容,Preview是格式化后的视图,Response是原始文本。
- Timing:请求各阶段耗时,比如DNS解析、TCP连接、请求发送、等待响应、内容下载各占多长时间,能快速找出慢在哪一步。
举一个实际排查例子:页面上的接口偶尔返回500,刷新几次又好了。打开Timing看到Waiting (TTFB)时间特别长,说明服务器处理请求耗时太久,多半是后端逻辑有慢查询或接口阻塞。如果看到Content Download时间特别长,说明响应体太大或网络链路质量差,需要考虑压缩或CDN加速。
4. 高频问题排查实录:这些热词背后的常见坑
4.1 400 Bad Request:最常见却不是最省心的错
热词里有一条关于API返回400的报错,大意是某个大模型接口在thinking mode下没有把reasoning_content回传给服务端,导致上游返回了400。这类问题的本质是:客户端发送的请求不符合服务端对格式和内容的预期。
HTTP 400 Bad Request表示服务器无法理解客户端发送的请求。你可能觉得这个错误码很常见,但排查起来反而不轻松,因为服务器通常不会告诉你具体哪部分格式不对。常见的触发原因有:
- 请求头格式错误,比如Header少了冒号、值里包含非法字符。
- 请求体是无效JSON,比如多了个逗号、少了个引号。
- Content-Type和实际请求体内容不匹配,服务端按JSON解析,实际发的是表单格式。
- 请求包含服务端不认识的参数或字段,严格校验的框架会直接拒绝。
排查400问题的思路,我推荐三步走:
第一步,用curl手动复现请求,去掉代码里的动态变量,确认问题能否稳定出现。
第二步,对比正常请求和异常请求的报文差异,用curl -v打印完整报文,看Header、请求体到底哪里不一样。
第三步,看服务端框架的校验规则,比如Spring Boot的@RequestBody要求请求体必须是合法JSON,Django的JSONParser对JSON语法也很敏感。
对接口调用方来说,400是“你的问题,不是我的问题”。拿到400先别急着找服务端的人,自己把请求报文打出来检查一遍,通常能省下大量沟通成本。
4.2 401和403:认证失败与权限不足
热词里多次出现“remote: HTTP Basic: access denied”这类报错,常见于Git仓库操作时。HTTP Basic是HTTP协议里最简单的认证方式,把用户名:密码做Base64编码后放在Authorization头里。Git客户端推送代码时如果认证信息错误,服务器就会返回401或者403。
这类报错要区分两种情况:
- 返回401 Unauthorized,说明凭证缺失或错误,检查用户名、密码、Token是否写对了,尤其注意密码里如果含有特殊字符,要检查是否被转义。
- 返回403 Forbidden,说明凭证有效,但账号没有对应仓库的操作权限。可能是项目中没有加成员,也可能是分支保护规则禁止直接推送。
补充一个运维排查经验:有些网页登录后可以正常访问,但用curl模拟时需要手动处理Cookie。很多人第一次写爬虫时,不带Cookie直接请求登录后才能访问的页面,返回401。正确的做法是用浏览器登录,从开发者工具里复制Cookie值,加到curl的-H "Cookie: ..."参数里。
4.3 404 Not Found:路径、镜像源、配置文件
404是出现频率最高的错误码,但“找不到资源”的原因可能出在不同层次:
- 请求路径拼写错误,少个斜杠、多个字符都会导致404。
- 资源确实被删除或改名。热词里有条“HTTP错误404.0”,后面跟着“您要找的资源已被删除、已更名或暂时不可用”,这就是经典的文件不存在场景。
- 服务端路由配置错误,比如Nginx的
location规则匹配不上。 - 静态部署时,刷新某个前端路由返回404,因为服务器上根本没有这个路径对应的文件,要配置
try_files规则回退到index.html。 - 包管理器源地址返回404。热词里好几条Conda的报错,类似
UnavailableInvalidChannel: HTTP 404 NOT FOUND for channel anaconda/pkgs/free,说明配置的channel源中对应的包索引不存在了。
Conda的404多数情况是源地址失效,或者旧版channel已经停止维护。解决方案是切换到一个可用的镜像源,例如清华开源软件镜像站提供的Anaconda仓库。
bash复制conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --set show_channel_urls yes
配置完先执行conda clean -i清理索引缓存,再重新安装包。同理,Docker拉取镜像时如果出现404,也可能是镜像仓库路径不对,或者镜像Tag不存在,先用docker search确认镜像存在,再确认Tag是否写对。
4.4 500、502、503:服务器侧故障的判断思路
5xx状态码表示服务器端出了问题,但具体问题还是要细分:
- 500 Internal Server Error:服务器内部异常。可能是代码报错、数据库连接失败、配置文件错误。排查方案是先看应用日志,找到完整的异常堆栈。很多框架默认不把异常堆栈返回给客户端,所以不要只盯着响应体看,要进服务器看日志。
- 502 Bad Gateway:反向代理拿到了后端返回的“无效响应”,比如Nginx后面挂的Flask服务崩了、没启动、或者超时。热词里有一条
unexpected status 502 bad gateway: unknown error, url: http://127.0.0.1:1572,这种带具体URL的502,一看就是代理后端服务没有正常响应。 - 503 Service Unavailable:服务暂时不可用。常见原因是服务器过载、正在重启、或者刻意维护中。Nginx实例未启动时访问Nginx端口,也可能返回502或503,具体看代理层配置。
排查502有个标准套路。假设Nginx代理Flask服务,访问返回502,按顺序做四件事:
- 检查Flask进程是否存活:
ps aux | grep flask。 - 本地直接访问后端看是否正常:
curl http://127.0.0.1:8080/hello。 - 看Nginx错误日志,通常在
/var/log/nginx/error.log,里面会明确记录connect() failed (111: Connection refused)还是upstream timed out。 - 如果是
Connection refused,后端服务没起来或端口监听错误,用ss -lntp查监听情况;如果upstream timed out,后端处理太慢,调大proxy_read_timeout。
4.5 超时与连接失败:网络层排查要点
网络超时类报错是团队里高频出现的困扰。比如热词里的client.timeout exceeded while awaiting headers,来自Docker拉取镜像时的报错,还有Connection timed out: getsockopt这类经典网络层超时。
不要一看到超时就只想到“网不好”,超时可能发生在好几个不同的阶段:
- 建立TCP连接超时:连不上目标主机的端口,服务器没启动、防火墙拦截、路由不可达都有可能。
- 等待TLS握手超时:目标端口存在但没正常进行TLS协议。
- 等待响应头超时:连接建立成功,但服务器迟迟没有返回任何HTTP响应,通常是后端处理卡住了。
- 等待响应体超时:响应头收到了,但响应体传输太慢或中断。
排查网络层问题,我习惯按这个顺序操作:
先用ping看目标IP通不通,排除路由不通;再用telnet 目标IP 端口或nc -vz 目标IP 端口测试端口是否监听;如果端口通,再用curl带--connect-timeout和--max-time参数区分是连接耗时还是响应耗时;最后用ss -s看本机连接数,有些服务器连接数耗尽后新连接会卡在握手阶段。
Docker拉取镜像超时属于特殊场景,除了网络链路本身的问题,镜像仓库连接不稳定也很常见。解决方案是给Docker配置registry加速器,编辑/etc/docker/daemon.json:
json复制{
"registry-mirrors": [
"https://docker.m.daocloud.io"
]
}
配置完执行sudo systemctl restart docker,再重新拉取镜像。注意不同镜像加速地址的服务质量可能随时间变化,遇到失效时要及时更换可用源。
4.6 高频问题速查表
最后把前面涉及的常见问题整理成一个速查表,方便你排查时对照。
| 现象 | 可能原因 | 首选排查方向 |
|---|---|---|
| 400 Bad Request | 请求格式错误、JSON语法错误、字段不符 | curl -v 对比报文,检查请求体 |
| 401 Unauthorized | 缺少凭证、用户名密码错误 | 检查Authorization头和Token有效性 |
| 403 Forbidden | 有凭证但无权限 | 检查账号权限、仓库访问权限 |
| 404 Not Found | 路径错误、资源不存在、源失效 | 核对URL路径,检查包管理源配置 |
| 500 Internal Server Error | 后端代码异常、依赖服务异常 | 看应用日志的异常堆栈 |
| 502 Bad Gateway | 代理后端未启动、响应无效 | 本地直连后端,查Nginx错误日志 |
| 503 Service Unavailable | 服务过载、重启、维护中 | 查负载情况,确认服务状态 |
| 连接超时 | 防火墙拦截、端口不通、服务器负载高 | ping + telnet 分段测试 |
| 等待响应头超时 | 后端处理卡住、连接池耗尽 | 查后端慢日志,看线程池/连接池状态 |
再补充几条经验,是我用了很久的排查原则:
第一,先curl,后代码。出问题第一时间用命令行工具手动复现,把代码参与降到最低,能直接确认是不是HTTP协议层的问题。
第二,看日志比看响应体更重要。响应体是给客户端看的,日志是给排查者看的,很多关键错误细节只写在服务端日志里。
第三,500和502类错误,大多数时候不是HTTP本身的问题,而是周边依赖问题,数据库、缓存、消息队列任何一个挂了,都可能表现为5xx。
我在实际排查中最大的体会是:HTTP协议本身不复杂,复杂的是它连接的各种系统。遇到问题别慌,把一次请求拆成“客户端构造报文—网络传输—服务端解析处理—服务端返回—客户端解析响应”这几个环节,逐段验证,基本没有排查不出来的问题。掌握这个思路,比背下所有状态码的含义有用得多。
