Laravel HTTP客户端
简介
Laravel 在 Guzzle HTTP Client 基础上封装了一个优雅的、最小化的 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',
]);
获取请求的Query 参数
发出GET请求时,可以直接将查询字符串附加到 URL 或将键/值对数组作为第二个参数传递给该get方法:
$response = Http::get('http://test.com/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
方法设置认证方式:
// Basic authentication...
$response = Http::withBasicAuth('taylor@laravel.com', 'secret')->post(...);
// Digest authentication...
$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';
});