命令行 HTTP 请求提供一个范围较小、容易检查的服务视角:不用经过浏览器渲染,就能看到状态、响应头、正文和耗时。开始之前先明确问题,是检查原地址是否正确跳转、最终页面能否读取,还是某种请求方法是否被接受。不同问题需要不同探测方式。一条 curl 命令成功,只能说明这次请求满足了对应条件,不能证明网站所有功能都正常,也不能直接代替用户实际访问路径的验证。
分开保存四类证据
默认情况下,curl 输出响应正文,即使 HTTP 表达错误,进程也可能成功退出。命令退出状态描述传输工具的结果,HTTP 状态则描述已经收到的响应,两者都应该保留。收到正文为“missing”的四零四响应,与根本没有取得 HTTP 响应的连接失败,并不是同一种故障。监控脚本需要明确判断自己关心的响应契约,不能只靠 shell 有没有返回非零状态。
-D 用于保存响应头,-o 指定传输输出去哪里,-w 则单独打印选定的元数据。-sS 隐藏进度条,同时保留错误信息;--max-time 限制传输时间,跟随跳转的探测还应限制最多跳转次数。保存结果时记录所用选项,之后比较才知道方法、地址和请求头是否相同。把这些维度混在一起,往往会把客户端条件变化误认为服务器行为变化。
耗时指标也受连接复用、代理路径和跳转影响。复用连接可能根本不发起新的 DNS 查询;对比之前应保证条件相当,不能因为查询时间一项下降,就判断 DNS 服务性能已经改善。
坏掉的样子
一开始就加 -L,可能让健康的最终页面掩盖错误的第一跳。排查路由时先看原始状态和 Location,验证目标内容时再明确选择跟随。跳转循环、意外的目标主机或方法变化,都可能有问题,即便链条后面某个页面返回二百。不能把“总是跟随跳转”当作所有探测都适用的成功定义;跳转行为本身有时就是要验证的契约。
-I 发送的是 HEAD,不是 curl 发出 GET 后简单丢弃正文。服务器、代理和授权规则可能对这两种方法处理不同。HEAD 失败、GET 成功时,应先检查方法差异,不要立即判定资源不可用。同样,修改 User-Agent 请求头,也不会让 curl 自动具备浏览器的 JavaScript、排版、Cookie 规则或 CORS 执行能力。这些行为仍需要在对应环境里分别检查。
详细调试输出可能包含凭据和私有请求信息,应只收集诊断需要的字段,避免把秘密放进命令和长期保存的轨迹。关闭证书验证会改变测试问题,不能再用这次成功证明普通 HTTPS 客户端信任服务器。继承的代理设置和 curl 配置也可能改变路径或行为;需要可复现结果时,应明确控制相关输入,而不是默认每台机器都会发出同样的请求。
快速验证
把代码保存为 curl-probe.py,在安装 curl 的环境执行 python3 curl-probe.py。本例使用 curl 8.7.1 核验。脚本运行临时回环 HTTP 服务,并调用真正的 curl 命令;第一个选项禁用默认 curl 配置,本地请求也显式绕过代理。实验不涉及外部网站或 TLS 服务,所有返回内容都是脚本自己的演示数据。
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from tempfile import TemporaryDirectory
from threading import Thread
import json
import subprocess
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
status = 302 if self.path == '/move' else 200 if self.path == '/ok' else 404
data = b'healthy\n' if status == 200 else b'missing\n' if status == 404 else b''
self.send_response(status)
if status == 302:
self.send_header('Location', '/ok')
self.send_header('Content-Length', str(len(data)))
self.end_headers()
self.wfile.write(data)
def do_HEAD(self):
self.send_response(405)
self.send_header('Allow', 'GET')
self.send_header('Content-Length', '0')
self.end_headers()
def log_message(self, *args):
pass
server = ThreadingHTTPServer(('127.0.0.1', 0), Handler)
thread = Thread(target=server.serve_forever)
thread.start()
try:
origin = f'http://127.0.0.1:{server.server_port}'
with TemporaryDirectory() as directory:
root = Path(directory)
def probe(path, *options):
body, headers = root / 'body', root / 'headers'
result = subprocess.run([
'curl', '-q', '--noproxy', '*', '--max-time', '5', '-sS',
'-o', str(body), '-D', str(headers), '-w', '%{http_code} %{num_redirects}',
*options, origin + path], capture_output=True, text=True, timeout=10)
status, redirects = map(int, result.stdout.split())
return {'exit': result.returncode, 'status': status, 'redirects': redirects,
'output': body.read_text(), 'allow_get': 'Allow: GET' in headers.read_text()}
results = {'missing_default': probe('/missing'),
'missing_fail': probe('/missing', '--fail-with-body'),
'redirect_inspect': probe('/move'),
'redirect_follow': probe('/move', '--location', '--max-redirs', '2'),
'head': probe('/ok', '--head'), 'get': probe('/ok')}
assert results['missing_default']['exit'] == 0
assert results['missing_fail']['exit'] == 22
assert results['missing_fail']['output'] == 'missing\n'
assert (results['redirect_inspect']['status'], results['redirect_inspect']['redirects']) == (302, 0)
assert (results['redirect_follow']['status'], results['redirect_follow']['redirects']) == (200, 1)
assert results['head']['status'] == 405 and results['head']['allow_get']
assert results['get']['status'] == 200 and results['get']['output'] == 'healthy\n'
print(json.dumps(results))
finally:
server.shutdown()
thread.join()
server.server_close()
普通的缺失页面请求得到 HTTP 四零四,进程退出码仍为零。加上 --fail-with-body 后,退出码变成二十二,同时保留用于诊断的正文。它直接说明为什么只检查进程状态,会漏掉部分 HTTP 失败。该选项需要安装版本支持,移植命令时应查看当前版本手册;不能因为某台机器能执行,就推断所有旧版本都具备相同参数。
不跟随时,跳转探测报告三零二、已跟随次数为零;显式跟随后,经过一跳得到二百。同一个 /ok 地址,对 HEAD 返回四零五以及 Allow: GET,对 GET 则返回二百和 healthy。HEAD 模式下,curl 保存的输出是响应头,而不是资源正文,所以实验字段命名为 output,避免把工具的展示输出误称为 HTTP 消息正文。
这些案例覆盖传输分类、跳转选择和方法差异,没有验证 TLS、浏览器行为、负载下延迟或应用的下游依赖。把人工命令变成自动检查之前,应明确预期状态、必要响应头、正文条件和超时时间。否则,自动化只是更频繁地重复一个含义不清的问题。只有输出与问题对应,探测结果才能支持后续定位,而不是制造新的误报。
延伸阅读
来源
- curl manual,退出状态、响应头、重定向与有界传输,已核验 2026-09-13。
- RFC 9110: HTTP Semantics,探测中需要区分的方法和响应语义,已核验 2026-09-13。