#Введение
Laravel предоставляет выразительный, минималистичный API поверх HTTP клиента Guzzle, позволяющий быстро выполнять исходящие HTTP-запросы для взаимодействия с другими веб-приложениями. Обёртка Laravel вокруг Guzzle ориентирована на наиболее распространённые сценарии использования и удобство разработчика.
Перед началом работы убедитесь, что пакет Guzzle установлен как зависимость вашего приложения. По умолчанию Laravel автоматически включает эту зависимость. Однако, если вы ранее удаляли пакет, вы можете установить его снова через Composer:
composer require guzzlehttp/guzzle
#Выполнение запросов
Для выполнения запросов вы можете использовать методы head, get, post, put, patch и delete, предоставляемые фасадом Http. Сначала рассмотрим, как сделать базовый GET запрос к другому URL:
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) : array|mixed;
$response->object() : object;
$response->collect($key = null) : Illuminate\Support\Collection;
$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 клиент также позволяет строить URL запросов с использованием спецификации URI шаблонов. Чтобы определить параметры URL, которые могут быть расширены вашим URI шаблоном, используйте метод withUrlParameters:
Http::withUrlParameters([
'endpoint' => 'https://laravel.com',
'page' => 'docs',
'version' => '9.x',
'topic' => 'validation',
])->get('{+endpoint}/{page}/{version}/{topic}');
#Вывод запросов
Если вы хотите вывести исходящий запрос перед отправкой и завершить выполнение скрипта, добавьте метод 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,
]);
Альтернативно можно использовать метод 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',
]);
#Отправка необработанного тела запроса
Вы можете использовать метод withBody, если хотите передать необработанное тело запроса. Тип содержимого указывается вторым аргументом метода:
$response = Http::withBody(
base64_encode($photo), 'image/jpeg'
)->post('http://example.com/photo');
#Многочастные запросы
Если вы хотите отправить файлы в многочастном запросе, вызовите метод 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. Метод 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');
Для удобства можно использовать метод 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',
]);
#Аутентификация
Вы можете указать учётные данные для базовой и digest-аутентификации с помощью методов withBasicAuth и withDigestAuth соответственно:
// Базовая аутентификация...
$response = Http::withBasicAuth('taylor@laravel.com', 'secret')->post(/* ... */);
// Digest-аутентификация...
$response = Http::withDigestAuth('taylor@laravel.com', 'secret')->post(/* ... */);
#Токены Bearer
Если вы хотите быстро добавить токен bearer в заголовок Authorization запроса, используйте метод withToken:
$response = Http::withToken('token')->post(/* ... */);
#Таймаут
Метод timeout позволяет указать максимальное количество секунд ожидания ответа. По умолчанию HTTP клиент ждёт 30 секунд:
$response = Http::timeout(3)->get(/* ... */);
Если время ожидания превышено, будет выброшено исключение Illuminate\Http\Client\ConnectionException.
Метод connectTimeout позволяет указать максимальное время ожидания подключения к серверу:
$response = Http::connectTimeout(3)->get(/* ... */);
#Повторы
Если вы хотите, чтобы HTTP-клиент автоматически повторял запрос при возникновении ошибки клиента или сервера, используйте метод retry. Метод retry принимает максимальное количество попыток запроса и число миллисекунд, которое Laravel должен ждать между попытками:
$response = Http::retry(3, 100)->post(/* ... */);
Если вы хотите самостоятельно вычислять время ожидания между попытками, передайте замыкание вторым аргументом методу retry:
use Exception;
$response = Http::retry(3, function (int $attempt, Exception $exception) {
return $attempt * 100;
})->post(/* ... */);
Для удобства можно передать массив первым аргументом методу retry. Этот массив будет использоваться для определения времени ожидания между попытками:
$response = Http::retry([100, 200])->post(/* ... */);
Если нужно, вы можете передать третий аргумент в метод retry. Третий аргумент должен быть callable, который определяет, стоит ли действительно пытаться выполнить повторные попытки. Например, вы можете захотеть повторять запрос только в том случае, если первоначальный запрос столкнулся с ConnectionException:
use Exception;
use Illuminate\Http\Client\PendingRequest;
$response = Http::retry(3, 100, function (Exception $exception, PendingRequest $request) {
return $exception instanceof ConnectionException;
})->post(/* ... */);
Если попытка запроса не удалась, вы можете изменить запрос перед новой попыткой, модифицируя аргумент запроса, переданный в callable метода retry. Например, можно повторить запрос с новым токеном авторизации, если первая попытка вернула ошибку аутентификации:
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(/* ... */);
Если все запросы завершаются неудачей из-за проблем с соединением, будет выброшено Illuminate\Http\Client\ConnectionException даже при установленном в false аргументе throw.
#Обработка ошибок
В отличие от поведения Guzzle по умолчанию, обёртка HTTP-клиента Laravel не выбрасывает исключения при клиентских или серверных ошибках (ответы серверов с кодами уровня 400 и 500). Можно проверить, вернул ли сервер одну из таких ошибок, с помощью методов successful, clientError или serverError:
// Проверить, что код статуса >= 200 и < 300...
$response->successful();
// Проверить, что код статуса >= 400...
$response->failed();
// Проверить, что код статуса 400 уровня...
$response->clientError();
// Проверить, что код статуса 500 уровня...
$response->serverError();
// Немедленно выполнить переданный callback при ошибке клиента или сервера...
$response->onError(callable $callback);
#Генерация исключений
Если у вас есть экземпляр ответа и вы хотите выбросить исключение Illuminate\Http\Client\RequestException, если код статуса указывает на ошибку клиента или сервера, используйте методы throw или throwIf:
use Illuminate\Http\Client\Response;
$response = Http::post(/* ... */);
// Выбросить исключение при ошибке клиента или сервера...
$response->throw();
// Выбросить исключение при ошибке и если условие истинно...
$response->throwIf($condition);
// Выбросить исключение при ошибке и если замыкание возвращает true...
$response->throwIf(fn (Response $response) => true);
// Выбросить исключение при ошибке и если условие ложно...
$response->throwUnless($condition);
// Выбросить исключение при ошибке и если замыкание возвращает false...
$response->throwUnless(fn (Response $response) => false);
// Выбросить исключение, если ответ имеет конкретный код статуса...
$response->throwIfStatus(403);
// Выбросить исключение, если ответ не имеет конкретный код статуса...
$response->throwUnlessStatus(200);
return $response['user']['id'];
Экземпляр Illuminate\Http\Client\RequestException содержит публичное свойство $response, позволяющее проанализировать возвращённый ответ.
Метод throw возвращает экземпляр ответа, если ошибки не произошло, что позволяет цепочечно вызывать другие методы после 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();
#Промежуточное ПО Guzzle
Поскольку HTTP клиент Laravel основан на 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');
#Глобальное промежуточное ПО
Иногда нужно зарегистрировать промежуточное ПО, которое будет применяться ко всем исходящим запросам и входящим ответам. Для этого используйте методы globalRequestMiddleware и globalResponseMiddleware. Обычно эти методы вызываются в методе boot вашего AppServiceProvider:
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. Метод 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();
#Настройка параллельных запросов
Метод pool нельзя цепочечно вызывать с другими методами HTTP клиента, такими как withHeaders или middleware. Чтобы применить заголовки или промежуточное ПО к запросам в пуле, настройте эти параметры для каждого запроса отдельно:
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 клиент Laravel позволяет определять «макросы» — удобный и выразительный способ конфигурировать общие пути запросов и заголовки при взаимодействии с сервисами в приложении. Для начала определите макрос в методе boot класса App\Providers\AppServiceProvider вашего приложения:
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('/');
#Тестирование
Многие сервисы Laravel предоставляют функциональность для удобного и выразительного написания тестов, и HTTP клиент Laravel не исключение. Метод fake фасада Http позволяет заставить HTTP клиент возвращать поддельные ответы при выполнении запросов.
#Подмена ответов
Например, чтобы заставить HTTP клиент возвращать пустые ответы с кодом 200 для всех запросов, вызовите метод fake без аргументов:
use Illuminate\Support\Facades\Http;
Http::fake();
$response = Http::post(/* ... */);
#Подмена для конкретных URL
Альтернативно можно передать массив в метод fake. Ключи массива — шаблоны URL, для которых нужно подменить ответы, а значения — соответствующие ответы. Символ * используется как подстановочный знак. Запросы к URL, не попавшим под подмену, будут выполнены реально. Для создания поддельных ответов используйте метод response фасада Http:
Http::fake([
// Подмена JSON-ответа для GitHub...
'github.com/*' => Http::response(['foo' => 'bar'], 200, $headers),
// Подмена строкового ответа для Google...
'google.com/*' => Http::response('Hello World', 200, $headers),
]);
Если хотите указать шаблон по умолчанию для всех непопавших под подмену URL, используйте одиночный символ *:
Http::fake([
// Подмена JSON-ответа для GitHub...
'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());
#Callback для подмены
Если требуется более сложная логика выбора ответов для определённых эндпоинтов, можно передать замыкание в метод fake. Оно получит экземпляр Illuminate\Http\Client\Request и массив опций, и должно вернуть экземпляр ответа. Внутри замыкания можно реализовать любую необходимую логику:
use Illuminate\Http\Client\Request;
Http::fake(function (Request $request, array $options) {
return Http::response('Hello World', 200);
});
#Предотвращение лишних запросов
Если хотите убедиться, что все запросы, отправленные через HTTP клиент, были подменены в рамках отдельного теста или всего тестового набора, вызовите метод preventStrayRequests. После этого любые запросы без соответствующей подмены вызовут исключение вместо реального HTTP-запроса:
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::assertSent после Http::fake.
Метод assertSent принимает замыкание, которое получает экземпляр Illuminate\Http\Client\Request и должно вернуть булево значение, указывающее, соответствует ли запрос ожиданиям. Для успешного прохождения теста должен быть хотя бы один запрос, удовлетворяющий условию:
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 для сбора всех запросов и соответствующих им ответов. Метод recorded возвращает коллекцию массивов, содержащих экземпляры Illuminate\Http\Client\Request и 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();
[$request, $response] = $recorded[0];
Кроме того, метод recorded принимает замыкание, которое получает экземпляры Illuminate\Http\Client\Request и Illuminate\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();
});
#События
Laravel генерирует три события в процессе отправки HTTP запросов. Событие RequestSending вызывается перед отправкой запроса, ResponseReceived — после получения ответа, а ConnectionFailed — если ответ не получен.
События RequestSending и ConnectionFailed содержат публичное свойство $request, которое можно использовать для просмотра экземпляра Illuminate\Http\Client\Request. Аналогично, событие ResponseReceived содержит свойства $request и $response, которые можно использовать для просмотра экземпляра Illuminate\Http\Client\Response. Вы можете зарегистрировать слушателей этого события в вашем сервис-провайдере App\Providers\EventServiceProvider:
/**
* Отображение слушателей событий для приложения.
*
* @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',
],
];