Httplug — абстрагирование от клиента HTTP для PHP
В прошлом году PHP-FIG приняла стандарт PSR-7, описывающий работу с сообщениями HTTP. Хорошая статья об этом стандарте и его применении была на Хабре. И хотя PSR-7 — большой шаг вперёд, ему не хватает логичного продолжения — общего интерфейса клиентов HTTP. Созданием недостающего компонента занялась группа PHP-HTTP.
Проблема
Используя интерфейсы PSR-7 вы можете абстрагироваться от конкретной реализации запросов и ответов HTTP. Но только до тех пор, пока вам не понадобится сделать запрос самостоятельно. Тут вам придётся по-прежнему жёстко привязываться к каким-либо реализациям. При написании приложения это естественно. При написании библиотеки — совсем нехорошо. Например, наш сайт взаимодействует с пятью сторонними службами при помощи официальных и неофициальных библиотек и SDK. И каждая из них использует собственного клиента HTTP. Кто-то Guzzle, кто-то cURL, кто-то просто file_get_contents. Пять разных клиентов HTTP в одном приложении! У каждого свои особенности, свои ограничения, свои возможности настройки. Было бы здорово заменить этот зоопарк одним единственным клиентом, который использовался бы всеми компонентами приложения и библиотеками?
Httplug
Основная разработка группы — набор интерфейсов Httplug, позволяющий библиотекам абстрагироваться от конкретного клиента HTTP, используемого в приложении. Уже есть несколько реализаций клиентов (сокеты, cURL) и адаптеров (Guzzle, React). Кроме того в рамках проекта разработано множество вспомогательных пакетов, включая пакет для Symfony.
И что же это всё даёт разработчикам?
Применение в библиотеках
Если вы пишете библиотеку, которая должна выполнять запросы HTTP, у вас больше нет необходимости привязываться к конкретному клиенту. Вместо этого в ''composer.json'' можно указать:
{
"require": {
"php-http/client-implementation": "^1.0"
},
"require-dev": {
"php-http/curl-client": "^1.4"
}
}
php-http/client-implementation
указывает, что вашей библиотеке требуется клиент HTTP, php-http/curl-client
— любая на выбор реализация, которую можно будет использовать во время отладки и которая подтянет все необходимые для разработки интерфейсы и классы. Для отладки также может потребоваться реализация PSR-7, например guzzlehttp/psr7.
Предположим, что ваша библиотека должна работать с неким API, и главный компонент — клиент этого API:
class ApiClient
{
/**
* Клиент HTTP.
*/
private $httpClient;
/**
* Фабрика запросов HTTP.
*/
private $requestFactory;
public function __construct(HttpClient $httpClient, RequestFactory $requestFactory)
{
$this->httpClient = $httpClient;
$this->requestFactory = $requestFactory;
}
Здесь:
- HttpClient — собственно, клиент HTTP (пакет php-http/httplug);
- RequestFactory — фабрика, позволяющая создавать запросы PSR-7 (пакет php-http/message-factory).
Теперь, когда вам надо сделать запрос, можно написать что-то такое:
/**
* @param string $uri
* @param string $payload тело запроса
*/
public function apiCall($uri, $payload)
{
$request = $this->requestFactory->createRequest('POST', $uri, ['content-type' => 'foo/bar'], $payload);
$response = $this->httpClient->sendRequest($request);
// ...
}
Вот, по большому счёту, и всё, что требуется. Теперь ваша библиотека не зависит от конкретного клиента, и её пользователь сможет выбрать тот, который ему больше подходит. Посмотрим, как это делается.
Использование в приложении
Для использования описанной выше библиотеки разработчику приложения понадобится выбрать одну из реализаций клиента, реализацию PSR-7 и подключить их вместе с вашей библиотекой:
$ composer require php-http/guzzle6-adapter
$ composer require ваша/библиотека
guzzle6-adapter
автоматически подтянет guzzlehttp/psr7
, поэтому отдельно его указывать необязательно.
Ещё потребуется php-http/message в качестве моста к guzzlehttp/psr7
:
$ composer require php-http/message
Далее нужно создать клиента, адаптер, фабрику запросов и передать два последних в конструктор ApiClient:
use GuzzleHttp\Client as GuzzleClient;
use Http\Adapter\Guzzle6\Client as GuzzleAdapter;
use Http\Message\MessageFactory\GuzzleMessageFactory;
$config = [
// ...
];
$guzzle = new GuzzleClient($config);
$adapter = new GuzzleAdapter($guzzle);
$apiClient = new ApiClient($adapter, new GuzzleMessageFactory);
ApiClient готов к работе. Этот же объект ($adapter) можно передать во все компоненты, которым нужен клиент HTTP.
Если в будущем возникнет надобность заменить Guzzle на что-то другое, то переписать потребуется только вот эту часть кода. Весь остальной код, работающий с HTTP, трогать не придётся.
Что ещё есть интересного?
Полностью с имеющимися пакетами и их возможностями можно ознакомиться в официальной документации, я же отмечу только некоторые интересные на мой взгляд.
Асинхронные запросы
Специальный интерфейс HttpAsyncClient позволяет выполнять запросы асинхронно, используя механизм обещаний.
Автообнаружение реализаций (Discovery)
Система автообнаружения на основе Puli позволяет получать объекты клиента и фабрик без привязки к конкретным реализациям:
$httpClient = HttpClientDiscovery::find();
Система расширений (плагинов)
Позволяет добавлять сквозную функциональность ко всем или только некоторым клиентам HTTP. Вот некоторые примеры:
- кэширование;
- куки;
- преобразование статусов 4xx и 5xx в исключения;
- управление заголовками;
- журналирование;
HttplugBundle
Пакет для Symfony, включающий поддержку нескольких клиентов, расширений и отладочной панели Symfony.
В заключение
Проект находится в начале своего пути, но уже достаточно стабилен для использования в боевых условиях. Ребята ставят перед собой амбициозную цель — добиться принятия их разработок в качестве рекомендации PSR.