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

  1. Luôn verify SSL certificates - Đừng vô hiệu hóa ở production
  2. Đặt reasonable timeouts - Ngăn hanging requests
  3. Triển khai exponential backoff - Cho các transient failures
  4. Log tất cả - Debug issues nhanh chóng
  5. Sử dụng dedicated services - Encapsulate API logic
  6. Xử lý rate limits - Tôn trọng API quotas
  7. Transform data sớm - Decouple internal formats từ external schemas
  8. 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

Bình luận