메인 콘텐츠로 건너뛰기

HTTP 클라이언트란

Laravel의 HTTP 클라이언트는 Guzzle을 래핑한 사용하기 쉬운 API입니다. Http 파사드를 통해 외부의 웹 서비스나 API에의 HTTP 요청을 간결하게 기술할 수 있습니다.
use Illuminate\Support\Facades\Http;

$response = Http::get('https://api.example.com/users');
Guzzle은 사전 설치되어 있기 때문에 추가 설정 없이 바로 사용하기 시작할 수 있습니다.

기본적인 요청

GET 요청

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' => 20,
]);

POST 요청

데이터는 기본적으로 application/json으로 전송됩니다.
$response = Http::post('https://api.example.com/users', [
    'name' => '山田 太郎',
    'email' => '[email protected]',
]);

PUT / PATCH / DELETE

// PUT 요청
$response = Http::put('https://api.example.com/users/1', [
    'name' => '山田 花子',
]);

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

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

응답의 처리

Http::get() 등의 메서드는 Illuminate\Http\Client\Response 인스턴스를 반환합니다. 이 객체에는 응답을 검사하기 위한 다수의 메서드가 준비되어 있습니다.
$response = Http::get('https://api.example.com/users/1');

// 응답 바디
$response->body();        // 문자열로 취득
$response->json();        // 배열로 변환
$response->json('name');  // JSON 의 특정 키를 취득
$response->object();      // stdClass 객체로서 취득
$response->collect();     // Collection 으로서 취득

// 스테이터스
$response->status();      // 스테이터스 코드(예: 200)
$response->successful();  // 200번대라면 true
$response->failed();      // 400번대 이상이라면 true
$response->clientError(); // 400번대라면 true
$response->serverError(); // 500번대라면 true

// 자주 사용하는 스테이터스 코드의 판정
$response->ok();           // 200
$response->created();      // 201
$response->noContent();    // 204
$response->notFound();     // 404
$response->unauthorized(); // 401
$response->forbidden();    // 403
$response->unprocessableEntity(); // 422
$response->tooManyRequests();     // 429
JSON 응답은 배열 접근으로도 취득할 수 있습니다.
$name = Http::get('https://api.example.com/users/1')['name'];

요청 옵션

헤더의 설정

$response = Http::withHeaders([
    'X-Api-Version' => '2',
    'Accept-Language' => 'ja',
])->get('https://api.example.com/users');
application/json을 받아들일 것을 나타내는 경우 acceptJson()이 편리합니다.
$response = Http::acceptJson()->get('https://api.example.com/users');
브라우저에서 Laravel로 보내는 AJAX 요청의 CSRF 헤더(X-CSRF-TOKEN / X-XSRF-TOKEN)에 대해서는 CSRF 보호를 참조해 주십시오.

인증

Bearer 토큰 인증(가장 일반적):
$response = Http::withToken($token)->get('https://api.example.com/me');
Basic 인증:
$response = Http::withBasicAuth('[email protected]', 'password')
    ->get('https://api.example.com/private');

베이스 URL 의 설정

같은 호스트로의 요청이 많은 경우 baseUrl()로 정리할 수 있습니다.
$response = Http::baseUrl('https://api.example.com')
    ->withToken($token)
    ->get('/users/1');

폼 데이터의 전송

application/x-www-form-urlencoded로 전송하고 싶은 경우 asForm()을 사용합니다.
$response = Http::asForm()->post('https://api.example.com/login', [
    'username' => 'taro',
    'password' => 'secret',
]);

타임아웃

// 응답 타임아웃(기본은 30초)
$response = Http::timeout(10)->get('https://api.example.com/slow-endpoint');

// 접속 타임아웃(기본은 10초)
$response = Http::connectTimeout(5)->get('https://api.example.com/endpoint');
타임아웃을 초과한 경우 Illuminate\Http\Client\ConnectionException이 스로우됩니다. 외부 API의 호출에는 반드시 타임아웃을 설정하는 것을 권장합니다.

리트라이

일시적인 네트워크 장애나 서버 에러에 대해 자동 리트라이를 설정할 수 있습니다.
// 최대 3회, 100밀리초 간격으로 리트라이
$response = Http::retry(3, 100)->post('https://api.example.com/orders', $data);
조건부 리트라이(접속 에러일 때만 리트라이 등):
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);

에러 핸들링

수동으로 에러를 확인

Laravel의 HTTP 클라이언트는 기본적으로는 400·500번대의 응답에 대해 예외를 스로우하지 않습니다. failed()clientError() 등으로 명시적으로 확인합니다.
$response = Http::get('https://api.example.com/users/999');

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

if ($response->failed()) {
    // 에러 로그를 기록하는 등
    logger()->error('API request failed', ['status' => $response->status()]);
}

예외를 스로우

throw()를 사용하면 에러 시에 Illuminate\Http\Client\RequestException을 스로우합니다.
// 에러 응답(4xx/5xx)이면 예외를 스로우
$response = Http::post('https://api.example.com/users', $data)->throw();

// throwIf(): 조건이 true 일 때 스로우(아래는 독립된 사용 예시)
$response = Http::get('https://api.example.com/users/1');
$response->throwIf($response->status() === 422);

// throwUnlessStatus(): 지정 스테이터스 코드 이외에서 스로우(아래도 독립된 사용 예시)
$response = Http::post('https://api.example.com/orders', $data);
$response->throwUnlessStatus(201);
throw()는 응답 인스턴스를 반환하기 때문에 메서드 체인으로 쓸 수 있습니다.
$user = Http::post('https://api.example.com/users', $data)
    ->throw()
    ->json();
예외를 캐치해 처리하는 경우:
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) {
    // 4xx / 5xx 에러
    logger()->error('API error', ['status' => $e->response->status()]);
}

병행 요청

여러 API를 동시에 호출하고 싶은 경우, 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();
순서대로 실행하는 것보다 대폭 고속화할 수 있습니다. 외부 API를 여러 번 호출하는 대시보드 등에 유효합니다.

테스트

Http::fake() 에 의한 목

테스트에서는 Http::fake()를 사용해 실제 HTTP 요청을 보내지 않고 응답을 시뮬레이트합니다.
use Illuminate\Support\Facades\Http;

Http::fake();

// 모든 요청에 200을 반환
$response = Http::get('https://api.example.com/users');
$response->successful(); // true
특정 URL에 대해 응답을 설정하는 경우:
Http::fake([
    'api.example.com/users/*' => Http::response(['id' => 1, 'name' => '山田 太郎'], 200),
    'api.example.com/posts/*' => Http::response(['error' => 'Not Found'], 404),
    '*' => Http::response('OK', 200),  // 그 외의 모두
]);
응답의 시퀀스(여러 번 호출되었을 때 순서대로 응답을 반환):
Http::fake([
    // 1회째: 200, 2회째: 200, 3회째: 429 를 반환
    // 시퀀스가 소진되면 이후의 요청에서 예외가 스로우됨
    'api.example.com/*' => Http::sequence()
        ->push(['id' => 1], 200)
        ->push(['id' => 2], 200)
        ->pushStatus(429),
]);

요청의 검증

Http::assertSent()로 요청의 내용을 검증할 수 있습니다.
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;

Http::fake();

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

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

// 전송되지 않았음을 확인
Http::assertNotSent(function (Request $request) {
    return $request->url() === 'https://api.example.com/admin';
});

// 요청이 1건만 전송되었음을 확인
Http::assertSentCount(1);
테스트에서는 반드시 Http::fake()를 선두에서 호출합시다. 호출 잊음이 있으면 실제 외부 API로 요청이 날아가 버립니다. Http::preventStrayRequests()를 사용하면 페이크하지 않은 URL로의 요청에서 예외를 스로우시킬 수 있습니다.

Stray 요청의 방지

Http::preventStrayRequests();

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

// 페이크되지 않은 URL 로의 요청은 예외가 됨
Http::get('https://other.example.com/endpoint'); // 예외 스로우

실전 예시: 외부 API 를 호출하는 서비스 클래스

실제 프로젝트에서는 HTTP 클라이언트의 로직을 서비스 클래스에 정리하는 것이 모범 사례입니다.
1

서비스 클래스 작성

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

    /**
     * 사용자 정보를 취득
     *
     * @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();
    }

    /**
     * 리포지토리 목록을 취득
     *
     * @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

서비스 프로바이더에서 등록

// 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

컨트롤러에서 이용

<?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' => 'GitHub API 에 접속할 수 없었습니다.'], 503);
        } catch (RequestException $e) {
            $status = $e->response->status();
            if ($status === 404) {
                return response()->json(['error' => '사용자를 찾을 수 없습니다.'], 404);
            }
            return response()->json(['error' => 'GitHub API 에서 에러가 발생했습니다.'], 502);
        }
    }
}
4

테스트 작성

<?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_사용자_정보를_취득할_수_있다(): 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_존재하지_않는_사용자는_예외가_스로우된다(): void
    {
        $this->expectException(RequestException::class);

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

정리

메서드용도
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)커스텀 헤더
->timeout($seconds)타임아웃 설정
->retry($times, $sleep)자동 리트라이
->throw()에러 시에 예외를 스로우
Http::fake()테스트용 목
Http::pool($callback)병행 요청
메서드설명
successful()200번대
failed()400번대 이상
clientError()400번대
serverError()500번대
ok()200
created()201
notFound()404
unauthorized()401
forbidden()403
unprocessableEntity()422
tooManyRequests()429
  • HTTP 클라이언트의 로직은 서비스 클래스에 정리
  • 반드시 타임아웃을 설정(timeout()connectTimeout())
  • 일시적인 장애에 대해 리트라이를 설정(retry())
  • 테스트에서는 Http::fake()를 반드시 사용해 외부 API를 실제로 두드리지 않음
  • Http::preventStrayRequests()를 테스트의 셋업에 추가하면 안심
  • API 의 토큰이나 인증 정보는 환경 변수와 config/services.php 로 관리
마지막 수정일 2026년 7월 13일