Passer au contenu principal

Qu’est-ce que le client HTTP

Le client HTTP de Laravel est une API simple qui enveloppe Guzzle. Via la façade Http, envoyez facilement des requêtes vers des services web externes.
use Illuminate\Support\Facades\Http;

$response = Http::get('https://api.example.com/users');
Guzzle est préinstallé — aucune configuration nécessaire.

Requêtes de base

GET

use Illuminate\Support\Facades\Http;

$response = Http::get('https://api.example.com/users');
Paramètres de requête en tableau :
$response = Http::get('https://api.example.com/users', [
    'page' => 1,
    'per_page' => 20,
]);

POST

Envoi en application/json par défaut.
$response = Http::post('https://api.example.com/users', [
    'name' => 'Taro Yamada',
    'email' => '[email protected]',
]);

PUT / PATCH / DELETE

$response = Http::put('https://api.example.com/users/1', [
    'name' => 'Hanako Yamada',
]);

$response = Http::patch('https://api.example.com/users/1', [
    'email' => '[email protected]',
]);

$response = Http::delete('https://api.example.com/users/1');

Traiter la réponse

Http::get() retourne un Illuminate\Http\Client\Response.
$response = Http::get('https://api.example.com/users/1');

// Corps
$response->body();        // Chaîne
$response->json();        // Tableau
$response->json('name');  // Clé précise
$response->object();      // stdClass
$response->collect();     // Collection

// Statut
$response->status();
$response->successful();  // 2xx
$response->failed();      // >= 400
$response->clientError(); // 4xx
$response->serverError(); // 5xx

// Codes fréquents
$response->ok();           // 200
$response->created();      // 201
$response->noContent();    // 204
$response->notFound();     // 404
$response->unauthorized(); // 401
$response->forbidden();    // 403
$response->unprocessableEntity(); // 422
$response->tooManyRequests();     // 429
Accès par index :
$name = Http::get('https://api.example.com/users/1')['name'];

Options de requête

En-têtes

$response = Http::withHeaders([
    'X-Api-Version' => '2',
    'Accept-Language' => 'fr',
])->get('https://api.example.com/users');
Pour accepter du JSON, acceptJson().
$response = Http::acceptJson()->get('https://api.example.com/users');
Pour les headers CSRF (X-CSRF-TOKEN / X-XSRF-TOKEN) côté navigateur, voir Protection CSRF.

Authentification

Bearer (le plus courant) :
$response = Http::withToken($token)->get('https://api.example.com/me');
Basic :
$response = Http::withBasicAuth('[email protected]', 'password')
    ->get('https://api.example.com/private');

URL de base

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

Données de formulaire

application/x-www-form-urlencoded :
$response = Http::asForm()->post('https://api.example.com/login', [
    'username' => 'taro',
    'password' => 'secret',
]);

Timeout

// Timeout de réponse (30 s par défaut)
$response = Http::timeout(10)->get('https://api.example.com/slow-endpoint');

// Timeout de connexion (10 s par défaut)
$response = Http::connectTimeout(5)->get('https://api.example.com/endpoint');
Un dépassement lève Illuminate\Http\Client\ConnectionException. Toujours définir des timeouts.

Retry

// 3 tentatives max, 100 ms entre
$response = Http::retry(3, 100)->post('https://api.example.com/orders', $data);
Retry conditionnel (uniquement erreur de connexion, par exemple) :
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);

Gestion des erreurs

Vérifier manuellement

Par défaut, aucune exception n’est levée sur 4xx/5xx. Vérifiez avec failed(), clientError(), etc.
$response = Http::get('https://api.example.com/users/999');

if ($response->notFound()) {
    // 404
}

if ($response->failed()) {
    logger()->error('API request failed', ['status' => $response->status()]);
}

Lever une exception

throw() lance Illuminate\Http\Client\RequestException en cas d’erreur.
// Lève sur 4xx/5xx
$response = Http::post('https://api.example.com/users', $data)->throw();

// throwIf() : si la condition est vraie
$response = Http::get('https://api.example.com/users/1');
$response->throwIf($response->status() === 422);

// throwUnlessStatus() : sauf pour ce code
$response = Http::post('https://api.example.com/orders', $data);
$response->throwUnlessStatus(201);
throw() retourne la réponse — chaînable :
$user = Http::post('https://api.example.com/users', $data)
    ->throw()
    ->json();
Gestion des exceptions :
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) {
    // Timeout / connexion
    logger()->error('Connection failed: ' . $e->getMessage());
} catch (RequestException $e) {
    // 4xx / 5xx
    logger()->error('API error', ['status' => $e->response->status()]);
}

Requêtes en parallèle

pool() exécute plusieurs requêtes en concurrence.
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();
Bien plus rapide qu’en séquentiel. Idéal pour un dashboard qui aggrège plusieurs API.

Tests

Http::fake() pour mocker

Sans envoyer de vraies requêtes.
use Illuminate\Support\Facades\Http;

Http::fake();

// Retourne 200 partout
$response = Http::get('https://api.example.com/users');
$response->successful(); // true
Réponses par URL :
Http::fake([
    'api.example.com/users/*' => Http::response(['id' => 1, 'name' => 'Taro Yamada'], 200),
    'api.example.com/posts/*' => Http::response(['error' => 'Not Found'], 404),
    '*' => Http::response('OK', 200),
]);
Séquence de réponses :
Http::fake([
    // 1er : 200, 2e : 200, 3e : 429
    // Épuisement → exception aux appels suivants
    'api.example.com/*' => Http::sequence()
        ->push(['id' => 1], 200)
        ->push(['id' => 2], 200)
        ->pushStatus(429),
]);

Vérification des requêtes

use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;

Http::fake();

Http::withToken('my-token')->post('https://api.example.com/users', [
    'name' => 'Taro Yamada',
]);

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

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

Http::assertSentCount(1);
Appelez toujours Http::fake() en début de test. Sans lui, de vraies requêtes partent vers l’extérieur. Http::preventStrayRequests() fait échouer toute requête non fakée.

Empêcher les requêtes non prévues

Http::preventStrayRequests();

Http::fake([
    'api.example.com/*' => Http::response(['ok' => true]),
]);

// Non fakée → exception
Http::get('https://other.example.com/endpoint');

Exemple : service class pour une API externe

Bonne pratique : encapsuler la logique HTTP dans une service class.
1

Créer la service class

<?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,
    ) {}

    /**
     * Récupère un utilisateur.
     *
     * @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();
    }

    /**
     * Liste les dépôts.
     *
     * @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();
    }
}
2

Enregistrer dans le service provider

// 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'),
],
3

Utiliser dans un contrôleur

<?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' => 'Impossible de contacter l\'API GitHub.'], 503);
        } catch (RequestException $e) {
            $status = $e->response->status();
            if ($status === 404) {
                return response()->json(['error' => 'Utilisateur introuvable.'], 404);
            }
            return response()->json(['error' => 'Erreur de l\'API GitHub.'], 502);
        }
    }
}
4

Écrire un test

<?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_get_user(): 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_not_found_throws(): void
    {
        $this->expectException(RequestException::class);

        $this->service->getUser('notfound');
    }
}

Récapitulatif

MéthodeUsage
Http::get($url, $query)GET
Http::post($url, $data)POST (JSON)
Http::put($url, $data)PUT
Http::patch($url, $data)PATCH
Http::delete($url)DELETE
->withToken($token)Bearer
->withHeaders($headers)En-têtes
->timeout($seconds)Timeout
->retry($times, $sleep)Retry auto
->throw()Exception sur erreur
Http::fake()Mock pour tests
Http::pool($callback)Requêtes en parallèle
MéthodeDescription
successful()2xx
failed()>= 400
clientError()4xx
serverError()5xx
ok()200
created()201
notFound()404
unauthorized()401
forbidden()403
unprocessableEntity()422
tooManyRequests()429
  • Encapsulez la logique HTTP dans des service classes
  • Toujours définir un timeout() et un connectTimeout()
  • Configurez un retry() pour les erreurs transitoires
  • En test, Http::fake() obligatoire — ne jamais appeler l’API réelle
  • Ajoutez Http::preventStrayRequests() dans le setup des tests
  • Gérez tokens et secrets via variables d’env + config/services.php
Dernière modification le 13 juillet 2026