大家好,我是木木。
今天给大家分享一个硬核的 Python 库,httptap。
httptap
如果你排查 HTTP 接口时,经常只能看到“慢了”,却看不清到底卡在 DNS、连接、TLS 还是服务端等待,那 httptap 这类工具就很有价值。它把一次请求拆成阶段指标,还顺手给你重定向链、JSON 导出和 SLO 门禁,特别适合接口诊断、回归对比和上线巡检。
项目地址:https://github.com/ozeranskii/httptap
官方文档:https://httptap.dev/
三大特点
阶段拆得细
DNS、连接、TLS、TTFB 和传输时间都能分开看,定位瓶颈不再只靠猜。
导出链路全
重定向链、响应元数据和网络信息都能落成 JSON,方便留档和对比。
自动化友好
除了 CLI,它还暴露 Python API,适合直接塞进脚本、巡检和 CI 门禁。
最佳实践
安装方式:python -m pip install httptap
这篇我不依赖外部网站,而是每段代码都先起一个本地 HTTP 服务,再让 httptap 去分析它。这样输出更稳定,也更容易把“请求拆解”“重定向导出”和“SLO 校验”这三类核心能力看清楚。
功能一:把单次请求拆成可读的阶段指标
这段代码解决什么问题:很多排查工作不是“请求失败”,而是“请求变慢了”。这时你最需要的不是再打一遍日志,而是先把一次请求拆开,看看时间主要耗在了哪一段。
importjsonimportthreadingimporttimefromhttp.serverimportBaseHTTPRequestHandler,ThreadingHTTPServerfromhttptapimportHTTPTapAnalyzerPORT=18931classHandler(BaseHTTPRequestHandler):defdo_GET(self):ifself.path.startswith("/metrics"):time.sleep(0.08)body=json.dumps({"ok":True,"path":self.path}).encode()self.send_response(200)self.send_header("Content-Type","application/json")self.send_header("Content-Length",str(len(body)))self.end_headers()self.wfile.write(body)else:self.send_response(404)self.end_headers()deflog_message(self,fmt,*args):passserver=ThreadingHTTPServer(("127.0.0.1",PORT),Handler)thread=threading.Thread(target=server.serve_forever,daemon=True)thread.start()time.sleep(0.1)try:analyzer=HTTPTapAnalyzer(timeout=5)step=analyzer.analyze_url(f"http://127.0.0.1:{PORT}/metrics?tab=summary")[0]print("status :",step.response.status)print("url :",step.url)print("bytes :",step.response.bytes)print("ttfb :",round(step.timing.ttfb_ms,1),"ms")print("total :",round(step.timing.total_ms,1),"ms")finally:server.shutdown()server.server_close()

这里最实用的是 step.timing 和 step.response 已经是现成结构化数据了。你可以先在本地验证慢点大概落在哪个阶段,再把同样的逻辑塞进内部诊断脚本里持续跑。
功能二:跟踪重定向链,并把结果直接导出成 JSON
这段代码解决什么问题:登录跳转、旧域名迁移和 CDN 回源链路里,经常会出现“最后能打开,但中间绕了好几步”的情况。只看终点 URL 往往不够,你还得把整条链完整记下来。
importioimportjsonimporttempfileimportthreadingimporttimefromhttp.serverimportBaseHTTPRequestHandler,ThreadingHTTPServerfrompathlibimportPathfromrich.consoleimportConsolefromhttptapimportHTTPTapAnalyzer,JSONExporterPORT=18932classHandler(BaseHTTPRequestHandler):defdo_GET(self):ifself.path=="/jump1":self.send_response(302)self.send_header("Location","/jump2")self.end_headers()elifself.path=="/jump2":self.send_response(302)self.send_header("Location","/final?from=redirect")self.end_headers()elifself.path.startswith("/final"):body=json.dumps({"ok":True,"path":self.path}).encode()self.send_response(200)self.send_header("Content-Type","application/json")self.send_header("Content-Length",str(len(body)))self.end_headers()self.wfile.write(body)else:self.send_response(404)self.end_headers()deflog_message(self,fmt,*args):passserver=ThreadingHTTPServer(("127.0.0.1",PORT),Handler)thread=threading.Thread(target=server.serve_forever,daemon=True)thread.start()time.sleep(0.1)try:analyzer=HTTPTapAnalyzer(follow_redirects=True,timeout=5)steps=analyzer.analyze_url(f"http://127.0.0.1:{PORT}/jump1")console=Console(file=io.StringIO(),force_terminal=False)withtempfile.TemporaryDirectory()astmp:report=Path(tmp)/"report.json"JSONExporter(console).export(steps,f"http://127.0.0.1:{PORT}/jump1",str(report))data=json.loads(report.read_text(encoding="utf-8"))print("steps :",data["total_steps"])forstepindata["steps"]:location=step["response"]["location"]or"-"print(f"step {step['step_number']}: {step['response']['status']} -> {location}")print("final :",data["summary"]["final_status"],data["summary"]["final_url"])finally:server.shutdown()server.server_close()

这样做的好处是,你不只是看到“最后成功了”,而是把每一步 302 和最终落点都存成了结构化结果。后面不管是写回归测试、留性能基线,还是把报告发给同事,都比手抄终端输出靠谱得多。
环境与版本信息
- Demo 环境:Windows 11,Python 3.11
- 关键依赖:
httpx、rich、dnspython - 仓库
pyproject.toml 当前要求 Python >=3.10 - GitHub 最近一次推送时间:
2026-04-21T10:21:44Z
高级功能
这段代码解决什么问题:当你想把延迟预算直接变成自动化门禁时,只看“快不快”已经不够了,还要把超标项明确算出来。httptap 的 SLO API 可以直接在代码里完成这件事。
importthreadingimporttimefromhttp.serverimportBaseHTTPRequestHandler,ThreadingHTTPServerfromhttptapimportHTTPTapAnalyzer,evaluate_slo,parse_slo_spec,select_step_for_evaluationPORT=18933classHandler(BaseHTTPRequestHandler):defdo_GET(self):ifself.path=="/slow":time.sleep(0.22)body=b"ok"self.send_response(200)self.send_header("Content-Type","text/plain")self.send_header("Content-Length",str(len(body)))self.end_headers()self.wfile.write(body)else:self.send_response(404)self.end_headers()deflog_message(self,fmt,*args):passserver=ThreadingHTTPServer(("127.0.0.1",PORT),Handler)thread=threading.Thread(target=server.serve_forever,daemon=True)thread.start()time.sleep(0.1)try:analyzer=HTTPTapAnalyzer(timeout=5)steps=analyzer.analyze_url(f"http://127.0.0.1:{PORT}/slow")target=select_step_for_evaluation(steps)slo=evaluate_slo(target,parse_slo_spec("total=150,ttfb=120"))print("passed :",slo.passed)print("status :",target.response.status)foriteminslo.violations:print(f"{item.key}: {item.actual_ms:.1f} > {item.threshold_ms:.1f} ms")finally:server.shutdown()server.server_close()

这种写法很适合健康检查、CI 冒烟和发布前巡检。你可以把“总耗时不能超过多少”“TTFB 不能超过多少”写成明确规则,而不是等线上慢了再回头翻日志。
适用场景
- 你要排查接口变慢,但希望知道问题更像 DNS、连接、TLS 还是服务端处理
- 你要跟踪重定向链、保留 JSON 报告,给回归分析或巡检系统复用
- 你想把延迟预算做成脚本里的自动判定,而不是人工盯终端
不适用场景
- 你更需要的是通用 HTTP 客户端功能,本身并不关心链路拆解和时延分析
- 你在做高并发压测或完整性能基准,需求已经超出单次请求剖析的范围
- 你需要浏览器级前端瀑布图,而不是服务端或接口层的 HTTP 请求诊断
上线检查
- 先在你自己的网络环境里跑一遍样本请求,确认代理、证书和 DNS 条件跟生产接近
- 把
total、ttfb 之类的阈值先基于真实基线设定,不要直接拿演示值上 CI - 如果要做回归对比,记得把 JSON 导出结果和触发时的 URL、重定向链一起保存
总结
如果你想把 HTTP 请求从“感觉慢”变成“哪一段慢、慢了多少、要不要拦下发布”这类可执行信息,httptap 这种硬核小工具会非常顺手。