Laravel 13 HTTP 客户端详解

Laravel 的 HTTP 客户端基于 Guzzle,提供了流畅的 API 来发送 HTTP 请求。本文将深入探讨 Laravel 13 HTTP 客户端的高级用法。

基本请求

GET 请求

1
2
3
4
5
6
7
8
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' => 10,
]);

POST 请求

1
2
3
4
5
6
7
8
9
$response = Http::post('https://api.example.com/users', [
'name' => 'John Doe',
'email' => 'john@example.com',
]);

$response = Http::asForm()->post('https://api.example.com/login', [
'email' => 'john@example.com',
'password' => 'password',
]);

其他 HTTP 方法

1
2
3
4
5
Http::put('https://api.example.com/users/1', $data);
Http::patch('https://api.example.com/users/1', $data);
Http::delete('https://api.example.com/users/1');
Http::head('https://api.example.com/users/1');
Http::options('https://api.example.com/users');

请求头

设置请求头

1
2
3
4
5
6
7
8
9
10
$response = Http::withHeaders([
'Authorization' => 'Bearer token',
'X-Custom-Header' => 'value',
])->get('https://api.example.com/users');

$response = Http::withToken('token')->get('https://api.example.com/users');

$response = Http::withBasicAuth('username', 'password')->get('https://api.example.com/users');

$response = Http::withDigestAuth('username', 'password')->get('https://api.example.com/users');

User-Agent

1
$response = Http::withUserAgent('MyApp/1.0')->get('https://api.example.com/users');

请求体

JSON 请求

1
2
3
4
$response = Http::asJson()->post('https://api.example.com/users', $data);

// 默认就是 JSON
$response = Http::post('https://api.example.com/users', $data);

表单请求

1
2
3
4
$response = Http::asForm()->post('https://api.example.com/login', [
'email' => 'john@example.com',
'password' => 'password',
]);

多部分请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$response = Http::asMultipart()->post('https://api.example.com/upload', [
[
'name' => 'file',
'contents' => fopen('/path/to/file', 'r'),
'filename' => 'document.pdf',
],
[
'name' => 'description',
'contents' => 'File description',
],
]);

$response = Http::attach(
'file', file_get_contents('/path/to/file'), 'document.pdf'
)->post('https://api.example.com/upload');

响应处理

获取响应内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$response = Http::get('https://api.example.com/users');

$body = $response->body();
$json = $response->json();
$object = $response->object();
$collection = $response->collect();

$status = $response->status();
$ok = $response->ok();
$successful = $response->successful();
$failed = $response->failed();
$clientError = $response->clientError();
$serverError = $response->serverError();

$headers = $response->headers();
$header = $response->header('Content-Type');

访问 JSON 数据

1
2
3
4
5
$response = Http::get('https://api.example.com/users/1');

$name = $response->json('name');
$email = $response->json('contact.email');
$users = $response->json('data.*.name');

错误处理

异常处理

1
2
3
4
5
6
7
8
9
10
use Illuminate\Http\Client\ConnectionException;

try {
$response = Http::get('https://api.example.com/users');
$response->throw();
} catch (ConnectionException $e) {
// 连接错误
} catch (\Exception $e) {
// 其他错误
}

条件异常

1
2
3
4
5
6
7
8
$response = Http::get('https://api.example.com/users');

if ($response->failed()) {
$response->throw();
}

$response->throwIf($condition);
$response->throwUnless($condition);

自定义异常

1
2
3
4
5
6
7
8
$response = Http::get('https://api.example.com/users');

$response->throw(function ($response, $e) {
Log::error('API request failed', [
'status' => $response->status(),
'body' => $response->body(),
]);
});

超时和重试

超时设置

1
2
$response = Http::timeout(30)->get('https://api.example.com/users');
$response = Http::connectTimeout(10)->get('https://api.example.com/users');

重试机制

1
2
3
4
5
6
7
$response = Http::retry(3, 100)->get('https://api.example.com/users');

$response = Http::retry(3, 100, function ($exception) {
return $exception instanceof ConnectionException;
})->get('https://api.example.com/users');

$response = Http::retry(3, 100, throw: false)->get('https://api.example.com/users');

并发请求

并行请求

1
2
3
4
5
6
7
8
9
10
11
use Illuminate\Http\Client\Pool;

$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();

带配置的并发请求

1
2
3
4
$responses = Http::pool(fn (Pool $pool) => [
$pool->as('users')->withToken('token')->get('https://api.example.com/users'),
$pool->as('posts')->withToken('token')->get('https://api.example.com/posts'),
]);

宏请求

定义宏

1
2
3
4
5
6
7
8
9
10
11
use Illuminate\Support\Facades\Http;

Http::macro('github', function () {
return Http::withHeaders([
'Authorization' => 'token ' . config('services.github.token'),
'Accept' => 'application/vnd.github.v3+json',
])->baseUrl('https://api.github.com');
});

// 使用
$response = Http::github()->get('/repos/laravel/framework');

全局配置

全局请求头

1
2
3
4
5
6
7
8
9
use Illuminate\Support\Facades\Http;

Http::globalOptions([
'timeout' => 30,
]);

Http::globalHeaders([
'X-App-Version' => '1.0',
]);

全局中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use Illuminate\Support\Facades\Http;
use Illuminate\Http\Client\Request;

Http::globalRequestMiddleware(function (Request $request) {
$request->withHeader('X-Request-Id', Str::uuid()->toString());
return $request;
});

Http::globalResponseMiddleware(function ($response) {
Log::info('API Response', [
'status' => $response->status(),
'url' => $response->effectiveUri(),
]);
return $response;
});

测试 HTTP 客户端

伪造响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use Illuminate\Support\Facades\Http;

Http::fake();

Http::fake([
'github.com/*' => Http::response(['foo' => 'bar'], 200),
'google.com/*' => Http::response('Hello World', 200),
]);

Http::fake([
'example.com/*' => Http::response(['error' => 'Not found'], 404),
]);

Http::fake([
'example.com/*' => Http::sequence()
->push(['page' => 1], 200)
->push(['page' => 2], 200)
->pushStatus(404),
]);

断言请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Http::fake();

Http::withHeaders(['Authorization' => 'Bearer token'])
->post('https://api.example.com/users', ['name' => 'John']);

Http::assertSent(function ($request) {
return $request->url() === 'https://api.example.com/users' &&
$request->hasHeader('Authorization', 'Bearer token') &&
$request['name'] === 'John';
});

Http::assertNotSent(function ($request) {
return $request->url() === 'https://api.example.com/other';
});

Http::assertNothingSent();
Http::assertSentCount(2);

最佳实践

1. 使用宏封装 API

1
2
3
4
5
6
Http::macro('stripe', function () {
return Http::withBasicAuth(config('stripe.secret'), '')
->baseUrl('https://api.stripe.com/v1');
});

$response = Http::stripe()->post('/charges', $data);

2. 处理错误

1
2
3
4
5
6
7
8
9
10
11
$response = Http::retry(3, 100)->get('https://api.example.com/users');

if ($response->failed()) {
Log::error('API request failed', [
'status' => $response->status(),
'body' => $response->body(),
]);
return null;
}

return $response->json();

3. 使用并发请求

1
2
3
4
5
6
7
8
9
// 好的做法:并发请求
$responses = Http::pool(fn (Pool $pool) => [
$pool->as('users')->get('/users'),
$pool->as('posts')->get('/posts'),
]);

// 不好的做法:顺序请求
$users = Http::get('/users');
$posts = Http::get('/posts');

总结

Laravel 13 的 HTTP 客户端提供了强大而优雅的 HTTP 请求能力。通过合理使用请求头、超时重试、并发请求和错误处理,可以构建出可靠的 API 集成。记住使用宏封装常用 API、使用并发请求提高性能、并充分利用测试功能来验证 HTTP 请求。HTTP 客户端是与外部服务交互的重要工具,掌握它对于构建现代 Web 应用至关重要。