2019-06-10 12:49:36 +01:00
< ? php
/*
* This file is part of the Symfony package .
*
* ( c ) Fabien Potencier < fabien @ symfony . com >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Symfony\Component\HttpClient ;
2019-09-26 10:41:29 +01:00
use GuzzleHttp\Promise\Promise as GuzzlePromise ;
2019-06-10 12:49:36 +01:00
use Http\Client\Exception\NetworkException ;
use Http\Client\Exception\RequestException ;
2019-09-26 10:41:29 +01:00
use Http\Client\HttpAsyncClient ;
use Http\Client\HttpClient as HttplugInterface ;
2020-01-07 12:55:38 +00:00
use Http\Discovery\Exception\NotFoundException ;
2019-10-14 11:53:45 +01:00
use Http\Discovery\Psr17FactoryDiscovery ;
2019-06-10 12:49:36 +01:00
use Http\Message\RequestFactory ;
use Http\Message\StreamFactory ;
use Http\Message\UriFactory ;
2019-09-26 10:41:29 +01:00
use Http\Promise\Promise ;
use Http\Promise\RejectedPromise ;
use Nyholm\Psr7\Factory\Psr17Factory ;
use Nyholm\Psr7\Request ;
use Nyholm\Psr7\Uri ;
use Psr\Http\Message\RequestFactoryInterface ;
2019-06-10 12:49:36 +01:00
use Psr\Http\Message\RequestInterface ;
use Psr\Http\Message\ResponseFactoryInterface ;
2019-09-26 10:41:29 +01:00
use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface ;
2019-06-10 12:49:36 +01:00
use Psr\Http\Message\StreamFactoryInterface ;
use Psr\Http\Message\StreamInterface ;
2019-09-26 10:41:29 +01:00
use Psr\Http\Message\UriFactoryInterface ;
2019-06-10 12:49:36 +01:00
use Psr\Http\Message\UriInterface ;
2019-10-10 12:51:24 +01:00
use Symfony\Component\HttpClient\Internal\HttplugWaitLoop ;
2019-09-26 10:41:29 +01:00
use Symfony\Component\HttpClient\Response\HttplugPromise ;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface ;
2019-06-10 12:49:36 +01:00
use Symfony\Contracts\HttpClient\HttpClientInterface ;
2019-09-26 10:41:29 +01:00
use Symfony\Contracts\HttpClient\ResponseInterface ;
2019-06-10 12:49:36 +01:00
2019-09-26 10:41:29 +01:00
if ( ! interface_exists ( HttplugInterface :: class )) {
2019-06-10 12:49:36 +01:00
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 ( 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 .
*
2019-09-26 10:41:29 +01:00
* Run " composer require nyholm/psr7 " to install an efficient implementation of response
2019-06-10 12:49:36 +01:00
* and stream factories with flex - provided autowiring aliases .
*
* @ author Nicolas Grekas < p @ tchwork . com >
*/
2019-09-26 10:41:29 +01:00
final class HttplugClient implements HttplugInterface , HttpAsyncClient , RequestFactory , StreamFactory , UriFactory
2019-06-10 12:49:36 +01:00
{
private $client ;
2019-09-26 10:41:29 +01:00
private $responseFactory ;
private $streamFactory ;
2019-10-10 12:51:24 +01:00
private $promisePool ;
private $waitLoop ;
2019-06-10 12:49:36 +01:00
public function __construct ( HttpClientInterface $client = null , ResponseFactoryInterface $responseFactory = null , StreamFactoryInterface $streamFactory = null )
{
2019-09-26 10:41:29 +01:00
$this -> client = $client ? ? HttpClient :: create ();
$this -> responseFactory = $responseFactory ;
$this -> streamFactory = $streamFactory ? ? ( $responseFactory instanceof StreamFactoryInterface ? $responseFactory : null );
2019-10-10 12:51:24 +01:00
$this -> promisePool = \function_exists ( 'GuzzleHttp\Promise\queue' ) ? new \SplObjectStorage () : null ;
2019-09-26 10:41:29 +01:00
2019-10-10 12:51:24 +01:00
if ( null === $this -> responseFactory || null === $this -> streamFactory ) {
2019-10-14 11:53:45 +01:00
if ( ! class_exists ( Psr17Factory :: class ) && ! class_exists ( Psr17FactoryDiscovery :: class )) {
2019-10-10 12:51:24 +01:00
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".' );
}
2019-09-26 10:41:29 +01:00
2020-01-07 12:55:38 +00:00
try {
$psr17Factory = class_exists ( Psr17Factory :: class , false ) ? new Psr17Factory () : null ;
$this -> responseFactory = $this -> responseFactory ? ? $psr17Factory ? ? Psr17FactoryDiscovery :: findResponseFactory ();
$this -> streamFactory = $this -> streamFactory ? ? $psr17Factory ? ? Psr17FactoryDiscovery :: findStreamFactory ();
} catch ( NotFoundException $e ) {
throw new \LogicException ( 'You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been found. Try running "composer require nyholm/psr7".' , 0 , $e );
}
2019-09-26 10:41:29 +01:00
}
2019-10-10 12:51:24 +01:00
$this -> waitLoop = new HttplugWaitLoop ( $this -> client , $this -> promisePool , $this -> responseFactory , $this -> streamFactory );
2019-06-10 12:49:36 +01:00
}
/**
* { @ inheritdoc }
*/
2019-09-26 10:41:29 +01:00
public function sendRequest ( RequestInterface $request ) : Psr7ResponseInterface
2019-06-10 12:49:36 +01:00
{
try {
2019-10-10 12:51:24 +01:00
return $this -> waitLoop -> createPsr7Response ( $this -> sendPsr7Request ( $request ));
2019-09-26 10:41:29 +01:00
} catch ( TransportExceptionInterface $e ) {
2019-06-10 12:49:36 +01:00
throw new NetworkException ( $e -> getMessage (), $request , $e );
}
}
2019-09-26 10:41:29 +01:00
/**
* { @ inheritdoc }
*
* @ return HttplugPromise
*/
public function sendAsyncRequest ( RequestInterface $request ) : Promise
{
2019-10-10 12:51:24 +01:00
if ( ! $promisePool = $this -> promisePool ) {
2019-09-26 10:41:29 +01:00
throw new \LogicException ( sprintf ( 'You cannot use "%s()" as the "guzzlehttp/promises" package is not installed. Try running "composer require guzzlehttp/promises".' , __METHOD__ ));
}
try {
$response = $this -> sendPsr7Request ( $request , true );
} catch ( NetworkException $e ) {
return new RejectedPromise ( $e );
}
2019-10-10 12:51:24 +01:00
$waitLoop = $this -> waitLoop ;
2019-09-26 10:41:29 +01:00
2019-10-10 12:51:24 +01:00
$promise = new GuzzlePromise ( static function () use ( $response , $waitLoop ) {
$waitLoop -> wait ( $response );
}, static function () use ( $response , $promisePool ) {
$response -> cancel ();
unset ( $promisePool [ $response ]);
});
2019-09-26 10:41:29 +01:00
2019-10-10 12:51:24 +01:00
$promisePool [ $response ] = [ $request , $promise ];
2019-09-26 10:41:29 +01:00
2019-10-10 12:51:24 +01:00
return new HttplugPromise ( $promise );
2019-09-26 10:41:29 +01:00
}
/**
2019-10-10 12:51:24 +01:00
* Resolves pending promises that complete before the timeouts are reached .
2019-09-26 10:41:29 +01:00
*
* When $maxDuration is null and $idleTimeout is reached , promises are rejected .
*
* @ return int The number of remaining pending promises
*/
public function wait ( float $maxDuration = null , float $idleTimeout = null ) : int
{
2019-10-10 12:51:24 +01:00
return $this -> waitLoop -> wait ( null , $maxDuration , $idleTimeout );
2019-09-26 10:41:29 +01:00
}
2019-06-10 12:49:36 +01:00
/**
* { @ inheritdoc }
*/
public function createRequest ( $method , $uri , array $headers = [], $body = null , $protocolVersion = '1.1' ) : RequestInterface
{
2019-09-26 10:41:29 +01:00
if ( $this -> responseFactory instanceof RequestFactoryInterface ) {
$request = $this -> responseFactory -> createRequest ( $method , $uri );
2019-10-14 11:53:45 +01:00
} elseif ( class_exists ( Request :: class )) {
2019-09-26 10:41:29 +01:00
$request = new Request ( $method , $uri );
2019-10-14 11:53:45 +01:00
} elseif ( class_exists ( Psr17FactoryDiscovery :: class )) {
$request = Psr17FactoryDiscovery :: findRequestFactory () -> createRequest ( $method , $uri );
} else {
throw new \LogicException ( sprintf ( 'You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".' , __METHOD__ ));
2019-09-26 10:41:29 +01:00
}
$request = $request
2019-06-10 12:49:36 +01:00
-> withProtocolVersion ( $protocolVersion )
-> withBody ( $this -> createStream ( $body ))
;
foreach ( $headers as $name => $value ) {
$request = $request -> withAddedHeader ( $name , $value );
}
return $request ;
}
/**
* { @ inheritdoc }
*/
public function createStream ( $body = null ) : StreamInterface
{
if ( $body instanceof StreamInterface ) {
return $body ;
}
if ( \is_string ( $body ? ? '' )) {
2019-09-26 10:41:29 +01:00
$stream = $this -> streamFactory -> createStream ( $body ? ? '' );
} elseif ( \is_resource ( $body )) {
$stream = $this -> streamFactory -> createStreamFromResource ( $body );
} else {
2020-03-16 10:25:47 +00:00
throw new \InvalidArgumentException ( sprintf ( '"%s()" expects string, resource or StreamInterface, "%s" given.' , __METHOD__ , \gettype ( $body )));
2019-06-10 12:49:36 +01:00
}
2019-09-26 10:41:29 +01:00
if ( $stream -> isSeekable ()) {
$stream -> seek ( 0 );
2019-06-10 12:49:36 +01:00
}
2019-09-26 10:41:29 +01:00
return $stream ;
2019-06-10 12:49:36 +01:00
}
/**
* { @ inheritdoc }
*/
2019-09-26 10:41:29 +01:00
public function createUri ( $uri ) : UriInterface
2019-06-10 12:49:36 +01:00
{
2019-09-26 10:41:29 +01:00
if ( $uri instanceof UriInterface ) {
return $uri ;
}
if ( $this -> responseFactory instanceof UriFactoryInterface ) {
return $this -> responseFactory -> createUri ( $uri );
}
2019-10-14 11:53:45 +01:00
if ( class_exists ( Uri :: class )) {
return new Uri ( $uri );
}
if ( class_exists ( Psr17FactoryDiscovery :: class )) {
return Psr17FactoryDiscovery :: findUrlFactory () -> createUri ( $uri );
2019-09-26 10:41:29 +01:00
}
2019-10-14 11:53:45 +01:00
throw new \LogicException ( sprintf ( 'You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".' , __METHOD__ ));
2019-09-26 10:41:29 +01:00
}
2019-10-10 12:51:24 +01:00
public function __destruct ()
{
$this -> wait ();
}
2019-09-26 10:41:29 +01:00
private function sendPsr7Request ( RequestInterface $request , bool $buffer = null ) : ResponseInterface
{
try {
$body = $request -> getBody ();
if ( $body -> isSeekable ()) {
$body -> seek ( 0 );
}
return $this -> client -> request ( $request -> getMethod (), ( string ) $request -> getUri (), [
'headers' => $request -> getHeaders (),
'body' => $body -> getContents (),
'http_version' => '1.0' === $request -> getProtocolVersion () ? '1.0' : null ,
'buffer' => $buffer ,
]);
} catch ( \InvalidArgumentException $e ) {
throw new RequestException ( $e -> getMessage (), $request , $e );
} catch ( TransportExceptionInterface $e ) {
throw new NetworkException ( $e -> getMessage (), $request , $e );
}
}
2019-06-10 12:49:36 +01:00
}