什么是 HTTP 客户端
Laravel 的 HTTP 客户端是对 Guzzle 的封装,提供了一套易用的 API。
通过 Http Facade,可以简洁地写出对外部 Web 服务或 API 的 HTTP 请求。
use Illuminate\Support\Facades\Http;
$response = Http::get('https://api.example.com/users');
Guzzle 已经预装,无需额外配置即可开始使用。
基础请求
GET 请求
use Illuminate\Support\Facades\Http;
$response = Http::get('https://api.example.com/users');
查询参数可以通过数组传入。
$response = Http::get('https://api.example.com/users', [
'page' => 1,
'per_page' => 20,
]);
POST 请求
数据默认作为 application/json 发送。
$response = Http::post('https://api.example.com/users', [
'name' => '山田 太郎',
'email' => '[email protected]',
]);
PUT / PATCH / DELETE
// PUT 请求
$response = Http::put('https://api.example.com/users/1', [
'name' => '山田 花子',
]);
// PATCH 请求
$response = Http::patch('https://api.example.com/users/1', [
'email' => '[email protected]',
]);
// DELETE 请求
$response = Http::delete('https://api.example.com/users/1');
处理响应
Http::get() 等方法会返回一个 Illuminate\Http\Client\Response 实例。
在该对象上有大量用于检查响应的方法。
$response = Http::get('https://api.example.com/users/1');
// 响应体
$response->body(); // 以字符串形式获取
$response->json(); // 解析为数组
$response->json('name'); // 获取 JSON 中的指定键
$response->object(); // 以 stdClass 对象形式获取
$response->collect(); // 以 Collection 形式获取
// 状态
$response->status(); // 状态码(如 200)
$response->successful(); // 2xx 时返回 true
$response->failed(); // 4xx 及以上返回 true
$response->clientError(); // 4xx 时返回 true
$response->serverError(); // 5xx 时返回 true
// 常用状态码判定
$response->ok(); // 200
$response->created(); // 201
$response->noContent(); // 204
$response->notFound(); // 404
$response->unauthorized(); // 401
$response->forbidden(); // 403
$response->unprocessableEntity(); // 422
$response->tooManyRequests(); // 429
JSON 响应也可以通过数组访问方式取值。
$name = Http::get('https://api.example.com/users/1')['name'];
请求选项
设置请求头
$response = Http::withHeaders([
'X-Api-Version' => '2',
'Accept-Language' => 'zh',
])->get('https://api.example.com/users');
若要声明期望接收 application/json,可以使用 acceptJson()。
$response = Http::acceptJson()->get('https://api.example.com/users');
浏览器向 Laravel 发送 AJAX 请求时的 CSRF 头(X-CSRF-TOKEN / X-XSRF-TOKEN)可参考 CSRF 保护。
Bearer 令牌认证(最常见):
$response = Http::withToken($token)->get('https://api.example.com/me');
Basic 认证:
$response = Http::withBasicAuth('[email protected]', 'password')
->get('https://api.example.com/private');
设置基础 URL
如果对同一主机的请求较多,可以用 baseUrl() 统一设置。
$response = Http::baseUrl('https://api.example.com')
->withToken($token)
->get('/users/1');
发送表单数据
要以 application/x-www-form-urlencoded 发送,使用 asForm()。
$response = Http::asForm()->post('https://api.example.com/login', [
'username' => 'taro',
'password' => 'secret',
]);
// 响应超时(默认 30 秒)
$response = Http::timeout(10)->get('https://api.example.com/slow-endpoint');
// 连接超时(默认 10 秒)
$response = Http::connectTimeout(5)->get('https://api.example.com/endpoint');
超时会抛出 Illuminate\Http\Client\ConnectionException。
建议在调用外部 API 时始终设置超时。
可以为临时性的网络故障或服务器错误设置自动重试。
// 最多重试 3 次,每次间隔 100 毫秒
$response = Http::retry(3, 100)->post('https://api.example.com/orders', $data);
条件重试(例如仅在连接错误时重试):
use Illuminate\Http\Client\PendingRequest;
use Throwable;
use Illuminate\Http\Client\ConnectionException;
$response = Http::retry(3, 100, function (Throwable $exception, PendingRequest $request) {
return $exception instanceof ConnectionException;
})->post('https://api.example.com/orders', $data);
错误处理
手动检查错误
Laravel 的 HTTP 客户端默认不会对 4xx、5xx 响应抛出异常。
需要通过 failed()、clientError() 等方法显式判断。
$response = Http::get('https://api.example.com/users/999');
if ($response->notFound()) {
// 处理 404
}
if ($response->failed()) {
// 记录错误日志
logger()->error('API request failed', ['status' => $response->status()]);
}
抛出异常
使用 throw() 时,遇到错误就会抛出 Illuminate\Http\Client\RequestException。
// 遇到错误响应(4xx/5xx)时抛出异常
$response = Http::post('https://api.example.com/users', $data)->throw();
// throwIf(): 当条件为 true 时抛出(以下是独立示例)
$response = Http::get('https://api.example.com/users/1');
$response->throwIf($response->status() === 422);
// throwUnlessStatus(): 只要不是指定状态码就抛出(以下也是独立示例)
$response = Http::post('https://api.example.com/orders', $data);
$response->throwUnlessStatus(201);
throw() 会返回响应实例,因此可以链式调用。
$user = Http::post('https://api.example.com/users', $data)
->throw()
->json();
若要捕获异常处理:
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Client\ConnectionException;
try {
$response = Http::timeout(5)
->post('https://api.example.com/users', $data)
->throw();
} catch (ConnectionException $e) {
// 超时或连接错误
logger()->error('Connection failed: ' . $e->getMessage());
} catch (RequestException $e) {
// 4xx / 5xx 错误
logger()->error('API error', ['status' => $e->response->status()]);
}
并发请求
如果需要同时调用多个 API,可以通过 pool() 并发执行。
use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;
$responses = Http::pool(fn (Pool $pool) => [
$pool->as('users')->get('https://api.example.com/users'),
$pool->as('posts')->get('https://api.example.com/posts'),
$pool->as('comments')->get('https://api.example.com/comments'),
]);
$users = $responses['users']->json();
$posts = $responses['posts']->json();
$comments = $responses['comments']->json();
比顺序执行明显更快,非常适合需要同时调用多个外部 API 的仪表盘等场景。
使用 Http::fake() 模拟
测试中使用 Http::fake() 可以在不发起真实 HTTP 请求的情况下模拟响应。
use Illuminate\Support\Facades\Http;
Http::fake();
// 所有请求都返回 200
$response = Http::get('https://api.example.com/users');
$response->successful(); // true
针对特定 URL 设置响应:
Http::fake([
'api.example.com/users/*' => Http::response(['id' => 1, 'name' => '山田 太郎'], 200),
'api.example.com/posts/*' => Http::response(['error' => 'Not Found'], 404),
'*' => Http::response('OK', 200), // 其他所有请求
]);
响应序列(多次调用时按顺序返回):
Http::fake([
// 第 1 次: 200,第 2 次: 200,第 3 次: 429
// 序列耗尽后再发请求会抛出异常
'api.example.com/*' => Http::sequence()
->push(['id' => 1], 200)
->push(['id' => 2], 200)
->pushStatus(429),
]);
校验请求
Http::assertSent() 可以校验发出的请求内容。
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;
Http::fake();
Http::withToken('my-token')->post('https://api.example.com/users', [
'name' => '山田 太郎',
]);
Http::assertSent(function (Request $request) {
return $request->url() === 'https://api.example.com/users'
&& $request->hasHeader('Authorization', 'Bearer my-token')
&& $request['name'] === '山田 太郎';
});
// 确认没有发送过某个请求
Http::assertNotSent(function (Request $request) {
return $request->url() === 'https://api.example.com/admin';
});
// 确认只发送过 1 次请求
Http::assertSentCount(1);
测试中一定要在最开始调用 Http::fake()。
忘记调用可能会真的对外部 API 发起请求。
使用 Http::preventStrayRequests() 可以让未被 fake 的 URL 请求抛出异常。
阻止“漏网”请求
Http::preventStrayRequests();
Http::fake([
'api.example.com/*' => Http::response(['ok' => true]),
]);
// 未被 fake 的 URL 请求会抛异常
Http::get('https://other.example.com/endpoint'); // 抛出异常
实战示例:调用外部 API 的服务类
在真实项目中,把 HTTP 客户端逻辑封装到服务类中是最佳实践。
创建服务类
<?php
namespace App\Services;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Http;
class GitHubService
{
private string $baseUrl = 'https://api.github.com';
public function __construct(
private readonly string $token,
) {}
/**
* 获取用户信息。
*
* @throws ConnectionException
* @throws RequestException
*/
public function getUser(string $username): array
{
return Http::baseUrl($this->baseUrl)
->withToken($this->token)
->acceptJson()
->timeout(10)
->get("/users/{$username}")
->throw()
->json();
}
/**
* 获取仓库列表。
*
* @throws ConnectionException
* @throws RequestException
*/
public function getRepositories(string $username, int $page = 1): array
{
return Http::baseUrl($this->baseUrl)
->withToken($this->token)
->acceptJson()
->timeout(10)
->retry(2, 500)
->get("/users/{$username}/repos", [
'page' => $page,
'per_page' => 30,
'sort' => 'updated',
])
->throw()
->json();
}
}
在服务提供者中注册
// app/Providers/AppServiceProvider.php
use App\Services\GitHubService;
public function register(): void
{
$this->app->singleton(GitHubService::class, function () {
return new GitHubService(
token: config('services.github.token'),
);
});
}
// config/services.php
'github' => [
'token' => env('GITHUB_TOKEN'),
],
在控制器中使用
<?php
namespace App\Http\Controllers;
use App\Services\GitHubService;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\JsonResponse;
class GitHubController extends Controller
{
public function __construct(
private readonly GitHubService $github,
) {}
public function show(string $username): JsonResponse
{
try {
$user = $this->github->getUser($username);
return response()->json($user);
} catch (ConnectionException) {
return response()->json(['error' => '无法连接到 GitHub API。'], 503);
} catch (RequestException $e) {
$status = $e->response->status();
if ($status === 404) {
return response()->json(['error' => '用户不存在。'], 404);
}
return response()->json(['error' => 'GitHub API 出错。'], 502);
}
}
}
编写测试
<?php
namespace Tests\Unit\Services;
use App\Services\GitHubService;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class GitHubServiceTest extends TestCase
{
private GitHubService $service;
protected function setUp(): void
{
parent::setUp();
Http::fake([
'api.github.com/users/octocat' => Http::response([
'login' => 'octocat',
'name' => 'The Octocat',
'public_repos' => 8,
], 200),
'api.github.com/users/notfound' => Http::response(
['message' => 'Not Found'],
404
),
]);
$this->service = new GitHubService(token: 'test-token');
}
public function test_可以获取用户信息(): void
{
$user = $this->service->getUser('octocat');
$this->assertEquals('octocat', $user['login']);
$this->assertEquals('The Octocat', $user['name']);
Http::assertSent(function ($request) {
return $request->url() === 'https://api.github.com/users/octocat'
&& $request->hasHeader('Authorization', 'Bearer test-token');
});
}
public function test_不存在的用户会抛出异常(): void
{
$this->expectException(RequestException::class);
$this->service->getUser('notfound');
}
}
| 方法 | 说明 |
|---|
successful() | 2xx |
failed() | 4xx 及以上 |
clientError() | 4xx |
serverError() | 5xx |
ok() | 200 |
created() | 201 |
notFound() | 404 |
unauthorized() | 401 |
forbidden() | 403 |
unprocessableEntity() | 422 |
tooManyRequests() | 429 |
- 将 HTTP 客户端逻辑封装到服务类中
- 始终设置超时(
timeout() 与 connectTimeout())
- 对临时故障设置自动重试(
retry())
- 测试中一定使用
Http::fake(),不要真的访问外部 API
- 在测试的 setUp 中加入
Http::preventStrayRequests() 更保险
- API 令牌和凭证通过环境变量和
config/services.php 管理