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