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

HTTP 클라이언트 (HTTP Client)

소개

라라벨은 Guzzle HTTP 클라이언트 위에 표현적이며 최소한의 API를 제공합니다. 이를 통해 다른 웹 애플리케이션과 통신할 때 빠르게 외부 HTTP 요청을 보낼 수 있습니다. 라라벨의 Guzzle 래퍼는 가장 흔히 사용하는 기능들에 중점을 두고, 개발자 경험이 탁월하도록 설계되어 있습니다.

시작하기 전에, Guzzle 패키지가 애플리케이션의 의존성으로 설치되어 있는지 확인해야 합니다. 기본적으로 라라벨에는 이 의존성이 자동으로 포함되어 있습니다. 하지만 만약 이전에 이 패키지를 제거한 적이 있다면, 다음과 같이 Composer를 통해 다시 설치할 수 있습니다.

composer require guzzlehttp/guzzle

요청 보내기

요청을 보내려면 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) : array|mixed;
$response->object() : object;
$response->collect($key = null) : Illuminate\Support\Collection;
$response->status() : int;
$response->ok() : bool;
$response->successful() : bool;
$response->redirect(): bool;
$response->failed() : bool;
$response->serverError() : 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'];

요청 디버깅(dump)하기

요청이 전송되기 전에 해당 요청 인스턴스를 덤프하고 스크립트 실행을 즉시 종료하고 싶다면, 요청 정의의 처음에 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 메서드의 두 번째 인수로 키/값 쌍 배열을 전달할 수 있습니다.

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

Form URL 인코딩 방식으로 요청 보내기

만약 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'
)->post('http://example.com/attachments');

파일의 raw 데이터를 전달하는 대신, 스트림 리소스를 전달하는 것도 가능합니다.

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

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

헤더

요청에 헤더를 추가하려면 withHeaders 메서드를 사용합니다. 이 메서드는 키/값 쌍의 배열을 받습니다.

$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');

간편하게 application/json 콘텐츠 타입을 기대한다고 지정하려면, acceptJson 메서드를 사용할 수 있습니다.

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

인증

Basic 인증과 Digest 인증을 각각 withBasicAuth, withDigestAuth 메서드를 사용해 지정할 수 있습니다.

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

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

Bearer 토큰

요청의 Authorization 헤더에 Bearer 토큰을 빠르게 추가하고 싶다면, withToken 메서드를 사용합니다.

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

타임아웃

timeout 메서드를 통해 응답을 기다리는 최대 초(sec)를 지정할 수 있습니다.

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

만약 지정한 시간 내에 응답을 받지 못하면, Illuminate\Http\Client\ConnectionException 예외가 발생합니다.

재시도

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

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

필요하다면, 세 번째 인수로 콜러블을 전달할 수 있습니다. 세 번째 인수는 실제로 재시도를 할지 결정하는 콜백입니다. 예를 들어, 최초 요청에서 ConnectionException이 발생할 때만 재시도하도록 할 수 있습니다.

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

모든 시도가 실패한다면, Illuminate\Http\Client\RequestException 예외가 발생합니다.

에러 처리

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 메서드를 사용할 수 있습니다.

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

// 클라이언트 또는 서버 오류 시 예외 발생...
$response->throw();

// 오류가 발생하고, 주어진 조건이 true면 예외 발생...
$response->throwIf($condition);

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

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

throw 메서드는 오류가 없으면 응답 인스턴스를 반환하므로, throw 이후에 다른 메서드를 체이닝할 수 있습니다.

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

예외가 발생하기 전에 추가적인 로직을 실행하고 싶다면, 클로저를 throw 메서드에 전달할 수 있습니다. 이 경우, 클로저 실행 후 예외가 자동으로 발생하기 때문에 직접 예외를 다시 던질 필요는 없습니다.

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

Guzzle 옵션

추가적인 Guzzle 요청 옵션이 필요할 경우, withOptions 메서드를 사용하면 됩니다. 이 메서드는 키/값 쌍의 배열을 받습니다.

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

동시 요청

여러 개의 HTTP 요청을 동시에 보내야 할 때가 있습니다. 즉, 여러 요청을 순차적으로 처리하는 대신 한 번에 동시에 보냅니다. 이를 통해 반응이 느린 HTTP API와 통신할 때 성능이 크게 개선될 수 있습니다.

이럴 때는 pool 메서드를 사용하면 됩니다. pool 메서드는 Illuminate\Http\Client\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();

각 응답 인스턴스는 풀에 추가된 순서대로 접근할 수 있습니다. 또한 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();

매크로

라라벨 HTTP 클라이언트는 "매크로"를 정의할 수 있게 하여, 서비스와 상호작용할 때 자주 사용하는 요청 경로와 헤더를 손쉽게 구성할 수 있도록 지원합니다. 먼저, 애플리케이션의 App\Providers\AppServiceProvider 클래스의 boot 메서드 안에 매크로를 정의하세요.

use Illuminate\Support\Facades\Http;

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

매크로 설정을 완료했다면, 애플리케이션 어디에서든 지정한 설정으로 대기 중인(pending) 요청을 다음과 같이 만들 수 있습니다.

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

테스트

라라벨의 다양한 서비스들은 테스트 작성을 보다 쉽고 표현적으로 할 수 있게 도와주는 기능을 제공합니다. HTTP 래퍼 역시 예외는 아닙니다. Http 파사드의 fake 메서드를 이용하면, 요청 시 미리 준비된(stub) 또는 더미(dummmy) 응답을 반환하도록 지정할 수 있습니다.

응답을 가짜로 만들기

예를 들어, HTTP 클라이언트가 모든 요청마다 비어 있는 200 상태 코드 응답을 반환하도록 하려면, fake 메서드에 인수를 주지 않고 호출하면 됩니다.

use Illuminate\Support\Facades\Http;

Http::fake();

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

[!NOTE] 요청을 가짜로 만들면, HTTP 클라이언트의 미들웨어는 실행되지 않습니다. 가짜 응답에 대한 기대값을 정의할 때 이 미들웨어들이 정상적으로 동작했다고 가정하여 테스트를 작성해야 합니다.

특정 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을 위한 fallback URL 패턴을 지정하고 싶다면, 단일 * 문자를 사용할 수 있습니다.

Http::fake([
// GitHub 엔드포인트에 대한 JSON 응답 스텁...
'github.com/*' => Http::response(['foo' => 'bar'], 200, ['Headers']),

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

응답 시퀀스를 가짜로 지정하기

특정 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 콜백

특정 엔드포인트에 대해 반환할 응답을 동적으로 결정해야 하는 더 복잡한 로직이 필요하다면, fake 메서드에 클로저를 전달할 수 있습니다. 이 클로저는 Illuminate\Http\Client\Request 인스턴스를 인수로 받고, 응답 인스턴스를 반환해야 합니다. 클로저 내부에서 원하는 모든 로직을 실행할 수 있습니다.

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

요청 검사하기

가짜 응답을 만드는 도중, 클라이언트가 받은 요청을 검사해 애플리케이션이 올바른 데이터 또는 헤더를 보내는지 확인하고 싶을 수 있습니다. 이를 위해 Http::fake 호출 이후 Http::assertSent 메서드를 사용할 수 있습니다.

assertSent 메서드는 클로저를 받으며, 이 클로저는 Illuminate\Http\Client\Request 인스턴스를 인수로 받고, 요청이 기대에 부합하면 true를 반환하면 됩니다. 적어도 하나의 요청이 기대에 맞으면 테스트가 통과합니다.

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();

이벤트

라라벨은 HTTP 요청 전송 과정에서 세 가지 이벤트를 발생시킵니다. RequestSending 이벤트는 요청이 전송되기 전에 발생하고, ResponseReceived 이벤트는 주어진 요청에 대한 응답을 받은 후 발생합니다. 요청에 응답을 받지 못할 경우에는 ConnectionFailed 이벤트가 발생합니다.

RequestSendingConnectionFailed 이벤트 모두 요청 인스턴스를 검사할 수 있도록 public $request 속성을 포함합니다. ResponseReceived 이벤트는 $request뿐만 아니라 $response 속성도 포함하여, Illuminate\Http\Client\Response 인스턴스를 검사할 수 있습니다. 이 이벤트에 대한 리스터는 애플리케이션의 App\Providers\EventServiceProvider 서비스 프로바이더에서 등록할 수 있습니다.

/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
'Illuminate\Http\Client\Events\RequestSending' => [
'App\Listeners\LogRequestSending',
],
'Illuminate\Http\Client\Events\ResponseReceived' => [
'App\Listeners\LogResponseReceived',
],
'Illuminate\Http\Client\Events\ConnectionFailed' => [
'App\Listeners\LogConnectionFailed',
],
];