Client Common
The client-common package provides some useful tools for working with HTTPlug. Include them in your project with composer:
composer require php-http/client-common
HttpMethodsClient
This client wraps the HttpClient and provides convenience methods for common HTTP requests like GET
and POST
.
To be able to do that, it also wraps a message factory:
use Http\Discovery\HttpClientDiscovery;
use Http\Discovery\MessageFactoryDiscovery;
$client = new HttpMethodsClient(
HttpClientDiscovery::find(),
MessageFactoryDiscovery::find()
);
$foo = $client->get('http://example.com/foo');
$bar = $client->get('http://example.com/bar', ['accept-encoding' => 'application/json']);
$post = $client->post('http://example.com/update', [], 'My post body');
- ..versionadded:: 2.0
HttpMethodsClient
is final since version 2.0. You can typehint theHttpMethodsClientInterface
to allow mocking the client in unit tests.
BatchClient
This client wraps a HttpClient and extends it with the possibility to send an array of requests and to retrieve
their responses as a BatchResult
:
use Http\Discovery\HttpClientDiscovery;
use Http\Discovery\MessageFactoryDiscovery;
$messageFactory = MessageFactoryDiscovery::find();
$requests = [
$messageFactory->createRequest('GET', 'http://example.com/foo'),
$messageFactory->createRequest('POST', 'http://example.com/update', [], 'My post body'),
];
$client = new BatchClient(
HttpClientDiscovery::find()
);
$batchResult = $client->sendRequests($requests);
- ..versionadded:: 2.0
BatchClient
is final since version 2.0. You can typehint theBatchClientInterface
to allow mocking the client in unit tests.
The BatchResult
itself is an object that contains responses for all requests sent.
It provides methods that give appropriate information based on a given request:
$requests = [
$messageFactory->createRequest('GET', 'http://example.com/foo'),
$messageFactory->createRequest('POST', 'http://example.com/update', [], 'My post body'),
];
$batchResult = $client->sendRequests($requests);
if ($batchResult->hasResponses()) {
$fooSuccessful = $batchResult->isSuccessful($requests[0]);
$updateResponse = $batchResult->getResponseFor($request[1]);
}
If one or more of the requests throw exceptions, they are added to the
BatchResult
and the BatchClient
will ultimately throw a
BatchException
containing the BatchResult
and therefore its exceptions:
$requests = [
$messageFactory->createRequest('GET', 'http://example.com/update'),
];
try {
$batchResult = $client->sendRequests($requests);
} catch (BatchException $e) {
var_dump($e->getResult()->getExceptions());
}
PluginClient
HttpClientPool
The HttpClientPool
allows to balance requests between a pool of HttpClient
and/or HttpAsyncClient
.
The use cases are:
Using a cluster (like an Elasticsearch service with multiple master nodes)
Using fallback servers with the combination of the
RetryPlugin
(see Retry Plugin)
You can attach HTTP clients to this kind of client by using the addHttpClient
method:
use Http\Client\Common\HttpClientPool\LeastUsedClientPool;
use Http\Discovery\HttpAsyncClientDiscovery;
use Http\Discovery\HttpClientDiscovery;
use Http\Discovery\MessageFactoryDiscovery;
$messageFactory = MessageFactoryDiscovery::find();
$httpClient = HttpClientDiscovery::find();
$httpAsyncClient = HttpAsyncClientDiscovery::find();
$httpClientPool = new LeastUsedClientPool();
$httpClientPool->addHttpClient($httpClient);
$httpClientPool->addHttpClient($httpAsyncClient);
$httpClientPool->sendRequest($messageFactory->createRequest('GET', 'http://example.com/update'));
Clients added to the pool are decorated with the HttpClientPoolItem
class unless they already are an instance of this class.
The pool item class lets the pool be aware of the number of requests currently being processed by that client.
It is also used to deactivate clients when they receive errors.
Deactivated clients can be reactivated after a certain amount of time, however, by default, they stay deactivated forever.
To enable the behavior, wrap the clients with the HttpClientPoolItem
class yourself and specify the re-enable timeout:
// Reactivate after 30 seconds
$httpClientPool->addHttpClient(new HttpClientPoolItem($httpClient, 30));
// Reactivate after each call
$httpClientPool->addHttpClient(new HttpClientPoolItem($httpClient, 0));
// Never reactivate the client (default)
$httpClientPool->addHttpClient(new HttpClientPoolItem($httpClient, null));
HttpClientPool
is an interface. There are three concrete implementations with specific strategies on how to choose clients:
LeastUsedClientPool
LeastUsedClientPool
choose the client with the fewest requests in progress. As it sounds the best strategy for
sending a request on a pool of clients, this strategy has some limitations: :
The counter is not shared between PHP process, so this strategy is not so useful in a web context, however it will make better sense in a PHP command line context.
This pool only makes sense with asynchronous clients. If you use
sendRequest
, the call is blocking, and the pool will only ever use the first client as its request count will be 0 oncesendRequest
finished.
A deactivated client will be removed for the pool until it is reactivated, if none are available it will throw a
NotFoundHttpClientException
RoundRobinClientPool
RoundRobinClientPool
keeps an internal pointer on the pool. At each call the pointer is moved to the next client, if
the current client is disabled it will move to the next client, and if none are available it will throw a
NotFoundHttpClientException
The pointer is not shared across PHP processes, so for each new one it will always start on the first client.
RandomClientPool
RandomClientPool
randomly choose an available client, throw a NotFoundHttpClientException
if none are available.
HTTP Client Router
This client accepts pairs of clients and request matchers.
Every request is “routed” through the HttpClientRouter
, checked against the request matchers
and sent using the first matched client. If there is no matching client, an exception is thrown.
This allows a single client to be used for different requests.
In the following example we use the client router to access an API protected by basic auth and also to download an image from a static host:
use Http\Client\Common\HttpClientRouter;
use Http\Client\Common\PluginClient;
use Http\Client\Common\Plugin\AuthenticationPlugin;
use Http\Client\Common\Plugin\CachePlugin;
use Http\Discovery\HttpClientDiscovery;
use Http\Discovery\MessageFactoryDiscovery;
use Http\Message\Authentication\BasicAuth;
use Http\Message\RequestMatcher\RequestMatcher;
$client = new HttpClientRouter();
$requestMatcher = new RequestMatcher(null, 'api.example.com');
$pluginClient = new PluginClient(
HttpClientDiscovery::find(),
[new AuthenticationPlugin(new BasicAuth('user', 'password'))]
);
$client->addClient($pluginClient, $requestMatcher);
$requestMatcher = new RequestMatcher(null, 'images.example.com');
/** @var \Psr\Cache\CacheItemPoolInterface $pool */
$pool = ...
/** @var \Http\Message\StreamFactory $streamFactory */
$streamFactory = ...
$pluginClient = new PluginClient(
HttpClientDiscovery::find(),
[new CachePlugin($pool, $streamFactory)]
);
$client->addClient($pluginClient, $requestMatcher);
$messageFactory = MessageFactoryDiscovery::find();
// Get the user data
$request = $messageFactory->createRequest('GET', 'https://api.example.com/user/1');
$response = $client->send($request);
$imagePath = json_decode((string) $response->getBody(), true)['image_path'];
// Download the image and store it in cache
$request = $messageFactory->createRequest('GET', 'https://images.example.com/user/'.$imagePath);
$response = $client->send($request);
file_put_contents('path/to/images/'.$imagePath, (string) $response->getBody());
$request = $messageFactory->createRequest('GET', 'https://api2.example.com/user/1');
// Throws an Http\Client\Exception\RequestException
$client->send($request);
Note
When you have small difference between the underlying clients (for example different credentials based on host)
it’s easier to use the RequestConditionalPlugin
and the PluginClient
,
but in that case the routing logic is integrated into the linear request flow
which might make debugging harder.
- ..versionadded:: 2.0
HttpClientRouter
is final since version 2.0. You can typehint theHttpClientRouterInterface
to allow mocking the client in unit tests.