Vai al contenuto principale

Cos’è l’HTTP Client

Il client HTTP di Laravel è una comoda API che avvolge Guzzle. Con la facade Http scrivi in modo conciso richieste HTTP a servizi e API esterni.
use Illuminate\Support\Facades\Http;

$response = Http::get('https://api.example.com/users');
Guzzle è già installato: puoi iniziare subito senza configurazioni.

Richieste di base

GET

use Illuminate\Support\Facades\Http;

$response = Http::get('https://api.example.com/users');
Parametri di query come array.
$response = Http::get('https://api.example.com/users', [
    'page' => 1,
    'per_page' => 20,
]);

POST

I dati sono inviati di default come application/json.
$response = Http::post('https://api.example.com/users', [
    'name' => 'Mario Rossi',
    'email' => '[email protected]',
]);

PUT / PATCH / DELETE

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

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

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

Gestione delle risposte

Http::get() e i metodi affini restituiscono un’istanza Illuminate\Http\Client\Response con molti metodi per ispezionare la risposta.
$response = Http::get('https://api.example.com/users/1');

$response->body();        // Body come stringa
$response->json();        // In array
$response->json('name');  // Chiave specifica
$response->object();      // stdClass
$response->collect();     // Collection

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

$response->ok();           // 200
$response->created();      // 201
$response->noContent();    // 204
$response->notFound();     // 404
$response->unauthorized(); // 401
$response->forbidden();    // 403
$response->unprocessableEntity(); // 422
$response->tooManyRequests();     // 429
Puoi accedere alla risposta JSON anche come array.
$name = Http::get('https://api.example.com/users/1')['name'];

Opzioni di richiesta

$response = Http::withHeaders([
    'X-Api-Version' => '2',
    'Accept-Language' => 'it',
])->get('https://api.example.com/users');
Per accettare application/json è comodo acceptJson().
$response = Http::acceptJson()->get('https://api.example.com/users');
Per gli header CSRF (X-CSRF-TOKEN / X-XSRF-TOKEN) delle richieste AJAX dal browser verso Laravel, consulta Protezione CSRF.

Autenticazione

Bearer token (il più comune):
$response = Http::withToken($token)->get('https://api.example.com/me');
Basic auth:
$response = Http::withBasicAuth('[email protected]', 'password')
    ->get('https://api.example.com/private');

Base URL

Se richiami spesso lo stesso host, usa baseUrl().
$response = Http::baseUrl('https://api.example.com')
    ->withToken($token)
    ->get('/users/1');

Form data

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

Timeout

// Timeout di risposta (default 30s)
$response = Http::timeout(10)->get('https://api.example.com/slow-endpoint');

// Timeout di connessione (default 10s)
$response = Http::connectTimeout(5)->get('https://api.example.com/endpoint');
In caso di timeout viene sollevata Illuminate\Http\Client\ConnectionException. Imposta sempre timeout quando chiami API esterne.

Retry

$response = Http::retry(3, 100)->post('https://api.example.com/orders', $data);
Retry condizionali:
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);

Gestione degli errori

Verifica manuale

Di default il client HTTP non solleva eccezioni per 4xx/5xx. Verifica esplicitamente.
$response = Http::get('https://api.example.com/users/999');

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

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

Sollevare eccezioni

throw() solleva Illuminate\Http\Client\RequestException in caso di errore.
$response = Http::post('https://api.example.com/users', $data)->throw();

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

$response = Http::post('https://api.example.com/orders', $data);
$response->throwUnlessStatus(201);
throw() restituisce la Response per la concatenazione.
$user = Http::post('https://api.example.com/users', $data)
    ->throw()
    ->json();
Gestione dell’eccezione:
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) {
    logger()->error('API error', ['status' => $e->response->status()]);
}

Richieste parallele

Per chiamare più API in parallelo usa 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();
Molto più veloce rispetto all’esecuzione sequenziale. Utile in dashboard che chiamano più API.

Test

Mock con Http::fake()

Nei test usa Http::fake() per simulare le risposte senza chiamate reali.
use Illuminate\Support\Facades\Http;

Http::fake();

$response = Http::get('https://api.example.com/users');
$response->successful(); // true
Mock per URL specifici:
Http::fake([
    'api.example.com/users/*' => Http::response(['id' => 1, 'name' => 'Mario Rossi'], 200),
    'api.example.com/posts/*' => Http::response(['error' => 'Not Found'], 404),
    '*' => Http::response('OK', 200),
]);
Sequenze di risposte:
Http::fake([
    'api.example.com/*' => Http::sequence()
        ->push(['id' => 1], 200)
        ->push(['id' => 2], 200)
        ->pushStatus(429),
]);

Verifica delle richieste

Con Http::assertSent() verifichi il contenuto delle richieste.
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;

Http::fake();

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

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

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

Http::assertSentCount(1);
Chiama sempre Http::fake() all’inizio del test. Con Http::preventStrayRequests() fai in modo che le richieste non mockate lancino eccezioni.

Prevenire richieste sfuggite

Http::preventStrayRequests();

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

Http::get('https://other.example.com/endpoint'); // Lancia eccezione

Esempio pratico: classe di servizio per chiamate API

1

Crea la classe di servizio

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

    public function getUser(string $username): array
    {
        return Http::baseUrl($this->baseUrl)
            ->withToken($this->token)
            ->acceptJson()
            ->timeout(10)
            ->get("/users/{$username}")
            ->throw()
            ->json();
    }

    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

Registralo nel service provider

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

Usalo nel controller

<?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' => 'Impossibile connettersi alla GitHub API.'], 503);
        } catch (RequestException $e) {
            $status = $e->response->status();
            if ($status === 404) {
                return response()->json(['error' => 'Utente non trovato.'], 404);
            }
            return response()->json(['error' => 'Errore della GitHub API.'], 502);
        }
    }
}
4

Scrivi i 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_ottiene_utente(): 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_utente_non_trovato_lancia_eccezione(): void
    {
        $this->expectException(RequestException::class);

        $this->service->getUser('notfound');
    }
}
MetodoUso
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)Header
->timeout($seconds)Timeout
->retry($times, $sleep)Retry
->throw()Eccezione su errore
Http::fake()Mock per test
Http::pool($callback)Parallelo
MetodoDescrizione
successful()2xx
failed()4xx+
clientError()4xx
serverError()5xx
ok()200
created()201
notFound()404
unauthorized()401
forbidden()403
unprocessableEntity()422
tooManyRequests()429
  • Raccogli la logica HTTP in una service class
  • Imposta sempre timeout (timeout() e connectTimeout())
  • Configura retry per errori transitori (retry())
  • Nei test usa Http::fake() per evitare chiamate reali
  • Aggiungi Http::preventStrayRequests() nel setup dei test
  • Gestisci token e credenziali con variabili d’ambiente e config/services.php
Ultima modifica il 13 luglio 2026