HTTP Client
Introduction
Laravel은 Guzzle HTTP client 위에 표현적이며 최소한의 API를 제공합니다. 이를 통해 다른 웹 애플리케이션과 통신할 때 빠르게 외부 HTTP 요청을 보낼 수 있습니다. Laravel의 Guzzle 래퍼는 가장 흔히 사용하는 기능들에 중점을 두고, 개발자 경험이 탁월하도록 설계되어 있습니다.
시작하기 전에, Guzzle 패키지가 애플리케이션의 의존성으로 설치되어 있는지 확인해야 합니다. 기본적으로 Laravel에는 이 의존성이 자동으로 포함되어 있습니다. 하지만 만약 이전에 이 패키지를 제거한 적이 있다면, 다음과 같이 Composer를 통해 다시 설치할 수 있습니다.
composer require guzzlehttp/guzzle
Making Requests
요청을 보내려면 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'];
Dumping Requests
요청이 전송되기 전에 해당 요청 인스턴스를 덤프하고 스크립트 실행을 즉시 종료하고 싶다면, 요청 정의의 처음에 dd 메서드를 추가하면 됩니다.
return Http::dd()->get('http://example.com');
Request Data
POST, PUT, PATCH 요청 시에는 추가 데이터를 함께 보내는 경우가 많습니다. 이런 메서드들은 두 번째 인수로 데이터 배열을 받을 수 있습니다. 기본적으로, 데이터는 application/json 콘텐츠 타입으로 전송됩니다.
use Illuminate\Support\Facades\Http;
$response = Http::post('http://example.com/users', [
'name' => 'Steve',
'role' => 'Network Administrator',
]);
GET Request Query Parameters
GET 요청을 할 때, 쿼리 문자열을 URL에 직접 추가하거나, get 메서드의 두 번째 인수로 키/값 쌍 배열을 전달할 수 있습니다.
$response = Http::get('http://example.com/users', [
'name' => 'Taylor',
'page' => 1,
]);
Sending Form URL Encoded Requests
만약 application/x-www-form-urlencoded 콘텐츠 타입으로 데이터를 보내고 싶다면, 요청 전에 asForm 메서드를 호출해야 합니다.
$response = Http::asForm()->post('http://example.com/users', [
'name' => 'Sara',
'role' => 'Privacy Consultant',
]);
Sending A Raw Request Body
요청을 보낼 때 raw 데이터를 직접 주고 싶다면, withBody 메서드를 사용할 수 있습니다. 두 번째 인수로 콘텐츠 타입을 지정할 수 있습니다.
$response = Http::withBody(
base64_encode($photo), 'image/jpeg'
)->post('http://example.com/photo');
Multi-Part Requests
파일을 다중 파트 요청으로 보내야 할 경우, 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');
Headers
요청에 헤더를 추가하려면 withHeaders 메서드를 사용합니다. 이 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');
Authentication
Basic 인증과 Digest 인증을 각각 withBasicAuth, withDigestAuth 메서드를 사용해 지정할 수 있습니다.
// Basic authentication...
// Digest authentication...
Bearer Tokens
요청의 Authorization 헤더에 Bearer 토큰을 빠르게 추가하고 싶다면, withToken 메서드를 사용합니다.
$response = Http::withToken('token')->post(...);
Timeout
timeout 메서드를 통해 응답을 기다리는 최대 초(sec)를 지정할 수 있습니다.
$response = Http::timeout(3)->get(...);
만약 지정한 시간 내에 응답을 받지 못하면, Illuminate\Http\Client\ConnectionException 예외가 발생합니다.
Retries
클라이언트 오류나 서버 오류가 발생했을 때 HTTP 클라이언트가 자동으로 요청을 재시도하게 하려면, retry 메서드를 사용할 수 있습니다. retry 메서드는 요청을 시도할 최대 횟수와 요청 사이에 Laravel이 대기할 시간(밀리초 단위)을 각각 받습니다.
$response = Http::retry(3, 100)->post(...);
필요하다면, retry 메서드에 세 번째 인수로 콜러블을 전달할 수 있습니다. 세 번째 인수는 실제로 재시도를 할지 결정하는 콜백입니다. 예를 들어, 최초 요청에서 ConnectionException이 발생할 때만 재시도하도록 할 수 있습니다.
$response = Http::retry(3, 100, function ($exception) {
return $exception instanceof ConnectionException;
})->post(...);
모든 시도가 실패한다면, Illuminate\Http\Client\RequestException 예외가 발생합니다.
Error Handling
Guzzle의 기본 동작과 달리, Laravel HTTP 클라이언트 래퍼는 클라이언트 또는 서버 오류(서버에서 400, 500번대 응답)에서 예외를 발생시키지 않습니다. successful, clientError, serverError 메서드를 사용해 이러한 오류가 발생했는지 확인할 수 있습니다.
// Determine if the status code is >= 200 and < 300...
$response->successful();
// Determine if the status code is >= 400...
$response->failed();
// Determine if the response has a 400 level status code...
$response->clientError();
// Determine if the response has a 500 level status code...
$response->serverError();
// Immediately execute the given callback if there was a client or server error...
$response->onError(callable $callback);
Throwing Exceptions
응답 인스턴스가 있고, 상태 코드가 클라이언트 또는 서버 오류임을 나타낸다면 Illuminate\Http\Client\RequestException 예외를 발생시키도록 throw 또는 throwIf 메서드를 사용할 수 있습니다.
$response = Http::post(...);
// Throw an exception if a client or server error occurred...
$response->throw();
// Throw an exception if an error occurred and the given condition is 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 Options
추가적인 Guzzle request options이 필요할 경우, withOptions 메서드를 사용하면 됩니다. 이 withOptions 메서드는 키/값 쌍의 배열을 받습니다.
$response = Http::withOptions([
'debug' => true,
])->get('http://example.com/users');
Concurrent Requests
여러 개의 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();
Macros
Laravel 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('/');
Testing
Laravel의 다양한 서비스들은 테스트 작성을 보다 쉽고 표현적으로 할 수 있게 도와주는 기능을 제공합니다. HTTP 래퍼 역시 예외는 아닙니다. Http 파사드의 fake 메서드를 이용하면, 요청 시 미리 준비된(stub) 또는 더미(dummmy) 응답을 반환하도록 지정할 수 있습니다.
Faking Responses
예를 들어, HTTP 클라이언트가 모든 요청마다 비어 있는 200 상태 코드 응답을 반환하도록 하려면, fake 메서드에 인수를 주지 않고 호출하면 됩니다.
use Illuminate\Support\Facades\Http;
Http::fake();
$response = Http::post(...);
요청을 가짜로 만들면, HTTP 클라이언트의 미들웨어는 실행되지 않습니다. 가짜 응답에 대한 기대값을 정의할 때 이 미들웨어들이 정상적으로 동작했다고 가정하여 테스트를 작성해야 합니다.
Faking Specific URLs
또는, fake 메서드에 배열을 전달할 수도 있습니다. 이 배열의 키는 가짜로 만들고자 하는 URL 패턴이고, 값은 해당 응답입니다. * 문자를 와일드카드로 사용할 수 있습니다. 가짜로 지정되지 않은 URL로의 요청은 실제로 실행됩니다. 이런 엔드포인트용 가짜 응답을 만들려면, Http 파사드의 response 메서드를 사용할 수 있습니다.
Http::fake([
// Stub a JSON response for GitHub endpoints...
'github.com/*' => Http::response(['foo' => 'bar'], 200, $headers),
// Stub a string response for Google endpoints...
'google.com/*' => Http::response('Hello World', 200, $headers),
]);
모든 매칭되지 않은 URL을 위한 fallback URL 패턴을 지정하고 싶다면, 단일 * 문자를 사용할 수 있습니다.
Http::fake([
// Stub a JSON response for GitHub endpoints...
'github.com/*' => Http::response(['foo' => 'bar'], 200, ['Headers']),
// Stub a string response for all other endpoints...
'*' => Http::response('Hello World', 200, ['Headers']),
]);
Faking Response Sequences
특정 URL에서 여러 개의 가짜 응답을 순서대로 반환해야 할 때가 있습니다. 이럴 때는 Http::sequence 메서드로 응답 시퀀스를 만들 수 있습니다.
Http::fake([
// Stub a series of responses for GitHub endpoints...
'github.com/*' => Http::sequence()
->push('Hello World', 200)
->push(['foo' => 'bar'], 200)
->pushStatus(404),
]);
시퀀스의 모든 응답이 소진되면, 그 이후의 요청은 예외를 발생시킵니다. 시퀀스가 비었을 때 반환할 기본 응답을 지정하고 싶다면, whenEmpty 메서드를 사용할 수 있습니다.
Http::fake([
// Stub a series of responses for GitHub endpoints...
'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 Callback
특정 엔드포인트에 대해 반환할 응답을 동적으로 결정해야 하는 더 복잡한 로직이 필요하다면, fake 메서드에 클로저를 전달할 수 있습니다. 이 클로저는 Illuminate\Http\Client\Request 인스턴스를 인수로 받고, 응답 인스턴스를 반환해야 합니다. 클로저 내부에서 원하는 모든 로직을 실행할 수 있습니다.
Http::fake(function ($request) {
return Http::response('Hello World', 200);
});
Inspecting Requests
가짜 응답을 만드는 도중, 클라이언트가 받은 요청을 검사해 애플리케이션이 올바른 데이터 또는 헤더를 보내는지 확인하고 싶을 수 있습니다. 이를 위해 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();
Events
Laravel은 HTTP 요청 전송 과정에서 세 가지 이벤트를 발생시킵니다. RequestSending 이벤트는 요청이 전송되기 전에 발생하고, ResponseReceived 이벤트는 주어진 요청에 대한 응답을 받은 후 발생합니다. 요청에 응답을 받지 못할 경우에는 ConnectionFailed 이벤트가 발생합니다.
RequestSending과 ConnectionFailed 이벤트 모두 Illuminate\Http\Client\Request 인스턴스를 검사할 수 있도록 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',
],
];