Бывает так, что в проект не хочется тащить мощный и тяжелый HTTP-клиент, а хочется взять что-то легкое, но его функционала не достаточно. Для решения таких компромиссов у меня есть небольшие классы декораторы, которые я опубликовал на всеобщее обозрение под лицензией MIT.
Смена версии протокола
К нам пришёл HTTP/2, но не каждый сервер, как и не каждый клиент
его поддерживает. Если попробовать отправить запрос, принудительно
указав версию протокола 2, можно получить от сервера ошибку
505 HTTP Version Not Supported
. Пакет webclient/ext-protocol-version
решает эту, возможно надуманную, проблему. При получении ответа 505
клиент повторит запрос, но уже с версией протокола, указанной в
ответе сервера.
<?phpuse Webclient\Extension\ProtocolVersion\Client;use Psr\Http\Client\ClientInterface;use Psr\Http\Message\RequestInterface;/** * @var ClientInterface $client Ваш PSR-18 совместимый HTTP Client. */$http = new Client($client);/** @var RequestInterface $request */$response = $http->sendRequest($request);
Редиректы
Очень редко, но встречается, когда клиент не умеет следовать
редиректам при ответе с кодом 3xx
. В этом случае
поможет пакет webclient/ext-redirect. Тут
всё элементарно, Передаём в конструктор наш клиент и максимальное
количество допустимых редиректов на один запрос.
<?phpuse Webclient\Extension\Redirect\Client;use Psr\Http\Client\ClientInterface;use Psr\Http\Message\RequestInterface;/** * @var ClientInterface $client Ваш PSR-18 совместимый HTTP Client. * @var int $maxRedirects Максимальное количество допустимых редиректов. */$http = new Client($client, $maxRedirects);/** @var RequestInterface $request */$response = $http->sendRequest($request);
Логирование
Иногда нам необходимо логировать запросы и ответы. Пакет
webclient/ext-log позволяет
настроить логирование так, как этого требует проект. Помимо
клиента, вам понадобится PSR-3 совместимый логгер.
Для формирования строки лога используется интерфейс
Webclient\Extension\Log\Formatter\Formatter
, а для
формирования ID запроса (для поиска в логах пары запрос-ответ) -
Webclient\Extension\Log\IdGenerator\IdGenerator
. По
одной реализации этих интерфейсов поставляется из коробки:
-
Webclient\Extension\Log\IdGenerator\UniqueIdGenerator
- элементарный генератор на основеuniqid()
-
Webclient\Extension\Log\Formatter\RawHttpFormatter
- логирование запросов и ответов в виде RAW-текста.
<?phpuse Psr\Http\Client\ClientInterface;use Psr\Http\Message\RequestInterface;use Psr\Log\LoggerInterface;use Psr\Log\LogLevel;use Webclient\Extension\Log\Client;use Webclient\Extension\Log\Formatter\Formatter;use Webclient\Extension\Log\IdGenerator\IdGenerator;/** * @var ClientInterface $client Ваш PSR-18 совместимый HTTP Client. * @var LoggerInterface $logger Ваш PSR-3 совместимый логгер. * @var IdGenerator|null $idGenerator Ваш ID-генератор. * При передаче null будет использоваться * Webclient\Extension\Log\IdGenerator\UniqueIdGenerator. * @var Formatter|null $formatter Ваш форматировщик лога. * При передаче null будет использоваться * Webclient\Extension\Log\Formatter\RawHttpFormatter. */$http = new Client( $client, $logger, $idGenerator, $formatter, LogLevel::INFO, // Уровень логирования запросов LogLevel::INFO, // Уровень логирования информационных ответов (Коды 1xx) LogLevel::INFO, // Уровень логирования успешных ответов (Коды 2xx) LogLevel::INFO, // Уровень лоигрования ответов с редиректом (Коды 3xx) LogLevel::ERROR, // Уровень логирования ответов об ошибках клиента (Коды 4xx) LogLevel::ERROR, // Уровень логирования ответов об ошибках сервера (Коды 5xx) LogLevel::WARNING // Уровень логирования исключений HTTP клиента);/** @var RequestInterface $request */$response = $http->sendRequest($request);
Куки
Бывают проекты, в которых не достаточно просто отправить запрос
и получить ответ. Иногда нужно поддерживать сессию (ну или ещё
что-то). Чтобы добавить вашему клиенту поддержку печенек, нужно
отнаследоваться от абстрактного класса
Webclient\Extension\Cookie\Cookie\Storage
из пакета
webclient/ext-cookie, либо
воспользоваться одной из поставляемых с пакетом реализацией:
-
Webclient\Extension\Cookie\Cookie\ArrayStorage
- держит куки в памяти. Слетает после завершения скрипта; -
Webclient\Extension\Cookie\Cookie\NetscapeCookieFile
- хранит куки в файле в соответствии с форматом Netscape.
<?phpuse Psr\Http\Client\ClientInterface;use Psr\Http\Message\RequestInterface;use Webclient\Extension\Cookie\Client;use Webclient\Extension\Cookie\Cookie\Storage;/** * @var ClientInterface $client Ваш PSR-18 совместимый HTTP Client. * @var Storage $storage Хранилище куков. * Вы можете отнаследоваться от этого класса для реализации своего хранилища. */$http = new Client($client, $storage);/** @var RequestInterface $request */$response = $http->sendRequest($request);
Кэширование
О кэшировании, его плюсах и минусах сказано уже много. Если вам
хочется разгрузить какой-то из своих редко обновляемых
микросервисов (и нагрузить кэш), поможет пакет webclient/ext-cache. Чтобы
завернуть в него свой клиент, вам понадобится реализация
Psr\SimpleCache\CacheInterface
из PSR-6,
Psr\Http\Message\ResponseFactoryInterface
и
Psr\Http\Message\StreamFactoryInterface
из PSR-18.
Кэширование происходит на основе соответствующих заголовков HTTP-запросов и ответов.
<?phpuse Psr\Http\Client\ClientInterface;use Psr\Http\Message\RequestInterface;use Psr\Http\Message\ResponseFactoryInterface;use Psr\Http\Message\StreamFactoryInterface;use Psr\SimpleCache\CacheInterface;use Webclient\Extension\Cache\Client;/** * @var ClientInterface $client Ваш PSR-18 совместимый HTTP Client. * @var CacheInterface $cache Ваш PSR-6 совместимый кэш. * @var ResponseFactoryInterface $responseFactory * Ваша PSR-17 совместимая фабрика ответов. * @var StreamFactoryInterface $streamFactory * Ваша PSR-17 совместимая фабрика потоков. * @var string Строка, уникальная для приватного кэша (например, ID сессии). */$http = new Client( $client, $cache, $responseFactory, $streamFactory, $privateKey);/** @var RequestInterface $request */$response = $http->sendRequest($request);
На этом расширения для клиентов заканчиваются, но статья продолжается. У меня есть ещё парочка инструментов, которые могут кому-то облегчить жизнь.
Создание запросов с файлами
Как известно, в PSR-7 есть два вида запросов - обычный запрос и
серверный запрос. Они оба реализуют интерфейс
Psr\Http\Message\RequestInterface
(Psr\Http\Message\ServerRequestInterface
его
расширяет). При отправке файлов на сервер мы не можем просто взять
серверный запрос, выполнить его метод
withUploadedFiles($files)
и передать полученный объект
в HTTP-клиент. Для того, чтобы клиент корректно отправил запрос с
файлами, эти файлы должны быть записаны в поток тела запроса.
Библиотека webclient/helper-form
предназначена для упрощения создания таких запросов. Для работы вам
понадобятся реализации интерфейсов
Psr\Http\Message\ResponseFactoryInterface
и
Psr\Http\Message\StreamFactoryInterface
.
фейковый клиент
В проектах бывает необходимость запросов к сервисам, которые не желательно дергать лишний раз (например, платные API), но код, использующий такие обращения, тестировать нужно.
Для такого тестирования можно воспользоваться webclient/fake-http-client,
который является реализацией
Psr\Http\Client\ClientInterface
, но под капотом вместо
запроса к серверу вызывает
Psr\Http\Server\RequestHandlerInterface
из PSR-15
(преобразовав при необходимости
Psr\Http\Message\RequestInterface
в
Psr\Http\Message\ServerRequestInterface
). Реализация
Psr\Http\Server\RequestHandlerInterface
остаётся за
вами - эмулируйте поведение, как вам нужно для тестирования.
<?phpuse Webclient\Fake\Client;use Psr\Http\Message\RequestInterface;use Psr\Http\Server\RequestHandlerInterface;/** * @var RequestHandlerInterface $handler Ваш обработчик запроса. * @var array $serverParams Параметры сервера, которые будут добавлены * при преобразовании из Psr\Http\Message\RequestInterface в * Psr\Http\Message\ServerRequestInterface. */$client = new Client($handler, $serverParams);/** * @var RequestInterface $request Ваш HTTP-запрос */$response = $client->sendRequest($request);
Если вы передаете объект
Psr\Http\Message\ServerRequestInterface
клиенту и
хотите, чтобы обработчик получил его как есть, добавьте атрибут
Webclient\Fake\Client::NOREPLACEATTRIBUTE
(иначе будет
создан новый объект запроса).
<?phpuse Webclient\Fake\Client;use Psr\Http\Server\RequestHandlerInterface;/** * @var Client $client. * @var ServerRequestInterface $request. */$request = $request->withAttribute(Client::NOREPLACEATTRIBUTE, true);$response = $client->sendRequest($request);
Чтобы хоть чуть-чуть упростить вам написание обработчика, в
пакете поставляется класс
Webclient\Fake\Handler\SimpleRoutingHandler
-
обработчик с примитивным роутингом.
<?phpuse Webclient\Fake\Client;use Webclient\Fake\Handler\SimpleRoutingHandler;use Psr\Http\Message\RequestInterface;use Psr\Http\Server\RequestHandlerInterface;/** * @var RequestHandlerInterface $notFoundHandler Обработчик запросов, * для которых не нашлось роута. * @var RequestHandlerInterface $entityCreatedHandler Обработчик запросов * для эмуляции созания сущности (POST /entities). * @var RequestHandlerInterface $entityHandler Обработчик запросов * для получения сущности (GET /entities/1). * @var RequestHandlerInterface $entityDeletedHandler обработчик запросов * для удаления сущности (DELETE /entities/2). * @var RequestInterface $errorRequest Запрос несуществующего URI (GET /users). * @var RequestInterface $entityCreatingRequest Запрос создания * сущности (POST /entities). * @var RequestInterface $entityRequest Запрос получения * сущности (GET /entities/1). * @var RequestInterface $entityDeletingRequest Запрос удаления * сущности (DELETE /entities/2). */$handler = new SimpleRoutingHandler($notFoundHandler);$handler ->route(['GET', 'HEAD'], '/entities/1', $entityHandler) ->route(['POST'], '/entities', $entityCreatedHandler) ->route(['DELETE'], '/entities/2', $entityDeletedHandler);$client = new Client($handler);$resp1 = $client->sendRequest($errorRequest); // Вернёт ошибку 404$resp2 = $client->sendRequest($entityCreatingRequest); // Вернёт ответс кодом 201$resp3 = $client->sendRequest($entityRequest); // Вернёт ответ с кодом 200$resp4 = $client->sendRequest($entityDeletingRequest); // Вернёт ответ с кодом 204
Надеюсь, вы не зря потратили время на прочтение этой статьи и кому-то приглянутся предоставленные инструменты. Спасибо за внимание!