Làm chủ Laravel HTTP Client cho External APIs
·
5 min read
HTTP client của Laravel (dựa trên Guzzle) làm cho việc sử dụng các external APIs trở nên đơn giản và thanh lịch. Tuy nhiên, xây dựng các tích hợp mạnh mẽ yêu cầu hiểu biết về retry strategies, error handling, và request/response transformation.
Cơ bản
use Illuminate\Support\Facades\Http;
$response = Http::get('https://api.example.com/users');
if ($response->successful()) {
$users = $response->json();
} else {
$response->throw(); // Throws HttpRequestException
}
Thêm Retry Logic
Các external APIs có thể fail tạm thời. Luôn triển khai các retry strategies:
$response = Http::retry(3, 100) // 3 attempts, 100ms giữa các retries
->get('https://api.example.com/users');
// Exponential backoff (100ms, 200ms, 400ms)
$response = Http::retry(3, 100, exponential: true)
->get('https://api.example.com/users');
Timeout Handling
$response = Http::timeout(10) // 10 giây timeout
->connectTimeout(5) // 5 giây connection timeout
->get('https://api.example.com/users');
// Bắt timeout exceptions
try {
$response = Http::timeout(5)->get('https://api.example.com/users');
} catch (ConnectException | RequestException $e) {
Log::error('API timeout: ' . $e->getMessage());
}
Các mẫu Authentication
Bearer Token
$response = Http::withToken($token)
->get('https://api.example.com/users');
Basic Auth
$response = Http::withBasicAuth('username', 'password')
->get('https://api.example.com/users');
Custom Headers
$response = Http::withHeaders([
'X-API-Key' => config('services.external-api.key'),
'Accept' => 'application/json',
])
->get('https://api.example.com/users');
Xây dựng một Reusable API Client
Thay vì phân tán HTTP calls khắp ứng dụng, hãy tạo một dedicated service:
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Http\Client\PendingRequest;
class ExternalApiClient
{
private string $baseUrl = 'https://api.example.com';
private string $apiKey;
public function __construct()
{
$this->apiKey = config('services.external-api.key');
}
protected function client(): PendingRequest
{
return Http::retry(3, 100, exponential: true)
->timeout(10)
->connectTimeout(5)
->withToken($this->apiKey)
->baseUrl($this->baseUrl);
}
public function getUsers(): array
{
return $this->client()
->get('/users')
->throw()
->json();
}
public function createUser(array $data): array
{
return $this->client()
->post('/users', $data)
->throw()
->json();
}
public function updateUser(int $id, array $data): array
{
return $this->client()
->patch("/users/{$id}", $data)
->throw()
->json();
}
}
Error Handling với Callbacks
$response = Http::get('https://api.example.com/users');
// Validate response
if ($response->status() === 429) {
// Rate limited
Log::warning('API rate limited. Retry-After: ' . $response->header('Retry-After'));
} elseif ($response->serverError()) {
// 5xx error
Log::error('API server error: ' . $response->status());
} elseif ($response->clientError()) {
// 4xx error
Log::warning('API client error: ' . $response->json());
}
Async Requests với Promises
Cho các non-blocking requests:
$promises = [
'users' => Http::async()->get('https://api.example.com/users'),
'posts' => Http::async()->get('https://api.example.com/posts'),
];
$results = Http::pool(function ($pool) {
$pool->get('https://api.example.com/users');
$pool->get('https://api.example.com/posts');
});
foreach ($results as $response) {
if ($response->successful()) {
echo $response->json();
}
}
Request/Response Logging
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
$response = Http::withoutRedirecting()
->timeout(30)
->get('https://api.example.com/users');
// Log tất cả
Log::info('API Request', [
'method' => 'GET',
'url' => 'https://api.example.com/users',
'status' => $response->status(),
'body' => $response->json(),
]);
Rate Limiting
Nếu external API thực thi rate limits:
use Illuminate\Support\Facades\Cache;
public function getFromRateLimitedApi($url)
{
$key = 'api_call_' . md5($url);
if (Cache::has($key)) {
throw new Exception('Rate limit exceeded. Try again later.');
}
$response = Http::get($url);
if ($response->header('X-RateLimit-Remaining') < 10) {
Cache::put($key, true, now()->addMinutes(5));
}
return $response->json();
}
Data Transformation
class ExternalApiClient
{
public function getFormattedUsers(): array
{
$data = $this->client()
->get('/users')
->throw()
->json();
return collect($data['users'])
->map(fn($user) => [
'id' => $user['id'],
'name' => $user['full_name'],
'email' => $user['email_address'],
])
->toArray();
}
}
Testing API Calls
use Illuminate\Support\Facades\Http;
public function test_can_fetch_users()
{
Http::fake([
'api.example.com/*' => Http::response([
'users' => [
['id' => 1, 'name' => 'John'],
]
], 200),
]);
$client = new ExternalApiClient();
$users = $client->getUsers();
$this->assertCount(1, $users);
Http::assertSent(function ($request) {
return $request->url() === 'https://api.example.com/users';
});
}
Best Practices
- Luôn verify SSL certificates - Đừng vô hiệu hóa ở production
- Đặt reasonable timeouts - Ngăn hanging requests
- Triển khai exponential backoff - Cho các transient failures
- Log tất cả - Debug issues nhanh chóng
- Sử dụng dedicated services - Encapsulate API logic
- Xử lý rate limits - Tôn trọng API quotas
- Transform data sớm - Decouple internal formats từ external schemas
- Test với fakes - Mock external APIs trong tests
Tóm tắt
HTTP client của Laravel rất mạnh mẽ và linh hoạt. Bằng cách kết hợp retry logic, timeout handling, proper authentication, và error management, bạn có thể xây dựng các tích hợp mạnh mẽ với bất kỳ external API nào.
Các điểm chính:
- Sử dụng retry với exponential backoff
- Luôn đặt timeouts
- Tạo dedicated service classes
- Mock APIs trong tests
- Log requests và responses
- Transform data tại API boundaries