HTTP 客户端
简介
Laravel 基于 Guzzle HTTP 客户端封装了一个优雅的、最小化的 API,从而方便开发者快速创建 HTTP 请求与其他 Web 应用进行通信。Laravel 对 Guzzle 的封装围绕的是最常用的场景,并且致力于提供更好的开发体验。
开始使用之前,需要确保已经在项目中安装过 Guzzle 扩展包依赖,默认情况下,Laravel 会自动包含这个依赖:
composer require guzzlehttp/guzzle
创建请求
基本使用
要创建请求,可以使用 get
、post
、put
、patch
以及 delete
方法。首先,让我们看看如何创建最基本的 GET
请求:
use Illuminate\Support\Facades\Http;
$response = Http::get('http://blog.test');
get
方法返回一个 Illuminate\Http\Client\Response
实例,该实例上提供了多个对响应进行「透视」的方法:
$response->body() : string;
$response->json() : array;
$response->status() : int;
$response->ok() : bool;
$response->successful() : bool;
$response->serverError() : bool;
$response->clientError() : bool;
$response->header($header) : string;
$response->headers() : array;
Illuminate\Http\Client\Response
对象还实现了 PHP ArrayAccess
接口,这样一来,就可以直接在响应实例上访问 JSON 响应数据:
return Http::get('http://blog.test/users/1')['name'];
请求数据
当然,在 POST
、GET
和 PATCH
请求中发送额外请求数据很常见,这些方法可以接收数组格式请求数据作为第二个参数。默认情况下,数据会以 application/json
内容类型发送:
$response = Http::post('http://blog.test/users', [
'name' => 'Steve',
'role' => 'Network Administrator',
]);
GET 请求查询字符串
发送 GET 请求时,查询字符串可以直接添加到 URL 或者以键值对数组形式作为 get
方法的第二个参数传递:
$response = Http::get('http://blog.test/users', [
'name' => 'Taylor',
'page' => 1,
]);
发送表单 URL 编码请求
如果你想使用 application/x-www-form-urlencoded
内容类型发送数据(一般 HTML 表单都是这个格式),需要在创建请求前调用 asForm
方法:
$response = Http::asForm()->post('http://blog.test/users', [
'name' => 'Sara',
'role' => 'Privacy Consultant',
]);
发送原生请求实体
你可以在发起请求时,使用 withBody
方法提供原生的请求实体:
$response = Http::withBody(
base64_encode($photo), 'image/jpeg'
)->post('http://test.com/photo');
Multi-Part 请求
对于文件上传请求,需要以 multi-part 内容类型发起请求,这可以通过在创建请求前调用 attach
方法完成。该方法接收文件名和内容作为参数,还可以传递第三个参数作为期望文件名:
$response = Http::attach(
'attachment', file_get_contents('photo.jpg'), 'photo.jpg'
)->post('http://blog.test/attachments');
除了传递文件原生内容外,还可以传递流资源:
$photo = fopen('photo.jpg', 'r');
$response = Http::attach(
'attachment', $photo, 'photo.jpg'
)->post('http://blog.test/attachments');
请求头
可以使用 withHeaders
方法添加请求头到请求,withHeaders
方法接收数据格式是键值对数组:
$response = Http::withHeaders([
'X-First' => 'foo',
'X-Second' => 'bar'
])->post('http://blog.test/users', [
'name' => '学院君',
]);
认证
你可以使用 withBasicAuth
和 withDigestAuth
方法设置认证方式:
// HTTP Basic 认证...
$response = Http::withBasicAuth('taylor@laravel.com', 'secret')->post(...);
// HTTP Digest 认证...
$response = Http::withDigestAuth('taylor@laravel.com', 'secret')->post(...);
Bearer Tokens
如果你想要快速添加 Authorization
Bearer Token 头到请求,可以使用 withToken
方法:
$response = Http::withToken('token')->post(...);
超时
可以通过 timeout
方法指定等待响应的最大时长(单位:秒),即请求的超时时间:
$response = Http::timeout(3)->get(...);
如果给定的超时时间已到,则会抛出 Illuminate\Http\Client\ConnectionException
异常。
重试
如果你想要 HTTP 客户端在客户端或服务端发生错误时自动重发请求,可以使用 retry
方法。该方法接收两个参数 —— 重试次数和两次重试之间的时间间隔(ms):
$response = Http::retry(3, 100)->post(...);
如果所有重试也失败,则抛出 Illuminate\Http\Client\RequestException
异常。
错误处理
不同于 Guzzle 默认的行为,Laravel 的 HTTP 客户端封装并不会在客户端或服务端发生错误时抛出异常,你需要使用 successful
、clientError
或者 serverError
方法来判断是否返回错误:
// Determine if the status code was >= 200 and < 300...
$response->successful();
// Determine if the response has a 400 level status code...
$response->clientError();
// Determine if the response has a 500 level status code...
$response->serverError();
抛出异常
如果你持有一个响应实例,并且在该响应是客户端或服务端错误的情况下想要抛出一个 Illuminate\Http\Client\RequestException
异常实例,可以使用 throw
方法:
$response = Http::post(...);
// Throw an exception if a client or server error occurred...
$response->throw();
return $response['user']['id'];
Illuminate\Http\Client\RequestException
实例包含一个公开的 $response
属性,你可以通过它来检查返回的响应。
throw
方法会在没有错误发生时返回响应实例,所以你可以通过方法链的形式在上面定义更多其他操作:
return Http::post(...)->throw()->json();
Guzzle 选项
你可以使用 withOptions
方法指定更多额外的 Guzzle 请求选项,该方法接收数组格式参数来指定多个选项:
$response = Http::withOptions([
'debug' => true,
])->get('http://blog.test/users');
测试
很多 Laravel 服务都提供了助力开发者轻松优雅编写测试代码的功能,Laravel HTTP 客户端封装库也不例外,通过 HTTP
门面的 fake
方法,你可以轻松构造 HTTP 客户端并在发起请求后返回预定义(通过桩文件或者模板代码设置)响应。
伪造响应
例如,要构建一个 HTTP 客户端针对任意请求都返回空实体、200
状态码响应,可以调用不传入任何参数的 fake
方法实现:
use Illuminate\Support\Facades\Http;
Http::fake();
$response = Http::post(...);
伪造指定 URL
你还可以传递数组到 fake
方法,该数组的键表示你希望伪造的请求 URL 模式字符串,该数组的值表示与之关联的响应。在 URL 模式字符串中可以使用 *
作为通配符。在预定义响应中,我们使用的是 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 模式来处理所有未匹配请求,可以使用 *
作为键:
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
实例并返回一个对应的响应实例:
Http::fake(function ($request) {
return Http::response('Hello World', 200);
});
检查请求
伪造响应时,你可能想要检查客户端请求以便确保应用发送的是正确的数据或者请求头。这可以通过在调用 Http::fake
之后调用 Http::assertSent
方法来实现。
assertSent
方法接收一个回调函数作为参数,该回调函数需要传入 Illuminate\Http\Client\Request
实例然后返回一个布尔类型的值,用来表明请求是否和预期匹配。为了让测试通过,至少要发出一个和预期匹配的请求:
Http::fake();
Http::withHeaders([
'X-First' => 'foo',
])->post('http://blog.test/users', [
'name' => '学院君',
'role' => 'Developer',
]);
Http::assertSent(function ($request) {
return $request->hasHeader('X-First', 'foo') &&
$request->url() == 'http://blog.test/users' &&
$request['name'] == '学院君' &&
$request['role'] == 'Developer';
});
如果需要的话,你可以使用 assertNotSent
方法断言指定请求没有发送:
Http::fake();
Http::post('http://blog.test/users', [
'name' => '学院君',
'role' => 'Developer',
]);
Http::assertNotSent(function (Request $request) {
return $request->url() === 'http://blog.test/posts';
});
或者,如果你想要断言没有发送任何请求,可以使用 assertNothingSent
方法:
Http::fake();
Http::assertNothingSent();
No Comments