Guzzle 7 中间件基于 HandlerStack 和 PSR-7,为 callable(RequestInterface $request, callable $next) 函数,须返回 PromiseInterface 或 ResponseInterface;签名中间件需用不可变方法修改请求并重置 body;响应中间件可统一处理解密、错误抛出等;注册时 push 顺序决定执行先后,Laravel Http facade 不支持自定义 handler。

理解 Guzzle 7 的中间件机制
Guzzle 7 不再使用 Laravel 风格的“中间件类 + handle()”链式调用,而是基于 PSR-7 和 HandlerStack 构建请求生命周期。所有请求最终交由一个 handler(如 cURL 或 Stream handler)执行,而中间件是插入在 handler 前后的函数,类型为 callable(RequestInterface $request, callable $next)。它必须返回 PromiseInterface(异步)或 ResponseInterface(同步),不能直接 return $next($request) —— 必须显式调用 $next 并 await 或 resolve 其结果。
实现请求签名中间件
签名通常需在请求发出前注入时间戳、随机串、签名摘要(如 HMAC-SHA256)等字段。关键点:修改 Request 对象必须用其不可变方法(如 withHeader()、withBody()),原对象不会被修改。
- 先获取原始 body 内容(若为 JSON 或 form_params,需提前序列化)
- 构造待签名字符串(例如:
method + uri + timestamp + nonce + body_hash) - 用密钥生成签名,添加到 headers(如
X-Signature)和/或 query(如sign=xxx) - 注意:body 是 StreamInterface,读取后需重置或重建(可用
Utils::streamFor())
示例签名中间件:
use GuzzleHttp\Promise\PromiseInterface;<br>use Psr\Http\Message\RequestInterface;<br>use Psr\Http\Message\ResponseInterface;<br><br>$signMiddleware = function (RequestInterface $request, callable $next): PromiseInterface {<br> $timestamp = (string) time();<br> $nonce = bin2hex(random_bytes(8));<br> $body = (string) $request->getBody();<br> $toSign = strtoupper($request->getMethod()) . $request->getUri()->getPath() . $timestamp . $nonce . md5($body);<br> $signature = hash_hmac('sha256', $toSign, 'your-secret-key');<br><br> $signedRequest = $request<br> ->withHeader('X-Timestamp', $timestamp)<br> ->withHeader('X-Nonce', $nonce)<br> ->withHeader('X-Signature', $signature);<br><br> return $next($signedRequest);<br>};
立即学习“PHP免费学习笔记(深入)”;
实现响应拦截中间件
响应拦截发生在 handler 返回 Response 后,可用于统一解密、状态码映射、错误结构标准化、日志记录等。中间件接收 $response 和 $request,可检查状态码、解析 body、抛出自定义异常。
- 响应 body 也是 Stream,读取后若需复用,应缓存内容并用
Utils::streamFor()重建 - 避免重复 json_decode;建议统一提取 data 字段或封装成 Result 对象
- 对 4xx/5xx 可选择性抛异常(如业务错误不抛,仅网络失败才 throw)
示例响应拦截:
$responseInterceptor = function (RequestInterface $request, callable $next): PromiseInterface {<br> return $next($request)->then(<br> function (ResponseInterface $response) use ($request) {<br> $body = (string) $response->getBody();<br> $data = json_decode($body, true) ?: [];<br><br> if (isset($data['code']) && $data['code'] !== 0) {<br> throw new BusinessException($data['msg'] ?? 'API error', $data['code']);<br> }<br><br> // 重新包装 body 供后续使用<br> $newBody = Utils::streamFor($body);<br> return $response->withBody($newBody);<br> }<br> );<br>};
注册中间件到客户端
创建 Client 时传入自定义 HandlerStack,并将中间件 push 到 stack 中。顺序很重要:先 push 的中间件更靠近请求发起端(即最先执行),后 push 的更靠近 handler(即最后执行)。
use GuzzleHttp\HandlerStack;<br>use GuzzleHttp\Client;<br><br>$stack = HandlerStack::create();<br>$stack->push($signMiddleware, 'sign');<br>$stack->push($responseInterceptor, 'response');<br><br>$client = new Client(['handler' => $stack]);
也可复用默认 handler(如 HandlerStack::create(new CurlHandler())),确保兼容性。Laravel 中若用 Http facade,需改用原生 Client 实例才能接入 HandlerStack —— facade 不暴露 handler 控制权。



















