Skip to content

Commit

Permalink
[HttpClient] Async HTTPlug client
Browse files Browse the repository at this point in the history
  • Loading branch information
Nyholm authored and nicolas-grekas committed Oct 4, 2019
1 parent 732c034 commit 1444872
Show file tree
Hide file tree
Showing 4 changed files with 394 additions and 33 deletions.
2 changes: 1 addition & 1 deletion src/Symfony/Component/HttpClient/CHANGELOG.md
Expand Up @@ -5,7 +5,7 @@ CHANGELOG
-----

* added `StreamWrapper`
* added `HttplugClient`
* added `HttplugClient` with support for sync and async requests
* added `max_duration` option
* added support for NTLM authentication
* added `$response->toStream()` to cast responses to regular PHP streams
Expand Down
120 changes: 88 additions & 32 deletions src/Symfony/Component/HttpClient/HttplugClient.php
Expand Up @@ -11,74 +11,120 @@

namespace Symfony\Component\HttpClient;

use Http\Client\Exception\NetworkException;
use Http\Client\Exception\RequestException;
use Http\Client\HttpClient;
use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient as HttplugInterface;
use Http\Message\RequestFactory;
use Http\Message\StreamFactory;
use Http\Message\UriFactory;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Client\RequestExceptionInterface;
use Http\Promise\Promise;
use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7\Request;
use Nyholm\Psr7\Uri;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Component\HttpClient\Response\HttplugPromise;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

if (!interface_exists(HttpClient::class)) {
if (!interface_exists(HttplugInterface::class)) {
throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/httplug" package is not installed. Try running "composer require php-http/httplug".');
}

if (!interface_exists(ClientInterface::class)) {
throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "psr/http-client" package is not installed. Try running "composer require psr/http-client".');
}

if (!interface_exists(RequestFactory::class)) {
throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/message-factory" package is not installed. Try running "composer require nyholm/psr7".');
}

/**
* An adapter to turn a Symfony HttpClientInterface into an Httplug client.
*
* Run "composer require psr/http-client" to install the base ClientInterface. Run
* "composer require nyholm/psr7" to install an efficient implementation of response
* Run "composer require nyholm/psr7" to install an efficient implementation of response
* and stream factories with flex-provided autowiring aliases.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class HttplugClient implements HttpClient, RequestFactory, StreamFactory, UriFactory
final class HttplugClient implements HttplugInterface, HttpAsyncClient, RequestFactory, StreamFactory, UriFactory
{
private $client;
private $responseFactory;
private $streamFactory;

public function __construct(HttpClientInterface $client = null, ResponseFactoryInterface $responseFactory = null, StreamFactoryInterface $streamFactory = null)
{
$this->client = new Psr18Client($client, $responseFactory, $streamFactory);
$this->client = $client ?? HttpClient::create();
$this->responseFactory = $responseFactory;
$this->streamFactory = $streamFactory ?? ($responseFactory instanceof StreamFactoryInterface ? $responseFactory : null);

if (null !== $this->responseFactory && null !== $this->streamFactory) {
return;
}

if (!class_exists(Psr17Factory::class)) {
throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been provided. Try running "composer require nyholm/psr7".');
}

$psr17Factory = new Psr17Factory();
$this->responseFactory = $this->responseFactory ?? $psr17Factory;
$this->streamFactory = $this->streamFactory ?? $psr17Factory;
}

/**
* {@inheritdoc}
*/
public function sendRequest(RequestInterface $request): ResponseInterface
{
return $this->sendAsyncRequest($request)->wait();
}

/**
* {@inheritdoc}
*
* @return HttplugPromise
*/
public function sendAsyncRequest(RequestInterface $request): Promise
{
try {
return $this->client->sendRequest($request);
} catch (RequestExceptionInterface $e) {
$body = $request->getBody();

if ($body->isSeekable()) {
$body->seek(0);
}

$response = $this->client->request($request->getMethod(), (string) $request->getUri(), [
'headers' => $request->getHeaders(),
'body' => $body->getContents(),
'http_version' => '1.0' === $request->getProtocolVersion() ? '1.0' : null,
'buffer' => true, // Make sure the response body can be read many times
]);
} catch (\InvalidArgumentException $e) {
throw new RequestException($e->getMessage(), $request, $e);
} catch (NetworkExceptionInterface $e) {
throw new NetworkException($e->getMessage(), $request, $e);
} catch (TransportExceptionInterface $e) {
$response = new NetworkException($e->getMessage(), $request, $e);
}

return new HttplugPromise($request, $response, $this->client, $this->responseFactory, $this->streamFactory);
}

/**
* {@inheritdoc}
*/
public function createRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1'): RequestInterface
{
$request = $this->client
->createRequest($method, $uri)
if ($this->responseFactory instanceof RequestFactoryInterface) {
$request = $this->responseFactory->createRequest($method, $uri);
} elseif (!class_exists(Request::class)) {
throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__));
} else {
$request = new Request($method, $uri);
}

$request = $request
->withProtocolVersion($protocolVersion)
->withBody($this->createStream($body))
;
Expand All @@ -100,27 +146,37 @@ public function createStream($body = null): StreamInterface
}

if (\is_string($body ?? '')) {
$body = $this->client->createStream($body ?? '');

if ($body->isSeekable()) {
$body->seek(0);
}

return $body;
$stream = $this->streamFactory->createStream($body ?? '');
} elseif (\is_resource($body)) {
$stream = $this->streamFactory->createStreamFromResource($body);
} else {
throw new \InvalidArgumentException(sprintf('%s() expects string, resource or StreamInterface, %s given.', __METHOD__, \gettype($body)));
}

if (\is_resource($body)) {
return $this->client->createStreamFromResource($body);
if ($stream->isSeekable()) {
$stream->seek(0);
}

throw new \InvalidArgumentException(sprintf('%s() expects string, resource or StreamInterface, %s given.', __METHOD__, \gettype($body)));
return $stream;
}

/**
* {@inheritdoc}
*/
public function createUri($uri = ''): UriInterface
public function createUri($uri): UriInterface
{
return $uri instanceof UriInterface ? $uri : $this->client->createUri($uri);
if ($uri instanceof UriInterface) {
return $uri;
}

if ($this->responseFactory instanceof UriFactoryInterface) {
return $this->responseFactory->createUri($uri);
}

if (!class_exists(Uri::class)) {
throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__));
}

return new Uri($uri);
}
}

0 comments on commit 1444872

Please sign in to comment.