HTTP 客户端
介绍
Laravel 在 Guzzle HTTP 客户端 的基础上提供了一个表达能力强、API 最少的包装器,允许您快速发出 HTTP 外部请求以与其他 Web 应用程序进行通信。Laravel 对 Guzzle 的包装专注于其最常见的用例,并提供了一种很棒的开发体验。
发出请求
要发出请求,您可以使用 Http
Facade 提供的 head
、get
、post
、put
、patch
和 delete
方法。首先,让我们检查如何向另一个 URL 发出基本的 GET
请求
use Illuminate\Support\Facades\Http; $response = Http::get('http://example.com');
get
方法返回一个 Illuminate\Http\Client\Response
实例,它提供了多种方法,可用于检查响应
$response->body() : string;$response->json($key = null, $default = null) : mixed;$response->object() : object;$response->collect($key = null) : Illuminate\Support\Collection;$response->resource() : resource;$response->status() : int;$response->successful() : bool;$response->redirect(): bool;$response->failed() : bool;$response->clientError() : bool;$response->header($header) : string;$response->headers() : array;
Illuminate\Http\Client\Response
对象还实现了 PHP ArrayAccess
接口,允许您直接在响应中访问 JSON 响应数据
return Http::get('http://example.com/users/1')['name'];
除了上面列出的响应方法之外,还可以使用以下方法来确定响应是否具有给定的状态码
$response->ok() : bool; // 200 OK$response->created() : bool; // 201 Created$response->accepted() : bool; // 202 Accepted$response->noContent() : bool; // 204 No Content$response->movedPermanently() : bool; // 301 Moved Permanently$response->found() : bool; // 302 Found$response->badRequest() : bool; // 400 Bad Request$response->unauthorized() : bool; // 401 Unauthorized$response->paymentRequired() : bool; // 402 Payment Required$response->forbidden() : bool; // 403 Forbidden$response->notFound() : bool; // 404 Not Found$response->requestTimeout() : bool; // 408 Request Timeout$response->conflict() : bool; // 409 Conflict$response->unprocessableEntity() : bool; // 422 Unprocessable Entity$response->tooManyRequests() : bool; // 429 Too Many Requests$response->serverError() : bool; // 500 Internal Server Error
URI 模板
HTTP 客户端还允许您使用 URI 模板规范 构建请求 URL。要定义 URI 模板可以扩展的 URL 参数,您可以使用 withUrlParameters
方法
Http::withUrlParameters([ 'endpoint' => 'https://laravel.net.cn', 'page' => 'docs', 'version' => '11.x', 'topic' => 'validation',])->get('{+endpoint}/{page}/{version}/{topic}');
转储请求
如果要在发送之前转储传出的请求实例并终止脚本执行,您可以在请求定义的开头添加 dd
方法
return Http::dd()->get('http://example.com');
请求数据
当然,在发出 POST
、PUT
和 PATCH
请求时,通常会与请求一起发送其他数据,因此这些方法接受数据数组作为它们的第二个参数。默认情况下,数据将使用 application/json
内容类型发送
use Illuminate\Support\Facades\Http; $response = Http::post('http://example.com/users', [ 'name' => 'Steve', 'role' => 'Network Administrator',]);
GET 请求查询参数
在发出 GET
请求时,您可以直接将查询字符串附加到 URL,或者将键 / 值对的数组作为第二个参数传递给 get
方法
$response = Http::get('http://example.com/users', [ 'name' => 'Taylor', 'page' => 1,]);
或者,可以使用 withQueryParameters
方法
Http::retry(3, 100)->withQueryParameters([ 'name' => 'Taylor', 'page' => 1,])->get('http://example.com/users')
发送表单 URL 编码请求
如果要使用 application/x-www-form-urlencoded
内容类型发送数据,您应该在发出请求之前调用 asForm
方法
$response = Http::asForm()->post('http://example.com/users', [ 'name' => 'Sara', 'role' => 'Privacy Consultant',]);
发送原始请求正文
如果要发出请求时提供原始请求正文,可以使用 withBody
方法。内容类型可以通过方法的第二个参数提供
$response = Http::withBody( base64_encode($photo), 'image/jpeg')->post('http://example.com/photo');
多部分请求
如果要将文件作为多部分请求发送,您应该在发出请求之前调用 attach
方法。此方法接受文件名及其内容。如果需要,您可以提供第三个参数,它将被视为文件的名称,而第四个参数可用于提供与文件相关的头部
$response = Http::attach( 'attachment', file_get_contents('photo.jpg'), 'photo.jpg', ['Content-Type' => 'image/jpeg'])->post('http://example.com/attachments');
您可以传递流资源,而不是传递文件的原始内容
$photo = fopen('photo.jpg', 'r'); $response = Http::attach( 'attachment', $photo, 'photo.jpg')->post('http://example.com/attachments');
头部
可以使用 withHeaders
方法将头部添加到请求。此 withHeaders
方法接受键 / 值对的数组
$response = Http::withHeaders([ 'X-First' => 'foo', 'X-Second' => 'bar'])->post('http://example.com/users', [ 'name' => 'Taylor',]);
您可以使用 accept
方法指定您的应用程序在响应您的请求时预期的内容类型
$response = Http::accept('application/json')->get('http://example.com/users');
为了方便起见,您可以使用 acceptJson
方法快速指定您的应用程序在响应您的请求时预期 application/json
内容类型
$response = Http::acceptJson()->get('http://example.com/users');
withHeaders
方法将新的头部合并到请求的现有头部中。如果需要,可以使用 replaceHeaders
方法完全替换所有头部
$response = Http::withHeaders([ 'X-Original' => 'foo',])->replaceHeaders([ 'X-Replacement' => 'bar',])->post('http://example.com/users', [ 'name' => 'Taylor',]);
身份验证
您可以分别使用 withBasicAuth
和 withDigestAuth
方法指定基本身份验证凭据和摘要身份验证凭据
// Basic authentication... // Digest authentication...
Bearer 令牌
如果要快速将 Bearer 令牌添加到请求的 Authorization
头部,您可以使用 withToken
方法
$response = Http::withToken('token')->post(/* ... */);
超时
timeout
方法可用于指定等待响应的最大秒数。默认情况下,HTTP 客户端将在 30 秒后超时
$response = Http::timeout(3)->get(/* ... */);
如果超过给定的超时时间,将抛出一个 Illuminate\Http\Client\ConnectionException
实例。
您可以使用 connectTimeout
方法指定尝试连接到服务器时等待的最大秒数
$response = Http::connectTimeout(3)->get(/* ... */);
重试
如果希望 HTTP 客户端在发生客户端或服务器错误时自动重试请求,可以使用 retry
方法。retry
方法接受请求应尝试的最大次数以及 Laravel 在两次尝试之间应等待的毫秒数
$response = Http::retry(3, 100)->post(/* ... */);
如果要手动计算两次尝试之间休眠的毫秒数,可以将闭包作为第二个参数传递给 retry
方法
use Exception; $response = Http::retry(3, function (int $attempt, Exception $exception) { return $attempt * 100;})->post(/* ... */);
为了方便起见,您还可以将数组作为第一个参数传递给 retry
方法。此数组将用于确定在后续尝试之间休眠多少毫秒
$response = Http::retry([100, 200])->post(/* ... */);
如果需要,可以将第三个参数传递给 retry
方法。第三个参数应该是确定是否应该实际尝试重试的可调用对象。例如,您可能希望仅在初始请求遇到 ConnectionException
时重试请求
use Exception;use Illuminate\Http\Client\PendingRequest; $response = Http::retry(3, 100, function (Exception $exception, PendingRequest $request) { return $exception instanceof ConnectionException;})->post(/* ... */);
如果请求尝试失败,您可能希望在进行新的尝试之前对请求进行更改。您可以通过修改传递给您提供给 retry
方法的可调用对象的请求参数来实现此目的。例如,如果第一次尝试返回身份验证错误,您可能希望使用新的授权令牌重试请求
use Exception;use Illuminate\Http\Client\PendingRequest;use Illuminate\Http\Client\RequestException; $response = Http::withToken($this->getToken())->retry(2, 0, function (Exception $exception, PendingRequest $request) { if (! $exception instanceof RequestException || $exception->response->status() !== 401) { return false; } $request->withToken($this->getNewToken()); return true;})->post(/* ... */);
如果所有请求都失败,将抛出一个 Illuminate\Http\Client\RequestException
实例。如果要禁用此行为,可以提供一个值为 false
的 throw
参数。禁用后,在所有重试都尝试完之后,将返回客户端接收到的最后一个响应
$response = Http::retry(3, 100, throw: false)->post(/* ... */);
如果所有请求都由于连接问题而失败,即使将 throw
参数设置为 false
,也会抛出一个 Illuminate\Http\Client\ConnectionException
。
错误处理
与 Guzzle 的默认行为不同,Laravel 的 HTTP 客户端包装器不会在客户端或服务器错误(服务器返回的 400
和 500
级响应)上抛出异常。您可以使用 successful
、clientError
或 serverError
方法确定是否返回了其中一个错误
// Determine if the status code is >= 200 and < 300...$response->successful(); // Determine if the status code is >= 400...$response->failed(); // Determine if the response has a 400 level status code...$response->clientError(); // Determine if the response has a 500 level status code...$response->serverError(); // Immediately execute the given callback if there was a client or server error...$response->onError(callable $callback);
抛出异常
如果您有响应实例,并且希望在响应状态码指示客户端或服务器错误时抛出一个 Illuminate\Http\Client\RequestException
实例,可以使用 throw
或 throwIf
方法
use Illuminate\Http\Client\Response; $response = Http::post(/* ... */); // Throw an exception if a client or server error occurred...$response->throw(); // Throw an exception if an error occurred and the given condition is true...$response->throwIf($condition); // Throw an exception if an error occurred and the given closure resolves to true...$response->throwIf(fn (Response $response) => true); // Throw an exception if an error occurred and the given condition is false...$response->throwUnless($condition); // Throw an exception if an error occurred and the given closure resolves to false...$response->throwUnless(fn (Response $response) => false); // Throw an exception if the response has a specific status code...$response->throwIfStatus(403); // Throw an exception unless the response has a specific status code...$response->throwUnlessStatus(200); return $response['user']['id'];
Illuminate\Http\Client\RequestException
实例具有一个公共的 $response
属性,它允许您检查返回的响应。
如果未发生错误,throw
方法将返回响应实例,允许您将其他操作链接到 throw
方法
return Http::post(/* ... */)->throw()->json();
如果您希望在抛出异常之前执行一些其他逻辑,可以将闭包传递给 throw
方法。在调用闭包之后,异常将被自动抛出,因此您不需要在闭包中重新抛出异常
use Illuminate\Http\Client\Response;use Illuminate\Http\Client\RequestException; return Http::post(/* ... */)->throw(function (Response $response, RequestException $e) { // ...})->json();
Guzzle 中间件
由于 Laravel 的 HTTP 客户端由 Guzzle 提供支持,您可以利用 Guzzle 中间件 来操作传出的请求或检查传入的响应。要操作传出的请求,请通过 withRequestMiddleware
方法注册 Guzzle 中间件
use Illuminate\Support\Facades\Http;use Psr\Http\Message\RequestInterface; $response = Http::withRequestMiddleware( function (RequestInterface $request) { return $request->withHeader('X-Example', 'Value'); })->get('http://example.com');
同样,您可以通过 withResponseMiddleware
方法注册中间件来检查传入的 HTTP 响应
use Illuminate\Support\Facades\Http;use Psr\Http\Message\ResponseInterface; $response = Http::withResponseMiddleware( function (ResponseInterface $response) { $header = $response->getHeader('X-Example'); // ... return $response; })->get('http://example.com');
全局中间件
有时,您可能希望注册一个适用于每个传出请求和传入响应的中间件。为此,您可以使用 globalRequestMiddleware
和 globalResponseMiddleware
方法。通常,这些方法应该在应用程序 AppServiceProvider
的 boot
方法中调用
use Illuminate\Support\Facades\Http; Http::globalRequestMiddleware(fn ($request) => $request->withHeader( 'User-Agent', 'Example Application/1.0')); Http::globalResponseMiddleware(fn ($response) => $response->withHeader( 'X-Finished-At', now()->toDateTimeString()));
Guzzle 选项
您可以使用 withOptions
方法指定用于传出请求的其他 Guzzle 请求选项。withOptions
方法接受一个键值对数组
$response = Http::withOptions([ 'debug' => true,])->get('http://example.com/users');
全局选项
要配置每个传出请求的默认选项,您可以使用 globalOptions
方法。通常,此方法应从应用程序 AppServiceProvider
的 boot
方法中调用
use Illuminate\Support\Facades\Http; /** * Bootstrap any application services. */public function boot(): void{ Http::globalOptions([ 'allow_redirects' => false, ]);}
并发请求
有时,您可能希望同时发出多个 HTTP 请求。换句话说,您希望多个请求同时发出,而不是依次发出。在与缓慢的 HTTP API 交互时,这可以带来可观的性能提升。
幸运的是,您可以使用 pool
方法来实现这一点。pool
方法接受一个闭包,该闭包接收一个 Illuminate\Http\Client\Pool
实例,使您可以轻松地将请求添加到请求池中以进行调度
use Illuminate\Http\Client\Pool;use Illuminate\Support\Facades\Http; $responses = Http::pool(fn (Pool $pool) => [ $pool->get('https://127.0.0.1/first'), $pool->get('https://127.0.0.1/second'), $pool->get('https://127.0.0.1/third'),]); return $responses[0]->ok() && $responses[1]->ok() && $responses[2]->ok();
如您所见,可以根据每个响应实例添加到池中的顺序访问它。如果需要,可以使用 as
方法为请求命名,这样就可以通过名称访问相应的响应
use Illuminate\Http\Client\Pool;use Illuminate\Support\Facades\Http; $responses = Http::pool(fn (Pool $pool) => [ $pool->as('first')->get('https://127.0.0.1/first'), $pool->as('second')->get('https://127.0.0.1/second'), $pool->as('third')->get('https://127.0.0.1/third'),]); return $responses['first']->ok();
自定义并发请求
pool
方法不能与其他 HTTP 客户端方法(例如 withHeaders
或 middleware
方法)链接。如果要将自定义标头或中间件应用于池化请求,则应在池中的每个请求上配置这些选项
use Illuminate\Http\Client\Pool;use Illuminate\Support\Facades\Http; $headers = [ 'X-Example' => 'example',]; $responses = Http::pool(fn (Pool $pool) => [ $pool->withHeaders($headers)->get('http://laravel.test/test'), $pool->withHeaders($headers)->get('http://laravel.test/test'), $pool->withHeaders($headers)->get('http://laravel.test/test'),]);
宏
Laravel HTTP 客户端允许您定义“宏”,这些宏可以作为一种流畅、富有表现力的机制,在与应用程序中各个服务交互时配置常见的请求路径和标头。要开始使用,您可以在应用程序 App\Providers\AppServiceProvider
类的 boot
方法中定义宏
use Illuminate\Support\Facades\Http; /** * Bootstrap any application services. */public function boot(): void{ Http::macro('github', function () { return Http::withHeaders([ 'X-Example' => 'example', ])->baseUrl('https://github.com'); });}
配置完宏后,您可以在应用程序的任何位置调用它,以使用指定的配置创建一个待处理的请求
$response = Http::github()->get('/');
测试
许多 Laravel 服务提供功能来帮助您轻松、富有表现力地编写测试,而 Laravel 的 HTTP 客户端也不例外。Http
门面的 fake
方法允许您指示 HTTP 客户端在发出请求时返回存根/虚拟响应。
模拟响应
例如,要指示 HTTP 客户端为每个请求返回空的 200
状态代码响应,您可以不带任何参数调用 fake
方法
use Illuminate\Support\Facades\Http; Http::fake(); $response = Http::post(/* ... */);
模拟特定 URL
或者,您可以将一个数组传递给 fake
方法。数组的键应代表您希望模拟的 URL 模式及其关联的响应。*
字符可用作通配符。对未模拟的 URL 的任何请求实际上都会执行。您可以使用 Http
门面的 response
方法为这些端点构建存根/虚拟响应
Http::fake([ // Stub a JSON response for GitHub endpoints... 'github.com/*' => Http::response(['foo' => 'bar'], 200, $headers), // Stub a string response for Google endpoints... 'google.com/*' => Http::response('Hello World', 200, $headers),]);
如果要指定一个将模拟所有不匹配的 URL 的回退 URL 模式,可以使用单个 *
字符
Http::fake([ // Stub a JSON response for GitHub endpoints... 'github.com/*' => Http::response(['foo' => 'bar'], 200, ['Headers']), // Stub a string response for all other endpoints... '*' => Http::response('Hello World', 200, ['Headers']),]);
模拟响应序列
有时,您可能需要指定单个 URL 应按特定顺序返回一系列虚拟响应。您可以使用 Http::sequence
方法来构建响应
Http::fake([ // Stub a series of responses for GitHub endpoints... 'github.com/*' => Http::sequence() ->push('Hello World', 200) ->push(['foo' => 'bar'], 200) ->pushStatus(404),]);
当响应序列中的所有响应都被使用后,任何进一步的请求都会导致响应序列抛出异常。如果要指定一个在序列为空时应返回的默认响应,可以使用 whenEmpty
方法
Http::fake([ // Stub a series of responses for GitHub endpoints... 'github.com/*' => Http::sequence() ->push('Hello World', 200) ->push(['foo' => 'bar'], 200) ->whenEmpty(Http::response()),]);
如果要模拟一系列响应,但不需要指定要模拟的特定 URL 模式,可以使用 Http::fakeSequence
方法
Http::fakeSequence() ->push('Hello World', 200) ->whenEmpty(Http::response());
模拟回调
如果您需要更复杂的逻辑来确定为某些端点返回哪些响应,您可以将一个闭包传递给 fake
方法。此闭包将接收一个 Illuminate\Http\Client\Request
实例,并应返回一个响应实例。在闭包中,您可以执行确定要返回的响应类型的任何逻辑
use Illuminate\Http\Client\Request; Http::fake(function (Request $request) { return Http::response('Hello World', 200);});
防止意外请求
如果您希望确保通过 HTTP 客户端发送的所有请求在整个单个测试或完整的测试套件中都被模拟,可以调用 preventStrayRequests
方法。调用此方法后,任何没有对应虚拟响应的请求都会抛出异常,而不是进行实际的 HTTP 请求
use Illuminate\Support\Facades\Http; Http::preventStrayRequests(); Http::fake([ 'github.com/*' => Http::response('ok'),]); // An "ok" response is returned...Http::get('https://github.com/laravel/framework'); // An exception is thrown...Http::get('https://laravel.net.cn');
检查请求
模拟响应时,您可能偶尔希望检查客户端接收到的请求,以确保您的应用程序正在发送正确的数据或标头。您可以通过在调用 Http::fake
后调用 Http::assertSent
方法来实现这一点。
assertSent
方法接受一个闭包,该闭包将接收一个 Illuminate\Http\Client\Request
实例,并应返回一个布尔值,指示请求是否符合您的预期。为了使测试通过,必须至少发出一个与给定预期相匹配的请求
use Illuminate\Http\Client\Request;use Illuminate\Support\Facades\Http; Http::fake(); Http::withHeaders([ 'X-First' => 'foo',])->post('http://example.com/users', [ 'name' => 'Taylor', 'role' => 'Developer',]); Http::assertSent(function (Request $request) { return $request->hasHeader('X-First', 'foo') && $request->url() == 'http://example.com/users' && $request['name'] == 'Taylor' && $request['role'] == 'Developer';});
如果需要,可以使用 assertNotSent
方法断言特定请求未被发送
use Illuminate\Http\Client\Request;use Illuminate\Support\Facades\Http; Http::fake(); Http::post('http://example.com/users', [ 'name' => 'Taylor', 'role' => 'Developer',]); Http::assertNotSent(function (Request $request) { return $request->url() === 'http://example.com/posts';});
您可以使用 assertSentCount
方法断言测试期间“发送”了多少个请求
Http::fake(); Http::assertSentCount(5);
或者,您可以使用 assertNothingSent
方法断言测试期间没有发送任何请求
Http::fake(); Http::assertNothingSent();
录制请求/响应
您可以使用 recorded
方法收集所有请求及其对应的响应。recorded
方法返回一个包含 Illuminate\Http\Client\Request
和 Illuminate\Http\Client\Response
实例的数组集合
Http::fake([ 'https://laravel.net.cn' => Http::response(status: 500), 'https://nova.laravel.net.cn/' => Http::response(),]); Http::get('https://laravel.net.cn');Http::get('https://nova.laravel.net.cn/'); $recorded = Http::recorded(); [$request, $response] = $recorded[0];
此外,recorded
方法接受一个闭包,该闭包将接收一个 Illuminate\Http\Client\Request
和 Illuminate\Http\Client\Response
实例,并可用于根据您的预期过滤请求/响应对
use Illuminate\Http\Client\Request;use Illuminate\Http\Client\Response; Http::fake([ 'https://laravel.net.cn' => Http::response(status: 500), 'https://nova.laravel.net.cn/' => Http::response(),]); Http::get('https://laravel.net.cn');Http::get('https://nova.laravel.net.cn/'); $recorded = Http::recorded(function (Request $request, Response $response) { return $request->url() !== 'https://laravel.net.cn' && $response->successful();});
事件
Laravel 在发送 HTTP 请求的过程中触发三个事件。RequestSending
事件在发送请求之前触发,而 ResponseReceived
事件在接收给定请求的响应后触发。如果未接收到给定请求的响应,则会触发 ConnectionFailed
事件。
RequestSending
和 ConnectionFailed
事件都包含一个公共的 $request
属性,您可以使用它来检查 Illuminate\Http\Client\Request
实例。同样,ResponseReceived
事件也包含 $request
属性以及 $response
属性,可以使用它来检查 Illuminate\Http\Client\Response
实例。您可以在应用程序中为这些事件创建 事件监听器
use Illuminate\Http\Client\Events\RequestSending; class LogRequest{ /** * Handle the given event. */ public function handle(RequestSending $event): void { // $event->request ... }}