diff --git a/composer.json b/composer.json index bdfbafb..7411dec 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "php": ">=5.4", "php-http/httplug": "^1.0", "php-http/message-factory": "^1.0.2", - "php-http/client-common": "^1.0", + "php-http/client-common": "dev-plugin_client", "php-http/message": "^1.0", "symfony/options-resolver": "^2.6|^3.0" }, diff --git a/spec/AddHostPluginSpec.php b/spec/AddHostPluginSpec.php index c02b05d..642bac6 100644 --- a/spec/AddHostPluginSpec.php +++ b/spec/AddHostPluginSpec.php @@ -22,14 +22,13 @@ function let(UriInterface $uri) function it_is_initializable(UriInterface $uri) { $uri->getHost()->shouldBeCalled()->willReturn('example.com'); - $this->beConstructedWith($uri); + $this->shouldHaveType('Http\Client\Plugin\AddHostPlugin'); } function it_is_a_plugin(UriInterface $uri) { $uri->getHost()->shouldBeCalled()->willReturn('example.com'); - $this->beConstructedWith($uri); $this->shouldImplement('Http\Client\Plugin\Plugin'); } diff --git a/spec/ContentLengthPluginSpec.php b/spec/ContentLengthPluginSpec.php index 0fa463f..de9a520 100644 --- a/spec/ContentLengthPluginSpec.php +++ b/spec/ContentLengthPluginSpec.php @@ -3,16 +3,20 @@ namespace spec\Http\Client\Plugin; use PhpSpec\Exception\Example\SkippingException; -use PhpSpec\ObjectBehavior; -use Prophecy\Argument; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\StreamInterface; +use PhpSpec\ObjectBehavior; +use Prophecy\Argument; class ContentLengthPluginSpec extends ObjectBehavior { function it_is_initializable() { $this->shouldHaveType('Http\Client\Plugin\ContentLengthPlugin'); + } + + function it_is_a_plugin() + { $this->shouldImplement('Http\Client\Plugin\Plugin'); } diff --git a/spec/HeaderAppendPluginSpec.php b/spec/HeaderAppendPluginSpec.php index 072b350..69e46e3 100644 --- a/spec/HeaderAppendPluginSpec.php +++ b/spec/HeaderAppendPluginSpec.php @@ -3,10 +3,10 @@ namespace spec\Http\Client\Plugin; use PhpSpec\Exception\Example\SkippingException; -use PhpSpec\ObjectBehavior; -use Prophecy\Argument; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\StreamInterface; +use PhpSpec\ObjectBehavior; +use Prophecy\Argument; class HeaderAppendPluginSpec extends ObjectBehavior { diff --git a/spec/HeaderDefaultsPluginSpec.php b/spec/HeaderDefaultsPluginSpec.php index 4fceba4..49f3164 100644 --- a/spec/HeaderDefaultsPluginSpec.php +++ b/spec/HeaderDefaultsPluginSpec.php @@ -3,10 +3,10 @@ namespace spec\Http\Client\Plugin; use PhpSpec\Exception\Example\SkippingException; -use PhpSpec\ObjectBehavior; -use Prophecy\Argument; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\StreamInterface; +use PhpSpec\ObjectBehavior; +use Prophecy\Argument; class HeaderDefaultsPluginSpec extends ObjectBehavior { diff --git a/spec/HeaderRemovePluginSpec.php b/spec/HeaderRemovePluginSpec.php index b2edebd..8c440fa 100644 --- a/spec/HeaderRemovePluginSpec.php +++ b/spec/HeaderRemovePluginSpec.php @@ -3,10 +3,10 @@ namespace spec\Http\Client\Plugin; use PhpSpec\Exception\Example\SkippingException; -use PhpSpec\ObjectBehavior; -use Prophecy\Argument; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\StreamInterface; +use PhpSpec\ObjectBehavior; +use Prophecy\Argument; class HeaderRemovePluginSpec extends ObjectBehavior { diff --git a/spec/HeaderSetPluginSpec.php b/spec/HeaderSetPluginSpec.php index 53c1b75..df73dbd 100644 --- a/spec/HeaderSetPluginSpec.php +++ b/spec/HeaderSetPluginSpec.php @@ -3,10 +3,10 @@ namespace spec\Http\Client\Plugin; use PhpSpec\Exception\Example\SkippingException; -use PhpSpec\ObjectBehavior; -use Prophecy\Argument; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\StreamInterface; +use PhpSpec\ObjectBehavior; +use Prophecy\Argument; class HeaderSetPluginSpec extends ObjectBehavior { diff --git a/spec/LoopPlugin.php b/spec/LoopPlugin.php deleted file mode 100644 index 2905f73..0000000 --- a/spec/LoopPlugin.php +++ /dev/null @@ -1,14 +0,0 @@ -shouldImplement('Http\Client\HttpAsyncClient'); } - function it_sends_request_with_underlying_client(HttpClient $client, RequestInterface $request, ResponseInterface $response) + function it_throws_loop_exception(HttpClient $client, RequestInterface $request, Plugin $plugin) { - $client->sendRequest($request)->willReturn($response); + $plugin + ->handleRequest( + $request, + Argument::type('callable'), + Argument::type('callable') + ) + ->will(function ($args) { + return $args[2]($args[0]); + }) + ; - $this->sendRequest($request)->shouldReturn($response); - } - - function it_sends_async_request_with_underlying_client(HttpAsyncClient $asyncClient, RequestInterface $request, Promise $promise) - { - $asyncClient->sendAsyncRequest($request)->willReturn($promise); - - $this->beConstructedWith($asyncClient); - $this->sendAsyncRequest($request)->shouldReturn($promise); - } - - function it_sends_async_request_if_no_send_request(HttpAsyncClient $asyncClient, RequestInterface $request, ResponseInterface $response, Promise $promise) - { - $this->beConstructedWith($asyncClient->getWrappedObject()); - $asyncClient->sendAsyncRequest($request)->willReturn($promise); - $promise->wait()->willReturn($response); - - $this->sendRequest($request)->shouldReturn($response); - } - - function it_prefers_send_request(StubClient $client, RequestInterface $request, ResponseInterface $response) - { - $client->sendRequest($request)->willReturn($response); - - $this->sendRequest($request)->shouldReturn($response); - } - - function it_throws_loop_exception(HttpClient $client, RequestInterface $request) - { - $this->beConstructedWith($client, [new LoopPlugin()]); + $this->beConstructedWith($client, [$plugin]); $this->shouldThrow('Http\Client\Plugin\Exception\LoopException')->duringSendRequest($request); } diff --git a/spec/StubClient.php b/spec/StubClient.php deleted file mode 100644 index aecea99..0000000 --- a/spec/StubClient.php +++ /dev/null @@ -1,18 +0,0 @@ - + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\Plugin\AddHostPlugin} instead. */ -class AddHostPlugin implements Plugin +class AddHostPlugin extends \Http\Client\Common\Plugin\AddHostPlugin implements Plugin { - /** - * @var UriInterface - */ - private $host; - - /** - * @var bool - */ - private $replace; - - /** - * @param UriInterface $host - * @param array $config { - * - * @var bool $replace True will replace all hosts, false will only add host when none is specified. - * } - */ - public function __construct(UriInterface $host, array $config = []) - { - if ($host->getHost() === '') { - throw new \LogicException('Host can not be empty'); - } - - $this->host = $host; - - $resolver = new OptionsResolver(); - $this->configureOptions($resolver); - $options = $resolver->resolve($config); - - $this->replace = $options['replace']; - } - - /** - * {@inheritdoc} - */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) - { - if ($this->replace || $request->getUri()->getHost() === '') { - $uri = $request->getUri()->withHost($this->host->getHost()); - $uri = $uri->withScheme($this->host->getScheme()); - - $request = $request->withUri($uri); - } - - return $next($request); - } - - /** - * @param OptionsResolver $resolver - */ - private function configureOptions(OptionsResolver $resolver) - { - $resolver->setDefaults([ - 'replace' => false, - ]); - $resolver->setAllowedTypes('replace', 'bool'); - } } diff --git a/src/AuthenticationPlugin.php b/src/AuthenticationPlugin.php index 29e368b..73900db 100644 --- a/src/AuthenticationPlugin.php +++ b/src/AuthenticationPlugin.php @@ -2,36 +2,11 @@ namespace Http\Client\Plugin; -use Http\Message\Authentication; -use Psr\Http\Message\RequestInterface; - /** - * Send an authenticated request. - * * @author Joel Wurtz + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\Plugin\AuthenticationPlugin} instead. */ -class AuthenticationPlugin implements Plugin +class AuthenticationPlugin extends \Http\Client\Common\Plugin\AuthenticationPlugin implements Plugin { - /** - * @var Authentication An authentication system - */ - private $authentication; - - /** - * @param Authentication $authentication - */ - public function __construct(Authentication $authentication) - { - $this->authentication = $authentication; - } - - /** - * {@inheritdoc} - */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) - { - $request = $this->authentication->authenticate($request); - - return $next($request); - } } diff --git a/src/ContentLengthPlugin.php b/src/ContentLengthPlugin.php index 068d65e..7fb3e70 100644 --- a/src/ContentLengthPlugin.php +++ b/src/ContentLengthPlugin.php @@ -2,34 +2,11 @@ namespace Http\Client\Plugin; -use Http\Message\Encoding\ChunkStream; -use Psr\Http\Message\RequestInterface; - /** - * Allow to set the correct content length header on the request or to transfer it as a chunk if not possible. - * * @author Joel Wurtz + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\Plugin\ContentLengthPlugin} instead. */ -class ContentLengthPlugin implements Plugin +class ContentLengthPlugin extends \Http\Client\Common\Plugin\ContentLengthPlugin implements Plugin { - /** - * {@inheritdoc} - */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) - { - if (!$request->hasHeader('Content-Length')) { - $stream = $request->getBody(); - - // Cannot determine the size so we use a chunk stream - if (null === $stream->getSize()) { - $stream = new ChunkStream($stream); - $request = $request->withBody($stream); - $request = $request->withAddedHeader('Transfer-Encoding', 'chunked'); - } else { - $request = $request->withHeader('Content-Length', $stream->getSize()); - } - } - - return $next($request); - } } diff --git a/src/CookiePlugin.php b/src/CookiePlugin.php index 71b56b2..12331e9 100644 --- a/src/CookiePlugin.php +++ b/src/CookiePlugin.php @@ -2,154 +2,13 @@ namespace Http\Client\Plugin; -use Http\Message\Cookie; -use Http\Message\CookieJar; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; - /** * Handle request cookies. * * @author Joel Wurtz + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\Plugin\CookiePlugin} instead. */ -class CookiePlugin implements Plugin +class CookiePlugin extends \Http\Client\Common\Plugin\CookiePlugin implements Plugin { - /** - * Cookie storage. - * - * @var CookieJar - */ - private $cookieJar; - - /** - * @param CookieJar $cookieJar - */ - public function __construct(CookieJar $cookieJar) - { - $this->cookieJar = $cookieJar; - } - - /** - * {@inheritdoc} - */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) - { - foreach ($this->cookieJar->getCookies() as $cookie) { - if ($cookie->isExpired()) { - continue; - } - - if (!$cookie->matchDomain($request->getUri()->getHost())) { - continue; - } - - if (!$cookie->matchPath($request->getUri()->getPath())) { - continue; - } - - if ($cookie->isSecure() && ($request->getUri()->getScheme() !== 'https')) { - continue; - } - - $request = $request->withAddedHeader('Cookie', sprintf('%s=%s', $cookie->getName(), $cookie->getValue())); - } - - return $next($request)->then(function (ResponseInterface $response) use ($request) { - if ($response->hasHeader('Set-Cookie')) { - $setCookies = $response->getHeader('Set-Cookie'); - - foreach ($setCookies as $setCookie) { - $cookie = $this->createCookie($request, $setCookie); - - // Cookie invalid do not use it - if (null === $cookie) { - continue; - } - - // Restrict setting cookie from another domain - if (false === strpos($cookie->getDomain(), $request->getUri()->getHost())) { - continue; - } - - $this->cookieJar->addCookie($cookie); - } - } - - return $response; - }); - } - - /** - * Creates a cookie from a string. - * - * @param RequestInterface $request - * @param $setCookie - * - * @return Cookie|null - */ - private function createCookie(RequestInterface $request, $setCookie) - { - $parts = array_map('trim', explode(';', $setCookie)); - - if (empty($parts) || !strpos($parts[0], '=')) { - return; - } - - list($name, $cookieValue) = $this->createValueKey(array_shift($parts)); - - $expires = 0; - $domain = $request->getUri()->getHost(); - $path = $request->getUri()->getPath(); - $secure = false; - $httpOnly = false; - - // Add the cookie pieces into the parsed data array - foreach ($parts as $part) { - list($key, $value) = $this->createValueKey($part); - - switch (strtolower($key)) { - case 'expires': - $expires = \DateTime::createFromFormat(DATE_COOKIE, $value); - break; - - case 'max-age': - $expires = (new \DateTime())->add(new \DateInterval('PT'.(int) $value.'S')); - break; - - case 'domain': - $domain = $value; - break; - - case 'path': - $path = $value; - break; - - case 'secure': - $secure = true; - break; - - case 'httponly': - $httpOnly = true; - break; - } - } - - return new Cookie($name, $cookieValue, $expires, $domain, $path, $secure, $httpOnly); - } - - /** - * Separates key/value pair from cookie. - * - * @param $part - * - * @return array - */ - private function createValueKey($part) - { - $parts = explode('=', $part, 2); - $key = trim($parts[0]); - $value = isset($parts[1]) ? trim($parts[1]) : true; - - return [$key, $value]; - } } diff --git a/src/DecoderPlugin.php b/src/DecoderPlugin.php index 4b49bfb..456e9cd 100644 --- a/src/DecoderPlugin.php +++ b/src/DecoderPlugin.php @@ -2,145 +2,11 @@ namespace Http\Client\Plugin; -use Http\Message\Encoding\DechunkStream; -use Http\Message\Encoding\DecompressStream; -use Http\Message\Encoding\GzipDecodeStream; -use Http\Message\Encoding\InflateStream; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\StreamInterface; -use Symfony\Component\OptionsResolver\OptionsResolver; - /** - * Allow to decode response body with a chunk, deflate, compress or gzip encoding. - * - * If zlib is not installed, only chunked encoding can be handled. - * - * If Content-Encoding is not disabled, the plugin will add an Accept-Encoding header for the encoding methods it supports. - * * @author Joel Wurtz + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\Plugin\DecoderPlugin} instead. */ -class DecoderPlugin implements Plugin +class DecoderPlugin extends \Http\Client\Common\Plugin\DecoderPlugin implements Plugin { - /** - * @var bool Whether this plugin decode stream with value in the Content-Encoding header (default to true). - * - * If set to false only the Transfer-Encoding header will be used. - */ - private $useContentEncoding; - - /** - * @param array $config { - * - * @var bool $use_content_encoding Whether this plugin should look at the Content-Encoding header first or only at the Transfer-Encoding (defaults to true). - * } - */ - public function __construct(array $config = []) - { - $resolver = new OptionsResolver(); - $resolver->setDefaults([ - 'use_content_encoding' => true, - ]); - $resolver->setAllowedTypes('use_content_encoding', 'bool'); - $options = $resolver->resolve($config); - - $this->useContentEncoding = $options['use_content_encoding']; - } - - /** - * {@inheritdoc} - */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) - { - $encodings = extension_loaded('zlib') ? ['gzip', 'deflate', 'compress'] : ['identity']; - - if ($this->useContentEncoding) { - $request = $request->withHeader('Accept-Encoding', $encodings); - } - $encodings[] = 'chunked'; - $request = $request->withHeader('TE', $encodings); - - return $next($request)->then(function (ResponseInterface $response) { - return $this->decodeResponse($response); - }); - } - - /** - * Decode a response body given its Transfer-Encoding or Content-Encoding value. - * - * @param ResponseInterface $response Response to decode - * - * @return ResponseInterface New response decoded - */ - private function decodeResponse(ResponseInterface $response) - { - $response = $this->decodeOnEncodingHeader('Transfer-Encoding', $response); - - if ($this->useContentEncoding) { - $response = $this->decodeOnEncodingHeader('Content-Encoding', $response); - } - - return $response; - } - - /** - * Decode a response on a specific header (content encoding or transfer encoding mainly). - * - * @param string $headerName Name of the header - * @param ResponseInterface $response Response - * - * @return ResponseInterface A new instance of the response decoded - */ - private function decodeOnEncodingHeader($headerName, ResponseInterface $response) - { - if ($response->hasHeader($headerName)) { - $encodings = $response->getHeader($headerName); - $newEncodings = []; - - while ($encoding = array_pop($encodings)) { - $stream = $this->decorateStream($encoding, $response->getBody()); - - if (false === $stream) { - array_unshift($newEncodings, $encoding); - - continue; - } - - $response = $response->withBody($stream); - } - - $response = $response->withHeader($headerName, $newEncodings); - } - - return $response; - } - - /** - * Decorate a stream given an encoding. - * - * @param string $encoding - * @param StreamInterface $stream - * - * @return StreamInterface|false A new stream interface or false if encoding is not supported - */ - private function decorateStream($encoding, StreamInterface $stream) - { - if (strtolower($encoding) == 'chunked') { - return new DechunkStream($stream); - } - - if (strtolower($encoding) == 'compress') { - return new DecompressStream($stream); - } - - if (strtolower($encoding) == 'deflate') { - return new InflateStream($stream); - } - - if (strtolower($encoding) == 'gzip') { - return new GzipDecodeStream($stream); - } - - return false; - } } diff --git a/src/ErrorPlugin.php b/src/ErrorPlugin.php index 36ca121..aeeb709 100644 --- a/src/ErrorPlugin.php +++ b/src/ErrorPlugin.php @@ -8,37 +8,15 @@ use Psr\Http\Message\ResponseInterface; /** - * Throw exception when the response of a request is not acceptable. - * - * By default an exception will be thrown for all status codes from 400 to 599. - * * @author Joel Wurtz + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\Plugin\ErrorPlugin} instead. */ -class ErrorPlugin implements Plugin +class ErrorPlugin extends \Http\Client\Common\Plugin\ErrorPlugin implements Plugin { /** * {@inheritdoc} */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) - { - $promise = $next($request); - - return $promise->then(function (ResponseInterface $response) use ($request) { - return $this->transformResponseToException($request, $response); - }); - } - - /** - * Transform response to an error if possible. - * - * @param RequestInterface $request Request of the call - * @param ResponseInterface $response Response of the call - * - * @throws ClientErrorException If response status code is a 4xx - * @throws ServerErrorException If response status code is a 5xx - * - * @return ResponseInterface If status code is not in 4xx or 5xx return response - */ protected function transformResponseToException(RequestInterface $request, ResponseInterface $response) { if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 500) { diff --git a/src/Exception/CircularRedirectionException.php b/src/Exception/CircularRedirectionException.php index bf71b1a..00904c8 100644 --- a/src/Exception/CircularRedirectionException.php +++ b/src/Exception/CircularRedirectionException.php @@ -2,13 +2,11 @@ namespace Http\Client\Plugin\Exception; -use Http\Client\Exception\HttpException; - /** - * Thrown when circular redirection is detected. - * * @author Joel Wurtz + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\Exception\CircularRedirectionException} instead. */ -class CircularRedirectionException extends HttpException +class CircularRedirectionException extends \Http\Client\Common\Exception\CircularRedirectionException { } diff --git a/src/Exception/ClientErrorException.php b/src/Exception/ClientErrorException.php index 8624660..215b6ab 100644 --- a/src/Exception/ClientErrorException.php +++ b/src/Exception/ClientErrorException.php @@ -2,13 +2,11 @@ namespace Http\Client\Plugin\Exception; -use Http\Client\Exception\HttpException; - /** - * Thrown when there is a client error (4xx). - * * @author Joel Wurtz + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\Exception\ClientErrorException} instead. */ -class ClientErrorException extends HttpException +class ClientErrorException extends \Http\Client\Common\Exception\ClientErrorException { } diff --git a/src/Exception/LoopException.php b/src/Exception/LoopException.php index 5194ed7..c2d6636 100644 --- a/src/Exception/LoopException.php +++ b/src/Exception/LoopException.php @@ -2,11 +2,11 @@ namespace Http\Client\Plugin\Exception; -use Http\Client\Exception\RequestException; - /** * @author Joel Wurtz + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\Exception\LoopException} instead. */ -class LoopException extends RequestException +class LoopException extends \Http\Client\Common\Exception\LoopException { } diff --git a/src/Exception/MultipleRedirectionException.php b/src/Exception/MultipleRedirectionException.php index b16c026..c9ad5f4 100644 --- a/src/Exception/MultipleRedirectionException.php +++ b/src/Exception/MultipleRedirectionException.php @@ -2,13 +2,11 @@ namespace Http\Client\Plugin\Exception; -use Http\Client\Exception\HttpException; - /** - * Redirect location cannot be chosen. - * * @author Joel Wurtz + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\Exception\MultipleRedirectionException} instead. */ -class MultipleRedirectionException extends HttpException +class MultipleRedirectionException extends \Http\Client\Common\Exception\MultipleRedirectionException { } diff --git a/src/Exception/ServerErrorException.php b/src/Exception/ServerErrorException.php index 65117f1..099fb23 100644 --- a/src/Exception/ServerErrorException.php +++ b/src/Exception/ServerErrorException.php @@ -2,13 +2,11 @@ namespace Http\Client\Plugin\Exception; -use Http\Client\Exception\HttpException; - /** - * Thrown when there is a server error (5xx). - * * @author Joel Wurtz + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\Exception\ServerErrorException} instead. */ -class ServerErrorException extends HttpException +class ServerErrorException extends \Http\Client\Common\Exception\ServerErrorException { } diff --git a/src/HeaderAppendPlugin.php b/src/HeaderAppendPlugin.php index b3fcac2..9c42e70 100644 --- a/src/HeaderAppendPlugin.php +++ b/src/HeaderAppendPlugin.php @@ -2,39 +2,11 @@ namespace Http\Client\Plugin; -use Psr\Http\Message\RequestInterface; - /** - * Adds headers to the request. - * If the header already exists the value will be appended to the current value. - * - * This only makes sense for headers that can have multiple values like 'Forwarded' - * - * @link https://en.wikipedia.org/wiki/List_of_HTTP_header_fields - * * @author Soufiane Ghzal + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\Plugin\HeaderAppendPlugin} instead. */ -class HeaderAppendPlugin implements Plugin +class HeaderAppendPlugin extends \Http\Client\Common\Plugin\HeaderAppendPlugin implements Plugin { - private $headers = []; - - /** - * @param array $headers headers to add to the request - */ - public function __construct(array $headers) - { - $this->headers = $headers; - } - - /** - * {@inheritdoc} - */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) - { - foreach ($this->headers as $header => $headerValue) { - $request = $request->withAddedHeader($header, $headerValue); - } - - return $next($request); - } } diff --git a/src/HeaderDefaultsPlugin.php b/src/HeaderDefaultsPlugin.php index fbee194..dc3eb98 100644 --- a/src/HeaderDefaultsPlugin.php +++ b/src/HeaderDefaultsPlugin.php @@ -2,37 +2,11 @@ namespace Http\Client\Plugin; -use Psr\Http\Message\RequestInterface; - /** - * Set default values for the request headers. - * If a given header already exists the value wont be replaced and the request wont be changed. - * * @author Soufiane Ghzal + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\Plugin\HeaderDefaultsPlugin} instead. */ -class HeaderDefaultsPlugin implements Plugin +class HeaderDefaultsPlugin extends \Http\Client\Common\Plugin\HeaderDefaultsPlugin implements Plugin { - private $headers = []; - - /** - * @param array $headers headers to set to the request - */ - public function __construct(array $headers) - { - $this->headers = $headers; - } - - /** - * {@inheritdoc} - */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) - { - foreach ($this->headers as $header => $headerValue) { - if (!$request->hasHeader($header)) { - $request = $request->withHeader($header, $headerValue); - } - } - - return $next($request); - } } diff --git a/src/HeaderRemovePlugin.php b/src/HeaderRemovePlugin.php index b49ac26..160b2a4 100644 --- a/src/HeaderRemovePlugin.php +++ b/src/HeaderRemovePlugin.php @@ -2,36 +2,11 @@ namespace Http\Client\Plugin; -use Psr\Http\Message\RequestInterface; - /** - * Removes headers from the request. - * * @author Soufiane Ghzal + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\Plugin\HeaderRemovePlugin} instead. */ -class HeaderRemovePlugin implements Plugin +class HeaderRemovePlugin extends \Http\Client\Common\Plugin\HeaderRemovePlugin implements Plugin { - private $headers = []; - - /** - * @param array $headers headers to remove from the request - */ - public function __construct(array $headers) - { - $this->headers = $headers; - } - - /** - * {@inheritdoc} - */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) - { - foreach ($this->headers as $header) { - if ($request->hasHeader($header)) { - $request = $request->withoutHeader($header); - } - } - - return $next($request); - } } diff --git a/src/HeaderSetPlugin.php b/src/HeaderSetPlugin.php index 0e855f9..8add4c5 100644 --- a/src/HeaderSetPlugin.php +++ b/src/HeaderSetPlugin.php @@ -2,35 +2,11 @@ namespace Http\Client\Plugin; -use Psr\Http\Message\RequestInterface; - /** - * Set headers to the request. - * If the header does not exist it wil be set, if the header already exists it will be replaced. - * * @author Soufiane Ghzal + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\Plugin\HeaderSetPlugin} instead. */ -class HeaderSetPlugin implements Plugin +class HeaderSetPlugin extends \Http\Client\Common\Plugin\HeaderSetPlugin implements Plugin { - private $headers = []; - - /** - * @param array $headers headers to set to the request - */ - public function __construct(array $headers) - { - $this->headers = $headers; - } - - /** - * {@inheritdoc} - */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) - { - foreach ($this->headers as $header => $headerValue) { - $request = $request->withHeader($header, $headerValue); - } - - return $next($request); - } } diff --git a/src/HistoryPlugin.php b/src/HistoryPlugin.php index 798b32e..a018861 100644 --- a/src/HistoryPlugin.php +++ b/src/HistoryPlugin.php @@ -2,45 +2,11 @@ namespace Http\Client\Plugin; -use Http\Client\Exception; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; - /** - * Record http call. + * @author Joel Wurtz + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\Plugin\HistoryPlugin} instead. */ -class HistoryPlugin implements Plugin +class HistoryPlugin extends \Http\Client\Common\Plugin\HistoryPlugin implements Plugin { - /** - * Journal use to store request / responses / exception. - * - * @var Journal - */ - private $journal; - - /** - * @param Journal $journal - */ - public function __construct(Journal $journal) - { - $this->journal = $journal; - } - - /** - * {@inheritdoc} - */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) - { - $journal = $this->journal; - - return $next($request)->then(function (ResponseInterface $response) use ($request, $journal) { - $journal->addSuccess($request, $response); - - return $response; - }, function (Exception $exception) use ($request, $journal) { - $journal->addFailure($request, $exception); - - throw $exception; - }); - } } diff --git a/src/Journal.php b/src/Journal.php index 711ed43..9902879 100644 --- a/src/Journal.php +++ b/src/Journal.php @@ -2,30 +2,11 @@ namespace Http\Client\Plugin; -use Http\Client\Exception; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; - /** - * Records history of http calls. - * * @author Joel Wurtz + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\Plugin\Journal} instead. */ -interface Journal +interface Journal extends \Http\Client\Common\Plugin\Journal { - /** - * Record a successful call. - * - * @param RequestInterface $request Request use to make the call - * @param ResponseInterface $response Response returned by the call - */ - public function addSuccess(RequestInterface $request, ResponseInterface $response); - - /** - * Record a failed call. - * - * @param RequestInterface $request Request use to make the call - * @param Exception $exception Exception returned by the call - */ - public function addFailure(RequestInterface $request, Exception $exception); } diff --git a/src/Plugin.php b/src/Plugin.php index 2f09ff1..f4a2e8a 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -2,29 +2,11 @@ namespace Http\Client\Plugin; -use Http\Promise\Promise; -use Psr\Http\Message\RequestInterface; - /** - * A plugin is a middleware to transform the request and/or the response. - * - * The plugin can: - * - break the chain and return a response - * - dispatch the request to the next middleware - * - restart the request - * * @author Joel Wurtz + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\Plugin} instead. */ -interface Plugin +interface Plugin extends \Http\Client\Common\Plugin { - /** - * Handle the request and return the response coming from the next callable. - * - * @param RequestInterface $request - * @param callable $next Next middleware in the chain, the request is passed as the first argument - * @param callable $first First middleware in the chain, used to to restart a request - * - * @return Promise - */ - public function handleRequest(RequestInterface $request, callable $next, callable $first); } diff --git a/src/PluginClient.php b/src/PluginClient.php index 2d276ed..f675378 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -2,151 +2,24 @@ namespace Http\Client\Plugin; -use Http\Client\Common\EmulatedHttpAsyncClient; use Http\Client\Exception; -use Http\Client\HttpAsyncClient; -use Http\Client\HttpClient; use Http\Client\Plugin\Exception\LoopException; -use Http\Promise\FulfilledPromise; -use Http\Promise\RejectedPromise; use Psr\Http\Message\RequestInterface; -use Symfony\Component\OptionsResolver\OptionsResolver; /** - * The client managing plugins and providing a decorator around HTTP Clients. - * * @author Joel Wurtz + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\PluginClient} instead. */ -final class PluginClient implements HttpClient, HttpAsyncClient +final class PluginClient extends \Http\Client\Common\PluginClient { - /** - * An HTTP async client. - * - * @var HttpAsyncClient - */ - private $client; - - /** - * The plugin chain. - * - * @var Plugin[] - */ - private $plugins; - - /** - * A list of options. - * - * @var array - */ - private $options; - - /** - * @param HttpClient|HttpAsyncClient $client - * @param Plugin[] $plugins - * @param array $options { - * - * @var int $max_restarts - * } - * - * @throws \RuntimeException if client is not an instance of HttpClient or HttpAsyncClient - */ - public function __construct($client, array $plugins = [], array $options = []) - { - if ($client instanceof HttpAsyncClient) { - $this->client = $client; - } elseif ($client instanceof HttpClient) { - $this->client = new EmulatedHttpAsyncClient($client); - } else { - throw new \RuntimeException('Client must be an instance of Http\\Client\\HttpClient or Http\\Client\\HttpAsyncClient'); - } - - $this->plugins = $plugins; - $this->options = $this->configure($options); - } - /** * {@inheritdoc} - */ - public function sendRequest(RequestInterface $request) - { - // If we don't have an http client, use the async call - if (!($this->client instanceof HttpClient)) { - return $this->sendAsyncRequest($request)->wait(); - } - - // Else we want to use the synchronous call of the underlying client, and not the async one in the case - // we have both an async and sync call - $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) { - try { - return new FulfilledPromise($this->client->sendRequest($request)); - } catch (Exception $exception) { - return new RejectedPromise($exception); - } - }); - - return $pluginChain($request)->wait(); - } - - /** - * {@inheritdoc} - */ - public function sendAsyncRequest(RequestInterface $request) - { - $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) { - return $this->client->sendAsyncRequest($request); - }); - - return $pluginChain($request); - } - - /** - * Configure the plugin client. * - * @param array $options - * - * @return array + * Throw the correct loop exception. */ - private function configure(array $options = []) + protected function createLoopException(RequestInterface $request) { - $resolver = new OptionsResolver(); - $resolver->setDefaults([ - 'max_restarts' => 10, - ]); - - return $resolver->resolve($options); - } - - /** - * Create the plugin chain. - * - * @param Plugin[] $pluginList A list of plugins - * @param callable $clientCallable Callable making the HTTP call - * - * @return callable - */ - private function createPluginChain($pluginList, callable $clientCallable) - { - $firstCallable = $lastCallable = $clientCallable; - - while ($plugin = array_pop($pluginList)) { - $lastCallable = function (RequestInterface $request) use ($plugin, $lastCallable, &$firstCallable) { - return $plugin->handleRequest($request, $lastCallable, $firstCallable); - }; - - $firstCallable = $lastCallable; - } - - $firstCalls = 0; - $firstCallable = function (RequestInterface $request) use ($lastCallable, &$firstCalls) { - if ($firstCalls > $this->options['max_restarts']) { - throw new LoopException('Too many restarts in plugin client', $request); - } - - ++$firstCalls; - - return $lastCallable($request); - }; - - return $firstCallable; + return new LoopException('Too many restarts in plugin client', $request); } } diff --git a/src/RedirectPlugin.php b/src/RedirectPlugin.php index 7a3f775..3e7c809 100644 --- a/src/RedirectPlugin.php +++ b/src/RedirectPlugin.php @@ -5,128 +5,17 @@ use Http\Client\Exception\HttpException; use Http\Client\Plugin\Exception\CircularRedirectionException; use Http\Client\Plugin\Exception\MultipleRedirectionException; -use Psr\Http\Message\MessageInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\UriInterface; -use Symfony\Component\OptionsResolver\OptionsResolver; /** - * Follow redirections. - * * @author Joel Wurtz + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\Plugin\RedirectPlugin} instead. */ -class RedirectPlugin implements Plugin +class RedirectPlugin extends \Http\Client\Common\Plugin\RedirectPlugin implements Plugin { - /** - * Rule on how to redirect, change method for the new request. - * - * @var array - */ - protected $redirectCodes = [ - 300 => [ - 'switch' => [ - 'unless' => ['GET', 'HEAD'], - 'to' => 'GET', - ], - 'multiple' => true, - 'permanent' => false, - ], - 301 => [ - 'switch' => [ - 'unless' => ['GET', 'HEAD'], - 'to' => 'GET', - ], - 'multiple' => false, - 'permanent' => true, - ], - 302 => [ - 'switch' => [ - 'unless' => ['GET', 'HEAD'], - 'to' => 'GET', - ], - 'multiple' => false, - 'permanent' => false, - ], - 303 => [ - 'switch' => [ - 'unless' => ['GET', 'HEAD'], - 'to' => 'GET', - ], - 'multiple' => false, - 'permanent' => false, - ], - 307 => [ - 'switch' => false, - 'multiple' => false, - 'permanent' => false, - ], - 308 => [ - 'switch' => false, - 'multiple' => false, - 'permanent' => true, - ], - ]; - - /** - * Determine how header should be preserved from old request. - * - * @var bool|array - * - * true will keep all previous headers (default value) - * false will ditch all previous headers - * string[] will keep only headers with the specified names - */ - protected $preserveHeader; - - /** - * Store all previous redirect from 301 / 308 status code. - * - * @var array - */ - protected $redirectStorage = []; - - /** - * Whether the location header must be directly used for a multiple redirection status code (300). - * - * @var bool - */ - protected $useDefaultForMultiple; - - /** - * @var array - */ - protected $circularDetection = []; - - /** - * @param array $config { - * - * @var bool|string[] $preserve_header True keeps all headers, false remove all of them, an array is interpreted as a list of header names to keep. - * @var bool $use_default_for_multiple Whether the location header must be directly used for a multiple redirection status code (300). - * } - */ - public function __construct(array $config = []) - { - $resolver = new OptionsResolver(); - $resolver->setDefaults([ - 'preserve_header' => true, - 'use_default_for_multiple' => true, - ]); - $resolver->setAllowedTypes('preserve_header', ['bool', 'array']); - $resolver->setAllowedTypes('use_default_for_multiple', 'bool'); - $resolver->setNormalizer('preserve_header', function (OptionsResolver $resolver, $value) { - if (is_bool($value) && false === $value) { - return []; - } - - return $value; - }); - $options = $resolver->resolve($config); - - $this->preserveHeader = $options['preserve_header']; - $this->useDefaultForMultiple = $options['use_default_for_multiple']; - } - /** * {@inheritdoc} */ @@ -176,36 +65,6 @@ public function handleRequest(RequestInterface $request, callable $next, callabl }); } - /** - * Builds the redirect request. - * - * @param RequestInterface $request Original request - * @param UriInterface $uri New uri - * @param int $statusCode Status code from the redirect response - * - * @return MessageInterface|RequestInterface - */ - protected function buildRedirectRequest(RequestInterface $request, UriInterface $uri, $statusCode) - { - $request = $request->withUri($uri); - - if (false !== $this->redirectCodes[$statusCode]['switch'] && !in_array($request->getMethod(), $this->redirectCodes[$statusCode]['switch']['unless'])) { - $request = $request->withMethod($this->redirectCodes[$statusCode]['switch']['to']); - } - - if (is_array($this->preserveHeader)) { - $headers = array_keys($request->getHeaders()); - - foreach ($headers as $name) { - if (!in_array($name, $this->preserveHeader)) { - $request = $request->withoutHeader($name); - } - } - } - - return $request; - } - /** * Creates a new Uri from the old request and the location header. * diff --git a/src/RetryPlugin.php b/src/RetryPlugin.php index 80097da..ce0fc38 100644 --- a/src/RetryPlugin.php +++ b/src/RetryPlugin.php @@ -2,82 +2,11 @@ namespace Http\Client\Plugin; -use Http\Client\Exception; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; -use Symfony\Component\OptionsResolver\OptionsResolver; - /** - * Retry the request if an exception is thrown. - * - * By default will retry only one time. - * * @author Joel Wurtz + * + * @deprecated since version 1.1, to be removed in 2.0. Use {@link \Http\Client\Common\Plugin\RetryPlugin} instead. */ -class RetryPlugin implements Plugin +class RetryPlugin extends \Http\Client\Common\Plugin\RetryPlugin implements Plugin { - /** - * Number of retry before sending an exception. - * - * @var int - */ - private $retry; - - /** - * Store the retry counter for each request. - * - * @var array - */ - private $retryStorage = []; - - /** - * @param array $config { - * - * @var int $retries Number of retries to attempt if an exception occurs before letting the exception bubble up. - * } - */ - public function __construct(array $config = []) - { - $resolver = new OptionsResolver(); - $resolver->setDefaults([ - 'retries' => 1, - ]); - $resolver->setAllowedTypes('retries', 'int'); - $options = $resolver->resolve($config); - - $this->retry = $options['retries']; - } - - /** - * {@inheritdoc} - */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) - { - $chainIdentifier = spl_object_hash((object) $first); - - return $next($request)->then(function (ResponseInterface $response) use ($request, $chainIdentifier) { - if (array_key_exists($chainIdentifier, $this->retryStorage)) { - unset($this->retryStorage[$chainIdentifier]); - } - - return $response; - }, function (Exception $exception) use ($request, $next, $first, $chainIdentifier) { - if (!array_key_exists($chainIdentifier, $this->retryStorage)) { - $this->retryStorage[$chainIdentifier] = 0; - } - - if ($this->retryStorage[$chainIdentifier] >= $this->retry) { - unset($this->retryStorage[$chainIdentifier]); - - throw $exception; - } - - ++$this->retryStorage[$chainIdentifier]; - - // Retry in synchrone - $promise = $this->handleRequest($request, $next, $first); - - return $promise->wait(); - }); - } }