본문으로 건너뛰기
버전: 12.x

HTTP 클라이언트 (HTTP Client)

소개

라라벨은 Guzzle HTTP 클라이언트를 감싸는 간결하고 직관적인 API를 제공합니다. 이를 통해 다른 웹 애플리케이션과 통신하기 위한 외부 HTTP 요청을 아주 빠르게 만들 수 있습니다. 라라벨의 Guzzle 감싸기(wrapper)는 가장 많이 쓰이는 사용 사례와 개발자의 편의성을 중심으로 설계되어 있습니다.

요청 보내기

요청을 보내기 위해서는 Http 파사드에서 제공하는 head, get, post, put, patch, delete 메서드를 사용할 수 있습니다. 먼저, 다른 URL로 기본적인 GET 요청을 보내는 방법을 살펴보겠습니다.

use Illuminate\Support\Facades\Http;

$response = Http::get('http://example.com');

get 메서드는 Illuminate\Http\Client\Response 인스턴스를 반환하며, 이를 통해 응답을 다양한 방식으로 확인할 수 있는 여러 메서드를 사용할 수 있습니다.

$response->body() : string;
$response->json($key = null, $default = null) : mixed;
$response->object() : object;
$response->collect($key = null) : Illuminate\Support\Collection;
$response->resource() : resource;
$response->status() : int;
$response->successful() : bool;
$response->redirect(): bool;
$response->failed() : bool;
$response->clientError() : bool;
$response->header($header) : string;
$response->headers() : array;

Illuminate\Http\Client\Response 객체는 PHP의 ArrayAccess 인터페이스도 구현하고 있기 때문에, 응답 데이터가 JSON일 경우 배열처럼 바로 접근할 수 있습니다.

return Http::get('http://example.com/users/1')['name'];

위에 소개한 응답 관련 메서드 외에도, 다음의 메서드를 사용하여 응답이 특정 상태 코드를 가지고 있는지 쉽게 확인할 수 있습니다.

$response->ok() : bool;                  // 200 OK
$response->created() : bool; // 201 Created
$response->accepted() : bool; // 202 Accepted
$response->noContent() : bool; // 204 No Content
$response->movedPermanently() : bool; // 301 Moved Permanently
$response->found() : bool; // 302 Found
$response->badRequest() : bool; // 400 Bad Request
$response->unauthorized() : bool; // 401 Unauthorized
$response->paymentRequired() : bool; // 402 Payment Required
$response->forbidden() : bool; // 403 Forbidden
$response->notFound() : bool; // 404 Not Found
$response->requestTimeout() : bool; // 408 Request Timeout
$response->conflict() : bool; // 409 Conflict
$response->unprocessableEntity() : bool; // 422 Unprocessable Entity
$response->tooManyRequests() : bool; // 429 Too Many Requests
$response->serverError() : bool; // 500 Internal Server Error

URI 템플릿

HTTP 클라이언트는 URI 템플릿 명세를 사용하여 요청 URL을 동적으로 구성할 수 있도록 지원합니다. withUrlParameters 메서드를 사용해서 URI 템플릿에서 확장할 수 있는 URL 파라미터를 정의할 수 있습니다.

Http::withUrlParameters([
'endpoint' => 'https://laravel.com',
'page' => 'docs',
'version' => '11.x',
'topic' => 'validation',
])->get('{+endpoint}/{page}/{version}/{topic}');

요청 디버깅(Dumping Requests)

요청이 실제로 전송되기 전에 요청 인스턴스를 화면에 출력하고 스크립트 실행을 중단하고 싶다면, 요청 정의의 시작 부분에 dd 메서드를 추가할 수 있습니다.

return Http::dd()->get('http://example.com');

요청 데이터

일반적으로 POST, PUT, PATCH 요청을 보낼 때는 요청 데이터도 함께 전송해야 합니다. 이들 메서드는 두 번째 인수로 데이터 배열을 받을 수 있습니다. 기본적으로 요청 데이터는 application/json 컨텐츠 타입으로 전송됩니다.

use Illuminate\Support\Facades\Http;

$response = Http::post('http://example.com/users', [
'name' => 'Steve',
'role' => 'Network Administrator',
]);

GET 요청 쿼리 파라미터

GET 요청을 할 때는 URL에 쿼리 문자열을 직접 붙여 사용할 수도 있고, get 메서드의 두 번째 인수로 key/value 쌍의 배열을 전달할 수도 있습니다.

$response = Http::get('http://example.com/users', [
'name' => 'Taylor',
'page' => 1,
]);

또는, withQueryParameters 메서드를 사용할 수도 있습니다.

Http::retry(3, 100)->withQueryParameters([
'name' => 'Taylor',
'page' => 1,
])->get('http://example.com/users')

Form URL Encoded 요청 보내기

application/x-www-form-urlencoded 타입으로 데이터를 전송하고 싶다면, 요청을 보내기 전에 asForm 메서드를 호출하면 됩니다.

$response = Http::asForm()->post('http://example.com/users', [
'name' => 'Sara',
'role' => 'Privacy Consultant',
]);

Raw 요청 바디 전송

요청을 보낼 때 raw(가공하지 않은) 형태의 요청 본문을 전송하고 싶다면, withBody 메서드를 사용할 수 있습니다. 이때 컨텐츠 타입은 두 번째 인수로 명시해야 합니다.

$response = Http::withBody(
base64_encode($photo), 'image/jpeg'
)->post('http://example.com/photo');

멀티파트(Multi-Part) 요청

파일을 첨부하여 멀티파트 요청을 보내려면, 요청 전에 attach 메서드를 사용해야 합니다. 이 메서드는 첫 번째 인자로 파일 이름, 두 번째 인자로 파일 내용을 받으며, 필요하다면 세 번째 인수로 파일의 실제 파일명, 네 번째 인수로 파일과 관련된 헤더를 지정할 수 있습니다.

$response = Http::attach(
'attachment', file_get_contents('photo.jpg'), 'photo.jpg', ['Content-Type' => 'image/jpeg']
)->post('http://example.com/attachments');

파일의 실제 내용이 아닌 스트림 리소스를 전달할 수도 있습니다.

$photo = fopen('photo.jpg', 'r');

$response = Http::attach(
'attachment', $photo, 'photo.jpg'
)->post('http://example.com/attachments');

헤더

요청에 헤더를 추가하려면 withHeaders 메서드를 사용할 수 있습니다. 이 메서드는 key/value 쌍의 배열을 인수로 받습니다.

$response = Http::withHeaders([
'X-First' => 'foo',
'X-Second' => 'bar'
])->post('http://example.com/users', [
'name' => 'Taylor',
]);

accept 메서드를 사용하면, 요청에 대해 애플리케이션이 어떤 컨텐츠 타입을 기대하는지 지정할 수 있습니다.

$response = Http::accept('application/json')->get('http://example.com/users');

더욱 간편하게, acceptJson 메서드를 사용해 application/json 타입을 바로 지정할 수도 있습니다.

$response = Http::acceptJson()->get('http://example.com/users');

withHeaders 메서드는 기존 요청 헤더에 새로운 헤더를 병합합니다. 만약 기존 헤더를 전부 다른 헤더로 교체하고 싶다면 replaceHeaders 메서드를 사용하면 됩니다.

$response = Http::withHeaders([
'X-Original' => 'foo',
])->replaceHeaders([
'X-Replacement' => 'bar',
])->post('http://example.com/users', [
'name' => 'Taylor',
]);

인증

기본 인증(Basic Authentication)과 다이제스트 인증(Digest Authentication) 정보를 각각 withBasicAuthwithDigestAuth 메서드로 지정할 수 있습니다.

// Basic authentication...
$response = Http::withBasicAuth('[email protected]', 'secret')->post(/* ... */);

// Digest authentication...
$response = Http::withDigestAuth('[email protected]', 'secret')->post(/* ... */);

Bearer 토큰

요청의 Authorization 헤더에 Bearer 토큰을 손쉽게 추가하려면, withToken 메서드를 사용하면 됩니다.

$response = Http::withToken('token')->post(/* ... */);

타임아웃

timeout 메서드는 응답을 기다리는 최대 초(second)를 지정할 수 있습니다. 기본적으로 HTTP 클라이언트는 30초 후에 시간 초과 처리합니다.

$response = Http::timeout(3)->get(/* ... */);

지정한 시간 초과를 넘기면 Illuminate\Http\Client\ConnectionException 인스턴스가 발생합니다.

서버에 연결 시도할 때 대기할 최대 초(second)를 지정하고 싶다면 connectTimeout 메서드를 사용할 수 있습니다. 기본값은 10초입니다.

$response = Http::connectTimeout(3)->get(/* ... */);

재시도

클라이언트 또는 서버 오류가 발생할 경우 HTTP 클라이언트가 요청을 자동으로 재시도하도록 하려면 retry 메서드를 사용할 수 있습니다. 이 메서드는 요청을 시도할 최대 횟수와 각 시도 사이에 라라벨이 대기할 밀리초(ms) 시간을 인수로 받습니다.

$response = Http::retry(3, 100)->post(/* ... */);

재시도 간 대기할 밀리초를 직접 계산하려면, 두 번째 인수로 클로저(closure)를 전달할 수 있습니다.

use Exception;

$response = Http::retry(3, function (int $attempt, Exception $exception) {
return $attempt * 100;
})->post(/* ... */);

편의를 위해, 첫 번째 인수로 배열을 전달할 수도 있습니다. 이 배열은 각 시도마다 얼마나 대기할지 밀리초 단위로 지정합니다.

$response = Http::retry([100, 200])->post(/* ... */);

필요하다면 세 번째 인수로 콜러블(callable)을 전달하여 실제로 재시도를 시도할지 결정할 수 있습니다. 예를 들어, 요청이 ConnectionException 오류를 만났을 때만 재시도를 원한다면 다음처럼 작성할 수 있습니다.

use Exception;
use Illuminate\Http\Client\PendingRequest;

$response = Http::retry(3, 100, function (Exception $exception, PendingRequest $request) {
return $exception instanceof ConnectionException;
})->post(/* ... */);

요청 시도가 실패한 경우, 새로운 시도를 하기 전에 요청을 변경하고 싶을 수도 있습니다. 이런 경우, retry 메서드에 전달한 콜러블에서 전달받는 request 인수를 활용하여 요청을 수정할 수 있습니다. 예를 들어, 첫 번째 요청에서 인증 오류가 발생하면 새로운 인증 토큰으로 다시 요청하도록 할 수도 있습니다.

use Exception;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\RequestException;

$response = Http::withToken($this->getToken())->retry(2, 0, function (Exception $exception, PendingRequest $request) {
if (! $exception instanceof RequestException || $exception->response->status() !== 401) {
return false;
}

$request->withToken($this->getNewToken());

return true;
})->post(/* ... */);

설정한 횟수만큼 시도를 모두 실패하면, Illuminate\Http\Client\RequestException 인스턴스가 던져집니다. 이 동작을 비활성화하고 싶다면 throw 인수를 false로 지정하면 됩니다. 비활성화하면 모든 재시도 후 마지막으로 받은 응답을 반환합니다.

$response = Http::retry(3, 100, throw: false)->post(/* ... */);

[!WARNING] 모든 요청이 연결 문제로 실패한 경우, throw 인수를 false로 설정했다 해도 Illuminate\Http\Client\ConnectionException 예외는 여전히 전달됩니다.

에러 핸들링

Guzzle의 기본 동작과 다르게, 라라벨의 HTTP 클라이언트 래퍼는 클라이언트/서버 오류(서버에서 400번대, 500번대 응답)가 발생할 때 예외를 던지지 않습니다. 이러한 오류가 발생했는지는 successful, clientError, serverError 메서드를 사용해 확인할 수 있습니다.

// 상태 코드가 200 이상 300 미만인지 확인...
$response->successful();

// 상태 코드가 400 이상인지 확인...
$response->failed();

// 응답이 400번대 상태 코드인지 확인...
$response->clientError();

// 응답이 500번대 상태 코드인지 확인...
$response->serverError();

// 클라이언트나 서버 에러가 있을 때 지정한 콜백을 즉시 실행...
$response->onError(callable $callback);

예외 던지기

응답 인스턴스가 있고, 응답 상태 코드가 클라이언트 또는 서버 오류임을 나타낼 경우 Illuminate\Http\Client\RequestException 예외를 던지려면, throw 또는 throwIf 메서드를 사용할 수 있습니다.

use Illuminate\Http\Client\Response;

$response = Http::post(/* ... */);

// 클라이언트 혹은 서버 오류가 발생하면 예외를 던집니다.
$response->throw();

// 오류가 발생했고, 주어진 조건이 true일 때 예외를 던집니다.
$response->throwIf($condition);

// 오류가 발생했고, 주어진 클로저가 true를 반환하면 예외를 던집니다.
$response->throwIf(fn (Response $response) => true);

// 오류가 발생했고, 주어진 조건이 false일 때 예외를 던집니다.
$response->throwUnless($condition);

// 오류가 발생했고, 주어진 클로저가 false를 반환하면 예외를 던집니다.
$response->throwUnless(fn (Response $response) => false);

// 응답이 특정 상태 코드일 때 예외를 던집니다.
$response->throwIfStatus(403);

// 응답이 특정 상태 코드가 아닐 때 예외를 던집니다.
$response->throwUnlessStatus(200);

return $response['user']['id'];

Illuminate\Http\Client\RequestException 인스턴스에는 반환된 응답을 확인할 수 있는 public $response 속성이 있습니다.

throw 메서드는 오류가 없는 경우에는 response 인스턴스를 그대로 반환하므로, throw 메서드 이후에 다른 작업을 체이닝해서 사용할 수 있습니다.

return Http::post(/* ... */)->throw()->json();

예외가 던져지기 전에 추가적으로 처리할 작업이 있다면, throw 메서드에 클로저를 전달할 수도 있습니다. 클로저 실행 후에는 예외가 자동으로 던져지므로, 클로저에서 직접 예외를 다시 던질 필요는 없습니다.

use Illuminate\Http\Client\Response;
use Illuminate\Http\Client\RequestException;

return Http::post(/* ... */)->throw(function (Response $response, RequestException $e) {
// ...
})->json();

기본적으로, RequestException 메시지는 로그나 리포트 시 120자까지 잘립니다. 이 동작을 변경 또는 비활성화하려면, 애플리케이션의 bootstrap/app.php 파일에서 truncateRequestExceptionsAtdontTruncateRequestExceptions 메서드를 사용할 수 있습니다.

use Illuminate\Foundation\Configuration\Exceptions;

->withExceptions(function (Exceptions $exceptions) {
// 요청 예외 메시지를 240자로 잘라서 저장하도록 설정...
$exceptions->truncateRequestExceptionsAt(240);

// 요청 예외 메시지 자르기 비활성화...
$exceptions->dontTruncateRequestExceptions();
})

Guzzle 미들웨어

라라벨의 HTTP 클라이언트는 내부적으로 Guzzle을 사용하므로, Guzzle 미들웨어를 활용하여 나가는 요청을 가공하거나 들어오는 응답을 검사할 수 있습니다. 나가는 요청을 가공하려면 withRequestMiddleware 메서드로 미들웨어를 등록합니다.

use Illuminate\Support\Facades\Http;
use Psr\Http\Message\RequestInterface;

$response = Http::withRequestMiddleware(
function (RequestInterface $request) {
return $request->withHeader('X-Example', 'Value');
}
)->get('http://example.com');

들어오는 HTTP 응답을 검사하고 싶을 때는 withResponseMiddleware 메서드를 이용하여 미들웨어를 등록할 수 있습니다.

use Illuminate\Support\Facades\Http;
use Psr\Http\Message\ResponseInterface;

$response = Http::withResponseMiddleware(
function (ResponseInterface $response) {
$header = $response->getHeader('X-Example');

// ...

return $response;
}
)->get('http://example.com');

전역 미들웨어

모든 나가는 요청 및 들어오는 응답에 항상 적용되는 미들웨어를 등록하고 싶을 때도 있습니다. 이럴 때는 globalRequestMiddlewareglobalResponseMiddleware 메서드를 사용할 수 있습니다. 보통 이 메서드들은 애플리케이션의 AppServiceProviderboot 메서드에서 호출하는 것이 좋습니다.

use Illuminate\Support\Facades\Http;

Http::globalRequestMiddleware(fn ($request) => $request->withHeader(
'User-Agent', 'Example Application/1.0'
));

Http::globalResponseMiddleware(fn ($response) => $response->withHeader(
'X-Finished-At', now()->toDateTimeString()
));

Guzzle 옵션

나가는 요청에 추가적인 Guzzle 요청 옵션이 필요하다면, withOptions 메서드를 사용하면 됩니다. 이 메서드는 key/value 쌍의 배열을 인수로 받습니다.

$response = Http::withOptions([
'debug' => true,
])->get('http://example.com/users');

전역 옵션

모든 나가는 요청에 기본 옵션을 지정하려면, globalOptions 메서드를 활용할 수 있습니다. 이는 마찬가지로 애플리케이션의 AppServiceProviderboot 메서드에서 호출하는 것이 일반적입니다.

use Illuminate\Support\Facades\Http;

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Http::globalOptions([
'allow_redirects' => false,
]);
}

동시 요청

여러 HTTP 요청을 동시에 보내고 싶을 때가 종종 있습니다. 즉, 여러 요청을 차례로 보내는 대신 한 번에 동시에 여러 요청을 발송하고 싶을 때 입니다. 이렇게 하면 느린 HTTP API와 상호작용할 때 성능이 크게 향상될 수 있습니다.

이럴 때는 pool 메서드를 사용하면 됩니다. pool 메서드는 클로저를 인수로 받아, 클로저 안에서 Illuminate\Http\Client\Pool 인스턴스를 통해 쉽게 여러 요청을 pool에 추가할 수 있습니다.

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

$responses = Http::pool(fn (Pool $pool) => [
$pool->get('http://localhost/first'),
$pool->get('http://localhost/second'),
$pool->get('http://localhost/third'),
]);

return $responses[0]->ok() &&
$responses[1]->ok() &&
$responses[2]->ok();

위 예시처럼, 각각의 응답 인스턴스는 요청이 pool에 추가된 순서대로 배열 인덱스로 접근할 수 있습니다. 원한다면 as 메서드로 요청에 이름을 붙여, 응답도 해당 이름으로 바로 접근할 수 있습니다.

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

$responses = Http::pool(fn (Pool $pool) => [
$pool->as('first')->get('http://localhost/first'),
$pool->as('second')->get('http://localhost/second'),
$pool->as('third')->get('http://localhost/third'),
]);

return $responses['first']->ok();

동시 요청 커스터마이징

pool 메서드는 withHeadersmiddleware 같은 다른 HTTP 클라이언트 메서드와 체이닝해서 사용할 수 없습니다. pool에 추가하는 각 요청에서 직접 옵션을 설정해야 합니다.

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

$headers = [
'X-Example' => 'example',
];

$responses = Http::pool(fn (Pool $pool) => [
$pool->withHeaders($headers)->get('http://laravel.test/test'),
$pool->withHeaders($headers)->get('http://laravel.test/test'),
$pool->withHeaders($headers)->get('http://laravel.test/test'),
]);

매크로

라라벨 HTTP 클라이언트는 "매크로"를 정의할 수 있습니다. 매크로는 서비스와 상호작용할 때 자주 사용하는 요청 경로나 헤더 설정을 손쉽게 구성할 수 있는 유연하고 직관적인 방법을 제공합니다. 먼저, 애플리케이션의 App\Providers\AppServiceProvider 클래스의 boot 메서드 안에서 매크로를 정의할 수 있습니다.

use Illuminate\Support\Facades\Http;

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Http::macro('github', function () {
return Http::withHeaders([
'X-Example' => 'example',
])->baseUrl('https://github.com');
});
}

이제 매크로가 설정되었으므로, 애플리케이션 어디서든 해당 매크로를 호출해 지정한 설정으로 요청을 만들 수 있습니다.

$response = Http::github()->get('/');

테스트 (Testing)

많은 라라벨 서비스에서는 테스트를 쉽고 명확하게 작성할 수 있도록 다양한 기능을 제공합니다. 라라벨의 HTTP 클라이언트 역시 예외는 아닙니다. Http 파사드의 fake 메서드를 사용하면, 실제 요청 대신 임의의(더미) 응답을 반환하도록 HTTP 클라이언트의 동작을 변경할 수 있습니다.

응답을 가짜로 만들기

예를 들어, 모든 요청에 대해 내용이 비어 있고, 상태 코드가 200인 응답을 반환하도록 하려면 fake 메서드를 별도의 인수 없이 호출하면 됩니다.

use Illuminate\Support\Facades\Http;

Http::fake();

$response = Http::post(/* ... */);

특정 URL에 대해 가짜 응답 만들기

또는, fake 메서드에 배열을 전달할 수도 있습니다. 배열의 키에는 가짜 응답을 지정할 URL 패턴을, 값에는 반환할 응답을 지정하세요. * 문자는 와일드카드로 사용될 수 있습니다. 지정하지 않은(미가짜화) URL로의 요청은 실제로 실행됩니다. 각 엔드포인트에 대해 Http 파사드의 response 메서드를 활용해 가짜 응답을 만들 수 있습니다.

Http::fake([
// GitHub 엔드포인트를 위한 JSON 응답 더미...
'github.com/*' => Http::response(['foo' => 'bar'], 200, $headers),

// Google 엔드포인트를 위한 문자열 응답 더미...
'google.com/*' => Http::response('Hello World', 200, $headers),
]);

미가짜화된 모든 URL에도 가짜 응답을 적용하는 기본값 패턴을 지정하고 싶다면, *만 전달하면 됩니다.

Http::fake([
// GitHub 엔드포인트를 위한 JSON 응답 더미...
'github.com/*' => Http::response(['foo' => 'bar'], 200, ['Headers']),

// 다른 모든 엔드포인트에 대한 문자열 응답 더미...
'*' => Http::response('Hello World', 200, ['Headers']),
]);

간편하게 문자열, JSON, 빈 응답을 만들 때에는 응답값에 문자열, 배열, 정수(상태 코드)만 전달해도 됩니다.

Http::fake([
'google.com/*' => 'Hello World',
'github.com/*' => ['foo' => 'bar'],
'chatgpt.com/*' => 200,
]);

예외를 가짜로 만들기

HTTP 클라이언트가 요청을 시도하다가 Illuminate\Http\Client\ConnectionException을 만났을 때 애플리케이션의 동작을 테스트하고 싶을 때가 있습니다. 이런 경우, failedConnection 메서드를 사용하면 HTTP 클라이언트가 연결 예외를 던지도록 할 수 있습니다.

Http::fake([
'github.com/*' => Http::failedConnection(),
]);

만약 Illuminate\Http\Client\RequestException이 발생하는 상황을 테스트하고 싶다면 failedRequest 메서드를 사용하면 됩니다.

Http::fake([
'github.com/*' => Http::failedRequest(['code' => 'not_found'], 404),
]);

연속된 응답을 순서대로 가짜로 만들기

때로는 하나의 URL에 대해 여러 개의 가짜 응답을 순서대로 반환하도록 지정해야 할 수 있습니다. 이 경우 Http::sequence 메서드를 사용해 응답 시퀀스를 만들 수 있습니다.

Http::fake([
// GitHub 엔드포인트에 대한 연속된 가짜 응답들...
'github.com/*' => Http::sequence()
->push('Hello World', 200)
->push(['foo' => 'bar'], 200)
->pushStatus(404),
]);

시퀀스에 포함된 모든 응답이 소진되면, 이후 요청은 예외를 발생시킵니다. 시퀀스가 비었을 때 반환할 기본 응답을 지정하고 싶다면 whenEmpty 메서드를 사용할 수 있습니다.

Http::fake([
// GitHub 엔드포인트에 대한 연속된 가짜 응답들...
'github.com/*' => Http::sequence()
->push('Hello World', 200)
->push(['foo' => 'bar'], 200)
->whenEmpty(Http::response()),
]);

특정 URL 패턴에 관계없이 간단히 연속된 가짜 응답 시퀀스를 지정하고 싶다면 Http::fakeSequence를 사용할 수 있습니다.

Http::fakeSequence()
->push('Hello World', 200)
->whenEmpty(Http::response());

가짜 콜백

특정 엔드포인트마다 반환할 응답을 더 복잡한 로직으로 동적으로 판단해야 한다면, fake 메서드에 클로저를 전달하세요. 이 클로저는 Illuminate\Http\Client\Request 인스턴스를 받아서 응답 인스턴스를 반환해야 합니다. 클로저 내에서 어떤 응답을 반환할지 자유롭게 로직을 작성할 수 있습니다.

use Illuminate\Http\Client\Request;

Http::fake(function (Request $request) {
return Http::response('Hello World', 200);
});

예기치 않은 실제 요청 방지하기

테스트 코드 전체 또는 개별 테스트 내에서 HTTP 클라이언트로 보내는 모든 요청이 반드시 가짜 응답을 사용했는지 보장하려면 preventStrayRequests 메서드를 사용할 수 있습니다. 이 메서드가 호출된 이후로, 가짜 응답이 지정되지 않은 요청은 실제로 전송되지 않고 예외를 발생시킵니다.

use Illuminate\Support\Facades\Http;

Http::preventStrayRequests();

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

// "ok" 응답이 반환됩니다...
Http::get('https://github.com/laravel/framework');

// 예외가 발생합니다...
Http::get('https://laravel.com');

요청 검사하기

가짜 응답을 사용하는 동안에도, 애플리케이션이 정상적으로 데이터나 헤더를 보내고 있는지 확인하고 싶을 수 있습니다. 이때 Http::fake 이후 Http::assertSent를 호출하여 요청을 검사할 수 있습니다.

assertSent 메서드는 클로저를 인수로 받으며, 이 클로저에는 Illuminate\Http\Client\Request 인스턴스가 전달됩니다. 클로저에서는 요청이 기대와 일치하는지 판단하는 코드를 작성해 true 또는 false를 반환하면 됩니다. 하나 이상의 요청이라도 이 조건에 부합하면 테스트는 통과하게 됩니다.

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

Http::fake();

Http::withHeaders([
'X-First' => 'foo',
])->post('http://example.com/users', [
'name' => 'Taylor',
'role' => 'Developer',
]);

Http::assertSent(function (Request $request) {
return $request->hasHeader('X-First', 'foo') &&
$request->url() == 'http://example.com/users' &&
$request['name'] == 'Taylor' &&
$request['role'] == 'Developer';
});

특정 요청이 전송되지 않았는지 검증하고 싶다면, assertNotSent 메서드를 사용할 수 있습니다.

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

Http::fake();

Http::post('http://example.com/users', [
'name' => 'Taylor',
'role' => 'Developer',
]);

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

테스트 동안 "전송"된 요청의 개수를 검증할 때는 assertSentCount 메서드를 사용할 수 있습니다.

Http::fake();

Http::assertSentCount(5);

또한, 테스트 중에 아무 요청도 전송되지 않았는지 검증하려면 assertNothingSent를 사용할 수 있습니다.

Http::fake();

Http::assertNothingSent();

요청 / 응답 기록하기

recorded 메서드를 활용하면 모든 요청과 해당하는 응답을 한 번에 모아 확인할 수 있습니다. 이 메서드는 Illuminate\Http\Client\RequestIlluminate\Http\Client\Response 인스턴스가 들어 있는 배열들의 컬렉션을 반환합니다.

Http::fake([
'https://laravel.com' => Http::response(status: 500),
'https://nova.laravel.com/' => Http::response(),
]);

Http::get('https://laravel.com');
Http::get('https://nova.laravel.com/');

$recorded = Http::recorded();

[$request, $response] = $recorded[0];

또한, recorded 메서드에 클로저를 전달하여 요청/응답 쌍 중에서 원하는 조건에 맞는 것만 필터링할 수도 있습니다. 이 클로저는 각각 Illuminate\Http\Client\RequestIlluminate\Http\Client\Response 인스턴스를 전달받습니다.

use Illuminate\Http\Client\Request;
use Illuminate\Http\Client\Response;

Http::fake([
'https://laravel.com' => Http::response(status: 500),
'https://nova.laravel.com/' => Http::response(),
]);

Http::get('https://laravel.com');
Http::get('https://nova.laravel.com/');

$recorded = Http::recorded(function (Request $request, Response $response) {
return $request->url() !== 'https://laravel.com' &&
$response->successful();
});

이벤트 (Events)

라라벨은 HTTP 요청을 전송하는 과정에서 세 가지 이벤트를 발생시킵니다. 요청을 실제로 보내기 전에 RequestSending 이벤트가 발생하며, 요청에 대한 응답을 받은 뒤에는 ResponseReceived 이벤트가 발생합니다. 만약 요청에 대해 응답을 받지 못했다면 ConnectionFailed 이벤트가 발생합니다.

RequestSendingConnectionFailed 이벤트에는 모두 Illuminate\Http\Client\Request 인스턴스를 담고 있는 public $request 속성이 포함되어 있습니다. 마찬가지로, ResponseReceived 이벤트에는 $request 속성뿐만 아니라 Illuminate\Http\Client\Response 인스턴스를 담고 있는 $response 속성도 있습니다. 이러한 이벤트들에 대해 이벤트 리스너를 애플리케이션 내에서 만들 수 있습니다.

use Illuminate\Http\Client\Events\RequestSending;

class LogRequest
{
/**
* 이벤트를 처리합니다.
*/
public function handle(RequestSending $event): void
{
// $event->request ...
}
}