这次把 GET、POST、文件上传到响应处理的坑给你一次填平

📅 2026/7/11 19:44:14
这次把 GET、POST、文件上传到响应处理的坑给你一次填平
如果你也在技术这条路上向前走关注我愿我们能彼此陪伴一起成为更好的自己 ️ 核心脉络 万物皆始于一个客户端实例 GET 请求参数拼接的艺术 POST 四大金刚表单、JSON、文件、原始数据 Header 与 Cookie 的正确注入方式 返回值处理从入门到高手的飞跃 流式请求大文件下载再也不怕 那些悄悄崩溃的细节与避坑指南️ 第一部分万物始于客户端但别乱来前面咱们说过了全局一个 AsyncClient 服用。这里再明确一下任何请求都要从它发出。import httpxasync with httpx.AsyncClient(base_url“https://httpbin.org”, timeout10.0) as client:# 里面可以疯狂发请求你会问“不是说要全局单例吗怎么又 async with 了”全局单例是在 FastAPI 里处理时通过 lifespan 和 Depends 管理的。这里为了演示方便下面的很多例子我是用的 async with 写临时上下文但你心里要时刻清楚生产环境保持单例别模仿我这种临时创建。 第二部分GET 请求——参数别只会拼在 URL 里最基础也是最常用的发送 GET 请求resp await client.get(“/get”)重点在于 传参怎么传。如果你还这么写client.get(f/get?name张三age18) # 这要给个差评了为什么一是中文编码问题二是拼接时很容易出错。正确的姿势应该是用 params 字典来处理参数对象params {“name”: “张三”, “age”: 18}resp await client.get(“/get”, paramsparams)httpx 会自动帮你编码成 /get?name%E5%BC%A0%E4%B8%89age18而且还有个骚操作就是你想同一个 key 传多个值也是可以的直接用列表params {“tag”: [“python”, “fastapi”]}结果: /get?tagpythontagfastapi但这里要说个容易翻车的点params 里如果某个key的值是 None默认会被忽略掉。如果你想要保留空字段可以在传参时手动转成字符串来处理。 第三部分POST 请求——四大金刚怎么选POST 请求的请求体类型有很多种如果选错了可能就被下游一巴掌呼回来了。 1. 表单数据application/x-www-form-urlencoded如果要模拟页面表单的提交这时候要用 data 参数来传字典前提是你的参数是 key-value 类型的data {“username”: “admin”, “password”: “123456”}resp await client.post(“/login”, datadata) 2. JSON 数据application/json这也是最常见的 API 交互方式用 json 参数直接传 Python 对象特别是参数存在复杂类型时httpx 会自动序列化并设好 Content-Type头信息。payload {“user_id”: 1, “items”: [“铅笔”, “橡皮”]}resp await client.post(“/order”, jsonpayload) 3. 文件上传multipart/form-data传文件是个高频需求但也是容易踩坑的重灾区。这时候要用 files 参数它的值可以是文件对象或元组。简单上传files {“avatar”: open(“photo.jpg”, “rb”)}resp await client.post(“/upload”, filesfiles)自定义文件名和 MIME 类型files {report: (2025年报.pdf, open(report.pdf, rb), application/pdf)}resp await client.post(“/upload”, filesfiles)这里要注意files 和 data 可以同时使用但别和 json 混用否则会冲突。 4. 原始二进制或纯文本如果你是用 content 参数传入 bytes 或 strhttpx 会原样塞进请求体里。raw_data b’\x00\x01\x02’resp await client.post(“/binary”, contentraw_data) 第四部分Header 与 Cookie细节决定成败如果要自定义 Header可以直接传字典headers {“X-API-Key”: “secret”, “User-Agent”: “MyApp/1.0”}resp await client.get(“/data”, headersheaders)但要特别注意通过 client.headers 设置的全局头会和单次请求的 headers 合并同名键单次请求的会覆盖全局的。不得不说这个设计还是很贴心的自定义和默认项都能实现了。Cookie 管理同样简单cookies {“session_id”: “abc123”}resp await client.get(“/dashboard”, cookiescookies)但如果你需要持久化 Cookie比如登录后维持会话这时候可以用 http.CookieJar 绑定到客户端我这里就不展开了但你知道它有这个能力就行需要的时候再深入研究。 第五部分返回值处理——别只看 resp.json()拿到响应对象后你应该根据 Content-Type 值选择合适的解析方法可别二话不说全部 .json() 梭哈。下面解释下常见的返回类型解析方式以及应对哪种类型的返回值 resp.status_code检查状态码好习惯是调用 resp.raise_for_status() 让 4xx/5xx 立即抛异常。 resp.text返回解码后的字符串默认用响应头里的编码智能。 resp.json()解析 JSON返回字典或列表。如果响应体不是合法 JSON 格式可能会爆 JSONDecodeError 错误要小心。 resp.content获取原始字节流下载图片、视频等。 resp.headers查看响应头比如获取 Content-Type 的值等。下面来个实战检查状态码后再解析返回值。resp await client.get(“/user/1”)if resp.status_code 200:user resp.json()else:print(f错误{resp.status_code} - {resp.text})上面这些算是常用的功能都有了但我想说它的能力还不止哦还有更优雅的流式读取接着看。 第六部分流式响应——大文件别想一次性吞进内存下载大文件时如果你直接傻傻地 content resp.content那几百兆或者上G的数据能直接撑爆内存。这时候就必须用流式响应了然后分片保存到本地文件中。async with client.stream(“GET”, “/bigfile.zip”) as resp:resp.raise_for_status() with open(download.zip, wb) as f: async for chunk in resp.aiter_bytes(): f.write(chunk)client.stream() 返回的响应对象不会立即读取所有数据而是通过 aiter_bytes() 逐块读取内存占用是恒定的一片的大小这才是工业级的优雅。同理流式读取文本行可以处理大型 JSON Lines比如日志文件async with client.stream(“GET”, “/logs.txt”) as resp:async for line in resp.aiter_lines(): print(line) 第七部分要特别注意的那些悄悄崩溃的细节 超时一定要设死前面强调过再强调一次。客户端级别设client httpx.AsyncClient(timeout10.0) # 10秒总超时你也可以精细控制连接、读取和写入的不同超时定义timeout httpx.Timeout(5.0, connect3.0, read5.0, write2.0) 代理配置不迷路如果公司网络需要代理那就直接传代理地址给它proxies “http://proxy.com:8080”client httpx.AsyncClient(proxiesproxies) 重试不要靠手写网络波动是永远不可避免的但这时别急着手动写 try/except 循环。httpx 可以结合 tenacity 库做自动重试但更建议在 AsyncClient 的 transport 层加 httpx.HTTPTransport(retries3)这是同步用法。异步重试通常用中间件或依赖注入实现这些先作个了解即可。