应直接使用 guzzlehttp\client 而非 http facade,因其支持连接池、分设超时、精细控制重试与中间件;需单例绑定容器、正确捕获 requestexception 并区分网络层与响应层错误,post json 时手动设 header 与 json_encode 确保格式合规。

直接在 Laravel 里用 GuzzleHttp\Client 发请求,别碰 Http facade
新版 Laravel(10+)自带的 Http facade 虽然方便,但底层还是 Guzzle,且封装后屏蔽了连接池、重试策略、中间件等关键控制点。真要调第三方 API,尤其是带认证、流式响应或需要精细超时控制的,直接上原生 GuzzleHttp\Client 更稳。
常见错误:用 Http::timeout(5)->get(...) 结果上游服务偶发延迟到 8 秒就整个请求失败,没法单独设置 connect timeout 和 read timeout —— GuzzleHttp\Client 允许分开配。
- 安装:运行
composer require guzzlehttp/guzzle(Laravel 10+ 默认已装,但版本可能旧,建议显式指定 ^7.5 或 ^8.0) - 实例化时传数组配置,比如:
new \GuzzleHttp\Client(['timeout' => 10, 'connect_timeout' => 3]) - 别在控制器里每次都
new客户端,它支持连接复用;推荐绑定到容器或用app(\GuzzleHttp\Client::class)
GuzzleHttp\Exception\RequestException 怎么捕获和区分
这个异常是 Guzzle 最常抛出的“总异常”,但它里面裹着真实状态:网络不通、DNS 失败、HTTP 状态码非 2xx、SSL 验证失败……全塞在一个类里,不拆开根本不知道该重试、该告警,还是该直接返回用户错误。
典型现象:日志里只看到 Client error: `POST https://api.example.com/v1/order` resulted in a `400 Bad Request` response,但没上下文,查不出是参数错还是签名过期。
- 必须用
try/catch包住$client->post()等调用 - 用
if ($e->hasResponse())判断是否收到响应体,再取$e->getResponse()->getStatusCode()和(string) $e->getResponse()->getBody() - 网络层失败(如 DNS 解析失败)会进
else分支,这时$e->getRequest()还能拿到原始请求信息,方便打点追踪
Laravel 中如何安全复用 Guzzle 连接池
Guzzle 默认启用连接池,但很多人在每次请求时 new 一个 Client,等于白搭 —— 每次新建都重建 TCP 连接,QPS 上不去,还容易触发目标服务的连接数限流。
正确做法是让 Laravel 容器管理单例客户端,同时确保配置里的 handler 支持连接复用(默认就是,除非你手动替换成不带 Pool 的 handler)。
- 在
App\Providers\AppServiceProvider::register()里绑定:$this->app->singleton(\GuzzleHttp\Client::class, function () { return new \GuzzleHttp\Client(['timeout' => 15]); }); - 避免在队列任务里长期持有 Client 实例并反复调用 —— Guzzle 不是线程安全的,但 Laravel 队列是进程模型,只要不跨进程共享对象就没事
- 如果并发极高(比如每秒几百请求),可以加
['curl' => [' CURLOPT_TCP_KEEPALIVE => 1]]减少 TIME_WAIT
POST JSON 数据时 Content-Type 和 body 怎么写才不被拒
很多外部 API(尤其金融、政务类)校验严格:Content-Type 少个分号、JSON 多个空格、body 是字符串不是 raw 字节,全 400。Guzzle 对 json 选项的处理看似智能,实则有坑。
错误写法:$client->post($url, ['json' => $data]) 看似省事,但它会自动序列化并设 Content-Type: application/json —— 可有些 API 要求 application/json; charset=utf-8,或者根本不认 json 选项,只吃 body + 手动 header。
- 保险写法:
$client->post($url, ['headers' => ['Content-Type' => 'application/json; charset=utf-8'], 'body' => json_encode($data, JSON_UNESCAPED_UNICODE)]) - 注意
json_encode返回的是 string,不是 array;别漏JSON_UNESCAPED_UNICODE,否则中文变 \uXXXX - 如果 API 文档写“接受 form-data”,那就别用
json,改用form_params数组,Guzzle 会自动设Content-Type: application/x-www-form-urlencoded
真正难的不是发出去,是发出去之后怎么判断该重试、该熔断、还是该记录为脏数据 —— 这些逻辑得自己写,Guzzle 不管。










