├── src ├── Message │ ├── ResponseInterface.php │ ├── RequestInterface.php │ ├── Response.php │ ├── Request.php │ ├── MessageInterface.php │ ├── MessageTrait.php │ ├── InternalRequestInterface.php │ ├── MessageFactoryInterface.php │ ├── InternalRequest.php │ └── MessageFactory.php ├── Asset │ └── AbstractUninstantiableAsset.php ├── Guzzle5HttpAdapter.php ├── Event │ ├── StatusCode │ │ ├── StatusCodeInterface.php │ │ └── StatusCode.php │ ├── Retry │ │ ├── Strategy │ │ │ ├── ConstantDelayedRetryStrategy.php │ │ │ ├── ExponentialDelayedRetryStrategy.php │ │ │ ├── LinearDelayedRetryStrategy.php │ │ │ ├── RetryStrategyChainInterface.php │ │ │ ├── RetryStrategyInterface.php │ │ │ ├── AbstractDelayedRetryStrategy.php │ │ │ ├── LimitedRetryStrategy.php │ │ │ ├── AbstractRetryStrategyChain.php │ │ │ └── CallbackRetryStrategy.php │ │ ├── RetryInterface.php │ │ └── Retry.php │ ├── Cookie │ │ ├── Jar │ │ │ ├── PersistentCookieJarInterface.php │ │ │ ├── SessionCookieJar.php │ │ │ ├── FileCookieJar.php │ │ │ ├── AbstractPersistentCookieJar.php │ │ │ └── CookieJarInterface.php │ │ ├── CookieFactoryInterface.php │ │ ├── CookieFactory.php │ │ └── CookieInterface.php │ ├── History │ │ ├── JournalEntryFactory.php │ │ ├── JournalEntryFactoryInterface.php │ │ ├── JournalEntryInterface.php │ │ ├── JournalEntry.php │ │ ├── JournalInterface.php │ │ └── Journal.php │ ├── Events.php │ ├── Timer │ │ ├── TimerInterface.php │ │ └── Timer.php │ ├── AbstractEvent.php │ ├── Cache │ │ ├── Adapter │ │ │ ├── CacheAdapterInterface.php │ │ │ ├── DoctrineCacheAdapter.php │ │ │ └── StashCacheAdapter.php │ │ └── CacheInterface.php │ ├── Subscriber │ │ ├── AbstractTimerSubscriber.php │ │ ├── AbstractFormatterSubscriber.php │ │ ├── BasicAuthSubscriber.php │ │ ├── RetrySubscriber.php │ │ ├── StatusCodeSubscriber.php │ │ ├── HistorySubscriber.php │ │ ├── CookieSubscriber.php │ │ ├── RedirectSubscriber.php │ │ ├── StopwatchSubscriber.php │ │ └── CacheSubscriber.php │ ├── Formatter │ │ ├── FormatterInterface.php │ │ └── Formatter.php │ ├── BasicAuth │ │ ├── BasicAuthInterface.php │ │ └── BasicAuth.php │ ├── RequestErroredEvent.php │ ├── Redirect │ │ └── RedirectInterface.php │ ├── RequestSentEvent.php │ └── RequestCreatedEvent.php ├── Extractor │ ├── StatusCodeExtractor.php │ ├── ProtocolVersionExtractor.php │ └── StatusLineExtractor.php ├── FileGetContentsHttpAdapter.php ├── Normalizer │ ├── BodyNormalizer.php │ └── HeadersNormalizer.php ├── FopenHttpAdapter.php ├── Parser │ ├── HeadersParser.php │ └── CookieParser.php ├── PsrHttpAdapterInterface.php ├── StopwatchHttpAdapter.php ├── MockHttpAdapter.php ├── AbstractStreamHttpAdapter.php ├── CakeHttpAdapter.php ├── RequestsHttpAdapter.php ├── ReactHttpAdapter.php ├── PeclHttpAdapter.php ├── Zend2HttpAdapter.php ├── ConfigurationInterface.php ├── HttpfulHttpAdapter.php ├── Zend1HttpAdapter.php ├── PsrHttpAdapterDecorator.php ├── BuzzHttpAdapter.php ├── AbstractCurlHttpAdapter.php ├── HttpAdapterInterface.php └── Configuration.php ├── LICENSE ├── doc ├── docker.md ├── installation.md ├── usage.md ├── psr-7.md └── configuration.md ├── composer.json ├── README.md └── CHANGELOG.md /src/Message/ResponseInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Message; 13 | 14 | use Psr\Http\Message\ResponseInterface as PsrResponseInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | interface ResponseInterface extends PsrResponseInterface, MessageInterface 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Asset/AbstractUninstantiableAsset.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Asset; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | abstract class AbstractUninstantiableAsset 18 | { 19 | /** 20 | * @codeCoverageIgnore 21 | */ 22 | final private function __construct() 23 | { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Guzzle5HttpAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | class Guzzle5HttpAdapter extends Guzzle4HttpAdapter 18 | { 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public function getName() 23 | { 24 | return 'guzzle5'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Event/StatusCode/StatusCodeInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\StatusCode; 13 | 14 | use Ivory\HttpAdapter\Message\ResponseInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | interface StatusCodeInterface 20 | { 21 | /** 22 | * @param ResponseInterface $response 23 | * 24 | * @return bool 25 | */ 26 | public function validate(ResponseInterface $response); 27 | } 28 | -------------------------------------------------------------------------------- /src/Event/Retry/Strategy/ConstantDelayedRetryStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Retry\Strategy; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class ConstantDelayedRetryStrategy extends AbstractDelayedRetryStrategy 20 | { 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | protected function doDelay(InternalRequestInterface $request) 25 | { 26 | return $this->getDelay(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Event/Cookie/Jar/PersistentCookieJarInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Cookie\Jar; 13 | 14 | use Ivory\HttpAdapter\HttpAdapterException; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | interface PersistentCookieJarInterface extends CookieJarInterface, \Serializable 20 | { 21 | /** 22 | * @throws HttpAdapterException 23 | */ 24 | public function load(); 25 | 26 | /** 27 | * @throws HttpAdapterException 28 | */ 29 | public function save(); 30 | } 31 | -------------------------------------------------------------------------------- /src/Extractor/StatusCodeExtractor.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Extractor; 13 | 14 | use Ivory\HttpAdapter\Asset\AbstractUninstantiableAsset; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class StatusCodeExtractor extends AbstractUninstantiableAsset 20 | { 21 | /** 22 | * @param array|string $headers 23 | * 24 | * @return int 25 | */ 26 | public static function extract($headers) 27 | { 28 | return (int) substr(StatusLineExtractor::extract($headers), 9, 3); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Event/StatusCode/StatusCode.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\StatusCode; 13 | 14 | use Ivory\HttpAdapter\Message\ResponseInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class StatusCode implements StatusCodeInterface 20 | { 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | public function validate(ResponseInterface $response) 25 | { 26 | $statusCode = (string) $response->getStatusCode(); 27 | 28 | return $statusCode[0] !== '4' && $statusCode[0] !== '5'; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Extractor/ProtocolVersionExtractor.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Extractor; 13 | 14 | use Ivory\HttpAdapter\Asset\AbstractUninstantiableAsset; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class ProtocolVersionExtractor extends AbstractUninstantiableAsset 20 | { 21 | /** 22 | * @param array|string $headers 23 | * 24 | * @return string 25 | */ 26 | public static function extract($headers) 27 | { 28 | return substr(StatusLineExtractor::extract($headers), 5, 3); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Event/History/JournalEntryFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\History; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | use Ivory\HttpAdapter\Message\ResponseInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class JournalEntryFactory implements JournalEntryFactoryInterface 21 | { 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public function create(InternalRequestInterface $request, ResponseInterface $response) 26 | { 27 | return new JournalEntry($request, $response); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Message/RequestInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Message; 13 | 14 | use Psr\Http\Message\RequestInterface as PsrRequestInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | interface RequestInterface extends PsrRequestInterface, MessageInterface 20 | { 21 | const METHOD_GET = 'GET'; 22 | const METHOD_HEAD = 'HEAD'; 23 | const METHOD_TRACE = 'TRACE'; 24 | const METHOD_POST = 'POST'; 25 | const METHOD_PUT = 'PUT'; 26 | const METHOD_PATCH = 'PATCH'; 27 | const METHOD_DELETE = 'DELETE'; 28 | const METHOD_OPTIONS = 'OPTIONS'; 29 | } 30 | -------------------------------------------------------------------------------- /src/Event/Retry/Strategy/ExponentialDelayedRetryStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Retry\Strategy; 13 | 14 | use Ivory\HttpAdapter\Event\Retry\RetryInterface; 15 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class ExponentialDelayedRetryStrategy extends AbstractRetryStrategyChain 21 | { 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | protected function doDelay(InternalRequestInterface $request) 26 | { 27 | return pow(2, $request->getParameter(RetryInterface::RETRY_COUNT)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Extractor/StatusLineExtractor.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Extractor; 13 | 14 | use Ivory\HttpAdapter\Asset\AbstractUninstantiableAsset; 15 | use Ivory\HttpAdapter\Parser\HeadersParser; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class StatusLineExtractor extends AbstractUninstantiableAsset 21 | { 22 | /** 23 | * @param array|string $headers 24 | * 25 | * @return string 26 | */ 27 | public static function extract($headers) 28 | { 29 | $headers = HeadersParser::parse($headers); 30 | 31 | return $headers[0]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Event/History/JournalEntryFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\History; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | use Ivory\HttpAdapter\Message\ResponseInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | interface JournalEntryFactoryInterface 21 | { 22 | /** 23 | * @param InternalRequestInterface $request 24 | * @param ResponseInterface $response 25 | * 26 | * @return JournalEntryInterface 27 | */ 28 | public function create(InternalRequestInterface $request, ResponseInterface $response); 29 | } 30 | -------------------------------------------------------------------------------- /src/Event/Retry/Strategy/LinearDelayedRetryStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Retry\Strategy; 13 | 14 | use Ivory\HttpAdapter\Event\Retry\RetryInterface; 15 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class LinearDelayedRetryStrategy extends AbstractDelayedRetryStrategy 21 | { 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | protected function doDelay(InternalRequestInterface $request) 26 | { 27 | return $this->getDelay() * $request->getParameter(RetryInterface::RETRY_COUNT); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/FileGetContentsHttpAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | class FileGetContentsHttpAdapter extends AbstractStreamHttpAdapter 18 | { 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public function getName() 23 | { 24 | return 'file_get_contents'; 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | protected function process($uri, $context) 31 | { 32 | $http_response_header = []; 33 | 34 | return [@file_get_contents($uri, false, $context), $http_response_header]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Event/Retry/Strategy/RetryStrategyChainInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Retry\Strategy; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | interface RetryStrategyChainInterface extends RetryStrategyInterface 18 | { 19 | /** 20 | * @return bool 21 | */ 22 | public function hasNext(); 23 | 24 | /** 25 | * @return RetryStrategyChainInterface|null 26 | */ 27 | public function getNext(); 28 | 29 | /** 30 | * @param RetryStrategyChainInterface|null $next 31 | */ 32 | public function setNext(RetryStrategyChainInterface $next = null); 33 | } 34 | -------------------------------------------------------------------------------- /src/Event/Cookie/CookieFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Cookie; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | interface CookieFactoryInterface 18 | { 19 | /** 20 | * @param string $name 21 | * @param string $value 22 | * @param array $attributes 23 | * @param int $createdAt 24 | * 25 | * @return CookieInterface 26 | */ 27 | public function create($name, $value, array $attributes, $createdAt); 28 | 29 | /** 30 | * @param string $header 31 | * 32 | * @return CookieInterface 33 | */ 34 | public function parse($header); 35 | } 36 | -------------------------------------------------------------------------------- /src/Event/Retry/Strategy/RetryStrategyInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Retry\Strategy; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | interface RetryStrategyInterface 20 | { 21 | /** 22 | * @param InternalRequestInterface $request 23 | * 24 | * @return bool 25 | */ 26 | public function verify(InternalRequestInterface $request); 27 | 28 | /** 29 | * @param InternalRequestInterface $request 30 | * 31 | * @return float 32 | */ 33 | public function delay(InternalRequestInterface $request); 34 | } 35 | -------------------------------------------------------------------------------- /src/Event/Events.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event; 13 | 14 | use Ivory\HttpAdapter\Asset\AbstractUninstantiableAsset; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class Events extends AbstractUninstantiableAsset 20 | { 21 | const REQUEST_CREATED = 'ivory.http_adapter.request_created'; 22 | const REQUEST_SENT = 'ivory.http_adapter.request_sent'; 23 | const REQUEST_ERRORED = 'ivory.http_adapter.request_errored'; 24 | const MULTI_REQUEST_CREATED = 'ivory.http_adapter.multi_request_created'; 25 | const MULTI_REQUEST_SENT = 'ivory.http_adapter.multi_request_sent'; 26 | const MULTI_REQUEST_ERRORED = 'ivory.http_adapter.multi_request_errored'; 27 | } 28 | -------------------------------------------------------------------------------- /src/Event/Cookie/CookieFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Cookie; 13 | 14 | use Ivory\HttpAdapter\Parser\CookieParser; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class CookieFactory implements CookieFactoryInterface 20 | { 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | public function create($name, $value, array $attributes, $createdAt) 25 | { 26 | return new Cookie($name, $value, $attributes, $createdAt); 27 | } 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function parse($header) 33 | { 34 | list($name, $value, $attributes) = CookieParser::parse($header); 35 | 36 | return $this->create($name, $value, $attributes, time()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Event/Timer/TimerInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Timer; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | interface TimerInterface 20 | { 21 | const START_TIME = 'start_time'; 22 | const TIME = 'time'; 23 | 24 | /** 25 | * @param InternalRequestInterface $internalRequest 26 | * 27 | * @return InternalRequestInterface 28 | */ 29 | public function start(InternalRequestInterface $internalRequest); 30 | 31 | /** 32 | * @param InternalRequestInterface $internalRequest 33 | * 34 | * @return InternalRequestInterface 35 | */ 36 | public function stop(InternalRequestInterface $internalRequest); 37 | } 38 | -------------------------------------------------------------------------------- /src/Event/AbstractEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event; 13 | 14 | use Ivory\HttpAdapter\HttpAdapterInterface; 15 | use Symfony\Component\EventDispatcher\Event; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | abstract class AbstractEvent extends Event 21 | { 22 | /** 23 | * @var HttpAdapterInterface 24 | */ 25 | private $httpAdapter; 26 | 27 | /** 28 | * @param HttpAdapterInterface $httpAdapter 29 | */ 30 | public function __construct(HttpAdapterInterface $httpAdapter) 31 | { 32 | $this->httpAdapter = $httpAdapter; 33 | } 34 | 35 | /** 36 | * @return HttpAdapterInterface 37 | */ 38 | public function getHttpAdapter() 39 | { 40 | return $this->httpAdapter; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Normalizer/BodyNormalizer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Normalizer; 13 | 14 | use Ivory\HttpAdapter\Asset\AbstractUninstantiableAsset; 15 | use Ivory\HttpAdapter\Message\RequestInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class BodyNormalizer extends AbstractUninstantiableAsset 21 | { 22 | /** 23 | * @param mixed $body 24 | * @param string $method 25 | * 26 | * @return mixed 27 | */ 28 | public static function normalize($body, $method) 29 | { 30 | if ($method === RequestInterface::METHOD_HEAD || empty($body)) { 31 | return; 32 | } 33 | 34 | if (is_callable($body)) { 35 | return call_user_func($body); 36 | } 37 | 38 | return $body; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Event/Cache/Adapter/CacheAdapterInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Cache\Adapter; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | interface CacheAdapterInterface 18 | { 19 | /** 20 | * @param string $id 21 | * 22 | * @return bool 23 | */ 24 | public function has($id); 25 | 26 | /** 27 | * @param string $id 28 | * 29 | * @return mixed 30 | */ 31 | public function get($id); 32 | 33 | /** 34 | * @param string $id 35 | * @param mixed $data 36 | * @param int $lifeTime 37 | * 38 | * @return bool 39 | */ 40 | public function set($id, $data, $lifeTime = 0); 41 | 42 | /** 43 | * @param string $id 44 | * 45 | * @return bool 46 | */ 47 | public function remove($id); 48 | } 49 | -------------------------------------------------------------------------------- /src/Event/History/JournalEntryInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\History; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | use Ivory\HttpAdapter\Message\ResponseInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | interface JournalEntryInterface 21 | { 22 | /** 23 | * @return InternalRequestInterface 24 | */ 25 | public function getRequest(); 26 | 27 | /** 28 | * @param InternalRequestInterface $request 29 | */ 30 | public function setRequest(InternalRequestInterface $request); 31 | 32 | /** 33 | * @return ResponseInterface 34 | */ 35 | public function getResponse(); 36 | 37 | /** 38 | * @param ResponseInterface $response 39 | */ 40 | public function setResponse(ResponseInterface $response); 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2017 Eric GELOEN 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 4 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 5 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 6 | persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 9 | Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 12 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 13 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 14 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /src/Event/Subscriber/AbstractTimerSubscriber.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Subscriber; 13 | 14 | use Ivory\HttpAdapter\Event\Timer\Timer; 15 | use Ivory\HttpAdapter\Event\Timer\TimerInterface; 16 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; 17 | 18 | /** 19 | * @author GeLo 20 | */ 21 | abstract class AbstractTimerSubscriber implements EventSubscriberInterface 22 | { 23 | /** 24 | * @var TimerInterface 25 | */ 26 | private $timer; 27 | 28 | /** 29 | * @param TimerInterface|null $timer 30 | */ 31 | public function __construct(TimerInterface $timer = null) 32 | { 33 | $this->timer = $timer ?: new Timer(); 34 | } 35 | 36 | /** 37 | * @return TimerInterface 38 | */ 39 | public function getTimer() 40 | { 41 | return $this->timer; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/FopenHttpAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | class FopenHttpAdapter extends AbstractStreamHttpAdapter 18 | { 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public function getName() 23 | { 24 | return 'fopen'; 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | protected function process($uri, $context) 31 | { 32 | $http_response_header = []; 33 | $resource = @fopen($uri, 'rb', false, $context); 34 | 35 | if (is_resource($resource)) { 36 | $copy = @fopen('php://memory', 'rb+'); 37 | stream_copy_to_stream($resource, $copy); 38 | fclose($resource); 39 | } else { 40 | $copy = $resource; 41 | } 42 | 43 | return [$copy, $http_response_header]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Message/Response.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Message; 13 | 14 | use Psr\Http\Message\StreamInterface; 15 | use Zend\Diactoros\Response as DiactorosResponse; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class Response extends DiactorosResponse implements ResponseInterface 21 | { 22 | use MessageTrait; 23 | 24 | /** 25 | * @param string|resource|StreamInterface $body 26 | * @param int $status 27 | * @param array $headers 28 | * @param array $parameters 29 | */ 30 | public function __construct( 31 | $body = 'php://memory', 32 | $status = 200, 33 | array $headers = [], 34 | array $parameters = [] 35 | ) { 36 | parent::__construct($body, $status, $headers); 37 | 38 | $this->parameters = $parameters; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Event/Retry/RetryInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Retry; 13 | 14 | use Ivory\HttpAdapter\Event\Retry\Strategy\RetryStrategyInterface; 15 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | interface RetryInterface 21 | { 22 | const RETRY_COUNT = 'retry_count'; 23 | 24 | /** 25 | * @return RetryStrategyInterface 26 | */ 27 | public function getStrategy(); 28 | 29 | /** 30 | * @param RetryStrategyInterface $strategy 31 | */ 32 | public function setStrategy(RetryStrategyInterface $strategy); 33 | 34 | /** 35 | * @param InternalRequestInterface $internalRequest 36 | * @param bool $wait 37 | * 38 | * @return InternalRequestInterface|bool 39 | */ 40 | public function retry(InternalRequestInterface $internalRequest, $wait = true); 41 | } 42 | -------------------------------------------------------------------------------- /src/Event/Formatter/FormatterInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Formatter; 13 | 14 | use Ivory\HttpAdapter\HttpAdapterException; 15 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 16 | use Ivory\HttpAdapter\Message\ResponseInterface; 17 | 18 | /** 19 | * @author GeLo 20 | */ 21 | interface FormatterInterface 22 | { 23 | /** 24 | * @param InternalRequestInterface $request 25 | * 26 | * @return array 27 | */ 28 | public function formatRequest(InternalRequestInterface $request); 29 | 30 | /** 31 | * @param ResponseInterface $response 32 | * 33 | * @return array 34 | */ 35 | public function formatResponse(ResponseInterface $response); 36 | 37 | /** 38 | * @param HttpAdapterException $exception 39 | * 40 | * @return array 41 | */ 42 | public function formatException(HttpAdapterException $exception); 43 | } 44 | -------------------------------------------------------------------------------- /src/Event/Retry/Strategy/AbstractDelayedRetryStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Retry\Strategy; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | abstract class AbstractDelayedRetryStrategy extends AbstractRetryStrategyChain 18 | { 19 | /** 20 | * @var float 21 | */ 22 | private $delay; 23 | 24 | /** 25 | * @param float $delay 26 | * @param RetryStrategyChainInterface|null $next 27 | */ 28 | public function __construct($delay = 5.0, RetryStrategyChainInterface $next = null) 29 | { 30 | parent::__construct($next); 31 | 32 | $this->setDelay($delay); 33 | } 34 | 35 | /** 36 | * @return float 37 | */ 38 | public function getDelay() 39 | { 40 | return $this->delay; 41 | } 42 | 43 | /** 44 | * @param float $delay 45 | */ 46 | public function setDelay($delay) 47 | { 48 | $this->delay = $delay; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Message/Request.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Message; 13 | 14 | use Psr\Http\Message\StreamInterface; 15 | use Psr\Http\Message\UriInterface; 16 | use Zend\Diactoros\Request as DiactorosRequest; 17 | 18 | /** 19 | * @author GeLo 20 | */ 21 | class Request extends DiactorosRequest implements RequestInterface 22 | { 23 | use MessageTrait; 24 | 25 | /** 26 | * @param string|UriInterface|null $uri 27 | * @param string|null $method 28 | * @param string|resource|StreamInterface $body 29 | * @param array $headers 30 | * @param array $parameters 31 | */ 32 | public function __construct( 33 | $uri = null, 34 | $method = null, 35 | $body = 'php://memory', 36 | array $headers = [], 37 | array $parameters = [] 38 | ) { 39 | parent::__construct($uri, $method, $body, $headers); 40 | 41 | $this->parameters = $parameters; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Event/Subscriber/AbstractFormatterSubscriber.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Subscriber; 13 | 14 | use Ivory\HttpAdapter\Event\Formatter\Formatter; 15 | use Ivory\HttpAdapter\Event\Formatter\FormatterInterface; 16 | use Ivory\HttpAdapter\Event\Timer\TimerInterface; 17 | 18 | /** 19 | * @author GeLo 20 | */ 21 | abstract class AbstractFormatterSubscriber extends AbstractTimerSubscriber 22 | { 23 | /** 24 | * @var FormatterInterface 25 | */ 26 | private $formatter; 27 | 28 | /** 29 | * @param FormatterInterface|null $formatter 30 | * @param TimerInterface|null $timer 31 | */ 32 | public function __construct(FormatterInterface $formatter = null, TimerInterface $timer = null) 33 | { 34 | parent::__construct($timer); 35 | 36 | $this->formatter = $formatter ?: new Formatter(); 37 | } 38 | 39 | /** 40 | * @return FormatterInterface 41 | */ 42 | public function getFormatter() 43 | { 44 | return $this->formatter; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /doc/docker.md: -------------------------------------------------------------------------------- 1 | # Docker 2 | 3 | The most easy way to set up the project is to install [Docker](https://www.docker.com) and 4 | [Docker Composer](https://docs.docker.com/compose/) and build the project. 5 | 6 | ## Configure 7 | 8 | The configuration is shipped with a distribution environment file allowing you to customize your IDE and XDebug 9 | settings as well as your current user/group ID: 10 | 11 | ``` bash 12 | $ cp .env.dist .env 13 | ``` 14 | 15 | **The most important part is the `USER_ID` and `GROUP_ID` which should match your current user/group.** 16 | 17 | ## Build 18 | 19 | Once you have configured your environment, you can build the project: 20 | 21 | ``` bash 22 | $ docker-compose build 23 | ``` 24 | 25 | ## Composer 26 | 27 | Install the dependencies via [Composer](https://getcomposer.org/): 28 | 29 | ``` bash 30 | $ docker-compose run --rm php composer install 31 | ``` 32 | 33 | ## Tests 34 | 35 | To run the test suite, you can use: 36 | 37 | ``` bash 38 | $ docker-compose run --rm php vendor/bin/phpunit 39 | ``` 40 | 41 | Lot of tests require a remote server, you can start it with: 42 | 43 | ``` bash 44 | $ docker-compose up -d 45 | ``` 46 | 47 | ## XDebug 48 | 49 | If you want to use XDebug, make sure you have fully configured your `.env` file and use: 50 | 51 | ``` bash 52 | $ docker-compose run --rm -e XDEBUG=1 php vendor/bin/phpunit 53 | ``` 54 | -------------------------------------------------------------------------------- /src/Event/Timer/Timer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Timer; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class Timer implements TimerInterface 20 | { 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | public function start(InternalRequestInterface $internalRequest) 25 | { 26 | return $internalRequest 27 | ->withParameter(self::START_TIME, $this->getTime()) 28 | ->withoutParameter(self::TIME); 29 | } 30 | 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | public function stop(InternalRequestInterface $internalRequest) 35 | { 36 | if ($internalRequest->hasParameter(self::START_TIME) && !$internalRequest->hasParameter(self::TIME)) { 37 | return $internalRequest->withParameter( 38 | self::TIME, 39 | $this->getTime() - $internalRequest->getParameter(self::START_TIME) 40 | ); 41 | } 42 | 43 | return $internalRequest; 44 | } 45 | 46 | /** 47 | * @return float 48 | */ 49 | private function getTime() 50 | { 51 | return microtime(true); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Event/Cache/Adapter/DoctrineCacheAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Cache\Adapter; 13 | 14 | use Doctrine\Common\Cache\Cache; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class DoctrineCacheAdapter implements CacheAdapterInterface 20 | { 21 | /** 22 | * @var Cache 23 | */ 24 | private $cache; 25 | 26 | /** 27 | * @param Cache $cache 28 | */ 29 | public function __construct(Cache $cache) 30 | { 31 | $this->cache = $cache; 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function has($id) 38 | { 39 | return $this->cache->contains($id); 40 | } 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | public function get($id) 46 | { 47 | return $this->has($id) ? $this->cache->fetch($id) : null; 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public function set($id, $data, $lifeTime = 0) 54 | { 55 | return $this->cache->save($id, $data, $lifeTime); 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | public function remove($id) 62 | { 63 | return $this->cache->delete($id); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Event/BasicAuth/BasicAuthInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\BasicAuth; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | interface BasicAuthInterface 20 | { 21 | /** 22 | * @return string 23 | */ 24 | public function getUsername(); 25 | 26 | /** 27 | * @param string $username 28 | */ 29 | public function setUsername($username); 30 | 31 | /** 32 | * @return string 33 | */ 34 | public function getPassword(); 35 | 36 | /** 37 | * @param string $password 38 | */ 39 | public function setPassword($password); 40 | 41 | /** 42 | * @return bool 43 | */ 44 | public function hasMatcher(); 45 | 46 | /** 47 | * @return string|callable|null 48 | */ 49 | public function getMatcher(); 50 | 51 | /** 52 | * @param string|callable|null $matcher 53 | */ 54 | public function setMatcher($matcher); 55 | 56 | /** 57 | * @param InternalRequestInterface $internalRequest 58 | * 59 | * @return InternalRequestInterface 60 | */ 61 | public function authenticate(InternalRequestInterface $internalRequest); 62 | } 63 | -------------------------------------------------------------------------------- /src/Parser/HeadersParser.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Parser; 13 | 14 | use Ivory\HttpAdapter\Asset\AbstractUninstantiableAsset; 15 | use Ivory\HttpAdapter\Normalizer\HeadersNormalizer; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class HeadersParser extends AbstractUninstantiableAsset 21 | { 22 | /** 23 | * @param array|string $headers 24 | * 25 | * @return array 26 | */ 27 | public static function parse($headers) 28 | { 29 | if (is_string($headers)) { 30 | $headers = explode("\r\n\r\n", trim($headers)); 31 | 32 | return explode("\r\n", end($headers)); 33 | } 34 | 35 | $parsedHeaders = []; 36 | 37 | foreach ($headers as $name => $value) { 38 | $value = HeadersNormalizer::normalizeHeaderValue($value); 39 | 40 | if (is_int($name)) { 41 | if (strpos($value, 'HTTP/') === 0) { 42 | $parsedHeaders = [$value]; 43 | } else { 44 | $parsedHeaders[] = $value; 45 | } 46 | } else { 47 | $parsedHeaders[] = $name.': '.$value; 48 | } 49 | } 50 | 51 | return $parsedHeaders; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Event/Retry/Strategy/LimitedRetryStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Retry\Strategy; 13 | 14 | use Ivory\HttpAdapter\Event\Retry\RetryInterface; 15 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class LimitedRetryStrategy extends AbstractRetryStrategyChain 21 | { 22 | /** 23 | * @var int 24 | */ 25 | private $limit; 26 | 27 | /** 28 | * @param int $limit 29 | * @param RetryStrategyChainInterface|null $next 30 | */ 31 | public function __construct($limit = 3, RetryStrategyChainInterface $next = null) 32 | { 33 | parent::__construct($next); 34 | 35 | $this->setLimit($limit); 36 | } 37 | 38 | /** 39 | * @return int 40 | */ 41 | public function getLimit() 42 | { 43 | return $this->limit; 44 | } 45 | 46 | /** 47 | * @param int $limit 48 | */ 49 | public function setLimit($limit) 50 | { 51 | $this->limit = $limit; 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | protected function doVerify(InternalRequestInterface $request) 58 | { 59 | return $request->getParameter(RetryInterface::RETRY_COUNT) < $this->limit; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Message/MessageInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Message; 13 | 14 | use Psr\Http\Message\MessageInterface as PsrMessageInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | interface MessageInterface extends PsrMessageInterface 20 | { 21 | const PROTOCOL_VERSION_1_0 = '1.0'; 22 | const PROTOCOL_VERSION_1_1 = '1.1'; 23 | 24 | /** 25 | * @return array 26 | */ 27 | public function getParameters(); 28 | 29 | /** 30 | * @param string $name 31 | * 32 | * @return bool 33 | */ 34 | public function hasParameter($name); 35 | 36 | /** 37 | * @param string $name 38 | * 39 | * @return mixed 40 | */ 41 | public function getParameter($name); 42 | 43 | /** 44 | * @param string $name 45 | * @param mixed $value 46 | * 47 | * @return MessageInterface 48 | */ 49 | public function withParameter($name, $value); 50 | 51 | /** 52 | * @param string $name 53 | * @param mixed $value 54 | * 55 | * @return MessageInterface 56 | */ 57 | public function withAddedParameter($name, $value); 58 | 59 | /** 60 | * @param string $name 61 | * 62 | * @return MessageInterface 63 | */ 64 | public function withoutParameter($name); 65 | } 66 | -------------------------------------------------------------------------------- /src/Event/Cache/Adapter/StashCacheAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Cache\Adapter; 13 | 14 | use Stash\Pool; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class StashCacheAdapter implements CacheAdapterInterface 20 | { 21 | /** 22 | * @var Pool 23 | */ 24 | private $pool; 25 | 26 | /** 27 | * @param Pool $pool 28 | */ 29 | public function __construct(Pool $pool) 30 | { 31 | $this->pool = $pool; 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function has($id) 38 | { 39 | return !$this->pool->getItem($id)->isMiss(); 40 | } 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | public function get($id) 46 | { 47 | return $this->pool->getItem($id)->get(); 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public function set($id, $data, $lifeTime = 0) 54 | { 55 | $result = $this->pool->getItem($id)->set($data, $lifeTime); 56 | $this->pool->flush(); 57 | 58 | return $result; 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | public function remove($id) 65 | { 66 | $result = $this->pool->getItem($id)->clear(); 67 | $this->pool->flush(); 68 | 69 | return $result; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/PsrHttpAdapterInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter; 13 | 14 | use Ivory\HttpAdapter\Message\ResponseInterface; 15 | use Psr\Http\Message\RequestInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | interface PsrHttpAdapterInterface 21 | { 22 | const VERSION = '0.8.0-DEV'; 23 | const VERSION_ID = '00800'; 24 | const MAJOR_VERSION = '0'; 25 | const MINOR_VERSION = '8'; 26 | const PATCH_VERSION = '0'; 27 | const EXTRA_VERSION = 'DEV'; 28 | 29 | /** 30 | * @return ConfigurationInterface 31 | */ 32 | public function getConfiguration(); 33 | 34 | /** 35 | * @param ConfigurationInterface $configuration 36 | */ 37 | public function setConfiguration(ConfigurationInterface $configuration); 38 | 39 | /** 40 | * @param RequestInterface $request 41 | * 42 | * @throws HttpAdapterException 43 | * 44 | * @return ResponseInterface 45 | */ 46 | public function sendRequest(RequestInterface $request); 47 | 48 | /** 49 | * @param array $requests 50 | * 51 | * @throws MultiHttpAdapterException 52 | * 53 | * @return array $responses 54 | */ 55 | public function sendRequests(array $requests); 56 | 57 | /** 58 | * @return string 59 | */ 60 | public function getName(); 61 | } 62 | -------------------------------------------------------------------------------- /src/Event/Cookie/Jar/SessionCookieJar.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Cookie\Jar; 13 | 14 | use Ivory\HttpAdapter\Event\Cookie\CookieFactoryInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class SessionCookieJar extends AbstractPersistentCookieJar 20 | { 21 | /** 22 | * @var string 23 | */ 24 | private $key; 25 | 26 | /** 27 | * @param string $key 28 | * @param CookieFactoryInterface|null $cookieFactory 29 | */ 30 | public function __construct($key, CookieFactoryInterface $cookieFactory = null) 31 | { 32 | $this->setKey($key); 33 | 34 | parent::__construct($cookieFactory); 35 | } 36 | 37 | /** 38 | * @return string 39 | */ 40 | public function getKey() 41 | { 42 | return $this->key; 43 | } 44 | 45 | /** 46 | * @param string $key 47 | */ 48 | public function setKey($key) 49 | { 50 | $this->key = $key; 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function load() 57 | { 58 | $this->unserialize(isset($_SESSION[$this->key]) ? $_SESSION[$this->key] : null); 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | public function save() 65 | { 66 | $_SESSION[$this->key] = $this->serialize(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /doc/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | To install the Ivory http adapter library, you will need [Composer](http://getcomposer.org). It's a PHP 5.3+ 4 | dependency manager which allows you to declare the dependent libraries your project needs and it will install & 5 | autoload them for you. 6 | 7 | ## Set up Composer 8 | 9 | Composer comes with a simple phar file. To easily access it from anywhere on your system, you can execute: 10 | 11 | ``` 12 | $ curl -s https://getcomposer.org/installer | php 13 | $ sudo mv composer.phar /usr/local/bin/composer 14 | ``` 15 | 16 | ## Define dependencies 17 | 18 | Create a ``composer.json`` file at the root directory of your project and simply require the 19 | ``egeloen/http-adapter`` package: 20 | 21 | ``` 22 | { 23 | "require": { 24 | "egeloen/http-adapter": "*" 25 | } 26 | } 27 | ``` 28 | 29 | Obviously, if you want to use an adapter which requires an extra package, you will need to require it too. See the 30 | [composer.json](/composer.json) for the allowed packages. 31 | 32 | ## Install dependencies 33 | 34 | Now, you have define your dependencies, you can install them: 35 | 36 | ``` 37 | $ composer install 38 | ``` 39 | 40 | Composer will automatically download your dependencies & create an autoload file in the ``vendor`` directory. 41 | 42 | ## Autoload 43 | 44 | So easy, you just have to require the generated autoload file and you are already ready to play: 45 | 46 | ``` php 47 | require __DIR__.'/vendor/autoload.php'; 48 | 49 | use Ivory\HttpAdapter; 50 | 51 | // ... 52 | ``` 53 | 54 | The Ivory Http Adapter library follows the [PSR-4 Standard](http://www.php-fig.org/psr/psr-4/). If you prefer install 55 | it manually, it can be autoload by any convenient autoloader. 56 | -------------------------------------------------------------------------------- /src/Event/Cache/CacheInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Cache; 13 | 14 | use Ivory\HttpAdapter\HttpAdapterException; 15 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 16 | use Ivory\HttpAdapter\Message\MessageFactoryInterface; 17 | use Ivory\HttpAdapter\Message\ResponseInterface; 18 | 19 | /** 20 | * @author GeLo 21 | */ 22 | interface CacheInterface 23 | { 24 | /** 25 | * @param InternalRequestInterface $internalRequest 26 | * @param MessageFactoryInterface $messageFactory 27 | * 28 | * @return ResponseInterface|null 29 | */ 30 | public function getResponse(InternalRequestInterface $internalRequest, MessageFactoryInterface $messageFactory); 31 | 32 | /** 33 | * @param InternalRequestInterface $internalRequest 34 | * @param MessageFactoryInterface $messageFactory 35 | * 36 | * @return HttpAdapterException|null 37 | */ 38 | public function getException(InternalRequestInterface $internalRequest, MessageFactoryInterface $messageFactory); 39 | 40 | /** 41 | * @param ResponseInterface $response 42 | * @param InternalRequestInterface $internalRequest 43 | */ 44 | public function saveResponse(ResponseInterface $response, InternalRequestInterface $internalRequest); 45 | 46 | /** 47 | * @param HttpAdapterException $exception 48 | * @param InternalRequestInterface $internalRequest 49 | */ 50 | public function saveException(HttpAdapterException $exception, InternalRequestInterface $internalRequest); 51 | } 52 | -------------------------------------------------------------------------------- /src/Event/History/JournalEntry.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\History; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | use Ivory\HttpAdapter\Message\ResponseInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class JournalEntry implements JournalEntryInterface 21 | { 22 | /** 23 | * @var \Ivory\HttpAdapter\Message\InternalRequestInterface 24 | */ 25 | private $request; 26 | 27 | /** 28 | * @var \Ivory\HttpAdapter\Message\ResponseInterface 29 | */ 30 | private $response; 31 | 32 | /** 33 | * @param InternalRequestInterface $request 34 | * @param ResponseInterface $response 35 | */ 36 | public function __construct(InternalRequestInterface $request, ResponseInterface $response) 37 | { 38 | $this->setRequest($request); 39 | $this->setResponse($response); 40 | } 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | public function getRequest() 46 | { 47 | return $this->request; 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public function setRequest(InternalRequestInterface $request) 54 | { 55 | $this->request = $request; 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | public function getResponse() 62 | { 63 | return $this->response; 64 | } 65 | 66 | /** 67 | * {@inheritdoc} 68 | */ 69 | public function setResponse(ResponseInterface $response) 70 | { 71 | $this->response = $response; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Parser/CookieParser.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Parser; 13 | 14 | use Ivory\HttpAdapter\Asset\AbstractUninstantiableAsset; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class CookieParser extends AbstractUninstantiableAsset 20 | { 21 | /** 22 | * @param string $header 23 | * 24 | * @return array 25 | */ 26 | public static function parse($header) 27 | { 28 | if (strpos($header, '=') === false) { 29 | $header = '='.$header; 30 | } 31 | 32 | list($name, $header) = explode('=', $header, 2); 33 | 34 | if (strpos($header, ';') === false) { 35 | $value = $header; 36 | $header = null; 37 | } else { 38 | list($value, $header) = explode(';', $header, 2); 39 | } 40 | 41 | $attributes = []; 42 | foreach (array_map('trim', explode(';', $header)) as $pair) { 43 | if (empty($pair)) { 44 | continue; 45 | } 46 | 47 | if (strpos($pair, '=') === false) { 48 | $attributeName = $pair; 49 | $attributeValue = null; 50 | } else { 51 | list($attributeName, $attributeValue) = explode('=', $pair); 52 | } 53 | 54 | $attributes[trim($attributeName)] = $attributeValue ? trim($attributeValue) : true; 55 | } 56 | 57 | $name = trim($name); 58 | $value = trim($value); 59 | 60 | return [!empty($name) ? $name : null, !empty($value) ? $value : null, $attributes]; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Event/Cookie/Jar/FileCookieJar.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Cookie\Jar; 13 | 14 | use Ivory\HttpAdapter\Event\Cookie\CookieFactoryInterface; 15 | use Ivory\HttpAdapter\HttpAdapterException; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class FileCookieJar extends AbstractPersistentCookieJar 21 | { 22 | /** 23 | * @var string 24 | */ 25 | private $file; 26 | 27 | /** 28 | * @param string $file 29 | * @param CookieFactoryInterface|null $cookieFactory 30 | */ 31 | public function __construct($file, CookieFactoryInterface $cookieFactory = null) 32 | { 33 | $this->setFile($file); 34 | 35 | parent::__construct($cookieFactory, file_exists($file)); 36 | } 37 | 38 | /** 39 | * @return string 40 | */ 41 | public function getFile() 42 | { 43 | return $this->file; 44 | } 45 | 46 | /** 47 | * @param string $file 48 | */ 49 | public function setFile($file) 50 | { 51 | $this->file = $file; 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function load() 58 | { 59 | if (($data = @file_get_contents($this->file)) === false) { 60 | $error = error_get_last(); 61 | throw HttpAdapterException::cannotLoadCookieJar($error['message']); 62 | } 63 | 64 | $this->unserialize($data); 65 | } 66 | 67 | /** 68 | * {@inheritdoc} 69 | */ 70 | public function save() 71 | { 72 | if (@file_put_contents($this->file, $this->serialize()) === false) { 73 | $error = error_get_last(); 74 | throw HttpAdapterException::cannotSaveCookieJar($error['message']); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Event/Retry/Retry.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Retry; 13 | 14 | use Ivory\HttpAdapter\Event\Retry\Strategy\ExponentialDelayedRetryStrategy; 15 | use Ivory\HttpAdapter\Event\Retry\Strategy\LimitedRetryStrategy; 16 | use Ivory\HttpAdapter\Event\Retry\Strategy\RetryStrategyInterface; 17 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 18 | 19 | /** 20 | * @author GeLo 21 | */ 22 | class Retry implements RetryInterface 23 | { 24 | /** 25 | * @var RetryStrategyInterface 26 | */ 27 | private $strategy; 28 | 29 | /** 30 | * @param RetryStrategyInterface|null $strategy 31 | */ 32 | public function __construct(RetryStrategyInterface $strategy = null) 33 | { 34 | $this->setStrategy($strategy ?: new LimitedRetryStrategy(3, new ExponentialDelayedRetryStrategy())); 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function getStrategy() 41 | { 42 | return $this->strategy; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function setStrategy(RetryStrategyInterface $strategy) 49 | { 50 | $this->strategy = $strategy; 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function retry(InternalRequestInterface $internalRequest, $wait = true) 57 | { 58 | if (!$this->strategy->verify($internalRequest)) { 59 | return false; 60 | } 61 | 62 | if ($wait && ($delay = $this->strategy->delay($internalRequest)) > 0) { 63 | usleep($delay * 1000000); 64 | } 65 | 66 | return $internalRequest->withParameter( 67 | self::RETRY_COUNT, 68 | $internalRequest->getParameter(self::RETRY_COUNT) + 1 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Event/RequestErroredEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event; 13 | 14 | use Ivory\HttpAdapter\HttpAdapterException; 15 | use Ivory\HttpAdapter\HttpAdapterInterface; 16 | use Ivory\HttpAdapter\Message\ResponseInterface; 17 | 18 | /** 19 | * @author GeLo 20 | */ 21 | class RequestErroredEvent extends AbstractEvent 22 | { 23 | /** 24 | * @var HttpAdapterException 25 | */ 26 | private $exception; 27 | 28 | /** 29 | * @var ResponseInterface|null 30 | */ 31 | private $response; 32 | 33 | /** 34 | * @param HttpAdapterInterface $httpAdapter 35 | * @param HttpAdapterException $exception 36 | */ 37 | public function __construct(HttpAdapterInterface $httpAdapter, HttpAdapterException $exception) 38 | { 39 | parent::__construct($httpAdapter); 40 | 41 | $this->setException($exception); 42 | } 43 | 44 | /** 45 | * @return HttpAdapterException 46 | */ 47 | public function getException() 48 | { 49 | return $this->exception; 50 | } 51 | 52 | /** 53 | * @param HttpAdapterException $exception 54 | */ 55 | public function setException(HttpAdapterException $exception) 56 | { 57 | $this->exception = $exception; 58 | } 59 | 60 | /** 61 | * @return bool 62 | */ 63 | public function hasResponse() 64 | { 65 | return $this->response !== null; 66 | } 67 | 68 | /** 69 | * @return ResponseInterface|null 70 | */ 71 | public function getResponse() 72 | { 73 | return $this->response; 74 | } 75 | 76 | /** 77 | * @param ResponseInterface $response 78 | */ 79 | public function setResponse(ResponseInterface $response) 80 | { 81 | $this->response = $response; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/StopwatchHttpAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | use Symfony\Component\Stopwatch\Stopwatch; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class StopwatchHttpAdapter extends PsrHttpAdapterDecorator 21 | { 22 | /** 23 | * @var Stopwatch\Stopwatch 24 | */ 25 | private $stopwatch; 26 | 27 | /** 28 | * @param HttpAdapterInterface $httpAdapter 29 | * @param Stopwatch $stopwatch 30 | */ 31 | public function __construct(HttpAdapterInterface $httpAdapter, Stopwatch $stopwatch) 32 | { 33 | parent::__construct($httpAdapter); 34 | 35 | $this->stopwatch = $stopwatch; 36 | } 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | protected function doSendInternalRequest(InternalRequestInterface $internalRequest) 42 | { 43 | $this->stopwatch->start($name = 'ivory.http_adapter'); 44 | 45 | try { 46 | $result = parent::doSendInternalRequest($internalRequest); 47 | } catch (\Exception $e) { 48 | $this->stopwatch->stop($name); 49 | 50 | throw $e; 51 | } 52 | 53 | $this->stopwatch->stop($name); 54 | 55 | return $result; 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | protected function doSendInternalRequests(array $internalRequests) 62 | { 63 | $this->stopwatch->start($name = 'ivory.http_adapter'); 64 | 65 | try { 66 | $result = parent::doSendInternalRequests($internalRequests); 67 | } catch (\Exception $e) { 68 | $this->stopwatch->stop($name); 69 | 70 | throw $e; 71 | } 72 | 73 | $this->stopwatch->stop($name); 74 | 75 | return $result; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/MockHttpAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | use Psr\Http\Message\ResponseInterface; 16 | 17 | /** 18 | * @author GeLo 19 | * @author Timothée Barray 20 | */ 21 | class MockHttpAdapter extends AbstractHttpAdapter 22 | { 23 | /** 24 | * @var array 25 | */ 26 | private $queuedResponses = []; 27 | 28 | /** 29 | * @var array 30 | */ 31 | private $receivedRequests = []; 32 | 33 | public function reset() 34 | { 35 | $this->receivedRequests = []; 36 | $this->queuedResponses = []; 37 | } 38 | 39 | /** 40 | * @return array 41 | */ 42 | public function getReceivedRequests() 43 | { 44 | return $this->receivedRequests; 45 | } 46 | 47 | /** 48 | * @return array 49 | */ 50 | public function getQueuedResponses() 51 | { 52 | return $this->queuedResponses; 53 | } 54 | 55 | /** 56 | * @param ResponseInterface $response 57 | */ 58 | public function appendResponse(ResponseInterface $response) 59 | { 60 | $this->queuedResponses[] = $response; 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function getName() 67 | { 68 | return 'mock'; 69 | } 70 | 71 | /** 72 | * {@inheritdoc} 73 | */ 74 | protected function sendInternalRequest(InternalRequestInterface $internalRequest) 75 | { 76 | if (count($this->queuedResponses) <= 0) { 77 | throw new \OutOfBoundsException('You must append a response in the queue before sending a request.'); 78 | } 79 | 80 | $this->receivedRequests[] = $internalRequest; 81 | 82 | return array_shift($this->queuedResponses); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Event/History/JournalInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\History; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | use Ivory\HttpAdapter\Message\ResponseInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | interface JournalInterface extends \Countable, \IteratorAggregate 21 | { 22 | /** 23 | * @param InternalRequestInterface $request 24 | * @param ResponseInterface $response 25 | */ 26 | public function record(InternalRequestInterface $request, ResponseInterface $response); 27 | 28 | public function clear(); 29 | 30 | /** 31 | * @return bool 32 | */ 33 | public function hasEntries(); 34 | 35 | /** 36 | * @return JournalEntryInterface[] 37 | */ 38 | public function getEntries(); 39 | 40 | /** 41 | * @param JournalEntryInterface[] $entries 42 | */ 43 | public function setEntries(array $entries); 44 | 45 | /** 46 | * @param JournalEntryInterface[] $entries 47 | */ 48 | public function addEntries(array $entries); 49 | 50 | /** 51 | * @param JournalEntryInterface[] $entries 52 | */ 53 | public function removeEntries(array $entries); 54 | 55 | /** 56 | * @param JournalEntryInterface $entry 57 | * 58 | * @return bool 59 | */ 60 | public function hasEntry(JournalEntryInterface $entry); 61 | 62 | /** 63 | * @param JournalEntryInterface $entry 64 | */ 65 | public function addEntry(JournalEntryInterface $entry); 66 | 67 | /** 68 | * @param JournalEntryInterface $entry 69 | */ 70 | public function removeEntry(JournalEntryInterface $entry); 71 | 72 | /** 73 | * @return int 74 | */ 75 | public function getLimit(); 76 | 77 | /** 78 | * @param int $limit 79 | */ 80 | public function setLimit($limit); 81 | } 82 | -------------------------------------------------------------------------------- /src/Message/MessageTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Message; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | trait MessageTrait 18 | { 19 | /** 20 | * @var array 21 | */ 22 | private $parameters = []; 23 | 24 | /** 25 | * @return array 26 | */ 27 | public function getParameters() 28 | { 29 | return $this->parameters; 30 | } 31 | 32 | /** 33 | * @param string $name 34 | * 35 | * @return bool 36 | */ 37 | public function hasParameter($name) 38 | { 39 | return isset($this->parameters[$name]); 40 | } 41 | 42 | /** 43 | * @param string $name 44 | * 45 | * @return mixed 46 | */ 47 | public function getParameter($name) 48 | { 49 | return $this->hasParameter($name) ? $this->parameters[$name] : null; 50 | } 51 | 52 | /** 53 | * @param string $name 54 | * @param mixed $value 55 | * 56 | * @return object 57 | */ 58 | public function withParameter($name, $value) 59 | { 60 | $new = clone $this; 61 | $new->parameters[$name] = $value; 62 | 63 | return $new; 64 | } 65 | 66 | /** 67 | * @param string $name 68 | * @param mixed $value 69 | * 70 | * @return object 71 | */ 72 | public function withAddedParameter($name, $value) 73 | { 74 | $new = clone $this; 75 | $new->parameters[$name] = $new->hasParameter($name) 76 | ? array_merge((array) $new->parameters[$name], (array) $value) 77 | : $value; 78 | 79 | return $new; 80 | } 81 | 82 | /** 83 | * @param string $name 84 | * 85 | * @return object 86 | */ 87 | public function withoutParameter($name) 88 | { 89 | $new = clone $this; 90 | unset($new->parameters[$name]); 91 | 92 | return $new; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Event/Cookie/Jar/AbstractPersistentCookieJar.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Cookie\Jar; 13 | 14 | use Ivory\HttpAdapter\Event\Cookie\CookieFactoryInterface; 15 | use Ivory\HttpAdapter\Event\Cookie\CookieInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | abstract class AbstractPersistentCookieJar extends CookieJar implements PersistentCookieJarInterface 21 | { 22 | /** 23 | * @param CookieFactoryInterface|null $cookieFactory 24 | * @param bool $load 25 | */ 26 | public function __construct(CookieFactoryInterface $cookieFactory = null, $load = true) 27 | { 28 | parent::__construct($cookieFactory); 29 | 30 | if ($load) { 31 | $this->load(); 32 | } 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function __destruct() 39 | { 40 | $this->save(); 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function serialize() 47 | { 48 | return json_encode(array_map(function (CookieInterface $cookie) { 49 | return $cookie->toArray(); 50 | }, $this->getCookies())); 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function unserialize($serialized) 57 | { 58 | $data = json_decode($serialized, true); 59 | 60 | if (empty($data)) { 61 | $this->clear(); 62 | } else { 63 | $cookieFactory = $this->getCookieFactory(); 64 | 65 | $this->setCookies(array_map(function (array $cookie) use ($cookieFactory) { 66 | return $cookieFactory->create( 67 | $cookie['name'], 68 | $cookie['value'], 69 | $cookie['attributes'], 70 | $cookie['created_at'] 71 | ); 72 | }, $data)); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Event/Subscriber/BasicAuthSubscriber.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Subscriber; 13 | 14 | use Ivory\HttpAdapter\Event\BasicAuth\BasicAuthInterface; 15 | use Ivory\HttpAdapter\Event\Events; 16 | use Ivory\HttpAdapter\Event\MultiRequestCreatedEvent; 17 | use Ivory\HttpAdapter\Event\RequestCreatedEvent; 18 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; 19 | 20 | /** 21 | * @author GeLo 22 | */ 23 | class BasicAuthSubscriber implements EventSubscriberInterface 24 | { 25 | /** 26 | * @var BasicAuthInterface 27 | */ 28 | private $basicAuth; 29 | 30 | /** 31 | * @param BasicAuthInterface $basicAuth 32 | */ 33 | public function __construct(BasicAuthInterface $basicAuth) 34 | { 35 | $this->basicAuth = $basicAuth; 36 | } 37 | 38 | /** 39 | * @return BasicAuthInterface 40 | */ 41 | public function getBasicAuth() 42 | { 43 | return $this->basicAuth; 44 | } 45 | 46 | /** 47 | * @param RequestCreatedEvent $event 48 | */ 49 | public function onRequestCreated(RequestCreatedEvent $event) 50 | { 51 | $event->setRequest($this->basicAuth->authenticate($event->getRequest())); 52 | } 53 | 54 | /** 55 | * @param MultiRequestCreatedEvent $event 56 | */ 57 | public function onMultiRequestCreated(MultiRequestCreatedEvent $event) 58 | { 59 | foreach ($event->getRequests() as $request) { 60 | $event->removeRequest($request); 61 | $event->addRequest($this->basicAuth->authenticate($request)); 62 | } 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public static function getSubscribedEvents() 69 | { 70 | return [ 71 | Events::REQUEST_CREATED => ['onRequestCreated', 300], 72 | Events::MULTI_REQUEST_CREATED => ['onMultiRequestCreated', 300], 73 | ]; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Normalizer/HeadersNormalizer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Normalizer; 13 | 14 | use Ivory\HttpAdapter\Asset\AbstractUninstantiableAsset; 15 | use Ivory\HttpAdapter\Parser\HeadersParser; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class HeadersNormalizer extends AbstractUninstantiableAsset 21 | { 22 | /** 23 | * @param string|array $headers 24 | * @param bool $associative 25 | * 26 | * @return array 27 | */ 28 | public static function normalize($headers, $associative = true) 29 | { 30 | $normalizedHeaders = []; 31 | 32 | if (!$associative) { 33 | $headers = self::normalize($headers); 34 | } 35 | 36 | foreach (HeadersParser::parse($headers) as $name => $value) { 37 | if (strpos($value, 'HTTP/') === 0) { 38 | continue; 39 | } 40 | 41 | list($name, $value) = explode(':', $value, 2); 42 | 43 | $name = self::normalizeHeaderName($name); 44 | $value = self::normalizeHeaderValue($value); 45 | 46 | if (!$associative) { 47 | $normalizedHeaders[] = $name.': '.$value; 48 | } else { 49 | $normalizedHeaders[$name] = isset($normalizedHeaders[$name]) 50 | ? $normalizedHeaders[$name].', '.$value 51 | : $value; 52 | } 53 | } 54 | 55 | return $normalizedHeaders; 56 | } 57 | 58 | /** 59 | * @param string $name 60 | * 61 | * @return string 62 | */ 63 | public static function normalizeHeaderName($name) 64 | { 65 | return trim($name); 66 | } 67 | 68 | /** 69 | * @param array|string $value 70 | * 71 | * @return string 72 | */ 73 | public static function normalizeHeaderValue($value) 74 | { 75 | return implode(', ', array_map('trim', is_array($value) ? $value : explode(',', $value))); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Event/Redirect/RedirectInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Redirect; 13 | 14 | use Ivory\HttpAdapter\HttpAdapterException; 15 | use Ivory\HttpAdapter\HttpAdapterInterface; 16 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 17 | use Ivory\HttpAdapter\Message\ResponseInterface; 18 | 19 | /** 20 | * @author GeLo 21 | */ 22 | interface RedirectInterface 23 | { 24 | const PARENT_REQUEST = 'parent_request'; 25 | const REDIRECT_COUNT = 'redirect_count'; 26 | const EFFECTIVE_URI = 'effective_uri'; 27 | 28 | /** 29 | * @return int 30 | */ 31 | public function getMax(); 32 | 33 | /** 34 | * @param int $max 35 | */ 36 | public function setMax($max); 37 | 38 | /** 39 | * @return bool 40 | */ 41 | public function isStrict(); 42 | 43 | /** 44 | * @param bool $strict 45 | */ 46 | public function setStrict($strict); 47 | 48 | /** 49 | * @return bool 50 | */ 51 | public function getThrowException(); 52 | 53 | /** 54 | * @param bool $throwException 55 | */ 56 | public function setThrowException($throwException); 57 | 58 | /** 59 | * @param ResponseInterface $response 60 | * @param InternalRequestInterface $internalRequest 61 | * @param HttpAdapterInterface $httpAdapter 62 | * 63 | * @throws HttpAdapterException 64 | * 65 | * @return InternalRequestInterface|false 66 | */ 67 | public function createRedirectRequest( 68 | ResponseInterface $response, 69 | InternalRequestInterface $internalRequest, 70 | HttpAdapterInterface $httpAdapter 71 | ); 72 | 73 | /** 74 | * @param ResponseInterface $response 75 | * @param InternalRequestInterface $internalRequest 76 | * 77 | * @return ResponseInterface 78 | */ 79 | public function prepareResponse(ResponseInterface $response, InternalRequestInterface $internalRequest); 80 | } 81 | -------------------------------------------------------------------------------- /src/AbstractStreamHttpAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter; 13 | 14 | use Ivory\HttpAdapter\Extractor\ProtocolVersionExtractor; 15 | use Ivory\HttpAdapter\Extractor\StatusCodeExtractor; 16 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 17 | use Ivory\HttpAdapter\Normalizer\BodyNormalizer; 18 | use Ivory\HttpAdapter\Normalizer\HeadersNormalizer; 19 | 20 | /** 21 | * @author GeLo 22 | */ 23 | abstract class AbstractStreamHttpAdapter extends AbstractHttpAdapter 24 | { 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | protected function sendInternalRequest(InternalRequestInterface $internalRequest) 29 | { 30 | $context = stream_context_create([ 31 | 'http' => [ 32 | 'follow_location' => false, 33 | 'max_redirects' => 1, 34 | 'ignore_errors' => true, 35 | 'timeout' => $this->getConfiguration()->getTimeout(), 36 | 'protocol_version' => $internalRequest->getProtocolVersion(), 37 | 'method' => $internalRequest->getMethod(), 38 | 'header' => $this->prepareHeaders($internalRequest, false), 39 | 'content' => $this->prepareBody($internalRequest), 40 | ], 41 | ]); 42 | 43 | list($body, $headers) = $this->process($uri = (string) $internalRequest->getUri(), $context); 44 | 45 | if ($body === false) { 46 | $error = error_get_last(); 47 | throw HttpAdapterException::cannotFetchUri($uri, $this->getName(), $error['message']); 48 | } 49 | 50 | return $this->getConfiguration()->getMessageFactory()->createResponse( 51 | StatusCodeExtractor::extract($headers), 52 | ProtocolVersionExtractor::extract($headers), 53 | HeadersNormalizer::normalize($headers), 54 | BodyNormalizer::normalize($body, $internalRequest->getMethod()) 55 | ); 56 | } 57 | 58 | /** 59 | * @param string $uri 60 | * @param resource $context 61 | * 62 | * @return array 63 | */ 64 | abstract protected function process($uri, $context); 65 | } 66 | -------------------------------------------------------------------------------- /src/CakeHttpAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter; 13 | 14 | use Cake\Network\Http\Client; 15 | use Cake\Network\Http\Request; 16 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 17 | 18 | /** 19 | * @author GeLo 20 | */ 21 | class CakeHttpAdapter extends AbstractHttpAdapter 22 | { 23 | /** 24 | * @var Client 25 | */ 26 | private $client; 27 | 28 | /** 29 | * @param Client|null $client 30 | * @param ConfigurationInterface|null $configuration 31 | */ 32 | public function __construct(Client $client = null, ConfigurationInterface $configuration = null) 33 | { 34 | parent::__construct($configuration); 35 | 36 | $this->client = $client ?: new Client(); 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public function getName() 43 | { 44 | return 'cake'; 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | protected function sendInternalRequest(InternalRequestInterface $internalRequest) 51 | { 52 | $request = new Request(); 53 | 54 | foreach ($this->prepareHeaders($internalRequest) as $name => $value) { 55 | $request->header($name, $value); 56 | } 57 | 58 | $request->method($internalRequest->getMethod()); 59 | $request->body($this->prepareBody($internalRequest)); 60 | $request->url($uri = (string) $internalRequest->getUri()); 61 | $request->version($this->getConfiguration()->getProtocolVersion()); 62 | 63 | try { 64 | $response = $this->client->send($request, [ 65 | 'timeout' => $this->getConfiguration()->getTimeout(), 66 | 'redirect' => false, 67 | ]); 68 | } catch (\Exception $e) { 69 | throw HttpAdapterException::cannotFetchUri($uri, $this->getName(), $e->getMessage()); 70 | } 71 | 72 | return $this->getConfiguration()->getMessageFactory()->createResponse( 73 | (int) $response->statusCode(), 74 | $response->version(), 75 | $response->headers(), 76 | $response->body() 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Message/InternalRequestInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Message; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | interface InternalRequestInterface extends RequestInterface 18 | { 19 | /** 20 | * @return array 21 | */ 22 | public function getDatas(); 23 | 24 | /** 25 | * @param string $name 26 | * 27 | * @return bool 28 | */ 29 | public function hasData($name); 30 | 31 | /** 32 | * @param string $name 33 | * 34 | * @return mixed 35 | */ 36 | public function getData($name); 37 | 38 | /** 39 | * @param string $name 40 | * @param mixed $value 41 | * 42 | * @return InternalRequestInterface 43 | */ 44 | public function withData($name, $value); 45 | 46 | /** 47 | * @param string $name 48 | * @param mixed $value 49 | * 50 | * @return InternalRequestInterface 51 | */ 52 | public function withAddedData($name, $value); 53 | 54 | /** 55 | * @param string $name 56 | * 57 | * @return InternalRequestInterface 58 | */ 59 | public function withoutData($name); 60 | 61 | /** 62 | * @return array 63 | */ 64 | public function getFiles(); 65 | 66 | /** 67 | * @param string $name 68 | * 69 | * @return bool 70 | */ 71 | public function hasFile($name); 72 | 73 | /** 74 | * @param string $name 75 | * 76 | * @return string 77 | */ 78 | public function getFile($name); 79 | 80 | /** 81 | * @param string $name 82 | * @param string $file 83 | * 84 | * @return InternalRequestInterface 85 | */ 86 | public function withFile($name, $file); 87 | 88 | /** 89 | * @param string $name 90 | * @param string $file 91 | * 92 | * @return InternalRequestInterface 93 | */ 94 | public function withAddedFile($name, $file); 95 | 96 | /** 97 | * @param string $name 98 | * 99 | * @return InternalRequestInterface 100 | */ 101 | public function withoutFile($name); 102 | } 103 | -------------------------------------------------------------------------------- /src/RequestsHttpAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class RequestsHttpAdapter extends AbstractHttpAdapter 20 | { 21 | /** 22 | * @var \Requests_Transport 23 | */ 24 | private $transport; 25 | 26 | /** 27 | * @param \Requests_Transport|null $transport 28 | * @param ConfigurationInterface|null $configuration 29 | */ 30 | public function __construct(\Requests_Transport $transport = null, ConfigurationInterface $configuration = null) 31 | { 32 | parent::__construct($configuration); 33 | 34 | $this->transport = $transport; 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function getName() 41 | { 42 | return 'requests'; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | protected function sendInternalRequest(InternalRequestInterface $internalRequest) 49 | { 50 | $options = [ 51 | 'timeout' => $this->getConfiguration()->getTimeout(), 52 | 'connect_timeout' => $this->getConfiguration()->getTimeout(), 53 | 'protocol_version' => (float) $this->getConfiguration()->getProtocolVersion(), 54 | 'follow_redirects' => 0, 55 | 'data_format' => 'body', 56 | ]; 57 | 58 | if ($this->transport !== null) { 59 | $options['transport'] = $this->transport; 60 | } 61 | 62 | try { 63 | $response = \Requests::request( 64 | $uri = (string) $internalRequest->getUri(), 65 | $this->prepareHeaders($internalRequest), 66 | $this->prepareBody($internalRequest), 67 | $internalRequest->getMethod(), 68 | $options 69 | ); 70 | } catch (\Exception $e) { 71 | throw HttpAdapterException::cannotFetchUri($uri, $this->getName(), $e->getMessage()); 72 | } 73 | 74 | return $this->getConfiguration()->getMessageFactory()->createResponse( 75 | $response->status_code, 76 | sprintf('%.1f', $response->protocol_version), 77 | $response->headers->getAll(), 78 | $response->body 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/ReactHttpAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | use Ivory\HttpAdapter\Normalizer\BodyNormalizer; 16 | use React\Dns\Resolver\Factory as DnsResolverFactory; 17 | use React\EventLoop\Factory as EventLoopFactory; 18 | use React\HttpClient\Factory as HttpClientFactory; 19 | use React\HttpClient\Response; 20 | 21 | /** 22 | * @author GeLo 23 | */ 24 | class ReactHttpAdapter extends AbstractHttpAdapter 25 | { 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function getName() 30 | { 31 | return 'react'; 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | protected function sendInternalRequest(InternalRequestInterface $internalRequest) 38 | { 39 | $loop = EventLoopFactory::create(); 40 | $dnsResolverFactory = new DnsResolverFactory(); 41 | $httpClientFactory = new HttpClientFactory(); 42 | 43 | $error = null; 44 | $response = null; 45 | $body = null; 46 | 47 | $request = $httpClientFactory->create($loop, $dnsResolverFactory->createCached('8.8.8.8', $loop))->request( 48 | $internalRequest->getMethod(), 49 | $uri = (string) $internalRequest->getUri(), 50 | $this->prepareHeaders($internalRequest, true, true, true) 51 | ); 52 | 53 | $request->on('error', function (\Exception $onError) use (&$error) { 54 | $error = $onError; 55 | }); 56 | 57 | $request->on('response', function (Response $onResponse) use (&$response, &$body) { 58 | $onResponse->on('data', function ($data) use (&$body) { 59 | $body .= $data; 60 | }); 61 | 62 | $response = $onResponse; 63 | }); 64 | 65 | $request->end($this->prepareBody($internalRequest)); 66 | $loop->run(); 67 | 68 | if ($error !== null) { 69 | throw HttpAdapterException::cannotFetchUri($uri, $this->getName(), $error->getMessage()); 70 | } 71 | 72 | return $this->getConfiguration()->getMessageFactory()->createResponse( 73 | (int) $response->getCode(), 74 | $response->getVersion(), 75 | $response->getHeaders(), 76 | BodyNormalizer::normalize($body, $internalRequest->getMethod()) 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/PeclHttpAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter; 13 | 14 | use http\Client; 15 | use http\Client\Request; 16 | use http\Message\Body; 17 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 18 | 19 | /** 20 | * @author GeLo 21 | */ 22 | class PeclHttpAdapter extends AbstractHttpAdapter 23 | { 24 | /** 25 | * @var Client 26 | */ 27 | private $client; 28 | 29 | /** 30 | * @param Client $client 31 | * @param ConfigurationInterface $configuration 32 | */ 33 | public function __construct(Client $client = null, ConfigurationInterface $configuration = null) 34 | { 35 | parent::__construct($configuration); 36 | 37 | $this->client = $client ?: new Client(); 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | protected function sendInternalRequest(InternalRequestInterface $internalRequest) 44 | { 45 | $body = new Body(); 46 | $body->append($this->prepareBody($internalRequest)); 47 | 48 | $request = new Request( 49 | $internalRequest->getMethod(), 50 | $uri = (string) $internalRequest->getUri(), 51 | $this->prepareHeaders($internalRequest), 52 | $body 53 | ); 54 | 55 | $httpVersion = $internalRequest->getProtocolVersion() === InternalRequestInterface::PROTOCOL_VERSION_1_0 56 | ? \http\Client\Curl\HTTP_VERSION_1_0 57 | : \http\Client\Curl\HTTP_VERSION_1_1; 58 | 59 | $request->setOptions([ 60 | 'protocol' => $httpVersion, 61 | 'timeout' => $this->getConfiguration()->getTimeout(), 62 | ]); 63 | 64 | try { 65 | $this->client->reset()->enqueue($request)->send(); 66 | } catch (\Exception $e) { 67 | throw HttpAdapterException::cannotFetchUri($uri, $this->getName(), $e->getMessage()); 68 | } 69 | 70 | $response = $this->client->getResponse(); 71 | 72 | return $this->getConfiguration()->getMessageFactory()->createResponse( 73 | $response->getResponseCode(), 74 | $response->getHttpVersion(), 75 | $response->getHeaders(), 76 | $response->getBody()->getResource() 77 | ); 78 | } 79 | 80 | /** 81 | * {@inheritdoc} 82 | */ 83 | public function getName() 84 | { 85 | return 'pecl_http'; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Event/Retry/Strategy/AbstractRetryStrategyChain.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Retry\Strategy; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | abstract class AbstractRetryStrategyChain implements RetryStrategyChainInterface 20 | { 21 | /** 22 | * @var RetryStrategyChainInterface 23 | */ 24 | private $next; 25 | 26 | /** 27 | * @param RetryStrategyChainInterface|null $next 28 | */ 29 | public function __construct(RetryStrategyChainInterface $next = null) 30 | { 31 | $this->setNext($next); 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function hasNext() 38 | { 39 | return $this->next !== null; 40 | } 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | public function getNext() 46 | { 47 | return $this->next; 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public function setNext(RetryStrategyChainInterface $next = null) 54 | { 55 | $this->next = $next; 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | public function verify(InternalRequestInterface $request) 62 | { 63 | $verify = $this->doVerify($request); 64 | 65 | if ($verify && $this->hasNext()) { 66 | return $this->next->verify($request); 67 | } 68 | 69 | return $verify; 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | public function delay(InternalRequestInterface $request) 76 | { 77 | $delay = $this->doDelay($request); 78 | 79 | if ($this->hasNext() && (($nextDelay = $this->next->delay($request)) > $delay)) { 80 | return $nextDelay; 81 | } 82 | 83 | return $delay; 84 | } 85 | 86 | /** 87 | * @param InternalRequestInterface $request 88 | * 89 | * @return bool 90 | */ 91 | protected function doVerify(InternalRequestInterface $request) 92 | { 93 | return true; 94 | } 95 | 96 | /** 97 | * @param InternalRequestInterface $request 98 | * 99 | * @return int 100 | */ 101 | protected function doDelay(InternalRequestInterface $request) 102 | { 103 | return 0; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Event/Formatter/Formatter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Formatter; 13 | 14 | use Ivory\HttpAdapter\HttpAdapterException; 15 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 16 | use Ivory\HttpAdapter\Message\ResponseInterface; 17 | 18 | /** 19 | * @author GeLo 20 | */ 21 | class Formatter implements FormatterInterface 22 | { 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | public function formatRequest(InternalRequestInterface $request) 27 | { 28 | return [ 29 | 'protocol_version' => $request->getProtocolVersion(), 30 | 'uri' => (string) $request->getUri(), 31 | 'method' => $request->getMethod(), 32 | 'headers' => $request->getHeaders(), 33 | 'body' => utf8_encode((string) $request->getBody()), 34 | 'datas' => $request->getDatas(), 35 | 'files' => $request->getFiles(), 36 | 'parameters' => $this->filterParameters($request->getParameters()), 37 | ]; 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function formatResponse(ResponseInterface $response) 44 | { 45 | return [ 46 | 'protocol_version' => $response->getProtocolVersion(), 47 | 'status_code' => $response->getStatusCode(), 48 | 'reason_phrase' => $response->getReasonPhrase(), 49 | 'headers' => $response->getHeaders(), 50 | 'body' => utf8_encode((string) $response->getBody()), 51 | 'parameters' => $this->filterParameters($response->getParameters()), 52 | ]; 53 | } 54 | 55 | /** 56 | * {@inheritdoc} 57 | */ 58 | public function formatException(HttpAdapterException $exception) 59 | { 60 | return [ 61 | 'code' => $exception->getCode(), 62 | 'message' => $exception->getMessage(), 63 | 'line' => $exception->getLine(), 64 | 'file' => $exception->getFile(), 65 | ]; 66 | } 67 | 68 | /** 69 | * @param array $parameters 70 | * 71 | * @return array 72 | */ 73 | private function filterParameters(array $parameters) 74 | { 75 | return array_filter($parameters, function ($parameter) { 76 | return !is_object($parameter) && !is_resource($parameter); 77 | }); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Zend2HttpAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | use Ivory\HttpAdapter\Normalizer\BodyNormalizer; 16 | use Zend\Http\Client; 17 | use Zend\Http\Response\Stream; 18 | 19 | /** 20 | * @author GeLo 21 | */ 22 | class Zend2HttpAdapter extends AbstractHttpAdapter 23 | { 24 | /** 25 | * @var Client 26 | */ 27 | private $client; 28 | 29 | /** 30 | * @param \Zend\Http\Client|null $client 31 | * @param ConfigurationInterface|null $configuration 32 | */ 33 | public function __construct(Client $client = null, ConfigurationInterface $configuration = null) 34 | { 35 | parent::__construct($configuration); 36 | 37 | $this->client = $client ?: new Client(); 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function getName() 44 | { 45 | return 'zend2'; 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | protected function sendInternalRequest(InternalRequestInterface $internalRequest) 52 | { 53 | $this->client 54 | ->resetParameters(true) 55 | ->setOptions([ 56 | 'httpversion' => $internalRequest->getProtocolVersion(), 57 | 'timeout' => $this->getConfiguration()->getTimeout(), 58 | 'maxredirects' => 0, 59 | ]) 60 | ->setUri($uri = (string) $internalRequest->getUri()) 61 | ->setMethod($internalRequest->getMethod()) 62 | ->setHeaders($this->prepareHeaders($internalRequest)) 63 | ->setRawBody($this->prepareBody($internalRequest)); 64 | 65 | try { 66 | $response = $this->client->send(); 67 | } catch (\Exception $e) { 68 | throw HttpAdapterException::cannotFetchUri($uri, $this->getName(), $e->getMessage()); 69 | } 70 | 71 | return $this->getConfiguration()->getMessageFactory()->createResponse( 72 | $response->getStatusCode(), 73 | $response->getVersion(), 74 | $response->getHeaders()->toArray(), 75 | BodyNormalizer::normalize( 76 | function () use ($response) { 77 | return $response instanceof Stream ? $response->getStream() : $response->getBody(); 78 | }, 79 | $internalRequest->getMethod() 80 | ) 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/ConfigurationInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter; 13 | 14 | use Ivory\HttpAdapter\Message\MessageFactoryInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | interface ConfigurationInterface 20 | { 21 | const ENCODING_TYPE_URLENCODED = 'application/x-www-form-urlencoded'; 22 | const ENCODING_TYPE_FORMDATA = 'multipart/form-data'; 23 | 24 | /** 25 | * @return MessageFactoryInterface 26 | */ 27 | public function getMessageFactory(); 28 | 29 | /** 30 | * @param MessageFactoryInterface $messageFactory 31 | */ 32 | public function setMessageFactory(MessageFactoryInterface $messageFactory); 33 | 34 | /** 35 | * @return string 36 | */ 37 | public function getProtocolVersion(); 38 | 39 | /** 40 | * @param string $protocolVersion 41 | */ 42 | public function setProtocolVersion($protocolVersion); 43 | 44 | /** 45 | * @return bool 46 | */ 47 | public function getKeepAlive(); 48 | 49 | /** 50 | * @param bool $keepAlive 51 | */ 52 | public function setKeepAlive($keepAlive); 53 | 54 | /** 55 | * @return bool 56 | */ 57 | public function hasEncodingType(); 58 | 59 | /** 60 | * @return string|null 61 | */ 62 | public function getEncodingType(); 63 | 64 | /** 65 | * @param string|null $encodingType 66 | */ 67 | public function setEncodingType($encodingType); 68 | 69 | /** 70 | * @return string 71 | */ 72 | public function getBoundary(); 73 | 74 | /** 75 | * @param string $boundary 76 | */ 77 | public function setBoundary($boundary); 78 | 79 | /** 80 | * @return float 81 | */ 82 | public function getTimeout(); 83 | 84 | /** 85 | * @param float $timeout 86 | */ 87 | public function setTimeout($timeout); 88 | 89 | /** 90 | * @return string 91 | */ 92 | public function getUserAgent(); 93 | 94 | /** 95 | * @param string $userAgent 96 | */ 97 | public function setUserAgent($userAgent); 98 | 99 | /** 100 | * @return bool 101 | */ 102 | public function hasBaseUri(); 103 | 104 | /** 105 | * @return string 106 | */ 107 | public function getBaseUri(); 108 | 109 | /** 110 | * @param string $baseUri 111 | */ 112 | public function setBaseUri($baseUri); 113 | } 114 | -------------------------------------------------------------------------------- /src/Event/Cookie/Jar/CookieJarInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Cookie\Jar; 13 | 14 | use Ivory\HttpAdapter\Event\Cookie\CookieFactoryInterface; 15 | use Ivory\HttpAdapter\Event\Cookie\CookieInterface; 16 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 17 | use Ivory\HttpAdapter\Message\ResponseInterface; 18 | 19 | /** 20 | * @author GeLo 21 | */ 22 | interface CookieJarInterface extends \Countable, \IteratorAggregate 23 | { 24 | /** 25 | * @return CookieFactoryInterface 26 | */ 27 | public function getCookieFactory(); 28 | 29 | /** 30 | * @param CookieFactoryInterface $cookieFactory 31 | */ 32 | public function setCookieFactory(CookieFactoryInterface $cookieFactory); 33 | 34 | public function clean(); 35 | 36 | /** 37 | * @param string|null $domain 38 | * @param string|null $path 39 | * @param string|null $name 40 | */ 41 | public function clear($domain = null, $path = null, $name = null); 42 | 43 | /** 44 | * @return bool 45 | */ 46 | public function hasCookies(); 47 | 48 | /** 49 | * @return CookieFactoryInterface[] 50 | */ 51 | public function getCookies(); 52 | 53 | /** 54 | * @param CookieFactoryInterface[] $cookies 55 | */ 56 | public function setCookies(array $cookies); 57 | 58 | /** 59 | * @param CookieFactoryInterface[] $cookies 60 | */ 61 | public function addCookies(array $cookies); 62 | 63 | /** 64 | * @param CookieFactoryInterface[] $cookies 65 | */ 66 | public function removeCookies(array $cookies); 67 | 68 | /** 69 | * @param CookieInterface $cookie 70 | * 71 | * @return bool 72 | */ 73 | public function hasCookie(CookieInterface $cookie); 74 | 75 | /** 76 | * @param CookieInterface $cookie 77 | */ 78 | public function addCookie(CookieInterface $cookie); 79 | 80 | /** 81 | * @param CookieInterface $cookie 82 | */ 83 | public function removeCookie(CookieInterface $cookie); 84 | 85 | /** 86 | * @param InternalRequestInterface $request 87 | * 88 | * @return InternalRequestInterface 89 | */ 90 | public function populate(InternalRequestInterface $request); 91 | 92 | /** 93 | * @param InternalRequestInterface $request 94 | * @param ResponseInterface $response 95 | */ 96 | public function extract(InternalRequestInterface $request, ResponseInterface $response); 97 | } 98 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "egeloen/http-adapter", 3 | "description": "Issue HTTP request for PHP 5.4.8+.", 4 | "keywords": [ "http", "http-adapter", "http-client", "psr-7" ], 5 | "type": "library", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Eric GELOEN", 10 | "email": "geloen.eric@gmail.com" 11 | } 12 | ], 13 | "require": { 14 | "php": "^5.4.8|^7.0", 15 | "zendframework/zend-diactoros": "^1.1" 16 | }, 17 | "require-dev": { 18 | "cakephp/cakephp": "^3.0.3", 19 | "doctrine/cache": "^1.4", 20 | "ext-curl": "*", 21 | "friendsofphp/php-cs-fixer": "^2.0", 22 | "guzzle/guzzle": "^3.9.4@dev", 23 | "guzzlehttp/guzzle": "^4.1.4|^5.0|^6.0", 24 | "kriswallsmith/buzz": "~0.13", 25 | "nategood/httpful": "^0.2.17", 26 | "phpunit/phpunit": "^4.0|^5.0", 27 | "phpunit/phpunit-mock-objects": "^2.1|^3.0", 28 | "psr/log": "^1.0", 29 | "react/http-client": "^0.4", 30 | "react/dns": "^0.4.1", 31 | "rmccue/requests": "^1.7", 32 | "symfony/event-dispatcher": "^2.0|^3.0", 33 | "symfony/phpunit-bridge": "^2.7|^3.0", 34 | "symfony/stopwatch": "^2.2|^3.0", 35 | "tedivm/stash": "~0.13", 36 | "zendframework/zendframework1": "^1.12.9", 37 | "zendframework/zend-http": "^2.3.4" 38 | }, 39 | "suggest": { 40 | "doctrine/cache": "Allows you to use the cache event subscriber", 41 | "ext-curl": "Allows you to use the cURL adapter", 42 | "ext-http": "Allows you to use the PECL adapter", 43 | "guzzle/guzzle": "Allows you to use the Guzzle 3 adapter", 44 | "guzzlehttp/guzzle": "Allows you to use the Guzzle 4 adapter", 45 | "kriswallsmith/buzz": "Allows you to use the Buzz adapter", 46 | "nategood/httpful": "Allows you to use the Httpful adapter", 47 | "psr/log": "Allows you to use the logger event subscriber", 48 | "rmccue/requests": "Allows you to use the Requests adapter", 49 | "symfony/event-dispatcher": "Allows you to use the event lifecycle", 50 | "symfony/stopwatch": "Allows you to use the stopwatch http adapter and event subscriber", 51 | "tedivm/stash": "Allows you to use the cache event subscriber", 52 | "zendframework/zendframework1": "Allows you to use the Zend 1 adapter", 53 | "zendframework/zend-http": "Allows you to use the Zend 2 adapter" 54 | }, 55 | "autoload": { 56 | "psr-4": { "Ivory\\HttpAdapter\\": "src/" } 57 | }, 58 | "autoload-dev": { 59 | "psr-4": { "Ivory\\Tests\\HttpAdapter\\": "tests/" } 60 | }, 61 | "extra": { 62 | "branch-alias": { 63 | "dev-master": "1.0-dev" 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/HttpfulHttpAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter; 13 | 14 | use Httpful\Mime; 15 | use Httpful\Request; 16 | use Ivory\HttpAdapter\Extractor\ProtocolVersionExtractor; 17 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 18 | use Ivory\HttpAdapter\Normalizer\BodyNormalizer; 19 | 20 | /** 21 | * @author GeLo 22 | */ 23 | class HttpfulHttpAdapter extends AbstractCurlHttpAdapter 24 | { 25 | /** 26 | * @param ConfigurationInterface|null $configuration 27 | */ 28 | public function __construct(ConfigurationInterface $configuration = null) 29 | { 30 | parent::__construct($configuration); 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function getName() 37 | { 38 | return 'httpful'; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | protected function sendInternalRequest(InternalRequestInterface $internalRequest) 45 | { 46 | $request = Request::init($internalRequest->getMethod()) 47 | ->whenError(function () { 48 | }) 49 | ->addOnCurlOption(CURLOPT_HTTP_VERSION, $this->prepareProtocolVersion($internalRequest)) 50 | ->timeout($this->getConfiguration()->getTimeout()) 51 | ->uri($uri = (string) $internalRequest->getUri()) 52 | ->addHeaders($this->prepareHeaders($internalRequest)) 53 | ->body($this->prepareContent($internalRequest)); 54 | 55 | if (defined('CURLOPT_CONNECTTIMEOUT_MS')) { 56 | $request->addOnCurlOption(CURLOPT_CONNECTTIMEOUT_MS, $this->getConfiguration()->getTimeout() * 1000); 57 | } else { // @codeCoverageIgnoreStart 58 | $request->addOnCurlOption(CURLOPT_CONNECTTIMEOUT, $this->getConfiguration()->getTimeout()); 59 | } // @codeCoverageIgnoreEnd 60 | 61 | $files = $internalRequest->getFiles(); 62 | 63 | if (!empty($files)) { 64 | $request->mime(Mime::UPLOAD); 65 | } 66 | 67 | try { 68 | $response = $request->send(); 69 | } catch (\Exception $e) { 70 | throw HttpAdapterException::cannotFetchUri($uri, $this->getName(), $e->getMessage()); 71 | } 72 | 73 | return $this->getConfiguration()->getMessageFactory()->createResponse( 74 | $response->code, 75 | ProtocolVersionExtractor::extract($response->raw_headers), 76 | $response->headers->toArray(), 77 | BodyNormalizer::normalize($response->body, $internalRequest->getMethod()) 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Zend1HttpAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | use Ivory\HttpAdapter\Normalizer\BodyNormalizer; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class Zend1HttpAdapter extends AbstractHttpAdapter 21 | { 22 | /** @var \Zend_Http_Client */ 23 | private $client; 24 | 25 | /** 26 | * @param \Zend_Http_Client|null $client 27 | * @param ConfigurationInterface|null $configuration 28 | */ 29 | public function __construct(\Zend_Http_Client $client = null, ConfigurationInterface $configuration = null) 30 | { 31 | parent::__construct($configuration); 32 | 33 | $this->client = $client ?: new \Zend_Http_Client(); 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function getName() 40 | { 41 | return 'zend1'; 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | protected function sendInternalRequest(InternalRequestInterface $internalRequest) 48 | { 49 | $this->client 50 | ->resetParameters(true) 51 | ->setConfig([ 52 | 'httpversion' => $internalRequest->getProtocolVersion(), 53 | 'timeout' => $this->getConfiguration()->getTimeout(), 54 | 'request_timeout' => $this->getConfiguration()->getTimeout(), 55 | 'maxredirects' => 0, 56 | ]) 57 | ->setUri($uri = (string) $internalRequest->getUri()) 58 | ->setMethod($internalRequest->getMethod()) 59 | ->setHeaders($this->prepareHeaders($internalRequest)) 60 | ->setRawData($this->prepareBody($internalRequest)); 61 | 62 | try { 63 | $response = $this->client->request(); 64 | } catch (\Exception $e) { 65 | throw HttpAdapterException::cannotFetchUri($uri, $this->getName(), $e->getMessage()); 66 | } 67 | 68 | return $this->getConfiguration()->getMessageFactory()->createResponse( 69 | $response->getStatus(), 70 | $response->getVersion(), 71 | $response->getHeaders(), 72 | BodyNormalizer::normalize( 73 | function () use ($response) { 74 | return $response instanceof \Zend_Http_Response_Stream 75 | ? $response->getStream() 76 | : $response->getBody(); 77 | }, 78 | $internalRequest->getMethod() 79 | ) 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Event/RequestSentEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event; 13 | 14 | use Ivory\HttpAdapter\HttpAdapterException; 15 | use Ivory\HttpAdapter\HttpAdapterInterface; 16 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 17 | use Ivory\HttpAdapter\Message\ResponseInterface; 18 | 19 | /** 20 | * @author GeLo 21 | */ 22 | class RequestSentEvent extends AbstractEvent 23 | { 24 | /** 25 | * @var InternalRequestInterface 26 | */ 27 | private $request; 28 | 29 | /** 30 | * @var ResponseInterface|null 31 | */ 32 | private $response; 33 | 34 | /** 35 | * @var HttpAdapterException|null 36 | */ 37 | private $exception; 38 | 39 | /** 40 | * @param HttpAdapterInterface $httpAdapter 41 | * @param InternalRequestInterface $request 42 | * @param ResponseInterface $response 43 | */ 44 | public function __construct( 45 | HttpAdapterInterface $httpAdapter, 46 | InternalRequestInterface $request, 47 | ResponseInterface $response 48 | ) { 49 | parent::__construct($httpAdapter); 50 | 51 | $this->setRequest($request); 52 | $this->setResponse($response); 53 | } 54 | 55 | /** 56 | * @return InternalRequestInterface 57 | */ 58 | public function getRequest() 59 | { 60 | return $this->request; 61 | } 62 | 63 | /** 64 | * @param InternalRequestInterface $request 65 | */ 66 | public function setRequest(InternalRequestInterface $request) 67 | { 68 | $this->request = $request; 69 | } 70 | 71 | /** 72 | * @return ResponseInterface|null 73 | */ 74 | public function getResponse() 75 | { 76 | return $this->response; 77 | } 78 | 79 | /** 80 | * @param ResponseInterface|null $response 81 | */ 82 | public function setResponse(ResponseInterface $response = null) 83 | { 84 | $this->response = $response; 85 | } 86 | 87 | /** 88 | * @return bool 89 | */ 90 | public function hasException() 91 | { 92 | return $this->exception !== null; 93 | } 94 | 95 | /** 96 | * @return HttpAdapterException|null 97 | */ 98 | public function getException() 99 | { 100 | return $this->exception; 101 | } 102 | 103 | /** 104 | * @param HttpAdapterException|null $exception 105 | */ 106 | public function setException(HttpAdapterException $exception = null) 107 | { 108 | $this->exception = $exception; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Event/RequestCreatedEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event; 13 | 14 | use Ivory\HttpAdapter\HttpAdapterException; 15 | use Ivory\HttpAdapter\HttpAdapterInterface; 16 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 17 | use Ivory\HttpAdapter\Message\ResponseInterface; 18 | 19 | /** 20 | * @author GeLo 21 | */ 22 | class RequestCreatedEvent extends AbstractEvent 23 | { 24 | /** 25 | * @var InternalRequestInterface 26 | */ 27 | private $request; 28 | 29 | /** 30 | * @var ResponseInterface|null 31 | */ 32 | private $response; 33 | 34 | /** 35 | * @var HttpAdapterException|null 36 | */ 37 | private $exception; 38 | 39 | /** 40 | * @param HttpAdapterInterface $httpAdapter 41 | * @param InternalRequestInterface $request 42 | */ 43 | public function __construct(HttpAdapterInterface $httpAdapter, InternalRequestInterface $request) 44 | { 45 | parent::__construct($httpAdapter); 46 | 47 | $this->setRequest($request); 48 | } 49 | 50 | /** 51 | * @return InternalRequestInterface 52 | */ 53 | public function getRequest() 54 | { 55 | return $this->request; 56 | } 57 | 58 | /** 59 | * @param InternalRequestInterface $request 60 | */ 61 | public function setRequest(InternalRequestInterface $request) 62 | { 63 | $this->request = $request; 64 | } 65 | 66 | /** 67 | * @return bool 68 | */ 69 | public function hasResponse() 70 | { 71 | return $this->response !== null; 72 | } 73 | 74 | /** 75 | * @return ResponseInterface|null 76 | */ 77 | public function getResponse() 78 | { 79 | return $this->response; 80 | } 81 | 82 | /** 83 | * @param ResponseInterface|null $response 84 | */ 85 | public function setResponse(ResponseInterface $response = null) 86 | { 87 | $this->response = $response; 88 | } 89 | 90 | /** 91 | * @return bool 92 | */ 93 | public function hasException() 94 | { 95 | return $this->exception !== null; 96 | } 97 | 98 | /** 99 | * @return HttpAdapterException|null 100 | */ 101 | public function getException() 102 | { 103 | return $this->exception; 104 | } 105 | 106 | /** 107 | * @param HttpAdapterException|null $exception 108 | */ 109 | public function setException(HttpAdapterException $exception = null) 110 | { 111 | $this->exception = $exception; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Message/MessageFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Message; 13 | 14 | use Guzzle\Stream\StreamInterface; 15 | use Ivory\HttpAdapter\HttpAdapterException; 16 | use Psr\Http\Message\UriInterface; 17 | 18 | /** 19 | * @author GeLo 20 | */ 21 | interface MessageFactoryInterface 22 | { 23 | /** 24 | * @return bool 25 | */ 26 | public function hasBaseUri(); 27 | 28 | /** 29 | * @return UriInterface|null 30 | */ 31 | public function getBaseUri(); 32 | 33 | /** 34 | * @param string|UriInterface|null $baseUri 35 | * 36 | * @throws HttpAdapterException 37 | */ 38 | public function setBaseUri($baseUri); 39 | 40 | /** 41 | * @param string|object $uri 42 | * @param string $method 43 | * @param string $protocolVersion 44 | * @param array $headers 45 | * @param resource|string|StreamInterface|null $body 46 | * @param array $parameters 47 | * 48 | * @return RequestInterface 49 | */ 50 | public function createRequest( 51 | $uri, 52 | $method = RequestInterface::METHOD_GET, 53 | $protocolVersion = RequestInterface::PROTOCOL_VERSION_1_1, 54 | array $headers = [], 55 | $body = null, 56 | array $parameters = [] 57 | ); 58 | 59 | /** 60 | * @param string|object $uri 61 | * @param string $method 62 | * @param string $protocolVersion 63 | * @param array $headers 64 | * @param array|string $datas 65 | * @param array $files 66 | * @param array $parameters 67 | * 68 | * @return InternalRequestInterface 69 | */ 70 | public function createInternalRequest( 71 | $uri, 72 | $method = RequestInterface::METHOD_GET, 73 | $protocolVersion = RequestInterface::PROTOCOL_VERSION_1_1, 74 | array $headers = [], 75 | $datas = [], 76 | array $files = [], 77 | array $parameters = [] 78 | ); 79 | 80 | /** 81 | * @param int $statusCode 82 | * @param string $protocolVersion 83 | * @param array $headers 84 | * @param resource|string|StreamInterface|null $body 85 | * @param array $parameters 86 | * 87 | * @return ResponseInterface 88 | */ 89 | public function createResponse( 90 | $statusCode = 200, 91 | $protocolVersion = RequestInterface::PROTOCOL_VERSION_1_1, 92 | array $headers = [], 93 | $body = null, 94 | array $parameters = [] 95 | ); 96 | } 97 | -------------------------------------------------------------------------------- /src/Event/Retry/Strategy/CallbackRetryStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Retry\Strategy; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class CallbackRetryStrategy extends AbstractRetryStrategyChain 20 | { 21 | /** 22 | * @var callable|null 23 | */ 24 | private $verifyCallback; 25 | 26 | /** 27 | * @var callable|null 28 | */ 29 | private $delayCallback; 30 | 31 | /** 32 | * @param callable|null $verifyCallback 33 | * @param callable|null $delayCallback 34 | * @param RetryStrategyChainInterface|null $next 35 | */ 36 | public function __construct($verifyCallback = null, $delayCallback = null, RetryStrategyChainInterface $next = null) 37 | { 38 | parent::__construct($next); 39 | 40 | $this->setVerifyCallback($verifyCallback); 41 | $this->setDelayCallback($delayCallback); 42 | } 43 | 44 | /** 45 | * @return bool 46 | */ 47 | public function hasVerifyCallback() 48 | { 49 | return $this->verifyCallback !== null; 50 | } 51 | 52 | /** 53 | * @return callable|null 54 | */ 55 | public function getVerifyCallback() 56 | { 57 | return $this->verifyCallback; 58 | } 59 | 60 | /** 61 | * @param callable|null $verifyCallback 62 | */ 63 | public function setVerifyCallback($verifyCallback) 64 | { 65 | $this->verifyCallback = $verifyCallback; 66 | } 67 | 68 | /** 69 | * @return bool 70 | */ 71 | public function hasDelayCallback() 72 | { 73 | return $this->delayCallback !== null; 74 | } 75 | 76 | /** 77 | * @return callable|null 78 | */ 79 | public function getDelayCallback() 80 | { 81 | return $this->delayCallback; 82 | } 83 | 84 | /** 85 | * @param callable|null $delayCallback 86 | */ 87 | public function setDelayCallback($delayCallback) 88 | { 89 | $this->delayCallback = $delayCallback; 90 | } 91 | 92 | /** 93 | * {@inheritdoc} 94 | */ 95 | protected function doVerify(InternalRequestInterface $request) 96 | { 97 | if ($this->hasVerifyCallback()) { 98 | return call_user_func($this->verifyCallback, $request); 99 | } 100 | 101 | return parent::doVerify($request); 102 | } 103 | 104 | /** 105 | * {@inheritdoc} 106 | */ 107 | protected function doDelay(InternalRequestInterface $request) 108 | { 109 | if ($this->hasDelayCallback()) { 110 | return call_user_func($this->delayCallback, $request); 111 | } 112 | 113 | return parent::doDelay($request); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/PsrHttpAdapterDecorator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class PsrHttpAdapterDecorator implements HttpAdapterInterface 20 | { 21 | use HttpAdapterTrait; 22 | 23 | /** 24 | * @var PsrHttpAdapterInterface 25 | */ 26 | private $httpAdapter; 27 | 28 | /** 29 | * @param PsrHttpAdapterInterface $httpAdapter 30 | */ 31 | public function __construct(PsrHttpAdapterInterface $httpAdapter) 32 | { 33 | $this->httpAdapter = $httpAdapter; 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function getConfiguration() 40 | { 41 | return $this->httpAdapter->getConfiguration(); 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function setConfiguration(ConfigurationInterface $configuration) 48 | { 49 | $this->httpAdapter->setConfiguration($configuration); 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | public function getName() 56 | { 57 | return $this->httpAdapter->getName(); 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | protected function sendInternalRequest(InternalRequestInterface $internalRequest) 64 | { 65 | return $this->doSendInternalRequest($internalRequest); 66 | } 67 | 68 | /** 69 | * {@inheritdoc} 70 | */ 71 | protected function sendInternalRequests(array $internalRequests, $success, $error) 72 | { 73 | $exceptions = []; 74 | 75 | try { 76 | $responses = $this->doSendInternalRequests($internalRequests); 77 | } catch (MultiHttpAdapterException $e) { 78 | $responses = $e->getResponses(); 79 | $exceptions = $e->getExceptions(); 80 | } 81 | 82 | foreach ($responses as $response) { 83 | call_user_func($success, $response); 84 | } 85 | 86 | foreach ($exceptions as $exception) { 87 | call_user_func($error, $exception); 88 | } 89 | } 90 | 91 | /** 92 | * @param InternalRequestInterface $internalRequest 93 | * 94 | * @throws HttpAdapterException 95 | * 96 | * @return ResponseInterface 97 | */ 98 | protected function doSendInternalRequest(InternalRequestInterface $internalRequest) 99 | { 100 | return $this->httpAdapter->sendRequest($internalRequest); 101 | } 102 | 103 | /** 104 | * @param array $internalRequests 105 | * 106 | * @throws MultiHttpAdapterException 107 | * 108 | * @return array 109 | */ 110 | protected function doSendInternalRequests(array $internalRequests) 111 | { 112 | return $this->httpAdapter->sendRequests($internalRequests); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/BuzzHttpAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter; 13 | 14 | use Buzz\Browser; 15 | use Buzz\Client\AbstractCurl; 16 | use Buzz\Client\MultiCurl; 17 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 18 | use Ivory\HttpAdapter\Normalizer\BodyNormalizer; 19 | use Ivory\HttpAdapter\Normalizer\HeadersNormalizer; 20 | 21 | /** 22 | * @author GeLo 23 | */ 24 | class BuzzHttpAdapter extends AbstractCurlHttpAdapter 25 | { 26 | /** 27 | * @var Browser 28 | */ 29 | private $browser; 30 | 31 | /** 32 | * @param Browser|null $browser 33 | * @param ConfigurationInterface|null $configuration 34 | * 35 | * @throws HttpAdapterException 36 | */ 37 | public function __construct(Browser $browser = null, ConfigurationInterface $configuration = null) 38 | { 39 | $browser = $browser ?: new Browser(); 40 | 41 | if ($browser->getClient() instanceof MultiCurl) { 42 | throw HttpAdapterException::doesNotSupportSubAdapter( 43 | $this->getName(), 44 | get_class($browser->getClient()) 45 | ); 46 | } 47 | 48 | parent::__construct($configuration, $browser->getClient() instanceof AbstractCurl); 49 | 50 | $this->browser = $browser; 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function getName() 57 | { 58 | return 'buzz'; 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | protected function sendInternalRequest(InternalRequestInterface $internalRequest) 65 | { 66 | $this->browser->getClient()->setTimeout($this->getConfiguration()->getTimeout()); 67 | $this->browser->getClient()->setMaxRedirects(0); 68 | 69 | $request = $this->browser->getMessageFactory()->createRequest( 70 | $internalRequest->getMethod(), 71 | $uri = (string) $internalRequest->getUri() 72 | ); 73 | 74 | $request->setProtocolVersion($internalRequest->getProtocolVersion()); 75 | $request->setHeaders($this->prepareHeaders($internalRequest, false)); 76 | 77 | $data = $this->browser->getClient() instanceof AbstractCurl 78 | ? $this->prepareContent($internalRequest) 79 | : $this->prepareBody($internalRequest); 80 | 81 | $request->setContent($data); 82 | 83 | try { 84 | $response = $this->browser->send($request); 85 | } catch (\Exception $e) { 86 | throw HttpAdapterException::cannotFetchUri($uri, $this->getName(), $e->getMessage()); 87 | } 88 | 89 | return $this->getConfiguration()->getMessageFactory()->createResponse( 90 | $response->getStatusCode(), 91 | sprintf('%.1f', $response->getProtocolVersion()), 92 | HeadersNormalizer::normalize($response->getHeaders()), 93 | BodyNormalizer::normalize($response->getContent(), $internalRequest->getMethod()) 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Event/Subscriber/RetrySubscriber.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Subscriber; 13 | 14 | use Ivory\HttpAdapter\Event\Events; 15 | use Ivory\HttpAdapter\Event\MultiRequestErroredEvent; 16 | use Ivory\HttpAdapter\Event\RequestErroredEvent; 17 | use Ivory\HttpAdapter\Event\Retry\Retry; 18 | use Ivory\HttpAdapter\Event\Retry\RetryInterface; 19 | use Ivory\HttpAdapter\HttpAdapterException; 20 | use Ivory\HttpAdapter\MultiHttpAdapterException; 21 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; 22 | 23 | /** 24 | * @author GeLo 25 | */ 26 | class RetrySubscriber implements EventSubscriberInterface 27 | { 28 | /** 29 | * @var RetryInterface 30 | */ 31 | private $retry; 32 | 33 | /** 34 | * @param RetryInterface|null $retry 35 | */ 36 | public function __construct(RetryInterface $retry = null) 37 | { 38 | $this->retry = $retry ?: new Retry(); 39 | } 40 | 41 | /** 42 | * @return RetryInterface 43 | */ 44 | public function getRetry() 45 | { 46 | return $this->retry; 47 | } 48 | 49 | /** 50 | * @param RequestErroredEvent $event 51 | */ 52 | public function onRequestErrored(RequestErroredEvent $event) 53 | { 54 | if (($request = $this->retry->retry($event->getException()->getRequest())) === false) { 55 | return; 56 | } 57 | 58 | $event->getException()->setRequest($request); 59 | 60 | try { 61 | $event->setResponse($event->getHttpAdapter()->sendRequest($request)); 62 | } catch (HttpAdapterException $e) { 63 | $event->setException($e); 64 | } 65 | } 66 | 67 | /** 68 | * @param MultiRequestErroredEvent $event 69 | */ 70 | public function onMultiResponseErrored(MultiRequestErroredEvent $event) 71 | { 72 | $retryRequests = []; 73 | 74 | foreach ($event->getExceptions() as $exception) { 75 | if (($request = $this->retry->retry($exception->getRequest(), false)) !== false) { 76 | $retryRequests[] = $request; 77 | $event->removeException($exception); 78 | } 79 | } 80 | 81 | if (empty($retryRequests)) { 82 | return; 83 | } 84 | 85 | try { 86 | $event->addResponses($event->getHttpAdapter()->sendRequests($retryRequests)); 87 | } catch (MultiHttpAdapterException $e) { 88 | $event->addResponses($e->getResponses()); 89 | $event->addExceptions($e->getExceptions()); 90 | } 91 | } 92 | 93 | /** 94 | * {@inheritdoc} 95 | */ 96 | public static function getSubscribedEvents() 97 | { 98 | return [ 99 | Events::REQUEST_ERRORED => ['onRequestErrored', 0], 100 | Events::MULTI_REQUEST_ERRORED => ['onMultiResponseErrored', 0], 101 | ]; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Event/BasicAuth/BasicAuth.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\BasicAuth; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class BasicAuth implements BasicAuthInterface 20 | { 21 | /** 22 | * @var string 23 | */ 24 | private $username; 25 | 26 | /** 27 | * @var string 28 | */ 29 | private $password; 30 | 31 | /** 32 | * @var string|callable|null 33 | */ 34 | private $matcher; 35 | 36 | /** 37 | * @param string $username 38 | * @param string $password 39 | * @param string|callable|null $matcher 40 | */ 41 | public function __construct($username, $password, $matcher = null) 42 | { 43 | $this->setUsername($username); 44 | $this->setPassword($password); 45 | $this->setMatcher($matcher); 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function getUsername() 52 | { 53 | return $this->username; 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | */ 59 | public function setUsername($username) 60 | { 61 | $this->username = $username; 62 | } 63 | 64 | /** 65 | * {@inheritdoc} 66 | */ 67 | public function getPassword() 68 | { 69 | return $this->password; 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | public function setPassword($password) 76 | { 77 | $this->password = $password; 78 | } 79 | 80 | /** 81 | * {@inheritdoc} 82 | */ 83 | public function hasMatcher() 84 | { 85 | return $this->matcher !== null; 86 | } 87 | 88 | /** 89 | * {@inheritdoc} 90 | */ 91 | public function getMatcher() 92 | { 93 | return $this->matcher; 94 | } 95 | 96 | /** 97 | * {@inheritdoc} 98 | */ 99 | public function setMatcher($matcher) 100 | { 101 | $this->matcher = $matcher; 102 | } 103 | 104 | /** 105 | * {@inheritdoc} 106 | */ 107 | public function authenticate(InternalRequestInterface $internalRequest) 108 | { 109 | if (!$this->match($internalRequest)) { 110 | return $internalRequest; 111 | } 112 | 113 | return $internalRequest->withHeader( 114 | 'Authorization', 115 | 'Basic '.base64_encode($this->username.':'.$this->password) 116 | ); 117 | } 118 | 119 | /** 120 | * @param InternalRequestInterface $request 121 | * 122 | * @return bool 123 | */ 124 | private function match(InternalRequestInterface $request) 125 | { 126 | return !$this->hasMatcher() 127 | || (is_string($this->matcher) && preg_match($this->matcher, (string) $request->getUri())) 128 | || (is_callable($this->matcher) && call_user_func($this->matcher, $request)); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /doc/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | To send a request, you can use the API defined by the `Ivory\HttpAdapter\HttpAdapterInterface`. All these methods 4 | throw an `Ivory\HttpAdapter\HttpAdapterException` if an error occurred (I would recommend you to always use a try/catch 5 | block everywhere) and return an `Ivory\HttpAdapter\Message\ResponseInterface`. 6 | 7 | Additionally, the url can be a string or an `Psr\Http\Message\UriInterface`. The headers parameter can be an 8 | associative array describing an header key/value pair or an indexed array already formatted. The datas can be an 9 | associative array or a string already formatted according to the content-type you want to use. Finally, the files are 10 | an associative array describing key/path pair. 11 | 12 | ## Send a GET request 13 | 14 | ``` php 15 | $response = $httpAdapter->get($url, $headers); 16 | ``` 17 | 18 | ## Send an HEAD request 19 | 20 | ``` php 21 | $response = $httpAdapter->head($url, $headers); 22 | ``` 23 | 24 | ## Send a TRACE request 25 | 26 | ``` php 27 | $response = $httpAdapter->trace($url, $headers); 28 | ``` 29 | 30 | ## Send a POST request 31 | 32 | ``` php 33 | $response = $httpAdapter->post($url, $headers, $datas, $files); 34 | ``` 35 | 36 | ## Send a PUT request 37 | 38 | ``` php 39 | $response = $httpAdapter->put($url, $headers, $datas, $files); 40 | ``` 41 | 42 | ## Send a PATCH request 43 | 44 | ``` php 45 | $response = $httpAdapter->patch($url, $headers, $datas, $files); 46 | ``` 47 | 48 | ## Send a DELETE request 49 | 50 | ``` php 51 | $response = $httpAdapter->delete($url, $headers, $datas, $files); 52 | ``` 53 | 54 | ## Send an OPTIONS request 55 | 56 | ``` php 57 | $response = $httpAdapter->options($url, $headers, $datas, $files); 58 | ``` 59 | 60 | ## Send a request 61 | 62 | ``` php 63 | $response = $httpAdapter->send($url, $method, $headers, $datas, $files); 64 | ``` 65 | 66 | All methods are described by the `Ivory\HttpAdapter\Message\RequestInterface::METHOD_*` constants. 67 | 68 | ## Send a PSR-7 request 69 | 70 | ``` php 71 | use Ivory\HttpAdapter\Message\InternalRequest; 72 | use Ivory\HttpAdapter\Message\Request; 73 | 74 | $response = $httpAdapter->sendRequest(new Request($url, $method)); 75 | // or 76 | $response = $httpAdapter->sendRequest(new InternalRequest($url, $method)); 77 | ``` 78 | 79 | If you want to learn more about the requests, your can read this [doc](/doc/psr-7.md). 80 | 81 | ## Send multiple requests 82 | 83 | The main purpose of this method is performance! Instead of sending requests serially, the library will send them in 84 | parallel if the sub adapter is able to do it otherwise, it will fallback on a serial implementation. 85 | 86 | ``` php 87 | use Ivory\HttpAdapter\Message\InternalRequest; 88 | use Ivory\HttpAdapter\Message\Request; 89 | use Ivory\HttpAdapter\MultiHttpAdapterException; 90 | 91 | $requests = array( 92 | // An url (GET 1.1) 93 | 'http://egeloen.fr', 94 | 95 | // An array representing the parameters of the `MessageFactoryInterface::createInternalRequest` 96 | array('http://egeloen.fr', 'GET', 1.1, array('Content-Type' => 'json', '{"foo":"bar"}')), 97 | 98 | // A PSR-7 request 99 | new Request('http://egeloen.fr', 'GET'), 100 | 101 | // An internal request 102 | new InternalRequest('http://egeloen.fr', 'GET'), 103 | ); 104 | 105 | try { 106 | $responses = $httpAdapter->sendRequests($requests); 107 | } catch (MultiHttpAdapterException $e) { 108 | $responses = $e->getResponses(); 109 | $exceptions = $e->getExceptions(); 110 | } 111 | ``` 112 | -------------------------------------------------------------------------------- /src/AbstractCurlHttpAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | abstract class AbstractCurlHttpAdapter extends AbstractHttpAdapter 20 | { 21 | /** 22 | * @param ConfigurationInterface|null $configuration 23 | * @param bool $checkExtension 24 | * 25 | * @throws HttpAdapterException 26 | */ 27 | public function __construct(ConfigurationInterface $configuration = null, $checkExtension = true) 28 | { 29 | if ($checkExtension && !function_exists('curl_init')) { 30 | throw HttpAdapterException::extensionIsNotLoaded('curl', $this->getName()); 31 | } 32 | 33 | parent::__construct($configuration); 34 | } 35 | 36 | /** 37 | * @param InternalRequestInterface $internalRequest 38 | * 39 | * @return int 40 | */ 41 | protected function prepareProtocolVersion(InternalRequestInterface $internalRequest) 42 | { 43 | return $internalRequest->getProtocolVersion() === InternalRequestInterface::PROTOCOL_VERSION_1_0 44 | ? CURL_HTTP_VERSION_1_0 45 | : CURL_HTTP_VERSION_1_1; 46 | } 47 | 48 | /** 49 | * @param InternalRequestInterface $internalRequest 50 | * 51 | * @return array|string 52 | */ 53 | protected function prepareContent(InternalRequestInterface $internalRequest) 54 | { 55 | $files = $internalRequest->getFiles(); 56 | 57 | if (empty($files)) { 58 | return $this->prepareBody($internalRequest); 59 | } 60 | 61 | $content = []; 62 | 63 | foreach ($internalRequest->getDatas() as $name => $data) { 64 | $content = array_merge($content, $this->prepareRawContent($name, $data)); 65 | } 66 | 67 | foreach ($files as $name => $file) { 68 | $content = array_merge($content, $this->prepareRawContent($name, $file, true)); 69 | } 70 | 71 | return $content; 72 | } 73 | 74 | /** 75 | * @param string $file 76 | * 77 | * @return mixed 78 | */ 79 | protected function createFile($file) 80 | { 81 | return $this->isSafeUpload() ? new \CurlFile($file) : '@'.$file; 82 | } 83 | 84 | /** 85 | * @return bool 86 | */ 87 | protected function isSafeUpload() 88 | { 89 | return defined('CURLOPT_SAFE_UPLOAD'); 90 | } 91 | 92 | /** 93 | * @param string $name 94 | * @param array|string $data 95 | * @param bool $isFile 96 | * 97 | * @return array 98 | */ 99 | private function prepareRawContent($name, $data, $isFile = false) 100 | { 101 | if (is_array($data)) { 102 | $preparedData = []; 103 | 104 | foreach ($data as $subName => $subData) { 105 | $preparedData = array_merge( 106 | $preparedData, 107 | $this->prepareRawContent($this->prepareName($name, $subName), $subData, $isFile) 108 | ); 109 | } 110 | 111 | return $preparedData; 112 | } 113 | 114 | return [$name => $isFile ? $this->createFile($data) : $data]; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/HttpAdapterInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter; 13 | 14 | use Ivory\HttpAdapter\Message\ResponseInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | interface HttpAdapterInterface extends PsrHttpAdapterInterface 20 | { 21 | /** 22 | * @param string|object $uri 23 | * @param array $headers 24 | * 25 | * @throws HttpAdapterException 26 | * 27 | * @return ResponseInterface 28 | */ 29 | public function get($uri, array $headers = []); 30 | 31 | /** 32 | * @param string|object $uri 33 | * @param array $headers 34 | * 35 | * @throws HttpAdapterException 36 | * 37 | * @return ResponseInterface 38 | */ 39 | public function head($uri, array $headers = []); 40 | 41 | /** 42 | * @param string|object $uri 43 | * @param array $headers 44 | * 45 | * @throws HttpAdapterException 46 | * 47 | * @return ResponseInterface 48 | */ 49 | public function trace($uri, array $headers = []); 50 | 51 | /** 52 | * @param string|object $uri 53 | * @param array $headers 54 | * @param array|string $datas 55 | * @param array $files 56 | * 57 | * @throws HttpAdapterException 58 | * 59 | * @return ResponseInterface 60 | */ 61 | public function post($uri, array $headers = [], $datas = [], array $files = []); 62 | 63 | /** 64 | * @param string|object $uri 65 | * @param array $headers 66 | * @param array|string $datas 67 | * @param array $files 68 | * 69 | * @throws HttpAdapterException 70 | * 71 | * @return ResponseInterface 72 | */ 73 | public function put($uri, array $headers = [], $datas = [], array $files = []); 74 | 75 | /** 76 | * @param string|object $uri 77 | * @param array $headers 78 | * @param array|string $datas 79 | * @param array $files 80 | * 81 | * @throws HttpAdapterException 82 | * 83 | * @return ResponseInterface 84 | */ 85 | public function patch($uri, array $headers = [], $datas = [], array $files = []); 86 | 87 | /** 88 | * @param string|object $uri 89 | * @param array $headers 90 | * @param array|string $datas 91 | * @param array $files 92 | * 93 | * @throws HttpAdapterException 94 | * 95 | * @return ResponseInterface 96 | */ 97 | public function delete($uri, array $headers = [], $datas = [], array $files = []); 98 | 99 | /** 100 | * @param string|object $uri 101 | * @param array $headers 102 | * @param array|string $datas 103 | * @param array $files 104 | * 105 | * @throws HttpAdapterException 106 | * 107 | * @return ResponseInterface 108 | */ 109 | public function options($uri, array $headers = [], $datas = [], array $files = []); 110 | 111 | /** 112 | * @param string|object $uri 113 | * @param string $method 114 | * @param array $headers 115 | * @param array|string $datas 116 | * @param array $files 117 | * 118 | * @throws HttpAdapterException 119 | * 120 | * @return ResponseInterface 121 | */ 122 | public function send($uri, $method, array $headers = [], $datas = [], array $files = []); 123 | } 124 | -------------------------------------------------------------------------------- /src/Event/Subscriber/StatusCodeSubscriber.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Subscriber; 13 | 14 | use Ivory\HttpAdapter\Event\Events; 15 | use Ivory\HttpAdapter\Event\MultiRequestSentEvent; 16 | use Ivory\HttpAdapter\Event\RequestSentEvent; 17 | use Ivory\HttpAdapter\Event\StatusCode\StatusCode; 18 | use Ivory\HttpAdapter\Event\StatusCode\StatusCodeInterface; 19 | use Ivory\HttpAdapter\HttpAdapterException; 20 | use Ivory\HttpAdapter\HttpAdapterInterface; 21 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 22 | use Ivory\HttpAdapter\Message\ResponseInterface; 23 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; 24 | 25 | /** 26 | * @author GeLo 27 | */ 28 | class StatusCodeSubscriber implements EventSubscriberInterface 29 | { 30 | /** 31 | * @var StatusCodeInterface 32 | */ 33 | private $statusCode; 34 | 35 | /** 36 | * @param StatusCodeInterface|null $statusCode 37 | */ 38 | public function __construct(StatusCodeInterface $statusCode = null) 39 | { 40 | $this->statusCode = $statusCode ?: new StatusCode(); 41 | } 42 | 43 | /** 44 | * @return StatusCodeInterface 45 | */ 46 | public function getStatusCode() 47 | { 48 | return $this->statusCode; 49 | } 50 | 51 | /** 52 | * @param RequestSentEvent $event 53 | */ 54 | public function onRequestSent(RequestSentEvent $event) 55 | { 56 | if (!$this->statusCode->validate($event->getResponse())) { 57 | $event->setException($this->createStatusCodeException( 58 | $event->getResponse(), 59 | $event->getRequest(), 60 | $event->getHttpAdapter() 61 | )); 62 | } 63 | } 64 | 65 | /** 66 | * @param MultiRequestSentEvent $event 67 | */ 68 | public function onMultiRequestSent(MultiRequestSentEvent $event) 69 | { 70 | foreach ($event->getResponses() as $response) { 71 | if (!$this->statusCode->validate($response)) { 72 | $event->addException($this->createStatusCodeException( 73 | $response, 74 | $response->getParameter('request'), 75 | $event->getHttpAdapter() 76 | )); 77 | 78 | $event->removeResponse($response); 79 | } 80 | } 81 | } 82 | 83 | /** 84 | * {@inheritdoc} 85 | */ 86 | public static function getSubscribedEvents() 87 | { 88 | return [ 89 | Events::REQUEST_SENT => ['onRequestSent', 200], 90 | Events::MULTI_REQUEST_SENT => ['onMultiRequestSent', 200], 91 | ]; 92 | } 93 | 94 | /** 95 | * @param ResponseInterface $response 96 | * @param InternalRequestInterface $internalRequest 97 | * @param HttpAdapterInterface $httpAdapter 98 | * 99 | * @return HttpAdapterException 100 | */ 101 | private function createStatusCodeException( 102 | ResponseInterface $response, 103 | InternalRequestInterface $internalRequest, 104 | HttpAdapterInterface $httpAdapter 105 | ) { 106 | $exception = HttpAdapterException::cannotFetchUri( 107 | (string) $internalRequest->getUri(), 108 | $httpAdapter->getName(), 109 | sprintf('Status code: %d', $response->getStatusCode()) 110 | ); 111 | 112 | $exception->setRequest($internalRequest); 113 | $exception->setResponse($response); 114 | 115 | return $exception; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /doc/psr-7.md: -------------------------------------------------------------------------------- 1 | # PSR-7 (Http) 2 | 3 | For http messaging, the library is based on the [PSR-7 Standard](https://github.com/php-fig/fig-standards/blob/master/proposed/http-message.md) 4 | by using the [zendframework/zend-diactoros](https://github.com/zendframework/zend-diactoros) package. 5 | 6 | ## Factory 7 | 8 | The PSR-7 only defines interfaces and zendframework/zend-diactoros only defines implementation. The library is shipped 9 | with a factory which ease the creation of requests/responses. As explained in the configuration 10 | [doc](/doc/configuration.md#messafe-factory), the requests/responses are created through a factory, so, if you want 11 | to create any PSR-7 objects, it is recommended to use the following API: 12 | 13 | ``` php 14 | $request = $messageFactory->createRequest( 15 | $uri, 16 | $method, 17 | $protocolVersion, 18 | $headers, 19 | $body, 20 | $parameters 21 | ); 22 | 23 | $internalRequest = $messageFactory->createInternalRequest( 24 | $uri, 25 | $method, 26 | $protocolVersion, 27 | $headers, 28 | $datas, 29 | $files, 30 | $parameters 31 | ); 32 | 33 | $response = $messageFactory->createResponse( 34 | $statusCode, 35 | $protocolVersion, 36 | $headers, 37 | $body, 38 | $parameters 39 | ); 40 | ``` 41 | 42 | ## Message 43 | 44 | The message implementation is based on `Zend\Diactoros\MessageTrait` with some features on top of it through the 45 | `Ivory\HttpAdapter\Message\MessageTrait` such as parameters. Basically, parameters are arbitrary values that you can 46 | store in your message. They are mostly used by the event system in order to store additional informations. 47 | 48 | The available API is: 49 | 50 | ``` php 51 | $parameters = $message->getParameters(); 52 | $hasParameter = $message->hasParameter($name); 53 | $parameter = $message->getParameter($name); 54 | $newMessage = $message->withParameter($name, $value); 55 | $newMessage = $message->withoutParameter($name); 56 | ``` 57 | 58 | ## Request 59 | 60 | The request is based on `Zend\Diactoros\Request` with additionally the Ivory message features. 61 | 62 | ## Internal Request 63 | 64 | The internal request is an extension of the request. It can be used as a request or as form. Basically, if you only use 65 | the API provided by the PSR-7 standard, it is your responsibility to encode the request body. Given, you want to send a 66 | simple form, you will need to encode the body as `application/x-www-form-urlencoded` or even worse, if you want to 67 | upload files, you will need to encode the body as `multipart/form-data`. Hopefully, the internal request is here and 68 | allow you to easily deal with such problematic. 69 | 70 | Be aware that if you use body, the datas/files will be ignored even if you provide them (the PSR request always win). 71 | 72 | ### Datas 73 | 74 | The datas represent the form datas which will be sent to the server. It is an associative array describing name/value 75 | pairs: 76 | 77 | ``` php 78 | $datas = $internalRequest->getDatas(); 79 | $hasData = $internalRequest->hasData('foo'); 80 | $data = $internalRequest->getData('foo'); 81 | $newInternalRequest = $internalRequest->withData('foo', 'bar') 82 | $newInternalRequest = $internalRequest->withoutData('foo'); 83 | ``` 84 | 85 | ### Files 86 | 87 | The files represents the form files which will be sent to the server. It is an associative array describing a name/file 88 | path pair: 89 | 90 | ``` php 91 | $files = $internalRequest->getFiles(); 92 | $hasFile = $internalRequest->hasFile('file'); 93 | $file = $internalRequest->getFile('file'); 94 | $newInternalRequest = $internalRequest->withFile('file', '/path/of/the/file'); 95 | $newInternalRequest = $internalRequest->withoutFile('file'); 96 | ``` 97 | 98 | ## Response 99 | 100 | The response is based on `Zend\Diactoros\Response` with additionally the Ivory message features. 101 | 102 | ## Stream 103 | 104 | The response is based on `Zend\Diactoros\Stream`. 105 | -------------------------------------------------------------------------------- /src/Event/Cookie/CookieInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Cookie; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | interface CookieInterface 20 | { 21 | const ATTR_DOMAIN = 'domain'; 22 | const ATTR_PATH = 'path'; 23 | const ATTR_SECURE = 'secure'; 24 | const ATTR_MAX_AGE = 'max-age'; 25 | const ATTR_EXPIRES = 'expires'; 26 | 27 | /** 28 | * @return bool 29 | */ 30 | public function hasName(); 31 | 32 | /** 33 | * @return string|null 34 | */ 35 | public function getName(); 36 | 37 | /** 38 | * @param string|null 39 | */ 40 | public function setName($name); 41 | 42 | /** 43 | * @return bool 44 | */ 45 | public function hasValue(); 46 | 47 | /** 48 | * @return string|null 49 | */ 50 | public function getValue(); 51 | 52 | /** 53 | * @param string|null $value 54 | */ 55 | public function setValue($value); 56 | 57 | public function clearAttributes(); 58 | 59 | /** 60 | * @return bool 61 | */ 62 | public function hasAttributes(); 63 | 64 | /** 65 | * @return array 66 | */ 67 | public function getAttributes(); 68 | 69 | /** 70 | * @param array $attributes 71 | */ 72 | public function setAttributes(array $attributes); 73 | 74 | /** 75 | * @param array $attributes 76 | */ 77 | public function addAttributes(array $attributes); 78 | 79 | /** 80 | * @param array $names 81 | */ 82 | public function removeAttributes(array $names); 83 | 84 | /** 85 | * @param string $name 86 | * 87 | * @return bool 88 | */ 89 | public function hasAttribute($name); 90 | 91 | /** 92 | * @param string $name 93 | * 94 | * @return string 95 | */ 96 | public function getAttribute($name); 97 | 98 | /** 99 | * @param string $name 100 | * @param mixed $value 101 | */ 102 | public function setAttribute($name, $value); 103 | 104 | /** 105 | * @param string $name 106 | */ 107 | public function removeAttribute($name); 108 | 109 | /** 110 | * @return int 111 | */ 112 | public function getCreatedAt(); 113 | 114 | /** 115 | * @param int $createdAt 116 | */ 117 | public function setCreatedAt($createdAt); 118 | 119 | /** 120 | * @return int|bool 121 | */ 122 | public function getExpires(); 123 | 124 | /** 125 | * @return bool 126 | */ 127 | public function isExpired(); 128 | 129 | /** 130 | * @param CookieInterface $cookie 131 | * 132 | * @return bool 133 | */ 134 | public function compare(CookieInterface $cookie); 135 | 136 | /** 137 | * @param InternalRequestInterface $request 138 | * 139 | * @return bool 140 | */ 141 | public function match(InternalRequestInterface $request); 142 | 143 | /** 144 | * @param string|null $domain 145 | * 146 | * @return bool 147 | */ 148 | public function matchDomain($domain); 149 | 150 | /** 151 | * @param string|null $path 152 | * 153 | * @return bool 154 | */ 155 | public function matchPath($path); 156 | 157 | /** 158 | * @param string|null $scheme 159 | * 160 | * @return bool 161 | */ 162 | public function matchScheme($scheme); 163 | 164 | /** 165 | * @return array 166 | */ 167 | public function toArray(); 168 | 169 | /** 170 | * @return string 171 | */ 172 | public function __toString(); 173 | } 174 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | Before starting, be aware **this package is deprecated in favor of [php-http](https://github.com/php-http)**. 4 | That means bugfixes would be accepted but new features will not (except maybe for already opened PRs). 5 | 6 | [![Build Status](https://secure.travis-ci.org/egeloen/ivory-http-adapter.png?branch=master)](http://travis-ci.org/egeloen/ivory-http-adapter) 7 | [![Code Coverage](https://scrutinizer-ci.com/g/egeloen/ivory-http-adapter/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/egeloen/ivory-http-adapter/?branch=master) 8 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/egeloen/ivory-http-adapter/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/egeloen/ivory-http-adapter/?branch=master) 9 | [![Dependency Status](http://www.versioneye.com/php/egeloen:http-adapter/badge.svg)](http://www.versioneye.com/php/egeloen:http-adapter) 10 | 11 | [![Latest Stable Version](https://poser.pugx.org/egeloen/http-adapter/v/stable.svg)](https://packagist.org/packages/egeloen/http-adapter) 12 | [![Latest Unstable Version](https://poser.pugx.org/egeloen/http-adapter/v/unstable.svg)](https://packagist.org/packages/egeloen/http-adapter) 13 | [![Total Downloads](https://poser.pugx.org/egeloen/http-adapter/downloads.svg)](https://packagist.org/packages/egeloen/http-adapter) 14 | [![License](https://poser.pugx.org/egeloen/http-adapter/license.svg)](https://packagist.org/packages/egeloen/http-adapter) 15 | 16 | ## Introduction 17 | 18 | The library allows to issue HTTP requests with PHP 5.4.8+. The supported adapters are: 19 | 20 | - [Buzz](https://github.com/kriswallsmith/Buzz) 21 | - [Cake](http://cakephp.org/) 22 | - [cURL](http://curl.haxx.se/) 23 | - [FileGetContents](http://php.net/manual/en/function.file-get-contents.php) 24 | - [Fopen](http://php.net/manual/en/function.fopen.php) 25 | - [Guzzle3](http://guzzle3.readthedocs.org/) 26 | - [Guzzle4](http://guzzle.readthedocs.org/en/v5/) 27 | - [Guzzle5](http://guzzle.readthedocs.org/en/v5/) 28 | - [Guzzle6](http://guzzle.readthedocs.org/en/v6/) 29 | - [Httpful](http://phphttpclient.com/) 30 | - [Pecl Http](http://devel-m6w6.rhcloud.com/mdref/http) 31 | - [React](http://reactphp.org/) 32 | - [Requests](http://requests.ryanmccue.info/) 33 | - [Socket](http://php.net/manual/en/function.stream-socket-client.php) 34 | - [Zend1](http://framework.zend.com/manual/1.12/en/zend.http.html) 35 | - [Zend2](http://framework.zend.com/manual/2.0/en/modules/zend.http.html) 36 | 37 | Additionally, it follows the [PSR-7 Standard](https://github.com/php-fig/fig-standards/blob/master/proposed/http-message.md) 38 | which defines how http message should be implemented. 39 | 40 | ## Documentation 41 | 42 | 1. [Installation](/doc/installation.md) 43 | 2. [Adapters](/doc/adapters.md) 44 | 3. [Configuration](/doc/configuration.md) 45 | 4. [PSR-7](/doc/psr-7.md) 46 | 5. [Usage](/doc/usage.md) 47 | 6. [Events](/doc/events.md) 48 | 49 | ## Cookbook 50 | 51 | - [Log requests, responses and exceptions](/doc/events.md#logger) 52 | - [Journalize requests and responses](/doc/events.md#history) 53 | - [Throw exceptions for errored responses](/doc/events.md#status-code) 54 | - [Retry errored requests](/doc/events.md#retry) 55 | - [Follow redirects](/doc/events.md#redirect) 56 | - [Cache responses and exceptions](/doc/events.md#cache) 57 | - [Manage cookies](/doc/events.md#cookie) 58 | 59 | ## Testing 60 | 61 | The library is fully unit tested by [PHPUnit](http://www.phpunit.de/) with a code coverage close to **100%**. To 62 | execute the test suite, check the travis [configuration](/.travis.yml). 63 | 64 | ## Contribute 65 | 66 | We love contributors! The library is open source, if you'd like to contribute, feel free to propose a PR! You 67 | can follow the [CONTRIBUTING](/CONTRIBUTING.md) file which will explain you how to set up the project. 68 | 69 | ## License 70 | 71 | The Ivory Http Adapter is under the MIT license. For the full copyright and license information, please read the 72 | [LICENSE](/LICENSE) file that was distributed with this source code. 73 | -------------------------------------------------------------------------------- /src/Event/Subscriber/HistorySubscriber.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Subscriber; 13 | 14 | use Ivory\HttpAdapter\Event\Events; 15 | use Ivory\HttpAdapter\Event\History\Journal; 16 | use Ivory\HttpAdapter\Event\History\JournalInterface; 17 | use Ivory\HttpAdapter\Event\MultiRequestCreatedEvent; 18 | use Ivory\HttpAdapter\Event\MultiRequestSentEvent; 19 | use Ivory\HttpAdapter\Event\RequestCreatedEvent; 20 | use Ivory\HttpAdapter\Event\RequestSentEvent; 21 | use Ivory\HttpAdapter\Event\Timer\TimerInterface; 22 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 23 | use Ivory\HttpAdapter\Message\ResponseInterface; 24 | 25 | /** 26 | * @author GeLo 27 | */ 28 | class HistorySubscriber extends AbstractTimerSubscriber 29 | { 30 | /** 31 | * @var JournalInterface 32 | */ 33 | private $journal; 34 | 35 | /** 36 | * @param JournalInterface|null $journal 37 | * @param TimerInterface|null $timer 38 | */ 39 | public function __construct(JournalInterface $journal = null, TimerInterface $timer = null) 40 | { 41 | parent::__construct($timer); 42 | 43 | $this->journal = $journal ?: new Journal(); 44 | } 45 | 46 | /** 47 | * @return JournalInterface 48 | */ 49 | public function getJournal() 50 | { 51 | return $this->journal; 52 | } 53 | 54 | /** 55 | * @param RequestCreatedEvent $event 56 | */ 57 | public function onRequestCreated(RequestCreatedEvent $event) 58 | { 59 | $event->setRequest($this->getTimer()->start($event->getRequest())); 60 | } 61 | 62 | /** 63 | * @param RequestSentEvent $event 64 | */ 65 | public function onRequestSent(RequestSentEvent $event) 66 | { 67 | $event->setRequest($this->record($event->getRequest(), $event->getResponse())); 68 | } 69 | 70 | /** 71 | * @param MultiRequestCreatedEvent $event 72 | */ 73 | public function onMultiRequestCreated(MultiRequestCreatedEvent $event) 74 | { 75 | foreach ($event->getRequests() as $request) { 76 | $event->removeRequest($request); 77 | $event->addRequest($this->getTimer()->start($request)); 78 | } 79 | } 80 | 81 | /** 82 | * @param MultiRequestSentEvent $event 83 | */ 84 | public function onMultiRequestSent(MultiRequestSentEvent $event) 85 | { 86 | foreach ($event->getResponses() as $response) { 87 | $request = $this->record($response->getParameter('request'), $response); 88 | 89 | $event->removeResponse($response); 90 | $event->addResponse($response->withParameter('request', $request)); 91 | } 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | public static function getSubscribedEvents() 98 | { 99 | return [ 100 | Events::REQUEST_CREATED => ['onRequestCreated', 100], 101 | Events::REQUEST_SENT => ['onRequestSent', 100], 102 | Events::MULTI_REQUEST_CREATED => ['onMultiRequestCreated', 100], 103 | Events::MULTI_REQUEST_SENT => ['onMultiRequestSent', 100], 104 | ]; 105 | } 106 | 107 | /** 108 | * @param InternalRequestInterface $internalRequest 109 | * @param ResponseInterface $response 110 | * 111 | * @return InternalRequestInterface 112 | */ 113 | private function record(InternalRequestInterface $internalRequest, ResponseInterface $response) 114 | { 115 | $internalRequest = $this->getTimer()->stop($internalRequest); 116 | $this->journal->record($internalRequest, $response); 117 | 118 | return $internalRequest; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Event/Subscriber/CookieSubscriber.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Subscriber; 13 | 14 | use Ivory\HttpAdapter\Event\Cookie\Jar\CookieJar; 15 | use Ivory\HttpAdapter\Event\Cookie\Jar\CookieJarInterface; 16 | use Ivory\HttpAdapter\Event\Events; 17 | use Ivory\HttpAdapter\Event\MultiRequestCreatedEvent; 18 | use Ivory\HttpAdapter\Event\MultiRequestErroredEvent; 19 | use Ivory\HttpAdapter\Event\MultiRequestSentEvent; 20 | use Ivory\HttpAdapter\Event\RequestCreatedEvent; 21 | use Ivory\HttpAdapter\Event\RequestErroredEvent; 22 | use Ivory\HttpAdapter\Event\RequestSentEvent; 23 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; 24 | 25 | /** 26 | * @author GeLo 27 | */ 28 | class CookieSubscriber implements EventSubscriberInterface 29 | { 30 | /** 31 | * @var CookieJarInterface 32 | */ 33 | private $cookieJar; 34 | 35 | /** 36 | * @param CookieJarInterface|null $cookieJar 37 | */ 38 | public function __construct(CookieJarInterface $cookieJar = null) 39 | { 40 | $this->cookieJar = $cookieJar ?: new CookieJar(); 41 | } 42 | 43 | /** 44 | * @return CookieJarInterface 45 | */ 46 | public function getCookieJar() 47 | { 48 | return $this->cookieJar; 49 | } 50 | 51 | /** 52 | * @param RequestCreatedEvent $event 53 | */ 54 | public function onRequestCreated(RequestCreatedEvent $event) 55 | { 56 | $event->setRequest($this->cookieJar->populate($event->getRequest())); 57 | } 58 | 59 | /** 60 | * @param RequestSentEvent $event 61 | */ 62 | public function onRequestSent(RequestSentEvent $event) 63 | { 64 | $this->cookieJar->extract($event->getRequest(), $event->getResponse()); 65 | } 66 | 67 | /** 68 | * @param RequestErroredEvent $event 69 | */ 70 | public function onRequestErrored(RequestErroredEvent $event) 71 | { 72 | if ($event->getException()->hasResponse()) { 73 | $this->cookieJar->extract($event->getException()->getRequest(), $event->getException()->getResponse()); 74 | } 75 | } 76 | 77 | /** 78 | * @param MultiRequestCreatedEvent $event 79 | */ 80 | public function onMultiRequestCreated(MultiRequestCreatedEvent $event) 81 | { 82 | foreach ($event->getRequests() as $request) { 83 | $event->removeRequest($request); 84 | $event->addRequest($this->cookieJar->populate($request)); 85 | } 86 | } 87 | 88 | /** 89 | * @param MultiRequestSentEvent $event 90 | */ 91 | public function onMultiRequestSent(MultiRequestSentEvent $event) 92 | { 93 | foreach ($event->getResponses() as $response) { 94 | $this->cookieJar->extract($response->getParameter('request'), $response); 95 | } 96 | } 97 | 98 | /** 99 | * @param MultiRequestErroredEvent $event 100 | */ 101 | public function onMultiResponseErrored(MultiRequestErroredEvent $event) 102 | { 103 | foreach ($event->getExceptions() as $exception) { 104 | if ($exception->hasResponse()) { 105 | $this->cookieJar->extract($exception->getRequest(), $exception->getResponse()); 106 | } 107 | } 108 | } 109 | 110 | /** 111 | * {@inheritdoc} 112 | */ 113 | public static function getSubscribedEvents() 114 | { 115 | return [ 116 | Events::REQUEST_CREATED => ['onRequestCreated', 300], 117 | Events::REQUEST_SENT => ['onRequestSent', 300], 118 | Events::REQUEST_ERRORED => ['onRequestErrored', 300], 119 | Events::MULTI_REQUEST_CREATED => ['onMultiRequestCreated', 300], 120 | Events::MULTI_REQUEST_SENT => ['onMultiRequestSent', 300], 121 | Events::MULTI_REQUEST_ERRORED => ['onMultiResponseErrored', 300], 122 | ]; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Event/Subscriber/RedirectSubscriber.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Subscriber; 13 | 14 | use Ivory\HttpAdapter\Event\Events; 15 | use Ivory\HttpAdapter\Event\MultiRequestSentEvent; 16 | use Ivory\HttpAdapter\Event\Redirect\Redirect; 17 | use Ivory\HttpAdapter\Event\Redirect\RedirectInterface; 18 | use Ivory\HttpAdapter\Event\RequestSentEvent; 19 | use Ivory\HttpAdapter\HttpAdapterException; 20 | use Ivory\HttpAdapter\MultiHttpAdapterException; 21 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; 22 | 23 | /** 24 | * @author GeLo 25 | */ 26 | class RedirectSubscriber implements EventSubscriberInterface 27 | { 28 | /** 29 | * @var RedirectInterface 30 | */ 31 | private $redirect; 32 | 33 | /** 34 | * @param RedirectInterface $redirect 35 | */ 36 | public function __construct(RedirectInterface $redirect = null) 37 | { 38 | $this->redirect = $redirect ?: new Redirect(); 39 | } 40 | 41 | /** 42 | * @return RedirectInterface 43 | */ 44 | public function getRedirect() 45 | { 46 | return $this->redirect; 47 | } 48 | 49 | /** 50 | * @param RequestSentEvent $event 51 | */ 52 | public function onRequestSent(RequestSentEvent $event) 53 | { 54 | try { 55 | $redirectRequest = $this->redirect->createRedirectRequest( 56 | $event->getResponse(), 57 | $event->getRequest(), 58 | $event->getHttpAdapter() 59 | ); 60 | } catch (HttpAdapterException $e) { 61 | $event->setException($e); 62 | 63 | return; 64 | } 65 | 66 | if ($redirectRequest === false) { 67 | $event->setResponse($this->redirect->prepareResponse($event->getResponse(), $event->getRequest())); 68 | 69 | return; 70 | } 71 | 72 | try { 73 | $event->setResponse($event->getHttpAdapter()->sendRequest($redirectRequest)); 74 | } catch (HttpAdapterException $e) { 75 | $event->setException($e); 76 | } 77 | } 78 | 79 | /** 80 | * @param MultiRequestSentEvent $event 81 | */ 82 | public function onMultiRequestSent(MultiRequestSentEvent $event) 83 | { 84 | $redirectRequests = []; 85 | 86 | foreach ($event->getResponses() as $response) { 87 | try { 88 | $redirectRequest = $this->redirect->createRedirectRequest( 89 | $response, 90 | $response->getParameter('request'), 91 | $event->getHttpAdapter() 92 | ); 93 | } catch (HttpAdapterException $e) { 94 | $event->removeResponse($response); 95 | $event->addException($e); 96 | continue; 97 | } 98 | 99 | if ($redirectRequest === false) { 100 | $event->removeResponse($response); 101 | $event->addResponse($this->redirect->prepareResponse($response, $response->getParameter('request'))); 102 | } else { 103 | $redirectRequests[] = $redirectRequest; 104 | $event->removeResponse($response); 105 | } 106 | } 107 | 108 | if (empty($redirectRequests)) { 109 | return; 110 | } 111 | 112 | try { 113 | $event->addResponses($event->getHttpAdapter()->sendRequests($redirectRequests)); 114 | } catch (MultiHttpAdapterException $e) { 115 | $event->addResponses($e->getResponses()); 116 | $event->addExceptions($e->getExceptions()); 117 | } 118 | } 119 | 120 | /** 121 | * {@inheritdoc} 122 | */ 123 | public static function getSubscribedEvents() 124 | { 125 | return [ 126 | Events::REQUEST_SENT => ['onRequestSent', 0], 127 | Events::MULTI_REQUEST_SENT => ['onMultiRequestSent', 0], 128 | ]; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Message/InternalRequest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Message; 13 | 14 | use Psr\Http\Message\StreamInterface; 15 | use Psr\Http\Message\UriInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class InternalRequest extends Request implements InternalRequestInterface 21 | { 22 | /** 23 | * @var array 24 | */ 25 | private $datas = []; 26 | 27 | /** 28 | * @var array 29 | */ 30 | private $files = []; 31 | 32 | /** 33 | * @param null|string|UriInterface $uri 34 | * @param null|string $method 35 | * @param string|resource|StreamInterface $body 36 | * @param array $datas 37 | * @param array $files 38 | * @param array $headers 39 | * @param array $parameters 40 | */ 41 | public function __construct( 42 | $uri = null, 43 | $method = null, 44 | $body = 'php://memory', 45 | array $datas = [], 46 | array $files = [], 47 | array $headers = [], 48 | array $parameters = [] 49 | ) { 50 | parent::__construct($uri, $method, $body, $headers, $parameters); 51 | 52 | $this->datas = $datas; 53 | $this->files = $files; 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | */ 59 | public function getDatas() 60 | { 61 | return $this->datas; 62 | } 63 | 64 | /** 65 | * {@inheritdoc} 66 | */ 67 | public function hasData($name) 68 | { 69 | return isset($this->datas[$name]); 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | public function getData($name) 76 | { 77 | return $this->hasData($name) ? $this->datas[$name] : null; 78 | } 79 | 80 | /** 81 | * {@inheritdoc} 82 | */ 83 | public function withData($name, $value) 84 | { 85 | $new = clone $this; 86 | $new->datas[$name] = $value; 87 | 88 | return $new; 89 | } 90 | 91 | /** 92 | * {@inheritdoc} 93 | */ 94 | public function withAddedData($name, $value) 95 | { 96 | $new = clone $this; 97 | $new->datas[$name] = $new->hasData($name) 98 | ? array_merge((array) $new->datas[$name], (array) $value) 99 | : $value; 100 | 101 | return $new; 102 | } 103 | 104 | /** 105 | * {@inheritdoc} 106 | */ 107 | public function withoutData($name) 108 | { 109 | $new = clone $this; 110 | unset($new->datas[$name]); 111 | 112 | return $new; 113 | } 114 | 115 | /** 116 | * {@inheritdoc} 117 | */ 118 | public function getFiles() 119 | { 120 | return $this->files; 121 | } 122 | 123 | /** 124 | * {@inheritdoc} 125 | */ 126 | public function hasFile($name) 127 | { 128 | return isset($this->files[$name]); 129 | } 130 | 131 | /** 132 | * {@inheritdoc} 133 | */ 134 | public function getFile($name) 135 | { 136 | return $this->hasFile($name) ? $this->files[$name] : null; 137 | } 138 | 139 | /** 140 | * {@inheritdoc} 141 | */ 142 | public function withFile($name, $file) 143 | { 144 | $new = clone $this; 145 | $new->files[$name] = $file; 146 | 147 | return $new; 148 | } 149 | 150 | /** 151 | * {@inheritdoc} 152 | */ 153 | public function withAddedFile($name, $file) 154 | { 155 | $new = clone $this; 156 | $new->files[$name] = $new->hasFile($name) 157 | ? array_merge((array) $new->files[$name], (array) $file) 158 | : $file; 159 | 160 | return $new; 161 | } 162 | 163 | /** 164 | * {@inheritdoc} 165 | */ 166 | public function withoutFile($name) 167 | { 168 | $new = clone $this; 169 | unset($new->files[$name]); 170 | 171 | return $new; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/Event/History/Journal.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\History; 13 | 14 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 15 | use Ivory\HttpAdapter\Message\ResponseInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class Journal implements JournalInterface 21 | { 22 | /** 23 | * @var JournalEntryFactoryInterface 24 | */ 25 | private $journalEntryFactory; 26 | 27 | /** 28 | * @var array 29 | */ 30 | private $entries = []; 31 | 32 | /** 33 | * @var int 34 | */ 35 | private $limit = 10; 36 | 37 | /** 38 | * @param JournalEntryFactoryInterface|null $journalEntryFactory 39 | */ 40 | public function __construct(JournalEntryFactoryInterface $journalEntryFactory = null) 41 | { 42 | $this->setJournalEntryFactory($journalEntryFactory ?: new JournalEntryFactory()); 43 | } 44 | 45 | /** 46 | * @return JournalEntryFactoryInterface 47 | */ 48 | public function getJournalEntryFactory() 49 | { 50 | return $this->journalEntryFactory; 51 | } 52 | 53 | /** 54 | * @param JournalEntryFactoryInterface $journalEntryFactory 55 | */ 56 | public function setJournalEntryFactory(JournalEntryFactoryInterface $journalEntryFactory) 57 | { 58 | $this->journalEntryFactory = $journalEntryFactory; 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | public function record(InternalRequestInterface $request, ResponseInterface $response) 65 | { 66 | $this->addEntry($this->journalEntryFactory->create($request, $response)); 67 | } 68 | 69 | /** 70 | * {@inheritdoc} 71 | */ 72 | public function clear() 73 | { 74 | $this->entries = []; 75 | } 76 | 77 | /** 78 | * {@inheritdoc} 79 | */ 80 | public function hasEntries() 81 | { 82 | return !empty($this->entries); 83 | } 84 | 85 | /** 86 | * {@inheritdoc} 87 | */ 88 | public function getEntries() 89 | { 90 | return $this->entries; 91 | } 92 | 93 | /** 94 | * {@inheritdoc} 95 | */ 96 | public function setEntries(array $entries) 97 | { 98 | $this->clear(); 99 | $this->addEntries($entries); 100 | } 101 | 102 | /** 103 | * {@inheritdoc} 104 | */ 105 | public function addEntries(array $entries) 106 | { 107 | foreach ($entries as $entry) { 108 | $this->addEntry($entry); 109 | } 110 | } 111 | 112 | /** 113 | * {@inheritdoc} 114 | */ 115 | public function removeEntries(array $entries) 116 | { 117 | foreach ($entries as $entry) { 118 | $this->removeEntry($entry); 119 | } 120 | } 121 | 122 | /** 123 | * {@inheritdoc} 124 | */ 125 | public function hasEntry(JournalEntryInterface $entry) 126 | { 127 | return array_search($entry, $this->entries, true) !== false; 128 | } 129 | 130 | /** 131 | * {@inheritdoc} 132 | */ 133 | public function addEntry(JournalEntryInterface $entry) 134 | { 135 | if (!$this->hasEntry($entry)) { 136 | $this->entries[] = $entry; 137 | $this->entries = array_slice($this->entries, $this->limit * -1); 138 | } 139 | } 140 | 141 | /** 142 | * {@inheritdoc} 143 | */ 144 | public function removeEntry(JournalEntryInterface $entry) 145 | { 146 | if ($this->hasEntry($entry)) { 147 | unset($this->entries[array_search($entry, $this->entries, true)]); 148 | $this->entries = array_values($this->entries); 149 | } 150 | } 151 | 152 | /** 153 | * {@inheritdoc} 154 | */ 155 | public function getLimit() 156 | { 157 | return $this->limit; 158 | } 159 | 160 | /** 161 | * {@inheritdoc} 162 | */ 163 | public function setLimit($limit) 164 | { 165 | $this->limit = $limit; 166 | } 167 | 168 | /** 169 | * {@inheritdoc} 170 | */ 171 | public function count() 172 | { 173 | return count($this->entries); 174 | } 175 | 176 | /** 177 | * {@inheritdoc} 178 | */ 179 | public function getIterator() 180 | { 181 | return new \ArrayIterator(array_reverse($this->entries)); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/Message/MessageFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Message; 13 | 14 | use Ivory\HttpAdapter\Normalizer\HeadersNormalizer; 15 | use Psr\Http\Message\StreamInterface; 16 | use Zend\Diactoros\Stream; 17 | use Zend\Diactoros\Uri; 18 | 19 | /** 20 | * @author GeLo 21 | */ 22 | class MessageFactory implements MessageFactoryInterface 23 | { 24 | /** 25 | * @var Uri|null 26 | */ 27 | private $baseUri; 28 | 29 | /** 30 | * @param string $baseUri 31 | */ 32 | public function __construct($baseUri = null) 33 | { 34 | $this->setBaseUri($baseUri); 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function hasBaseUri() 41 | { 42 | return $this->baseUri !== null; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function getBaseUri() 49 | { 50 | return $this->baseUri; 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function setBaseUri($baseUri = null) 57 | { 58 | if (is_string($baseUri)) { 59 | $baseUri = new Uri($baseUri); 60 | } 61 | 62 | $this->baseUri = $baseUri; 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function createRequest( 69 | $uri, 70 | $method = RequestInterface::METHOD_GET, 71 | $protocolVersion = RequestInterface::PROTOCOL_VERSION_1_1, 72 | array $headers = [], 73 | $body = null, 74 | array $parameters = [] 75 | ) { 76 | return (new Request( 77 | $this->createUri($uri), 78 | $method, 79 | $this->createStream($body), 80 | HeadersNormalizer::normalize($headers), 81 | $parameters 82 | ))->withProtocolVersion($protocolVersion); 83 | } 84 | 85 | /** 86 | * {@inheritdoc} 87 | */ 88 | public function createInternalRequest( 89 | $uri, 90 | $method = RequestInterface::METHOD_GET, 91 | $protocolVersion = RequestInterface::PROTOCOL_VERSION_1_1, 92 | array $headers = [], 93 | $datas = [], 94 | array $files = [], 95 | array $parameters = [] 96 | ) { 97 | $body = null; 98 | 99 | if (!is_array($datas)) { 100 | $body = $this->createStream($datas); 101 | $datas = $files = []; 102 | } 103 | 104 | return (new InternalRequest( 105 | $this->createUri($uri), 106 | $method, 107 | $body !== null ? $body : 'php://memory', 108 | $datas, 109 | $files, 110 | HeadersNormalizer::normalize($headers), 111 | $parameters 112 | ))->withProtocolVersion($protocolVersion); 113 | } 114 | 115 | /** 116 | * {@inheritdoc} 117 | */ 118 | public function createResponse( 119 | $statusCode = 200, 120 | $protocolVersion = RequestInterface::PROTOCOL_VERSION_1_1, 121 | array $headers = [], 122 | $body = null, 123 | array $parameters = [] 124 | ) { 125 | return (new Response( 126 | $this->createStream($body), 127 | $statusCode, 128 | HeadersNormalizer::normalize($headers), 129 | $parameters 130 | ))->withProtocolVersion($protocolVersion); 131 | } 132 | 133 | /** 134 | * @param string $uri 135 | * 136 | * @return string 137 | */ 138 | private function createUri($uri) 139 | { 140 | if ($this->hasBaseUri() && (stripos($uri, $baseUri = (string) $this->getBaseUri()) === false)) { 141 | return $baseUri.$uri; 142 | } 143 | 144 | return $uri; 145 | } 146 | 147 | /** 148 | * @param resource|string|StreamInterface|null $body 149 | * 150 | * @return StreamInterface 151 | */ 152 | private function createStream($body) 153 | { 154 | if ($body instanceof StreamInterface) { 155 | $body->rewind(); 156 | 157 | return $body; 158 | } 159 | 160 | if (is_resource($body)) { 161 | return $this->createStream(new Stream($body)); 162 | } 163 | 164 | $stream = new Stream('php://memory', 'rw'); 165 | 166 | if ($body === null) { 167 | return $stream; 168 | } 169 | 170 | $stream->write((string) $body); 171 | 172 | return $this->createStream($stream); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/Configuration.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter; 13 | 14 | use Ivory\HttpAdapter\Message\MessageFactory; 15 | use Ivory\HttpAdapter\Message\MessageFactoryInterface; 16 | use Ivory\HttpAdapter\Message\MessageInterface; 17 | 18 | /** 19 | * @author GeLo 20 | */ 21 | class Configuration implements ConfigurationInterface 22 | { 23 | /** 24 | * @var MessageFactoryInterface 25 | */ 26 | private $messageFactory; 27 | 28 | /** 29 | * @var string 30 | */ 31 | private $protocolVersion = MessageInterface::PROTOCOL_VERSION_1_1; 32 | 33 | /** 34 | * @var bool 35 | */ 36 | private $keepAlive = false; 37 | 38 | /** 39 | * @var string|null 40 | */ 41 | private $encodingType; 42 | 43 | /** 44 | * @var string 45 | */ 46 | private $boundary; 47 | 48 | /** 49 | * @var float 50 | */ 51 | private $timeout = 10; 52 | 53 | /** 54 | * @var string 55 | */ 56 | private $userAgent; 57 | 58 | /** 59 | * @param MessageFactoryInterface|null $messageFactory 60 | */ 61 | public function __construct(MessageFactoryInterface $messageFactory = null) 62 | { 63 | $this->setMessageFactory($messageFactory ?: new MessageFactory()); 64 | $this->setBoundary(sha1(microtime())); 65 | $this->setUserAgent('Ivory Http Adapter '.HttpAdapterInterface::VERSION); 66 | } 67 | 68 | /** 69 | * {@inheritdoc} 70 | */ 71 | public function getMessageFactory() 72 | { 73 | return $this->messageFactory; 74 | } 75 | 76 | /** 77 | * {@inheritdoc} 78 | */ 79 | public function setMessageFactory(MessageFactoryInterface $factory) 80 | { 81 | $this->messageFactory = $factory; 82 | } 83 | 84 | /** 85 | * {@inheritdoc} 86 | */ 87 | public function getProtocolVersion() 88 | { 89 | return $this->protocolVersion; 90 | } 91 | 92 | /** 93 | * {@inheritdoc} 94 | */ 95 | public function setProtocolVersion($protocolVersion) 96 | { 97 | $this->protocolVersion = $protocolVersion; 98 | } 99 | 100 | /** 101 | * {@inheritdoc} 102 | */ 103 | public function getKeepAlive() 104 | { 105 | return $this->keepAlive; 106 | } 107 | 108 | /** 109 | * {@inheritdoc} 110 | */ 111 | public function setKeepAlive($keepAlive) 112 | { 113 | $this->keepAlive = $keepAlive; 114 | } 115 | 116 | /** 117 | * {@inheritdoc} 118 | */ 119 | public function hasEncodingType() 120 | { 121 | return $this->encodingType !== null; 122 | } 123 | 124 | /** 125 | * {@inheritdoc} 126 | */ 127 | public function getEncodingType() 128 | { 129 | return $this->encodingType; 130 | } 131 | 132 | /** 133 | * {@inheritdoc} 134 | */ 135 | public function setEncodingType($encodingType) 136 | { 137 | $this->encodingType = $encodingType; 138 | } 139 | 140 | /** 141 | * {@inheritdoc} 142 | */ 143 | public function getBoundary() 144 | { 145 | return $this->boundary; 146 | } 147 | 148 | /** 149 | * {@inheritdoc} 150 | */ 151 | public function setBoundary($boundary) 152 | { 153 | $this->boundary = $boundary; 154 | } 155 | 156 | /** 157 | * {@inheritdoc} 158 | */ 159 | public function getTimeout() 160 | { 161 | return $this->timeout; 162 | } 163 | 164 | /** 165 | * {@inheritdoc} 166 | */ 167 | public function setTimeout($timeout) 168 | { 169 | $this->timeout = $timeout; 170 | } 171 | 172 | /** 173 | * {@inheritdoc} 174 | */ 175 | public function getUserAgent() 176 | { 177 | return $this->userAgent; 178 | } 179 | 180 | /** 181 | * {@inheritdoc} 182 | */ 183 | public function setUserAgent($userAgent) 184 | { 185 | $this->userAgent = $userAgent; 186 | } 187 | 188 | /** 189 | * {@inheritdoc} 190 | */ 191 | public function hasBaseUri() 192 | { 193 | return $this->messageFactory->hasBaseUri(); 194 | } 195 | 196 | /** 197 | * {@inheritdoc} 198 | */ 199 | public function getBaseUri() 200 | { 201 | return $this->messageFactory->getBaseUri(); 202 | } 203 | 204 | /** 205 | * {@inheritdoc} 206 | */ 207 | public function setBaseUri($baseUri) 208 | { 209 | $this->messageFactory->setBaseUri($baseUri); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /doc/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | The available configuration is defined through the `Ivory\HttpAdapter\ConfigurationInterface` and its default 4 | implementation is the `Ivory\HttpAdapter\Configuration`. The configuration can be passed to all adapters as last 5 | constructor parameters or via getter/setter and allow you to configure the them as explain above: 6 | 7 | ``` php 8 | $curlHttpAdapter = new CurlHttpAdapter(new Configuration()); 9 | // or 10 | $zend1HttpAdapter = new Zend1HttpAdapter(null, new Configuration()); 11 | // or 12 | $configuration = $httpAdapter->getConfiguration(); 13 | $httpAdapter->setConfiguration($configuration); 14 | ``` 15 | 16 | ## Message factory 17 | 18 | The message factory allows to create a PSR-7 request, an internal request and a response. So, if you want to 19 | use your own classes in order to add extra behaviors, you can define your own and instantiate it in your custom 20 | factory which implements the `Ivory\HttpAdapter\Message\MessageFactoryInterface` or extends the 21 | `Ivory\HttpAdapter\Message\MessageFactory`. Then, to get/set it, you can use: 22 | 23 | ``` php 24 | use My\MessageFactory; 25 | 26 | $messageFactory = $configuration->getMessageFactory(); 27 | $configuration->setMessageFactory(new MessageFactory()); 28 | // or 29 | $configuration = new Configuration($messageFactory); 30 | ``` 31 | 32 | ## Protocol version 33 | 34 | The protocol version defines the version for the http request sent (1.0 or 1.1, default: 1.1). If you want to get/set 35 | it, you can use: 36 | 37 | ``` php 38 | use Ivory\HttpAdapter\Message\RequestInterface; 39 | 40 | $protocolVersion = $configuration->getProtocolVersion(); 41 | 42 | $configuration->setProtocolVersion(RequestInterface::PROTOCOL_VERSION_1_0); 43 | // or 44 | $configuration->setProtocolVersion(RequestInterface::PROTOCOL_VERSION_1_1); 45 | ``` 46 | 47 | ## Keep alive 48 | 49 | The keep alive flag allows to define if the connection should be kept alive or not (default: false). Basically, if you 50 | don't provide the `connection` header, it will be automatically populated by the library according to the keep alive 51 | flag. So, if you provide the `connection` headers, the keep alive flag is ignored. If you want to get/set it, you can 52 | use: 53 | 54 | ``` php 55 | $keepAlive = $configuration->getKeepAlive(); 56 | $configuration->setKeepAlive(true); 57 | ``` 58 | 59 | ## Encoding type 60 | 61 | The encoding type defines the encoding of the request (url encoded, form data or none). The content type is 62 | automatically populated according to the datas/files you provide but if you encode yourself the datas as string, you 63 | need to set it explicitely or pass the `content-type` header yourself. Then, if you want to get/set it, you can use: 64 | 65 | ``` php 66 | $hasEncodingType = $configuration->hasEncodingType(); 67 | $encodingType = $configuration->getEncodingType(); 68 | 69 | $configuration->setEncodingType(HttpAdapterConfigInterface::ENCODING_TYPE_URLENCODED); 70 | // or 71 | $configuration->setEncodingType(HttpAdapterConfigInterface::ENCODING_TYPE_FORMDATA); 72 | // or 73 | $configuration->setEncodingType(null); 74 | ``` 75 | 76 | ## Boundary 77 | 78 | The boundary is a complement to the encoding type. If you configure it with form data, the multipart payload is 79 | separated by a boundary which needs to be append to the `content-type` header. If you provide datas/files, it will be 80 | automatically populated but, if you encode yourself the datas as string, you need to set it explicitely or pass the 81 | `content-type` header yourself. Then, if you want to get/set it, you can use: 82 | 83 | ``` php 84 | $boundary = $configuration->getBoundary(); 85 | $configuration->setBoundary('abcdefg'); 86 | ``` 87 | 88 | ## Timeout 89 | 90 | The timeout defines the maximum number of seconds the connection should be active since we consider it invalid 91 | (default: 10). If you want to get/set it, you can use: 92 | 93 | ``` php 94 | $timeout = $configuration->getTimeout(); 95 | $configuration->setTimeout(30); 96 | ``` 97 | 98 | ## User Agent 99 | 100 | The user agent defines which client have sent the request. For example, each browsers send a specific user agent in 101 | order to identify it. By default, all http adapters send the `Ivory Http Adapter` user agent but if you want to 102 | change it, you can use: 103 | 104 | ``` php 105 | $userAgent = $configuration->getUserAgent(); 106 | $configuration->setUserAgent('My user agent'); 107 | ``` 108 | 109 | ## Base url 110 | 111 | If set, requests created using a relative url are combined with the configured base url. Requests created using an 112 | absolute url are not affected by this setting. 113 | 114 | ``` php 115 | $hasBaseUrl = $configuration->hasBaseUrl(); 116 | $baseUrl = $configuration->getBaseUrl(); 117 | 118 | $configuration->setBaseUrl('http://api.example.com'); 119 | 120 | // Example 121 | $response = $http->get('/path/to/resource'); 122 | ``` 123 | -------------------------------------------------------------------------------- /src/Event/Subscriber/StopwatchSubscriber.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Subscriber; 13 | 14 | use Ivory\HttpAdapter\Event\Events; 15 | use Ivory\HttpAdapter\Event\MultiRequestCreatedEvent; 16 | use Ivory\HttpAdapter\Event\MultiRequestErroredEvent; 17 | use Ivory\HttpAdapter\Event\MultiRequestSentEvent; 18 | use Ivory\HttpAdapter\Event\RequestCreatedEvent; 19 | use Ivory\HttpAdapter\Event\RequestErroredEvent; 20 | use Ivory\HttpAdapter\Event\RequestSentEvent; 21 | use Ivory\HttpAdapter\HttpAdapterInterface; 22 | use Ivory\HttpAdapter\Message\InternalRequestInterface; 23 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; 24 | use Symfony\Component\Stopwatch\Stopwatch; 25 | 26 | /** 27 | * @author GeLo 28 | */ 29 | class StopwatchSubscriber implements EventSubscriberInterface 30 | { 31 | /** 32 | * @var Stopwatch 33 | */ 34 | private $stopwatch; 35 | 36 | /** 37 | * @param Stopwatch $stopwatch 38 | */ 39 | public function __construct(Stopwatch $stopwatch) 40 | { 41 | $this->stopwatch = $stopwatch; 42 | } 43 | 44 | /** 45 | * @return Stopwatch 46 | */ 47 | public function getStopwatch() 48 | { 49 | return $this->stopwatch; 50 | } 51 | 52 | /** 53 | * @param RequestCreatedEvent $event 54 | */ 55 | public function onRequestCreated(RequestCreatedEvent $event) 56 | { 57 | $this->stopwatch->start($this->getStopwatchName($event->getHttpAdapter(), $event->getRequest())); 58 | } 59 | 60 | /** 61 | * @param RequestSentEvent $event 62 | */ 63 | public function onRequestSent(RequestSentEvent $event) 64 | { 65 | if (!$event->hasException()) { 66 | $this->stopwatch->stop($this->getStopwatchName($event->getHttpAdapter(), $event->getRequest())); 67 | } 68 | } 69 | 70 | /** 71 | * @param RequestErroredEvent $event 72 | */ 73 | public function onRequestErrored(RequestErroredEvent $event) 74 | { 75 | $this->stopwatch->stop($this->getStopwatchName($event->getHttpAdapter(), $event->getException()->getRequest())); 76 | } 77 | 78 | /** 79 | * @param MultiRequestCreatedEvent $event 80 | */ 81 | public function onMultiRequestCreated(MultiRequestCreatedEvent $event) 82 | { 83 | foreach ($event->getRequests() as $request) { 84 | $this->stopwatch->start($this->getStopwatchName($event->getHttpAdapter(), $request)); 85 | } 86 | } 87 | 88 | /** 89 | * @param MultiRequestSentEvent $event 90 | */ 91 | public function onMultiRequestSent(MultiRequestSentEvent $event) 92 | { 93 | foreach ($event->getResponses() as $response) { 94 | $this->stopwatch->stop( 95 | $this->getStopwatchName($event->getHttpAdapter(), $response->getParameter('request')) 96 | ); 97 | } 98 | } 99 | 100 | /** 101 | * @param MultiRequestErroredEvent $event 102 | */ 103 | public function onMultiResponseErrored(MultiRequestErroredEvent $event) 104 | { 105 | foreach ($event->getResponses() as $response) { 106 | $this->stopwatch->stop( 107 | $this->getStopwatchName($event->getHttpAdapter(), $response->getParameter('request')) 108 | ); 109 | } 110 | 111 | foreach ($event->getExceptions() as $exception) { 112 | $this->stopwatch->stop( 113 | $this->getStopwatchName($event->getHttpAdapter(), $exception->getRequest()) 114 | ); 115 | } 116 | } 117 | 118 | /** 119 | * {@inheritdoc} 120 | */ 121 | public static function getSubscribedEvents() 122 | { 123 | return [ 124 | Events::REQUEST_CREATED => ['onRequestCreated', 10000], 125 | Events::REQUEST_SENT => ['onRequestSent', -10000], 126 | Events::REQUEST_ERRORED => ['onRequestErrored', -10000], 127 | Events::MULTI_REQUEST_CREATED => ['onMultiRequestCreated', 10000], 128 | Events::MULTI_REQUEST_SENT => ['onMultiRequestSent', -10000], 129 | Events::MULTI_REQUEST_ERRORED => ['onMultiResponseErrored', -10000], 130 | ]; 131 | } 132 | 133 | /** 134 | * @param HttpAdapterInterface $httpAdapter 135 | * @param InternalRequestInterface $internalRequest 136 | * 137 | * @return string 138 | */ 139 | private function getStopwatchName(HttpAdapterInterface $httpAdapter, InternalRequestInterface $internalRequest) 140 | { 141 | return sprintf('ivory.http_adapter.%s (%s)', $httpAdapter->getName(), (string) $internalRequest->getUri()); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/Event/Subscriber/CacheSubscriber.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\HttpAdapter\Event\Subscriber; 13 | 14 | use Ivory\HttpAdapter\Event\Cache\CacheInterface; 15 | use Ivory\HttpAdapter\Event\Events; 16 | use Ivory\HttpAdapter\Event\MultiRequestCreatedEvent; 17 | use Ivory\HttpAdapter\Event\MultiRequestErroredEvent; 18 | use Ivory\HttpAdapter\Event\MultiRequestSentEvent; 19 | use Ivory\HttpAdapter\Event\RequestCreatedEvent; 20 | use Ivory\HttpAdapter\Event\RequestErroredEvent; 21 | use Ivory\HttpAdapter\Event\RequestSentEvent; 22 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; 23 | 24 | /** 25 | * @author GeLo 26 | */ 27 | class CacheSubscriber implements EventSubscriberInterface 28 | { 29 | /** 30 | * @var CacheInterface 31 | */ 32 | private $cache; 33 | 34 | /** 35 | * @param CacheInterface $cache 36 | */ 37 | public function __construct(CacheInterface $cache) 38 | { 39 | $this->setCache($cache); 40 | } 41 | 42 | /** 43 | * @return CacheInterface 44 | */ 45 | public function getCache() 46 | { 47 | return $this->cache; 48 | } 49 | 50 | /** 51 | * @param CacheInterface $cache 52 | */ 53 | public function setCache(CacheInterface $cache) 54 | { 55 | $this->cache = $cache; 56 | } 57 | 58 | /** 59 | * @param RequestCreatedEvent $event 60 | */ 61 | public function onRequestCreated(RequestCreatedEvent $event) 62 | { 63 | $request = $event->getRequest(); 64 | $messageFactory = $event->getHttpAdapter()->getConfiguration()->getMessageFactory(); 65 | 66 | if (($response = $this->cache->getResponse($request, $messageFactory)) !== null) { 67 | $event->setResponse($response); 68 | } elseif (($exception = $this->cache->getException($request, $messageFactory)) !== null) { 69 | $event->setException($exception); 70 | } 71 | } 72 | 73 | /** 74 | * @param RequestSentEvent $event 75 | */ 76 | public function onRequestSent(RequestSentEvent $event) 77 | { 78 | $this->cache->saveResponse($event->getResponse(), $event->getRequest()); 79 | } 80 | 81 | /** 82 | * @param RequestErroredEvent $event 83 | */ 84 | public function onRequestErrored(RequestErroredEvent $event) 85 | { 86 | $this->cache->saveException($event->getException(), $event->getException()->getRequest()); 87 | } 88 | 89 | /** 90 | * @param MultiRequestCreatedEvent $event 91 | */ 92 | public function onMultiRequestCreated(MultiRequestCreatedEvent $event) 93 | { 94 | $messageFactory = $event->getHttpAdapter()->getConfiguration()->getMessageFactory(); 95 | 96 | foreach ($event->getRequests() as $request) { 97 | if (($response = $this->cache->getResponse($request, $messageFactory)) !== null) { 98 | $event->addResponse($response); 99 | $event->removeRequest($request); 100 | } elseif (($exception = $this->cache->getException($request, $messageFactory)) !== null) { 101 | $event->addException($exception); 102 | $event->removeRequest($request); 103 | } 104 | } 105 | } 106 | 107 | /** 108 | * @param MultiRequestSentEvent $event 109 | */ 110 | public function onMultiRequestSent(MultiRequestSentEvent $event) 111 | { 112 | foreach ($event->getResponses() as $response) { 113 | $this->cache->saveResponse($response, $response->getParameter('request')); 114 | } 115 | } 116 | 117 | /** 118 | * @param MultiRequestErroredEvent $event 119 | */ 120 | public function onMultiRequestErrored(MultiRequestErroredEvent $event) 121 | { 122 | foreach ($event->getResponses() as $response) { 123 | $this->cache->saveResponse($response, $response->getParameter('request')); 124 | } 125 | 126 | foreach ($event->getExceptions() as $exception) { 127 | $this->cache->saveException($exception, $exception->getRequest()); 128 | } 129 | } 130 | 131 | /** 132 | * {@inheritdoc} 133 | */ 134 | public static function getSubscribedEvents() 135 | { 136 | return [ 137 | Events::REQUEST_CREATED => ['onRequestCreated', -100], 138 | Events::REQUEST_SENT => ['onRequestSent', -100], 139 | Events::REQUEST_ERRORED => ['onRequestErrored', -100], 140 | Events::MULTI_REQUEST_CREATED => ['onMultiRequestCreated', -100], 141 | Events::MULTI_REQUEST_SENT => ['onMultiRequestSent', -100], 142 | Events::MULTI_REQUEST_ERRORED => ['onMultiRequestErrored', -100], 143 | ]; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ### 1.0.3 (????-??-??) 4 | 5 | * 0ea4ee2 - [Travis] Add docker build 6 | * e29072f - Add docker support 7 | * 0a96602 - Add PHP-CS-Fixer support 8 | * 0b38fb3 - [Travis] Simplify matrix 9 | * cdecb3b - Fix .gitattributes 10 | * 388c107 - Remove local bin directory 11 | * 4aff3cb - Remove .php_cs file 12 | * 638a0a1 - [Gitignore] Remove coveralls.json 13 | * 99e0285 - [Scrutinizer] Fix code coverage configuration 14 | * 7490304 - [Scrutinizer] Add configuration 15 | * 6ec9b75 - [License] Happy new year 16 | 17 | ### 1.0.2 (2017-01-12) 18 | 19 | * a7617d0 - [Composer] Upgrade deps 20 | * 2e8a33a - Fix tests 21 | * 9780bb2 - Fix docblock 22 | 23 | ### 1.0.1 (2016-05-06) 24 | 25 | * 3b1a38a - [Guzzle6] Removed unnecessary arguments pass 26 | * 6ada97d - [Travis] Fix build 27 | 28 | ### 1.0.0 (2015-12-13) 29 | 30 | * eb597ef - Add Requests http adapter 31 | * 3a5232d - Add Symfony3 support 32 | * bc4c186 - Add caching support 33 | * b6e4bdf - [Socket] Fix https connection 34 | 35 | ### 0.8.0 (2015-08-12) 36 | 37 | * 2d8061b - Add guzzle 6 support 38 | * 26d6b7b - Add mock http adapter 39 | * 1cd6ee4 - [GuzzleHttp] Catch an exception that is able to provide a request 40 | * 6ac4d3b - [Travis] Add Symfony 2.7 to stable + Add Symfony 2.8.*@dev as unstable 41 | * 4f35c0e - Replace phly/http by zendframework/zend-diactoros 42 | * fbc3c4d - [Message][Stream] Remove guzzle streams 43 | * 9de52ee - Rewind stream before setting it on the response 44 | * 9a29e09 - Rely on PHP built in server instead of Nginx or Apache 45 | * e6ef972 - [Travis] Add PHP 7 46 | * 1e609b9 - [CakePHP] Drop 2.x support in favor of 3.x 47 | * 19a0e44 - [Headers] Automatically add content-type when there are datas 48 | * 005efb0 - Add PECL http adapter support 49 | * 789326e - [Event] Fix redirect subscriber which throws an exception 50 | * 2d245b6 - Rely on PSR response interface for trait typehint closure 51 | * 458a9e2 - [Decorator] Make decorator calls explicit 52 | * 9d126f8 - Upgrade phly/http library 53 | * 79e75ea - Makes event subscribers immutable + rename events 54 | 55 | ### 0.7.1 (2015-04-13) 56 | 57 | * f996f8e - [Travis] Use minimum PHP version for lowest deps 58 | * 0ae6275 - Fix RedirectSubscriber 59 | * 1e174e7 - Fixed dependency to non existing branch 60 | 61 | ### 0.7.0 (2015-03-08) 62 | 63 | * 5e844f6 - Move event dispatcher into a decorator 64 | * d49ad91 - Rely on 'phly/http' for PSR-7 implementation 65 | 66 | ### 0.6.0 (2015-02-10) 67 | 68 | * cd7e18b - [HttpAdapterFactory] Added a guess and capable methods 69 | * 21055c2 - Add parallel requests support 70 | * c0fcdfd - Add base url support 71 | * 77173c4 - Make http_buil_query independant of arg_separator.output ini setting 72 | * bbcf51a - Introduce Version 73 | * 1b15d7b - [Event] Make timer subscriber stateless 74 | * f16767a - Set request/response on exception before passing it to exception listeners 75 | * 613132a - [Test] Reintroduce send internal request tests 76 | * 7eb424b - Rename AbstractHttpAdapter::doSend to doSendInternalRequest 77 | * b91f40f - Remove AbstractHttpAdapter::createResponse and rely on MessageFactory::createResponse instead 78 | * a260489 - [Travis] Move Symfony 2.6.*@dev to 2.6.* 79 | * 605d943 - Remove HttpAdapterInterface::sendInternalRequest method 80 | * 4c33b6c - [Travis] Update config 81 | * 90c58a3 - Add .gitattributes 82 | * dfe5877 - Add CakePHP http adapter 83 | * dc3fa00 - Add ReactPHP http adapter 84 | * 5145b12 - Introduce http adapter factory 85 | * c918dbf - [Composer] Rely on autoload-dev 86 | * a88709c - [Test] Rename debug file to ivory-http-adapter.log 87 | * d00e1f7 - [Test] Add Apache 2.4 compatibility 88 | * e4f6efb - Add Symfony2 stopwatch support 89 | * cc3f5f2 - [Event][Retry] Fix verify for limited retry strategy 90 | * 6e10883 - [Encapsulation] Move everything from protected to private (except for entry point) 91 | 92 | ### 0.5.0 (2014-11-05) 93 | 94 | * 4523e62 - [Message] Update according to PSR HTTP message 0.5.0 BC breaks 95 | * 4e0e387 - [Composer] Refine dependency 96 | * 198b6f4 - Add Guzzle 5 support 97 | * a2940f6 - [Exception] Add related request/response if available 98 | 99 | ### 0.4.0 (2014-10-26) 100 | 101 | * 75e5f69 - [Message] Update according to PSR HTTP message 0.4.0 BC breaks 102 | 103 | ### 0.3.0 (2014-10-26) 104 | 105 | * 65ad8ba - [Message] Update according to PSR HTTP message 0.3.0 BC breaks 106 | 107 | ### 0.2.0 (2014-10-26) 108 | 109 | * 4cbd0b5 - [Message] Update according to PSR HTTP message 0.2.0 BC breaks 110 | 111 | ### 0.1.2 (2014-10-25) 112 | 113 | * 6527485 - [Stream] Fix some returns + PHPDoc 114 | 115 | ### 0.1.1 (2014-10-25) 116 | 117 | * 51ac68c - [Test] Remove http adapter file after execution 118 | * 9c1382a - [Stream] Fix string stream 119 | * 8478738 - [StringStream] Fix boolean return 120 | * 50871fe - [Test] Lock share file 121 | * 9a77280 - [Curl] Reduce timeout code duplication 122 | * ceddaf2 - [Composer] Refine dependencies 123 | * 704188f - Normalize timeout handling 124 | * 6238854 - Typehint PSR request + CS Fixes + Refine Guzzle4 dependency 125 | 126 | ### 0.1.0 (2014-10-03) 127 | --------------------------------------------------------------------------------