diff --git a/Makefile b/Makefile
index 69bf3273e..315f5743e 100644
--- a/Makefile
+++ b/Makefile
@@ -1,10 +1,14 @@
all: clean coverage docs
-start-server:
- cd vendor/guzzlehttp/ringphp && make start-server
+start-server: stop-server
+ node tests/server.js &> /dev/null &
stop-server:
- cd vendor/guzzlehttp/ringphp && make stop-server
+ @PID=$(shell ps axo pid,command \
+ | grep 'tests/server.js' \
+ | grep -v grep \
+ | cut -f 1 -d " "\
+ ) && [ -n "$$PID" ] && kill $$PID || true
test: start-server
vendor/bin/phpunit
@@ -36,15 +40,4 @@ tag:
git commit -m '$(TAG) release'
chag tag
-perf: start-server
- php tests/perf.php
- $(MAKE) stop-server
-
-package: burgomaster
- php build/packager.php
-
-burgomaster:
- mkdir -p build/artifacts
- curl -s https://raw.githubusercontent.com/mtdowling/Burgomaster/0.0.2/src/Burgomaster.php > build/artifacts/Burgomaster.php
-
-.PHONY: docs burgomaster
+.PHONY: docs
diff --git a/composer.json b/composer.json
index e9615a01e..50a79b25a 100644
--- a/composer.json
+++ b/composer.json
@@ -1,7 +1,7 @@
{
"name": "guzzlehttp/guzzle",
"type": "library",
- "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients",
+ "description": "Guzzle is a PHP HTTP client library",
"keywords": ["framework", "http", "rest", "web service", "curl", "client", "HTTP client"],
"homepage": "http://guzzlephp.org/",
"license": "MIT",
@@ -14,7 +14,9 @@
],
"require": {
"php": ">=5.4.0",
- "guzzlehttp/ringphp": "~1.0"
+ "psr/http-message": "^0.9",
+ "guzzlehttp/psr7": "dev-master",
+ "guzzlehttp/promises": "dev-master"
},
"require-dev": {
"ext-curl": "*",
@@ -24,7 +26,8 @@
"autoload": {
"psr-4": {
"GuzzleHttp\\": "src/"
- }
+ },
+ "files": ["src/functions.php"]
},
"autoload-dev": {
"psr-4": {
@@ -33,7 +36,8 @@
},
"extra": {
"branch-alias": {
- "dev-master": "5.0-dev"
+ "dev-master": "5.0-dev",
+ "dev-6": "6.0-dev"
}
}
}
diff --git a/src/BatchResults.php b/src/BatchResults.php
deleted file mode 100644
index e5af433dd..000000000
--- a/src/BatchResults.php
+++ /dev/null
@@ -1,148 +0,0 @@
-hash = $hash;
- }
-
- /**
- * Get the keys that are available on the batch result.
- *
- * @return array
- */
- public function getKeys()
- {
- return iterator_to_array($this->hash);
- }
-
- /**
- * Gets a result from the container for the given object. When getting
- * results for a batch of requests, provide the request object.
- *
- * @param object $forObject Object to retrieve the result for.
- *
- * @return mixed|null
- */
- public function getResult($forObject)
- {
- return isset($this->hash[$forObject]) ? $this->hash[$forObject] : null;
- }
-
- /**
- * Get an array of successful results.
- *
- * @return array
- */
- public function getSuccessful()
- {
- $results = [];
- foreach ($this->hash as $key) {
- if (!($this->hash[$key] instanceof \Exception)) {
- $results[] = $this->hash[$key];
- }
- }
-
- return $results;
- }
-
- /**
- * Get an array of failed results.
- *
- * @return array
- */
- public function getFailures()
- {
- $results = [];
- foreach ($this->hash as $key) {
- if ($this->hash[$key] instanceof \Exception) {
- $results[] = $this->hash[$key];
- }
- }
-
- return $results;
- }
-
- /**
- * Allows iteration over all batch result values.
- *
- * @return \ArrayIterator
- */
- public function getIterator()
- {
- $results = [];
- foreach ($this->hash as $key) {
- $results[] = $this->hash[$key];
- }
-
- return new \ArrayIterator($results);
- }
-
- /**
- * Counts the number of elements in the batch result.
- *
- * @return int
- */
- public function count()
- {
- return count($this->hash);
- }
-
- /**
- * Checks if the batch contains a specific numerical array index.
- *
- * @param int $key Index to access
- *
- * @return bool
- */
- public function offsetExists($key)
- {
- return $key < count($this->hash);
- }
-
- /**
- * Allows access of the batch using a numerical array index.
- *
- * @param int $key Index to access.
- *
- * @return mixed|null
- */
- public function offsetGet($key)
- {
- $i = -1;
- foreach ($this->hash as $obj) {
- if ($key === ++$i) {
- return $this->hash[$obj];
- }
- }
-
- return null;
- }
-
- public function offsetUnset($key)
- {
- throw new \RuntimeException('Not implemented');
- }
-
- public function offsetSet($key, $value)
- {
- throw new \RuntimeException('Not implemented');
- }
-}
diff --git a/src/Client.php b/src/Client.php
index 273777553..91bcf88ce 100644
--- a/src/Client.php
+++ b/src/Client.php
@@ -1,352 +1,252 @@
request('GET', 'http://www.google.com');
+ * $response->then(
+ * function ($response) {
+ * echo "Got a response: \n" . GuzzleHttp\str_message($response) . "\n";
+ * },
+ * function ($e) {
+ * echo 'Got an error: ' . $e->getMessage() . "\n";
+ * }
+ * );
+ * $response->wait();
+ * echo $response->getStatusCode();
*/
class Client implements ClientInterface
{
- use HasEmitterTrait;
-
- /** @var MessageFactoryInterface Request factory used by the client */
- private $messageFactory;
-
- /** @var Url Base URL of the client */
- private $baseUrl;
+ /** @var Uri Base URI of the client */
+ private $baseUri;
/** @var array Default request options */
private $defaults;
- /** @var callable Request state machine */
- private $fsm;
+ /** @var callable Request handler */
+ private $handler;
+
+ /** @var callable Cached error middleware */
+ private $errorMiddleware;
+
+ /** @var callable Cached redirect middleware */
+ private $redirectMiddleware;
+
+ /** @var callable Cached cookie middleware */
+ private $cookieMiddleware;
+
+ /** @var array Known pass-through transfer request options */
+ private static $transferOptions = [
+ 'connect_timeout' => true,
+ 'timeout' => true,
+ 'verify' => true,
+ 'ssl_key' => true,
+ 'cert' => true,
+ 'progress' => true,
+ 'proxy' => true,
+ 'debug' => true,
+ 'sink' => true,
+ 'stream' => true,
+ 'expect' => true,
+ 'allow_redirects' => true,
+ 'sync' => true
+ ];
+
+ /** @var array Default allow_redirects request option settings */
+ private static $defaultRedirect = [
+ 'max' => 5,
+ 'strict' => false,
+ 'referer' => false,
+ 'protocols' => ['http', 'https']
+ ];
/**
* Clients accept an array of constructor parameters.
*
* Here's an example of creating a client using an URI template for the
- * client's base_url and an array of default request options to apply
+ * client's base_uri and an array of default request options to apply
* to each request:
*
* $client = new Client([
- * 'base_url' => [
+ * 'base_uri' => [
* 'http://www.foo.com/{version}/',
* ['version' => '123']
* ],
* 'defaults' => [
- * 'timeout' => 10,
+ * 'timeout' => 0,
* 'allow_redirects' => false,
* 'proxy' => '192.168.16.1:10'
* ]
* ]);
*
* @param array $config Client configuration settings
- * - base_url: Base URL of the client that is merged into relative URLs.
+ * - base_uri: Base URI of the client that is merged into relative URIs.
* Can be a string or an array that contains a URI template followed
* by an associative array of expansion variables to inject into the
* URI template.
* - handler: callable RingPHP handler used to transfer requests
- * - message_factory: Factory used to create request and response object
* - defaults: Default request options to apply to each request
- * - emitter: Event emitter used for request events
- * - fsm: (internal use only) The request finite state machine. A
- * function that accepts a transaction and optional final state. The
- * function is responsible for transitioning a request through its
- * lifecycle events.
*/
public function __construct(array $config = [])
{
- $this->configureBaseUrl($config);
+ $this->configureBaseUri($config);
$this->configureDefaults($config);
-
- if (isset($config['emitter'])) {
- $this->emitter = $config['emitter'];
- }
-
- $this->messageFactory = isset($config['message_factory'])
- ? $config['message_factory']
- : new MessageFactory();
-
- if (isset($config['fsm'])) {
- $this->fsm = $config['fsm'];
- } else {
- if (isset($config['handler'])) {
- $handler = $config['handler'];
- } elseif (isset($config['adapter'])) {
- $handler = $config['adapter'];
- } else {
- $handler = static::getDefaultHandler();
- }
- $this->fsm = new RequestFsm($handler, $this->messageFactory);
- }
+ $this->handler = isset($config['handler'])
+ ? $config['handler']
+ : \GuzzleHttp\default_handler();
}
- /**
- * Create a default handler to use based on the environment
- *
- * @throws \RuntimeException if no viable Handler is available.
- */
- public static function getDefaultHandler()
+ public function send(RequestInterface $request, array $options = [])
{
- $default = $future = null;
-
- if (extension_loaded('curl')) {
- $config = [
- 'select_timeout' => getenv('GUZZLE_CURL_SELECT_TIMEOUT') ?: 1
- ];
- if ($maxHandles = getenv('GUZZLE_CURL_MAX_HANDLES')) {
- $config['max_handles'] = $maxHandles;
- }
- if (function_exists('curl_reset')) {
- $default = new CurlHandler();
- $future = new CurlMultiHandler($config);
- } else {
- $default = new CurlMultiHandler($config);
- }
+ // Merge the base URI into the request URI if needed.
+ $original = $request->getUri();
+ $uri = $this->buildUri($original);
+ if ($uri !== $original) {
+ $request = $request->withUri($uri);
}
- if (ini_get('allow_url_fopen')) {
- $default = !$default
- ? new StreamHandler()
- : Middleware::wrapStreaming($default, new StreamHandler());
- } elseif (!$default) {
- throw new \RuntimeException('Guzzle requires cURL, the '
- . 'allow_url_fopen ini setting, or a custom HTTP handler.');
- }
-
- return $future ? Middleware::wrapFuture($default, $future) : $default;
+ return $this->transfer($request, $this->mergeDefaults($options));
}
- /**
- * Get the default User-Agent string to use with Guzzle
- *
- * @return string
- */
- public static function getDefaultUserAgent()
+ public function request($method, $uri = null, array $options = [])
{
- static $defaultAgent = '';
- if (!$defaultAgent) {
- $defaultAgent = 'Guzzle/' . self::VERSION;
- if (extension_loaded('curl')) {
- $defaultAgent .= ' curl/' . curl_version()['version'];
- }
- $defaultAgent .= ' PHP/' . PHP_VERSION;
- }
-
- return $defaultAgent;
+ $options = $this->mergeDefaults($options);
+ $headers = isset($options['headers']) ? $options['headers'] : [];
+ $body = isset($options['body']) ? $options['body'] : null;
+ $version = isset($options['version']) ? $options['version'] : '1.1';
+ // Merge the URI into the base URI.
+ $uri = $this->buildUri($uri);
+ $request = new Request($method, $uri, $headers, $body, $version);
+ unset($options['headers'], $options['body'], $options['version']);
+
+ return $this->transfer($request, $options);
}
public function getDefaultOption($keyOrPath = null)
{
return $keyOrPath === null
? $this->defaults
- : Utils::getPath($this->defaults, $keyOrPath);
+ : get_path($this->defaults, $keyOrPath);
}
public function setDefaultOption($keyOrPath, $value)
{
- Utils::setPath($this->defaults, $keyOrPath, $value);
- }
-
- public function getBaseUrl()
- {
- return (string) $this->baseUrl;
- }
-
- public function createRequest($method, $url = null, array $options = [])
- {
- $options = $this->mergeDefaults($options);
- // Use a clone of the client's emitter
- $options['config']['emitter'] = clone $this->getEmitter();
- $url = $url || (is_string($url) && strlen($url))
- ? $this->buildUrl($url)
- : (string) $this->baseUrl;
-
- return $this->messageFactory->createRequest($method, $url, $options);
- }
-
- public function get($url = null, $options = [])
- {
- return $this->send($this->createRequest('GET', $url, $options));
- }
-
- public function head($url = null, array $options = [])
- {
- return $this->send($this->createRequest('HEAD', $url, $options));
+ set_path($this->defaults, $keyOrPath, $value);
}
- public function delete($url = null, array $options = [])
+ public function getBaseUri()
{
- return $this->send($this->createRequest('DELETE', $url, $options));
- }
-
- public function put($url = null, array $options = [])
- {
- return $this->send($this->createRequest('PUT', $url, $options));
- }
-
- public function patch($url = null, array $options = [])
- {
- return $this->send($this->createRequest('PATCH', $url, $options));
- }
-
- public function post($url = null, array $options = [])
- {
- return $this->send($this->createRequest('POST', $url, $options));
- }
-
- public function options($url = null, array $options = [])
- {
- return $this->send($this->createRequest('OPTIONS', $url, $options));
- }
-
- public function send(RequestInterface $request)
- {
- $isFuture = $request->getConfig()->get('future');
- $trans = new Transaction($this, $request, $isFuture);
- $fn = $this->fsm;
-
- try {
- $fn($trans);
- if ($isFuture) {
- // Turn the normal response into a future if needed.
- return $trans->response instanceof FutureInterface
- ? $trans->response
- : new FutureResponse(new FulfilledPromise($trans->response));
- }
- // Resolve deep futures if this is not a future
- // transaction. This accounts for things like retries
- // that do not have an immediate side-effect.
- while ($trans->response instanceof FutureInterface) {
- $trans->response = $trans->response->wait();
- }
- return $trans->response;
- } catch (\Exception $e) {
- if ($isFuture) {
- // Wrap the exception in a promise
- return new FutureResponse(new RejectedPromise($e));
- }
- throw RequestException::wrapException($trans->request, $e);
- }
- }
-
- /**
- * Get an array of default options to apply to the client
- *
- * @return array
- */
- protected function getDefaultOptions()
- {
- $settings = [
- 'allow_redirects' => true,
- 'exceptions' => true,
- 'decode_content' => true,
- 'verify' => true
- ];
-
- // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set
- if ($proxy = getenv('HTTP_PROXY')) {
- $settings['proxy']['http'] = $proxy;
- }
-
- if ($proxy = getenv('HTTPS_PROXY')) {
- $settings['proxy']['https'] = $proxy;
- }
-
- return $settings;
+ return $this->baseUri;
}
/**
* Expand a URI template and inherit from the base URL if it's relative
*
- * @param string|array $url URL or an array of the URI template to expand
+ * @param string|array $uri URL or an array of the URI template to expand
* followed by a hash of template varnames.
- * @return string
+ * @return UriInterface
* @throws \InvalidArgumentException
*/
- private function buildUrl($url)
+ private function buildUri($uri)
{
// URI template (absolute or relative)
- if (!is_array($url)) {
- return strpos($url, '://')
- ? (string) $url
- : (string) $this->baseUrl->combine($url);
+ if (!is_array($uri)) {
+ return Uri::resolve($this->baseUri, $uri);
}
- if (!isset($url[1])) {
+ if (!isset($uri[1])) {
throw new \InvalidArgumentException('You must provide a hash of '
. 'varname options in the second element of a URL array.');
}
// Absolute URL
- if (strpos($url[0], '://')) {
- return Utils::uriTemplate($url[0], $url[1]);
+ if (strpos($uri[0], '://')) {
+ return new Uri(uri_template($uri[0], $uri[1]));
}
// Combine the relative URL with the base URL
- return (string) $this->baseUrl->combine(
- Utils::uriTemplate($url[0], $url[1])
- );
+ return Uri::resolve($this->baseUri, uri_template($uri[0], $uri[1]));
}
- private function configureBaseUrl(&$config)
+ private function configureBaseUri($config)
{
- if (!isset($config['base_url'])) {
- $this->baseUrl = new Url('', '');
- } elseif (!is_array($config['base_url'])) {
- $this->baseUrl = Url::fromString($config['base_url']);
- } elseif (count($config['base_url']) < 2) {
+ if (!isset($config['base_uri'])) {
+ $this->baseUri = new Uri('');
+ } elseif (!is_array($config['base_uri'])) {
+ $this->baseUri = new Uri($config['base_uri']);
+ } elseif (count($config['base_uri']) < 2) {
throw new \InvalidArgumentException('You must provide a hash of '
- . 'varname options in the second element of a base_url array.');
+ . 'varname options in the second element of a base_uri array.');
} else {
- $this->baseUrl = Url::fromString(
- Utils::uriTemplate(
- $config['base_url'][0],
- $config['base_url'][1]
+ $this->baseUri = new Uri(
+ uri_template(
+ $config['base_uri'][0],
+ $config['base_uri'][1]
)
);
- $config['base_url'] = (string) $this->baseUrl;
}
}
- private function configureDefaults($config)
+ /**
+ * Configures the default options for a client.
+ *
+ * @param array $config
+ *
+ * @return array
+ */
+ private function configureDefaults(array $config)
{
- if (!isset($config['defaults'])) {
- $this->defaults = $this->getDefaultOptions();
- } else {
- $this->defaults = array_replace(
- $this->getDefaultOptions(),
- $config['defaults']
- );
+ $defaults = [
+ 'allow_redirects' => self::$defaultRedirect,
+ 'exceptions' => true,
+ 'decode_content' => true,
+ 'verify' => true
+ ];
+
+ // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set
+ if ($proxy = getenv('HTTP_PROXY')) {
+ $defaults['proxy']['http'] = $proxy;
+ }
+
+ if ($proxy = getenv('HTTPS_PROXY')) {
+ $defaults['proxy']['https'] = $proxy;
}
- // Add the default user-agent header
+ $this->defaults = empty($config['defaults'])
+ ? $defaults
+ : $config['defaults'] + $defaults;
+
+ // Add the default user-agent header.
if (!isset($this->defaults['headers'])) {
$this->defaults['headers'] = [
- 'User-Agent' => static::getDefaultUserAgent()
+ 'User-Agent' => \GuzzleHttp\default_user_agent()
];
- } elseif (!Core::hasHeader($this->defaults, 'User-Agent')) {
- // Add the User-Agent header if one was not already set
- $this->defaults['headers']['User-Agent'] = static::getDefaultUserAgent();
+ } else {
+ // Add the User-Agent header if one was not already set.
+ foreach (array_keys($this->defaults['headers']) as $name) {
+ if (strtolower($name) === 'user-agent') {
+ return;
+ }
+ }
+ $this->defaults['headers']['User-Agent'] = \GuzzleHttp\default_user_agent();
}
}
/**
- * Merges default options into the array passed by reference.
+ * Merges default options into the array.
*
* @param array $options Options to modify by reference
*
@@ -359,7 +259,7 @@ private function mergeDefaults($options)
// Case-insensitively merge in default headers if both defaults and
// options have headers specified.
if (!empty($defaults['headers']) && !empty($options['headers'])) {
- // Create a set of lowercased keys that are present.
+ // Create a set of lowercase keys that are present.
$lkeys = [];
foreach (array_keys($options['headers']) as $k) {
$lkeys[strtolower($k)] = true;
@@ -385,11 +285,230 @@ private function mergeDefaults($options)
}
/**
- * @deprecated Use {@see GuzzleHttp\Pool} instead.
- * @see GuzzleHttp\Pool
+ * Transfers the given request and applies request options.
+ *
+ * The URI of the request is not modified and the request options are used
+ * as-is without merging in default options.
+ *
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return FulfilledPromise|FulfilledResponse|RejectedResponse|ResponsePromise
+ */
+ private function transfer(RequestInterface $request, array $options)
+ {
+ if (!isset($options['stack'])) {
+ $options['stack'] = new HandlerBuilder();
+ } elseif (!($options['stack'] instanceof HandlerBuilder)) {
+ throw new \InvalidArgumentException('The stack option must be an instance of GuzzleHttp\\HandlerBuilder');
+ }
+
+ $handler = $this->createHandler($request, $options);
+ $request = $this->applyOptions($request, $options);
+
+ try {
+ $response = $handler($request, $options);
+ if ($response instanceof ResponsePromiseInterface) {
+ return $response;
+ } elseif ($response instanceof PromiseInterface) {
+ return ResponsePromise::fromPromise($response);
+ }
+ return new FulfilledResponse($response);
+ } catch (\Exception $e) {
+ return new RejectedResponse($e);
+ }
+ }
+
+ /**
+ * Create a composite handler based on the given request options.
+ *
+ * @param RequestInterface $request Request to send.
+ * @param array $options Array of request options.
+ *
+ * @return callable
*/
- public function sendAll($requests, array $options = [])
+ private function createHandler(RequestInterface $request, array &$options)
{
- Pool::send($this, $requests, $options);
+ /** @var HandlerBuilder $stack */
+ $stack = $options['stack'];
+
+ // Add the redirect middleware if needed.
+ if (!empty($options['allow_redirects'])) {
+ if (!$this->errorMiddleware) {
+ $this->redirectMiddleware = Middleware::redirect();
+ }
+ $stack->append($this->redirectMiddleware);
+ if ($options['allow_redirects'] === true) {
+ $options['allow_redirects'] = self::$defaultRedirect;
+ } elseif (!is_array($options['allow_redirects'])) {
+ throw new Iae('allow_redirects must be true, false, or array');
+ } else {
+ // Merge the default settings with the provided settings
+ $options['allow_redirects'] += self::$defaultRedirect;
+ }
+ }
+
+ // Add the httpError middleware if needed.
+ if (!empty($options['exceptions'])) {
+ if (!$this->errorMiddleware) {
+ $this->errorMiddleware = Middleware::httpError();
+ }
+ $stack->append($this->errorMiddleware);
+ unset($options['exceptions']);
+ }
+
+ // Add the cookies middleware if needed.
+ if (!empty($options['cookies'])) {
+ if ($options['cookies'] === true) {
+ if (!$this->cookieMiddleware) {
+ $jar = new CookieJar();
+ $this->cookieMiddleware = Middleware::cookies($jar);
+ }
+ $cookie = $this->cookieMiddleware;
+ } elseif ($options['cookies'] instanceof CookieJarInterface) {
+ $cookie = Middleware::cookies($options['cookies']);
+ } elseif (is_array($options['cookies'])) {
+ $cookie = Middleware::cookies(CookieJar::fromArray(
+ $options['cookies'],
+ $request->getUri()->getHost()
+ ));
+ } else {
+ throw new Iae('cookies must be an array, true, or CookieJarInterface');
+ }
+ $stack->append($cookie);
+ }
+
+ if (!$stack->hasHandler()) {
+ $stack->setHandler($this->handler);
+ }
+
+ return $stack->resolve();
+ }
+
+ /**
+ * Applies the array of request options to a request.
+ *
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return RequestInterface
+ */
+ private function applyOptions(RequestInterface $request, array &$options)
+ {
+ $modify = [];
+ $this->extractPostData($options);
+
+ foreach ($options as $key => $value) {
+ if (isset(self::$transferOptions[$key])) {
+ $config[$key] = $value;
+ continue;
+ }
+ switch ($key) {
+
+ case 'decode_content':
+ if ($value === false) {
+ continue;
+ }
+ if ($value !== true) {
+ $modify['set_headers']['Accept-Encoding'] = $value;
+ }
+ break;
+
+ case 'headers':
+ if (!is_array($value)) {
+ throw new Iae('header value must be an array');
+ }
+ foreach ($value as $k => $v) {
+ $modify['set_headers'][$k] = $v;
+ }
+ unset($options['headers']);
+ break;
+
+ case 'body':
+ $modify['body'] = Stream::factory($value);
+ unset($options['body']);
+ break;
+
+ case 'auth':
+ if (!$value) {
+ continue;
+ }
+ if (is_array($value)) {
+ $type = isset($value[2]) ? strtolower($value[2]) : 'basic';
+ } else {
+ $type = strtolower($value);
+ }
+ $config['auth'] = $value;
+ if ($type == 'basic') {
+ $modify['set_headers']['Authorization'] = 'Basic ' . base64_encode("$value[0]:$value[1]");
+ } elseif ($type == 'digest') {
+ // @todo: Do not rely on curl
+ $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST;
+ $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
+ }
+ break;
+
+ case 'query':
+ if (is_array($value)) {
+ $value = http_build_query($value, null, null, PHP_QUERY_RFC3986);
+ }
+ if (!is_string($value)) {
+ throw new Iae('query must be a string or array');
+ }
+ $modify['query'] = $value;
+ unset($options['query']);
+ break;
+
+ case 'json':
+ $modify['body'] = Stream::factory(json_encode($value));
+ if (!$request->hasHeader('Content-Type')) {
+ $modify['set_headers']['Content-Type'] = 'application/json';
+ }
+ unset($options['json']);
+ break;
+ }
+ }
+
+ return \GuzzleHttp\modify_request($request, $modify);
+ }
+
+ /**
+ * Extracts post_fields and post_files into the "body" option.
+ *
+ * @param array $options
+ */
+ private function extractPostData(array &$options)
+ {
+ if (empty($options['post_files']) && empty($options['post_fields'])) {
+ return;
+ }
+
+ $contentType = null;
+ if (!empty($options['headers'])) {
+ foreach ($options['headers'] as $name => $value) {
+ if (strtolower($name) === 'content-type') {
+ $contentType = $value;
+ break;
+ }
+ }
+ }
+
+ $fields = [];
+ if (isset($options['post_fields'])) {
+ if (!isset($options['post_files'])) {
+ $options['body'] = http_build_query($options['post_fields']);
+ unset($options['post_fields']);
+ $options['headers']['Content-Type'] = $contentType ?: 'application/x-www-form-urlencoded';
+ return;
+ }
+ $fields = $options['post_fields'];
+ unset($options['post_fields']);
+ }
+
+ $files = $options['post_files'];
+ unset($options['post_files']);
+ $options['body'] = new MultipartPostBody($fields, $files);
+ $options['headers']['Content-Type'] = $contentType
+ ?: 'multipart/form-data; boundary=' . $options['body']->getBoundary();
}
}
diff --git a/src/ClientInterface.php b/src/ClientInterface.php
index fac88645d..c6f4eddca 100644
--- a/src/ClientInterface.php
+++ b/src/ClientInterface.php
@@ -1,121 +1,42 @@
data = $data;
- }
-
- /**
- * Create a new collection from an array, validate the keys, and add default
- * values where missing
- *
- * @param array $config Configuration values to apply.
- * @param array $defaults Default parameters
- * @param array $required Required parameter names
- *
- * @return self
- * @throws \InvalidArgumentException if a parameter is missing
- */
- public static function fromConfig(
- array $config = [],
- array $defaults = [],
- array $required = []
- ) {
- $data = $config + $defaults;
-
- if ($missing = array_diff($required, array_keys($data))) {
- throw new \InvalidArgumentException(
- 'Config is missing the following keys: ' .
- implode(', ', $missing));
- }
-
- return new self($data);
- }
-
- /**
- * Removes all key value pairs
- */
- public function clear()
- {
- $this->data = [];
- }
-
- /**
- * Get a specific key value.
- *
- * @param string $key Key to retrieve.
- *
- * @return mixed|null Value of the key or NULL
- */
- public function get($key)
- {
- return isset($this->data[$key]) ? $this->data[$key] : null;
- }
-
- /**
- * Set a key value pair
- *
- * @param string $key Key to set
- * @param mixed $value Value to set
- */
- public function set($key, $value)
- {
- $this->data[$key] = $value;
- }
-
- /**
- * Add a value to a key. If a key of the same name has already been added,
- * the key value will be converted into an array and the new value will be
- * pushed to the end of the array.
- *
- * @param string $key Key to add
- * @param mixed $value Value to add to the key
- */
- public function add($key, $value)
- {
- if (!array_key_exists($key, $this->data)) {
- $this->data[$key] = $value;
- } elseif (is_array($this->data[$key])) {
- $this->data[$key][] = $value;
- } else {
- $this->data[$key] = array($this->data[$key], $value);
- }
- }
-
- /**
- * Remove a specific key value pair
- *
- * @param string $key A key to remove
- */
- public function remove($key)
- {
- unset($this->data[$key]);
- }
-
- /**
- * Get all keys in the collection
- *
- * @return array
- */
- public function getKeys()
- {
- return array_keys($this->data);
- }
-
- /**
- * Returns whether or not the specified key is present.
- *
- * @param string $key The key for which to check the existence.
- *
- * @return bool
- */
- public function hasKey($key)
- {
- return array_key_exists($key, $this->data);
- }
-
- /**
- * Checks if any keys contains a certain value
- *
- * @param string $value Value to search for
- *
- * @return mixed Returns the key if the value was found FALSE if the value
- * was not found.
- */
- public function hasValue($value)
- {
- return array_search($value, $this->data, true);
- }
-
- /**
- * Replace the data of the object with the value of an array
- *
- * @param array $data Associative array of data
- */
- public function replace(array $data)
- {
- $this->data = $data;
- }
-
- /**
- * Add and merge in a Collection or array of key value pair data.
- *
- * @param Collection|array $data Associative array of key value pair data
- */
- public function merge($data)
- {
- foreach ($data as $key => $value) {
- $this->add($key, $value);
- }
- }
-
- /**
- * Overwrite key value pairs in this collection with all of the data from
- * an array or collection.
- *
- * @param array|\Traversable $data Values to override over this config
- */
- public function overwriteWith($data)
- {
- if (is_array($data)) {
- $this->data = $data + $this->data;
- } elseif ($data instanceof Collection) {
- $this->data = $data->toArray() + $this->data;
- } else {
- foreach ($data as $key => $value) {
- $this->data[$key] = $value;
- }
- }
- }
-
- /**
- * Returns a Collection containing all the elements of the collection after
- * applying the callback function to each one.
- *
- * The callable should accept three arguments:
- * - (string) $key
- * - (string) $value
- * - (array) $context
- *
- * The callable must return a the altered or unaltered value.
- *
- * @param callable $closure Map function to apply
- * @param array $context Context to pass to the callable
- *
- * @return Collection
- */
- public function map(callable $closure, array $context = [])
- {
- $collection = new static();
- foreach ($this as $key => $value) {
- $collection[$key] = $closure($key, $value, $context);
- }
-
- return $collection;
- }
-
- /**
- * Iterates over each key value pair in the collection passing them to the
- * callable. If the callable returns true, the current value from input is
- * returned into the result Collection.
- *
- * The callable must accept two arguments:
- * - (string) $key
- * - (string) $value
- *
- * @param callable $closure Evaluation function
- *
- * @return Collection
- */
- public function filter(callable $closure)
- {
- $collection = new static();
- foreach ($this->data as $key => $value) {
- if ($closure($key, $value)) {
- $collection[$key] = $value;
- }
- }
-
- return $collection;
- }
-}
diff --git a/src/Cookie/CookieJar.php b/src/Cookie/CookieJar.php
index f8ac7dd35..d70c47de1 100644
--- a/src/Cookie/CookieJar.php
+++ b/src/Cookie/CookieJar.php
@@ -1,14 +1,13 @@
getHeaderAsArray('Set-Cookie')) {
+ if ($cookieHeader = $response->getHeaderLines('Set-Cookie')) {
foreach ($cookieHeader as $cookie) {
$sc = SetCookie::fromString($cookie);
if (!$sc->getDomain()) {
- $sc->setDomain($request->getHost());
+ $sc->setDomain($request->getUri()->getHost());
}
$this->setCookie($sc);
}
}
}
- public function addCookieHeader(RequestInterface $request)
+ public function withCookieHeader(RequestInterface $request)
{
$values = [];
- $scheme = $request->getScheme();
- $host = $request->getHost();
- $path = $request->getPath();
+ $uri = $request->getUri();
+ $scheme = $uri->getScheme();
+ $host = $uri->getHost();
+ $path = $uri->getPath();
foreach ($this->cookies as $cookie) {
if ($cookie->matchesPath($path) &&
@@ -223,9 +223,9 @@ public function addCookieHeader(RequestInterface $request)
}
}
- if ($values) {
- $request->setHeader('Cookie', implode('; ', $values));
- }
+ return $values
+ ? $request->withHeader('Cookie', implode('; ', $values))
+ : $request;
}
/**
diff --git a/src/Cookie/CookieJarInterface.php b/src/Cookie/CookieJarInterface.php
index 4ea8567e8..2cf298a86 100644
--- a/src/Cookie/CookieJarInterface.php
+++ b/src/Cookie/CookieJarInterface.php
@@ -1,8 +1,8 @@
getExpires() && !$cookie->getDiscard()) {
$json[] = $cookie->toArray();
}
}
if (false === file_put_contents($filename, json_encode($json))) {
- // @codeCoverageIgnoreStart
throw new \RuntimeException("Unable to save file {$filename}");
- // @codeCoverageIgnoreEnd
}
}
@@ -69,14 +66,12 @@ public function load($filename)
{
$json = file_get_contents($filename);
if (false === $json) {
- // @codeCoverageIgnoreStart
throw new \RuntimeException("Unable to load file {$filename}");
- // @codeCoverageIgnoreEnd
}
- $data = Utils::jsonDecode($json, true);
+ $data = \GuzzleHttp\json_decode($json, true);
if (is_array($data)) {
- foreach (Utils::jsonDecode($json, true) as $cookie) {
+ foreach (\GuzzleHttp\json_decode($json, true) as $cookie) {
$this->setCookie(new SetCookie($cookie));
}
} elseif (strlen($data)) {
diff --git a/src/Cookie/SessionCookieJar.php b/src/Cookie/SessionCookieJar.php
index 71a02d56d..d935419f9 100644
--- a/src/Cookie/SessionCookieJar.php
+++ b/src/Cookie/SessionCookieJar.php
@@ -1,8 +1,6 @@
getExpires() && !$cookie->getDiscard()) {
$json[] = $cookie->toArray();
}
@@ -54,7 +53,7 @@ protected function load()
? $_SESSION[$this->sessionKey]
: null;
- $data = Utils::jsonDecode($cookieJar, true);
+ $data = \GuzzleHttp\json_decode($cookieJar, true);
if (is_array($data)) {
foreach ($data as $cookie) {
$this->setCookie(new SetCookie($cookie));
diff --git a/src/Cookie/SetCookie.php b/src/Cookie/SetCookie.php
index ac9a89081..cf3190625 100644
--- a/src/Cookie/SetCookie.php
+++ b/src/Cookie/SetCookie.php
@@ -1,12 +1,10 @@
propagationStopped;
- }
-
- public function stopPropagation()
- {
- $this->propagationStopped = true;
- }
-}
diff --git a/src/Event/AbstractRequestEvent.php b/src/Event/AbstractRequestEvent.php
deleted file mode 100644
index 8c8fbc94a..000000000
--- a/src/Event/AbstractRequestEvent.php
+++ /dev/null
@@ -1,61 +0,0 @@
-transaction = $transaction;
- }
-
- /**
- * Get the HTTP client associated with the event.
- *
- * @return ClientInterface
- */
- public function getClient()
- {
- return $this->transaction->client;
- }
-
- /**
- * Get the request object
- *
- * @return RequestInterface
- */
- public function getRequest()
- {
- return $this->transaction->request;
- }
-
- /**
- * Get the number of transaction retries.
- *
- * @return int
- */
- public function getRetryCount()
- {
- return $this->transaction->retries;
- }
-
- /**
- * @return Transaction
- */
- protected function getTransaction()
- {
- return $this->transaction;
- }
-}
diff --git a/src/Event/AbstractRetryableEvent.php b/src/Event/AbstractRetryableEvent.php
deleted file mode 100644
index bbbdfaf83..000000000
--- a/src/Event/AbstractRetryableEvent.php
+++ /dev/null
@@ -1,40 +0,0 @@
-transaction->state = 'retry';
-
- if ($afterDelay) {
- $this->transaction->request->getConfig()->set('delay', $afterDelay);
- }
-
- $this->stopPropagation();
- }
-}
diff --git a/src/Event/AbstractTransferEvent.php b/src/Event/AbstractTransferEvent.php
deleted file mode 100644
index 3b106df00..000000000
--- a/src/Event/AbstractTransferEvent.php
+++ /dev/null
@@ -1,63 +0,0 @@
-transaction->transferInfo;
- }
-
- return isset($this->transaction->transferInfo[$name])
- ? $this->transaction->transferInfo[$name]
- : null;
- }
-
- /**
- * Returns true/false if a response is available.
- *
- * @return bool
- */
- public function hasResponse()
- {
- return !($this->transaction->response instanceof FutureInterface);
- }
-
- /**
- * Get the response.
- *
- * @return ResponseInterface|null
- */
- public function getResponse()
- {
- return $this->hasResponse() ? $this->transaction->response : null;
- }
-
- /**
- * Intercept the request and associate a response
- *
- * @param ResponseInterface $response Response to set
- */
- public function intercept(ResponseInterface $response)
- {
- $this->transaction->response = $response;
- $this->transaction->exception = null;
- $this->stopPropagation();
- }
-}
diff --git a/src/Event/BeforeEvent.php b/src/Event/BeforeEvent.php
deleted file mode 100644
index f313c3756..000000000
--- a/src/Event/BeforeEvent.php
+++ /dev/null
@@ -1,26 +0,0 @@
-transaction->response = $response;
- $this->transaction->exception = null;
- $this->stopPropagation();
- }
-}
diff --git a/src/Event/CompleteEvent.php b/src/Event/CompleteEvent.php
deleted file mode 100644
index 56cc557e3..000000000
--- a/src/Event/CompleteEvent.php
+++ /dev/null
@@ -1,14 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- *
- * @link https://github.com/symfony/symfony/tree/master/src/Symfony/Component/EventDispatcher
- */
-class Emitter implements EmitterInterface
-{
- /** @var array */
- private $listeners = [];
-
- /** @var array */
- private $sorted = [];
-
- public function on($eventName, callable $listener, $priority = 0)
- {
- if ($priority === 'first') {
- $priority = isset($this->listeners[$eventName])
- ? max(array_keys($this->listeners[$eventName])) + 1
- : 1;
- } elseif ($priority === 'last') {
- $priority = isset($this->listeners[$eventName])
- ? min(array_keys($this->listeners[$eventName])) - 1
- : -1;
- }
-
- $this->listeners[$eventName][$priority][] = $listener;
- unset($this->sorted[$eventName]);
- }
-
- public function once($eventName, callable $listener, $priority = 0)
- {
- $onceListener = function (
- EventInterface $event,
- $eventName
- ) use (&$onceListener, $eventName, $listener, $priority) {
- $this->removeListener($eventName, $onceListener);
- $listener($event, $eventName, $this);
- };
-
- $this->on($eventName, $onceListener, $priority);
- }
-
- public function removeListener($eventName, callable $listener)
- {
- if (empty($this->listeners[$eventName])) {
- return;
- }
-
- foreach ($this->listeners[$eventName] as $priority => $listeners) {
- if (false !== ($key = array_search($listener, $listeners, true))) {
- unset(
- $this->listeners[$eventName][$priority][$key],
- $this->sorted[$eventName]
- );
- }
- }
- }
-
- public function listeners($eventName = null)
- {
- // Return all events in a sorted priority order
- if ($eventName === null) {
- foreach (array_keys($this->listeners) as $eventName) {
- if (empty($this->sorted[$eventName])) {
- $this->listeners($eventName);
- }
- }
- return $this->sorted;
- }
-
- // Return the listeners for a specific event, sorted in priority order
- if (empty($this->sorted[$eventName])) {
- $this->sorted[$eventName] = [];
- if (isset($this->listeners[$eventName])) {
- krsort($this->listeners[$eventName], SORT_NUMERIC);
- foreach ($this->listeners[$eventName] as $listeners) {
- foreach ($listeners as $listener) {
- $this->sorted[$eventName][] = $listener;
- }
- }
- }
- }
-
- return $this->sorted[$eventName];
- }
-
- public function hasListeners($eventName)
- {
- return !empty($this->listeners[$eventName]);
- }
-
- public function emit($eventName, EventInterface $event)
- {
- if (isset($this->listeners[$eventName])) {
- foreach ($this->listeners($eventName) as $listener) {
- $listener($event, $eventName);
- if ($event->isPropagationStopped()) {
- break;
- }
- }
- }
-
- return $event;
- }
-
- public function attach(SubscriberInterface $subscriber)
- {
- foreach ($subscriber->getEvents() as $eventName => $listeners) {
- if (is_array($listeners[0])) {
- foreach ($listeners as $listener) {
- $this->on(
- $eventName,
- [$subscriber, $listener[0]],
- isset($listener[1]) ? $listener[1] : 0
- );
- }
- } else {
- $this->on(
- $eventName,
- [$subscriber, $listeners[0]],
- isset($listeners[1]) ? $listeners[1] : 0
- );
- }
- }
- }
-
- public function detach(SubscriberInterface $subscriber)
- {
- foreach ($subscriber->getEvents() as $eventName => $listener) {
- $this->removeListener($eventName, [$subscriber, $listener[0]]);
- }
- }
-}
diff --git a/src/Event/EmitterInterface.php b/src/Event/EmitterInterface.php
deleted file mode 100644
index 9783efd15..000000000
--- a/src/Event/EmitterInterface.php
+++ /dev/null
@@ -1,96 +0,0 @@
-transaction->exception;
- }
-}
diff --git a/src/Event/ErrorEvent.php b/src/Event/ErrorEvent.php
deleted file mode 100644
index 7432134d0..000000000
--- a/src/Event/ErrorEvent.php
+++ /dev/null
@@ -1,27 +0,0 @@
-transaction->exception;
- }
-}
diff --git a/src/Event/EventInterface.php b/src/Event/EventInterface.php
deleted file mode 100644
index 97247e84c..000000000
--- a/src/Event/EventInterface.php
+++ /dev/null
@@ -1,23 +0,0 @@
-emitter) {
- $this->emitter = new Emitter();
- }
-
- return $this->emitter;
- }
-}
diff --git a/src/Event/ListenerAttacherTrait.php b/src/Event/ListenerAttacherTrait.php
deleted file mode 100644
index 407dc92dd..000000000
--- a/src/Event/ListenerAttacherTrait.php
+++ /dev/null
@@ -1,88 +0,0 @@
-getEmitter();
- foreach ($listeners as $el) {
- if ($el['once']) {
- $emitter->once($el['name'], $el['fn'], $el['priority']);
- } else {
- $emitter->on($el['name'], $el['fn'], $el['priority']);
- }
- }
- }
-
- /**
- * Extracts the allowed events from the provided array, and ignores anything
- * else in the array. The event listener must be specified as a callable or
- * as an array of event listener data ("name", "fn", "priority", "once").
- *
- * @param array $source Array containing callables or hashes of data to be
- * prepared as event listeners.
- * @param array $events Names of events to look for in the provided $source
- * array. Other keys are ignored.
- * @return array
- */
- private function prepareListeners(array $source, array $events)
- {
- $listeners = [];
- foreach ($events as $name) {
- if (isset($source[$name])) {
- $this->buildListener($name, $source[$name], $listeners);
- }
- }
-
- return $listeners;
- }
-
- /**
- * Creates a complete event listener definition from the provided array of
- * listener data. Also works recursively if more than one listeners are
- * contained in the provided array.
- *
- * @param string $name Name of the event the listener is for.
- * @param array|callable $data Event listener data to prepare.
- * @param array $listeners Array of listeners, passed by reference.
- *
- * @throws \InvalidArgumentException if the event data is malformed.
- */
- private function buildListener($name, $data, &$listeners)
- {
- static $defaults = ['priority' => 0, 'once' => false];
-
- // If a callable is provided, normalize it to the array format.
- if (is_callable($data)) {
- $data = ['fn' => $data];
- }
-
- // Prepare the listener and add it to the array, recursively.
- if (isset($data['fn'])) {
- $data['name'] = $name;
- $listeners[] = $data + $defaults;
- } elseif (is_array($data)) {
- foreach ($data as $listenerData) {
- $this->buildListener($name, $listenerData, $listeners);
- }
- } else {
- throw new \InvalidArgumentException('Each event listener must be a '
- . 'callable or an associative array containing a "fn" key.');
- }
- }
-}
diff --git a/src/Event/ProgressEvent.php b/src/Event/ProgressEvent.php
deleted file mode 100644
index 3fd0de4ac..000000000
--- a/src/Event/ProgressEvent.php
+++ /dev/null
@@ -1,51 +0,0 @@
-downloadSize = $downloadSize;
- $this->downloaded = $downloaded;
- $this->uploadSize = $uploadSize;
- $this->uploaded = $uploaded;
- }
-}
diff --git a/src/Event/RequestEvents.php b/src/Event/RequestEvents.php
deleted file mode 100644
index f51d42065..000000000
--- a/src/Event/RequestEvents.php
+++ /dev/null
@@ -1,56 +0,0 @@
- ['methodName']]
- * - ['eventName' => ['methodName', $priority]]
- * - ['eventName' => [['methodName'], ['otherMethod']]
- * - ['eventName' => [['methodName'], ['otherMethod', $priority]]
- * - ['eventName' => [['methodName', $priority], ['otherMethod', $priority]]
- *
- * @return array
- */
- public function getEvents();
-}
diff --git a/src/Exception/CouldNotRewindStreamException.php b/src/Exception/CouldNotRewindStreamException.php
deleted file mode 100644
index fbe2dcd7c..000000000
--- a/src/Exception/CouldNotRewindStreamException.php
+++ /dev/null
@@ -1,4 +0,0 @@
-response = $response;
- }
- /**
- * Get the associated response
- *
- * @return ResponseInterface|null
- */
- public function getResponse()
- {
- return $this->response;
- }
-}
diff --git a/src/Exception/RequestException.php b/src/Exception/RequestException.php
index f81d24836..2bcf90077 100644
--- a/src/Exception/RequestException.php
+++ b/src/Exception/RequestException.php
@@ -1,11 +1,9 @@
getStatusCode()
: 0;
parent::__construct($message, $code, $previous);
@@ -45,11 +43,9 @@ public static function wrapException(RequestInterface $request, \Exception $e)
{
if ($e instanceof RequestException) {
return $e;
- } elseif ($e instanceof ConnectException) {
- return new HttpConnectException($e->getMessage(), $request, null, $e);
- } else {
- return new RequestException($e->getMessage(), $request, null, $e);
}
+
+ return new RequestException($e->getMessage(), $request, null, $e);
}
/**
@@ -82,7 +78,7 @@ public static function create(
$className = __CLASS__;
}
- $message = $label . ' [url] ' . $request->getUrl()
+ $message = $label . ' [url] ' . $request->getUri()
. ' [status code] ' . $response->getStatusCode()
. ' [reason phrase] ' . $response->getReasonPhrase();
diff --git a/src/Exception/SeekException.php b/src/Exception/SeekException.php
new file mode 100644
index 000000000..eefc4c65a
--- /dev/null
+++ b/src/Exception/SeekException.php
@@ -0,0 +1,27 @@
+stream = $stream;
+ $msg = $msg ?: 'Could not seek the stream to position ' . $pos;
+ parent::__construct($msg);
+ }
+
+ /**
+ * @return StreamableInterface
+ */
+ public function getStream()
+ {
+ return $this->stream;
+ }
+}
diff --git a/src/Exception/StateException.php b/src/Exception/StateException.php
deleted file mode 100644
index a7652a384..000000000
--- a/src/Exception/StateException.php
+++ /dev/null
@@ -1,4 +0,0 @@
-error = $error;
- }
-
- /**
- * Get the associated error
- *
- * @return \LibXMLError|null
- */
- public function getError()
- {
- return $this->error;
- }
-}
diff --git a/src/FulfilledResponse.php b/src/FulfilledResponse.php
new file mode 100644
index 000000000..4e995b93b
--- /dev/null
+++ b/src/FulfilledResponse.php
@@ -0,0 +1,90 @@
+response = $response;
+ parent::__construct($response);
+ }
+
+ public function getStatusCode()
+ {
+ return $this->response->getStatusCode();
+ }
+
+ public function withStatus($code, $reasonPhrase = null)
+ {
+ return $this->response->withStatus($code, $reasonPhrase);
+ }
+
+ public function getReasonPhrase()
+ {
+ return $this->response->getReasonPhrase();
+ }
+
+ public function getProtocolVersion()
+ {
+ return $this->response->getProtocolVersion();
+ }
+
+ public function withProtocolVersion($version)
+ {
+ return $this->response->withProtocolVersion($version);
+ }
+
+ public function getHeaders()
+ {
+ return $this->response->getHeaders();
+ }
+
+ public function hasHeader($name)
+ {
+ return $this->response->hasHeader($name);
+ }
+
+ public function getHeader($name)
+ {
+ return $this->response->getHeader($name);
+ }
+
+ public function getHeaderLines($name)
+ {
+ return $this->response->getHeaderLines($name);
+ }
+
+ public function withHeader($name, $value)
+ {
+ return $this->response->withHeader($name, $value);
+ }
+
+ public function withAddedHeader($name, $value)
+ {
+ return $this->response->withAddedHeader($name, $value);
+ }
+
+ public function withoutHeader($name)
+ {
+ return $this->response->withoutHeader($name);
+ }
+
+ public function getBody()
+ {
+ return $this->response->getBody();
+ }
+
+ public function withBody(StreamableInterface $body)
+ {
+ return $this->response->withBody($body);
+ }
+}
diff --git a/src/Handler/CurlFactory.php b/src/Handler/CurlFactory.php
new file mode 100644
index 000000000..92aa13429
--- /dev/null
+++ b/src/Handler/CurlFactory.php
@@ -0,0 +1,554 @@
+getDefaultOptions($request, $headers);
+ $this->applyMethod($request, $options, $conf);
+ $this->applyHandlerOptions($request, $options, $conf);
+ $this->applyHeaders($request, $conf);
+ unset($conf['_headers']);
+ // Add handler options from the request configuration options
+ if (isset($options['curl'])) {
+ $options = $this->applyCustomCurlOptions($options['curl'], $conf);
+ }
+
+ if (!$handle) {
+ $handle = curl_init();
+ }
+
+ $body = $this->getOutputBody($request, $options, $conf);
+ curl_setopt_array($handle, $conf);
+
+ return [$handle, &$headers, $body];
+ }
+
+ /**
+ * Creates a response hash from a cURL result.
+ *
+ * @param callable $handler Handler that was used.
+ * @param RequestInterface $request Request that sent.
+ * @param array $options Request transfer options.
+ * @param array $response Response hash.
+ * @param array $headers Headers received during transfer.
+ * @param StreamableInterface $body Response body.
+ *
+ * @return ResponseInterface
+ */
+ public static function createResponse(
+ callable $handler,
+ RequestInterface $request,
+ array $options,
+ array $response,
+ array $headers,
+ StreamableInterface $body
+ ) {
+ if (isset($response['transfer_stats']['url'])) {
+ $response['effective_url'] = $response['transfer_stats']['url'];
+ }
+
+ if (!empty($headers)) {
+ $startLine = explode(' ', array_shift($headers), 3);
+ $headerList = \GuzzleHttp\headers_from_lines($headers);
+ $response['headers'] = $headerList;
+ $response['status'] = isset($startLine[1]) ? (int) $startLine[1] : null;
+ $response['reason'] = isset($startLine[2]) ? $startLine[2] : null;
+ $response['body'] = $body;
+ $response['body']->rewind();
+ }
+
+ if (!empty($response['curl']['errno']) || !isset($response['status'])) {
+ return self::createErrorResponse($handler, $request, $options, $response);
+ }
+
+ return new Response(
+ $response['status'],
+ $response['headers'],
+ $response['body'],
+ $response['reason']
+ );
+ }
+
+ private static function createErrorResponse(
+ callable $handler,
+ RequestInterface $request,
+ array $options,
+ array $response
+ ) {
+ static $connectionErrors = [
+ CURLE_OPERATION_TIMEOUTED => true,
+ CURLE_COULDNT_RESOLVE_HOST => true,
+ CURLE_COULDNT_CONNECT => true,
+ CURLE_SSL_CONNECT_ERROR => true,
+ CURLE_GOT_NOTHING => true,
+ ];
+
+ // Retry when nothing is present or when curl failed to rewind.
+ if (!isset($response['err_message'])
+ && (empty($response['curl']['errno'])
+ || $response['curl']['errno'] == 65)
+ ) {
+ return self::retryFailedRewind($handler, $request, $options, $response);
+ }
+
+ $message = isset($response['err_message'])
+ ? $response['err_message']
+ : sprintf('cURL error %s: %s',
+ $response['curl']['errno'],
+ isset($response['curl']['error'])
+ ? $response['curl']['error']
+ : 'See http://curl.haxx.se/libcurl/c/libcurl-errors.html');
+
+ if (isset($response['curl']['errno'])
+ && isset($connectionErrors[$response['curl']['errno']])
+ ) {
+ $error = new ConnectException($message, $request);
+ } else {
+ $error = new RequestException(
+ $message,
+ $request,
+ new Response(
+ isset($response['status']) ? $response['status'] : 200,
+ isset($response['headers']) ? $response['headers'] : [],
+ isset($response['body']) ? $response['body'] : null,
+ isset($response['reason']) ? $response['reason'] : null
+ )
+ );
+ }
+
+ return new RejectedResponse($error);
+ }
+
+ private function getOutputBody(RequestInterface $request, array $options, array &$conf)
+ {
+ // Determine where the body of the response (if any) will be streamed.
+ if (isset($conf[CURLOPT_WRITEFUNCTION])) {
+ return $options['sink'];
+ }
+
+ if (isset($conf[CURLOPT_FILE])) {
+ return $conf[CURLOPT_FILE];
+ }
+
+ if ($request->getMethod() !== 'HEAD') {
+ // Create a default body if one was not provided
+ return $conf[CURLOPT_FILE] = fopen('php://temp', 'w+');
+ }
+
+ return null;
+ }
+
+ private function getDefaultOptions(RequestInterface $request, array &$headers)
+ {
+ $url = (string) $request->getUri();
+ $startingResponse = false;
+
+ $options = [
+ '_headers' => $request->getHeaders(),
+ CURLOPT_CUSTOMREQUEST => $request->getMethod(),
+ CURLOPT_URL => $url,
+ CURLOPT_RETURNTRANSFER => false,
+ CURLOPT_HEADER => false,
+ CURLOPT_CONNECTTIMEOUT => 150,
+ CURLOPT_HEADERFUNCTION => function ($ch, $h) use (&$headers, &$startingResponse) {
+ $value = trim($h);
+ if ($value === '') {
+ $startingResponse = true;
+ } elseif ($startingResponse) {
+ $startingResponse = false;
+ $headers = [$value];
+ } else {
+ $headers[] = $value;
+ }
+ return strlen($h);
+ },
+ ];
+
+ $options[CURLOPT_HTTP_VERSION] = $request->getProtocolVersion() == 1.1
+ ? CURL_HTTP_VERSION_1_1
+ : CURL_HTTP_VERSION_1_0;
+
+ if (defined('CURLOPT_PROTOCOLS')) {
+ $options[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
+ }
+
+ return $options;
+ }
+
+ private function applyMethod(RequestInterface $request, array $options, array &$conf)
+ {
+ $body = $request->getBody();
+ $size = $body->getSize();
+
+ if ($size !== null && $size > 0) {
+ $this->applyBody($request, $options, $conf);
+ return;
+ }
+
+ switch ($request->getMethod()) {
+ case 'PUT':
+ case 'POST':
+ // See http://tools.ietf.org/html/rfc7230#section-3.3.2
+ if (!$request->hasHeader('Content-Length')) {
+ $conf[CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
+ }
+ break;
+ case 'HEAD':
+ $conf[CURLOPT_NOBODY] = true;
+ unset(
+ $conf[CURLOPT_WRITEFUNCTION],
+ $conf[CURLOPT_READFUNCTION],
+ $conf[CURLOPT_FILE],
+ $conf[CURLOPT_INFILE]
+ );
+ }
+ }
+
+ private function applyBody(RequestInterface $request, array $options, array &$conf)
+ {
+ $contentLength = $request->getHeader('Content-Length');
+ $size = $contentLength !== null ? (int) $contentLength : null;
+
+ // Send the body as a string if the size is less than 1MB OR if the
+ // [client][curl][body_as_string] request value is set.
+ if (($size !== null && $size < 1000000) ||
+ isset($options['curl']['body_as_string'])
+ ) {
+ $conf[CURLOPT_POSTFIELDS] = (string) $request->getBody();
+ // Don't duplicate the Content-Length header
+ $this->removeHeader('Content-Length', $conf);
+ $this->removeHeader('Transfer-Encoding', $conf);
+ } else {
+ $conf[CURLOPT_UPLOAD] = true;
+ if ($size !== null) {
+ // Let cURL handle setting the Content-Length header
+ $conf[CURLOPT_INFILESIZE] = $size;
+ $this->removeHeader('Content-Length', $conf);
+ }
+ $this->addStreamingBody($request, $conf);
+ }
+
+ // If the Expect header is not present, prevent curl from adding it
+ if (!$request->hasHeader('Expect')) {
+ $conf[CURLOPT_HTTPHEADER][] = 'Expect:';
+ }
+
+ // cURL sometimes adds a content-type by default. Prevent this.
+ if (!$request->hasHeader('Content-Type')) {
+ $conf[CURLOPT_HTTPHEADER][] = 'Content-Type:';
+ }
+ }
+
+ private function addStreamingBody(RequestInterface $request, array &$conf)
+ {
+ $body = $request->getBody();
+ $size = $body->getSize();
+
+ if ($size > 0 || $size === null) {
+ $conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
+ return (string) $body->read($length);
+ };
+ if ($size !== null && !isset($conf[CURLOPT_INFILESIZE])) {
+ $conf[CURLOPT_INFILESIZE] = $size;
+ }
+ }
+ }
+
+ private function applyHeaders(RequestInterface $request, array &$conf)
+ {
+ foreach ($conf['_headers'] as $name => $values) {
+ foreach ($values as $value) {
+ $conf[CURLOPT_HTTPHEADER][] = "$name: $value";
+ }
+ }
+
+ // Remove the Accept header if one was not set
+ if (!$request->hasHeader('Accept')) {
+ $conf[CURLOPT_HTTPHEADER][] = 'Accept:';
+ }
+ }
+
+ /**
+ * Takes an array of curl options specified in the 'curl' option of a
+ * request's configuration array and maps them to CURLOPT_* options.
+ *
+ * This method is only called when a request has a 'curl' config setting.
+ *
+ * @param array $options Configuration array of custom curl option
+ * @param array $conf Array of existing curl options
+ *
+ * @return array Returns a new array of curl options
+ */
+ private function applyCustomCurlOptions(array $options, array $conf)
+ {
+ $curlOptions = [];
+ foreach ($options as $key => $value) {
+ if (is_int($key)) {
+ $curlOptions[$key] = $value;
+ }
+ }
+
+ return $curlOptions + $conf;
+ }
+
+ /**
+ * Remove a header from the options array.
+ *
+ * @param string $name Case-insensitive header to remove
+ * @param array $options Array of options to modify
+ */
+ private function removeHeader($name, array &$options)
+ {
+ foreach (array_keys($options['_headers']) as $key) {
+ if (!strcasecmp($key, $name)) {
+ unset($options['_headers'][$key]);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Applies an array of request client options to a the options array.
+ *
+ * This method uses a large switch rather than double-dispatch to save on
+ * high overhead of calling functions in PHP.
+ *
+ * @param RequestInterface $request Request to send
+ * @param array $options Request transfer options.
+ * @param array $conf cURL configuration options.
+ */
+ private function applyHandlerOptions(
+ RequestInterface $request,
+ array $options,
+ array &$conf
+ ) {
+ foreach ($options as $key => $value) {
+ switch ($key) {
+ // Violating PSR-4 to provide more room.
+ case 'verify':
+
+ if ($value === false) {
+ unset($conf[CURLOPT_CAINFO]);
+ $conf[CURLOPT_SSL_VERIFYHOST] = 0;
+ $conf[CURLOPT_SSL_VERIFYPEER] = false;
+ continue;
+ }
+
+ $conf[CURLOPT_SSL_VERIFYHOST] = 2;
+ $conf[CURLOPT_SSL_VERIFYPEER] = true;
+
+ if (is_string($value)) {
+ $conf[CURLOPT_CAINFO] = $value;
+ if (!file_exists($value)) {
+ throw new \InvalidArgumentException(
+ "SSL CA bundle not found: $value"
+ );
+ }
+ }
+ break;
+
+ case 'decode_content':
+
+ if ($value === false) {
+ continue;
+ }
+
+ $accept = $request->getHeader('Accept-Encoding');
+ if ($accept) {
+ $conf[CURLOPT_ENCODING] = $accept;
+ } else {
+ $conf[CURLOPT_ENCODING] = '';
+ // Don't let curl send the header over the wire
+ $conf[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
+ }
+ break;
+
+ case 'sink':
+
+ if (is_string($value)) {
+ $value = new LazyOpenStream($value, 'w+');
+ }
+
+ if ($value instanceof StreamableInterface) {
+ $conf[CURLOPT_WRITEFUNCTION] =
+ function ($ch, $write) use ($value) {
+ return $value->write($write);
+ };
+ } elseif (is_resource($value)) {
+ $conf[CURLOPT_FILE] = $value;
+ } else {
+ throw new \InvalidArgumentException('sink must be a '
+ . 'Psr\Http\Message\StreamableInterface or resource');
+ }
+ break;
+
+ case 'timeout':
+
+ if (defined('CURLOPT_TIMEOUT_MS')) {
+ $conf[CURLOPT_TIMEOUT_MS] = $value * 1000;
+ } else {
+ $conf[CURLOPT_TIMEOUT] = $value;
+ }
+ break;
+
+ case 'connect_timeout':
+
+ if (defined('CURLOPT_CONNECTTIMEOUT_MS')) {
+ $conf[CURLOPT_CONNECTTIMEOUT_MS] = $value * 1000;
+ } else {
+ $conf[CURLOPT_CONNECTTIMEOUT] = $value;
+ }
+ break;
+
+ case 'proxy':
+
+ if (!is_array($value)) {
+ $conf[CURLOPT_PROXY] = $value;
+ } elseif (isset($request['scheme'])) {
+ $scheme = $request['scheme'];
+ if (isset($value[$scheme])) {
+ $conf[CURLOPT_PROXY] = $value[$scheme];
+ }
+ }
+ break;
+
+ case 'cert':
+
+ if (is_array($value)) {
+ $conf[CURLOPT_SSLCERTPASSWD] = $value[1];
+ $value = $value[0];
+ }
+
+ if (!file_exists($value)) {
+ throw new \InvalidArgumentException(
+ "SSL certificate not found: {$value}"
+ );
+ }
+
+ $conf[CURLOPT_SSLCERT] = $value;
+ break;
+
+ case 'ssl_key':
+
+ if (is_array($value)) {
+ $conf[CURLOPT_SSLKEYPASSWD] = $value[1];
+ $value = $value[0];
+ }
+
+ if (!file_exists($value)) {
+ throw new \InvalidArgumentException(
+ "SSL private key not found: {$value}"
+ );
+ }
+
+ $conf[CURLOPT_SSLKEY] = $value;
+ break;
+
+ case 'progress':
+
+ if (!is_callable($value)) {
+ throw new \InvalidArgumentException(
+ 'progress client option must be callable'
+ );
+ }
+
+ $conf[CURLOPT_NOPROGRESS] = false;
+ $conf[CURLOPT_PROGRESSFUNCTION] =
+ function () use ($value) {
+ $args = func_get_args();
+ // PHP 5.5 pushed the handle onto the start of the args
+ if (is_resource($args[0])) {
+ array_shift($args);
+ }
+ call_user_func_array($value, $args);
+ };
+ break;
+
+ case 'debug':
+
+ if ($value) {
+ $conf[CURLOPT_STDERR] = \GuzzleHttp\get_debug_resource($value);
+ $conf[CURLOPT_VERBOSE] = true;
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * This function ensures that a response was set on a transaction. If one
+ * was not set, then the request is retried if possible. This error
+ * typically means you are sending a payload, curl encountered a
+ * "Connection died, retrying a fresh connect" error, tried to rewind the
+ * stream, and then encountered a "necessary data rewind wasn't possible"
+ * error, causing the request to be sent through curl_multi_info_read()
+ * without an error status.
+ *
+ * @param callable $handler Handler that will retry.
+ * @param RequestInterface $request Request that was sent.
+ * @param array $options Request options.
+ * @param array $response Response hash.
+ *
+ * @return PromiseInterface
+ */
+ private static function retryFailedRewind(
+ callable $handler,
+ RequestInterface $request,
+ array $options,
+ array $response
+ ) {
+ if ($request->getBody()->rewind()) {
+ $response['err_message'] = 'The connection unexpectedly failed '
+ . 'without providing an error. The request would have been '
+ . 'retried, but attempting to rewind the request body failed.';
+ return self::createErrorResponse($handler, $request, $options, $response);
+ }
+
+ // Retry no more than 3 times before giving up.
+ if (!isset($options['curl']['retries'])) {
+ $options['curl']['retries'] = 1;
+ } elseif ($options['curl']['retries'] == 2) {
+ $response['err_message'] = 'The cURL request was retried 3 times '
+ . 'and did no succeed. cURL was unable to rewind the body of '
+ . 'the request and subsequent retries resulted in the same '
+ . 'error. Turn on the debug option to see what went wrong. '
+ . 'See https://bugs.php.net/bug.php?id=47204 for more information.';
+ return self::createErrorResponse($handler, $request, $options, $response);
+ } else {
+ $options['curl']['retries']++;
+ }
+
+ return $handler($request, $options);
+ }
+}
diff --git a/src/Handler/CurlHandler.php b/src/Handler/CurlHandler.php
new file mode 100644
index 000000000..00c8edb2a
--- /dev/null
+++ b/src/Handler/CurlHandler.php
@@ -0,0 +1,123 @@
+handles = $this->ownedHandles = [];
+ $this->factory = isset($options['handle_factory'])
+ ? $options['handle_factory']
+ : new CurlFactory();
+ $this->maxHandles = isset($options['max_handles'])
+ ? $options['max_handles']
+ : 5;
+ }
+
+ public function __destruct()
+ {
+ foreach ($this->handles as $handle) {
+ if (is_resource($handle)) {
+ curl_close($handle);
+ }
+ }
+ }
+
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ $factory = $this->factory;
+ // Ensure headers are by reference. They're updated elsewhere.
+ $result = $factory($request, $options, $this->checkoutEasyHandle());
+ $h = $result[0];
+ $hd =& $result[1];
+ $bd = $result[2];
+
+ if (isset($options['delay'])) {
+ usleep($options['delay'] * 1000);
+ }
+
+ curl_exec($h);
+ $response = ['transfer_stats' => curl_getinfo($h)];
+ $response['curl']['error'] = curl_error($h);
+ $response['curl']['errno'] = curl_errno($h);
+ $this->releaseEasyHandle($h);
+
+ return new FulfilledResponse(
+ CurlFactory::createResponse(
+ $this, $request, $options, $response, $hd, Stream::factory($bd)
+ )
+ );
+ }
+
+ private function checkoutEasyHandle()
+ {
+ // Find an unused handle in the cache
+ if (false !== ($key = array_search(false, $this->ownedHandles, true))) {
+ $this->ownedHandles[$key] = true;
+ return $this->handles[$key];
+ }
+
+ // Add a new handle
+ $handle = curl_init();
+ $id = (int) $handle;
+ $this->handles[$id] = $handle;
+ $this->ownedHandles[$id] = true;
+
+ return $handle;
+ }
+
+ private function releaseEasyHandle($handle)
+ {
+ $id = (int) $handle;
+ if (count($this->ownedHandles) > $this->maxHandles) {
+ curl_close($this->handles[$id]);
+ unset($this->handles[$id], $this->ownedHandles[$id]);
+ } else {
+ // curl_reset doesn't clear these out for some reason
+ static $unsetValues = [
+ CURLOPT_HEADERFUNCTION => null,
+ CURLOPT_WRITEFUNCTION => null,
+ CURLOPT_READFUNCTION => null,
+ CURLOPT_PROGRESSFUNCTION => null,
+ ];
+ curl_setopt_array($handle, $unsetValues);
+ curl_reset($handle);
+ $this->ownedHandles[$id] = false;
+ }
+ }
+}
diff --git a/src/Handler/CurlMultiHandler.php b/src/Handler/CurlMultiHandler.php
new file mode 100644
index 000000000..0926e0df7
--- /dev/null
+++ b/src/Handler/CurlMultiHandler.php
@@ -0,0 +1,243 @@
+factory = isset($options['handle_factory'])
+ ? $options['handle_factory'] : new CurlFactory();
+ $this->selectTimeout = isset($options['select_timeout'])
+ ? $options['select_timeout'] : 1;
+ $this->maxHandles = isset($options['max_handles'])
+ ? $options['max_handles'] : 100;
+ }
+
+ public function __get($name)
+ {
+ if ($name === '_mh') {
+ return $this->_mh = curl_multi_init();
+ }
+
+ throw new \BadMethodCallException();
+ }
+
+ public function __destruct()
+ {
+ // Finish any open connections before terminating the script.
+ if ($this->handles) {
+ $this->execute();
+ }
+
+ if (isset($this->_mh)) {
+ curl_multi_close($this->_mh);
+ unset($this->_mh);
+ }
+ }
+
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ $factory = $this->factory;
+ $result = $factory($request, $options);
+ $id = (int) $result[0];
+ $promise = new ResponsePromise(
+ [$this, 'execute'],
+ function () use ($id) { return $this->cancel($id); }
+ );
+ $entry = [
+ 'request' => $request,
+ 'options' => $options,
+ 'response' => [],
+ 'handle' => $result[0],
+ 'headers' => &$result[1],
+ 'body' => $result[2],
+ 'deferred' => $promise,
+ ];
+ $this->addRequest($entry);
+
+ // Transfer outstanding requests if there are too many open handles.
+ if (count($this->handles) >= $this->maxHandles) {
+ $this->execute();
+ }
+
+ return $promise;
+ }
+
+ /**
+ * Runs until all outstanding connections have completed.
+ */
+ public function execute()
+ {
+ do {
+
+ if ($this->active &&
+ curl_multi_select($this->_mh, $this->selectTimeout) === -1
+ ) {
+ // Perform a usleep if a select returns -1.
+ // See: https://bugs.php.net/bug.php?id=61141
+ usleep(250);
+ }
+
+ // Add any delayed futures if needed.
+ if ($this->delays) {
+ $this->addDelays();
+ }
+
+ do {
+ $mrc = curl_multi_exec($this->_mh, $this->active);
+ } while ($mrc === CURLM_CALL_MULTI_PERFORM);
+
+ $this->processMessages();
+
+ // If there are delays but no transfers, then sleep for a bit.
+ if (!$this->active && $this->delays) {
+ usleep(500);
+ }
+
+ } while ($this->active || $this->handles);
+ }
+
+ private function addRequest(array &$entry)
+ {
+ $id = (int) $entry['handle'];
+ $this->handles[$id] = $entry;
+
+ // If the request is a delay, then add the reques to the curl multi
+ // pool only after the specified delay.
+ if (isset($entry['options']['delay'])) {
+ $this->delays[$id] = microtime(true) + ($entry['options']['delay'] / 1000);
+ } elseif (empty($entry['options']['future'])) {
+ curl_multi_add_handle($this->_mh, $entry['handle']);
+ } else {
+ curl_multi_add_handle($this->_mh, $entry['handle']);
+ // "lazy" futures are only sent once the pool has many requests.
+ if ($entry['options']['future'] !== 'lazy') {
+ do {
+ $mrc = curl_multi_exec($this->_mh, $this->active);
+ } while ($mrc === CURLM_CALL_MULTI_PERFORM);
+ $this->processMessages();
+ }
+ }
+ }
+
+ private function removeProcessed($id)
+ {
+ if (isset($this->handles[$id])) {
+ curl_multi_remove_handle(
+ $this->_mh,
+ $this->handles[$id]['handle']
+ );
+ curl_close($this->handles[$id]['handle']);
+ unset($this->handles[$id], $this->delays[$id]);
+ }
+ }
+
+ /**
+ * Cancels a handle from sending and removes references to it.
+ *
+ * @param int $id Handle ID to cancel and remove.
+ *
+ * @return bool True on success, false on failure.
+ */
+ private function cancel($id)
+ {
+ // Cannot cancel if it has been processed.
+ if (!isset($this->handles[$id])) {
+ return false;
+ }
+
+ $handle = $this->handles[$id]['handle'];
+ unset($this->delays[$id], $this->handles[$id]);
+ curl_multi_remove_handle($this->_mh, $handle);
+ curl_close($handle);
+
+ return true;
+ }
+
+ private function addDelays()
+ {
+ $currentTime = microtime(true);
+
+ foreach ($this->delays as $id => $delay) {
+ if ($currentTime >= $delay) {
+ unset($this->delays[$id]);
+ curl_multi_add_handle(
+ $this->_mh,
+ $this->handles[$id]['handle']
+ );
+ }
+ }
+ }
+
+ private function processMessages()
+ {
+ while ($done = curl_multi_info_read($this->_mh)) {
+ $id = (int) $done['handle'];
+
+ if (!isset($this->handles[$id])) {
+ // Probably was cancelled.
+ continue;
+ }
+
+ $entry = $this->handles[$id];
+ $entry['response']['transfer_stats'] = curl_getinfo($done['handle']);
+
+ if ($done['result'] !== CURLM_OK) {
+ $entry['response']['curl']['errno'] = $done['result'];
+ if (function_exists('curl_strerror')) {
+ $entry['response']['curl']['error'] = curl_strerror($done['result']);
+ }
+ }
+
+ $result = CurlFactory::createResponse(
+ $this,
+ $entry['request'],
+ $entry['options'],
+ $entry['response'],
+ $entry['headers'],
+ Stream::factory($entry['body'])
+ );
+
+ $this->removeProcessed($id);
+ $entry['deferred']->resolve($result);
+ }
+ }
+}
diff --git a/src/Handler/MockHandler.php b/src/Handler/MockHandler.php
new file mode 100644
index 000000000..43e58f630
--- /dev/null
+++ b/src/Handler/MockHandler.php
@@ -0,0 +1,64 @@
+result = !is_callable($resultOrQueue) && is_array($resultOrQueue)
+ ? $this->createQueueFn($resultOrQueue)
+ : $resultOrQueue;
+ }
+
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ if (isset($options['delay'])) {
+ usleep($options['delay'] * 1000);
+ }
+
+ $response = is_callable($this->result)
+ ? call_user_func($this->result, $request)
+ : $this->result;
+
+ if ($response instanceof \Exception) {
+ return new RejectedResponse($response);
+ } elseif ($response instanceof ResponsePromiseInterface) {
+ return $response;
+ }
+
+ return new FulfilledResponse($response);
+ }
+
+ private function createQueueFn(array $queue)
+ {
+ return function () use (&$queue) {
+ if (empty($queue)) {
+ throw new \RuntimeException('Mock queue is empty');
+ }
+
+ return array_shift($queue);
+ };
+ }
+}
diff --git a/src/Handler/Proxy.php b/src/Handler/Proxy.php
new file mode 100644
index 000000000..9bd76d251
--- /dev/null
+++ b/src/Handler/Proxy.php
@@ -0,0 +1,54 @@
+withoutHeader('Expect');
+ $stream = $this->createStream($request, $options, $headers);
+ return $this->createResponse($options, $headers, $stream);
+ } catch (\Exception $e) {
+ // Determine if the error was a networking error.
+ $message = $e->getMessage();
+ // This list can probably get more comprehensive.
+ if (strpos($message, 'getaddrinfo') // DNS lookup failed
+ || strpos($message, 'Connection refused')
+ ) {
+ $e = new ConnectException($e->getMessage(), $request, null, $e);
+ }
+ return new RejectedResponse(
+ RequestException::wrapException($request, $e)
+ );
+ }
+ }
+
+ private function createResponse(
+ array $options,
+ array $hdrs,
+ $stream
+ ) {
+ $parts = explode(' ', array_shift($hdrs), 3);
+ $response = [
+ 'status' => $parts[1],
+ 'reason' => isset($parts[2]) ? $parts[2] : null,
+ 'headers' => \GuzzleHttp\headers_from_lines($hdrs)
+ ];
+
+ $stream = $this->checkDecode($options, $response['headers'], $stream);
+
+ // If not streaming, then drain the response into a stream.
+ if (empty($options['stream'])) {
+ $dest = isset($options['sink'])
+ ? $options['sink']
+ : fopen('php://temp', 'r+');
+ $stream = $this->drain($stream, $dest);
+ }
+
+ return new FulfilledResponse(
+ new Response(
+ $response['status'],
+ $response['headers'],
+ $stream
+ )
+ );
+ }
+
+ private function checkDecode(array $options, array $headers, $stream)
+ {
+ // Automatically decode responses when instructed.
+ if (!empty($options['decode_content'])) {
+ foreach ($headers as $key => $value) {
+ if (strtolower($key) == 'content-encoding') {
+ if ($value == 'gzip' || $value == 'deflate') {
+ return new InflateStream(Stream::factory($stream));
+ }
+ }
+ }
+ }
+
+ return $stream;
+ }
+
+ /**
+ * Drains the stream into the "sink" client option.
+ *
+ * @param resource $stream
+ * @param string|resource|StreamableInterface $dest
+ *
+ * @return StreamableInterface
+ * @throws \RuntimeException when the sink option is invalid.
+ */
+ private function drain($stream, $dest)
+ {
+ if (is_resource($stream)) {
+ if (!is_resource($dest)) {
+ $stream = Stream::factory($stream);
+ } else {
+ stream_copy_to_stream($stream, $dest);
+ fclose($stream);
+ rewind($dest);
+ return Stream::factory($dest);
+ }
+ }
+
+ // Stream the response into the destination stream
+ $dest = is_string($dest)
+ ? new Stream(Utils::open($dest, 'r+'))
+ : Stream::factory($dest);
+
+ Utils::copyToStream($stream, $dest);
+ $dest->seek(0);
+ $stream->close();
+
+ return $dest;
+ }
+
+ /**
+ * Create a resource and check to ensure it was created successfully
+ *
+ * @param callable $callback Callable that returns stream resource
+ *
+ * @return resource
+ * @throws \RuntimeException on error
+ */
+ private function createResource(callable $callback)
+ {
+ $errors = null;
+ set_error_handler(function ($_, $msg, $file, $line) use (&$errors) {
+ $errors[] = [
+ 'message' => $msg,
+ 'file' => $file,
+ 'line' => $line
+ ];
+ return true;
+ });
+
+ $resource = $callback();
+ restore_error_handler();
+
+ if (!$resource) {
+ $message = 'Error creating resource: ';
+ foreach ($errors as $err) {
+ foreach ($err as $key => $value) {
+ $message .= "[$key] $value" . PHP_EOL;
+ }
+ }
+ throw new \RuntimeException(trim($message));
+ }
+
+ return $resource;
+ }
+
+ private function createStream(
+ RequestInterface $request,
+ array $options,
+ &$http_response_header
+ ) {
+ static $methods;
+ if (!$methods) {
+ $methods = array_flip(get_class_methods(__CLASS__));
+ }
+
+ // HTTP/1.1 streams using the PHP stream wrapper require a
+ // Connection: close header
+ if ($request->getProtocolVersion() == '1.1'
+ && !$request->hasHeader('Connection')
+ ) {
+ $request = $request->withHeader('Connection', 'close');
+ }
+
+ // Ensure SSL is verified by default
+ if (!isset($options['verify'])) {
+ $options['verify'] = true;
+ }
+
+ $params = [];
+ $context = $this->getDefaultContext($request, $options);
+ if (isset($options['stream_context'])) {
+ $context = array_replace_recursive(
+ $context,
+ $options['stream_context']
+ );
+ }
+
+ if (!empty($options)) {
+ foreach ($options as $key => $value) {
+ $method = "add_{$key}";
+ if (isset($methods[$method])) {
+ $this->{$method}($request, $context, $value, $params);
+ }
+ }
+ }
+
+ $context = $this->createResource(
+ function () use ($context, $params) {
+ return stream_context_create($context, $params);
+ }
+ );
+
+ return $this->createResource(
+ function () use ($request, &$http_response_header, $context) {
+ return fopen($request->getUri(), 'r', null, $context);
+ }
+ );
+ }
+
+ private function getDefaultContext(RequestInterface $request)
+ {
+ $headers = '';
+ foreach ($request->getHeaders() as $name => $value) {
+ foreach ($value as $val) {
+ $headers .= "$name: $val\r\n";
+ }
+ }
+
+ $context = [
+ 'http' => [
+ 'method' => $request->getMethod(),
+ 'header' => $headers,
+ 'protocol_version' => $request->getProtocolVersion(),
+ 'ignore_errors' => true,
+ 'follow_location' => 0,
+ ],
+ ];
+
+ $body = (string) $request->getBody();
+
+ if (!empty($body)) {
+ $context['http']['content'] = $body;
+ // Prevent the HTTP handler from adding a Content-Type header.
+ if (!$request->hasHeader('Content-Type')) {
+ $context['http']['header'] .= "Content-Type:\r\n";
+ }
+ }
+
+ $context['http']['header'] = rtrim($context['http']['header']);
+
+ return $context;
+ }
+
+ private function add_proxy(RequestInterface $request, &$options, $value, &$params)
+ {
+ if (!is_array($value)) {
+ $options['http']['proxy'] = $value;
+ } else {
+ $scheme = $request->getUri()->getScheme();
+ if (isset($value[$scheme])) {
+ $options['http']['proxy'] = $value[$scheme];
+ }
+ }
+ }
+
+ private function add_timeout(RequestInterface $request, &$options, $value, &$params)
+ {
+ $options['http']['timeout'] = $value;
+ }
+
+ private function add_verify(RequestInterface $request, &$options, $value, &$params)
+ {
+ if ($value === true) {
+ // PHP 5.6 or greater will find the system cert by default. When
+ // < 5.6, use the Guzzle bundled cacert.
+ if (PHP_VERSION_ID < 50600) {
+ $options['ssl']['cafile'] = \GuzzleHttp\default_ca_bundle();
+ }
+ } elseif (is_string($value)) {
+ $options['ssl']['cafile'] = $value;
+ if (!file_exists($value)) {
+ throw new \RuntimeException("SSL CA bundle not found: $value");
+ }
+ } elseif ($value === false) {
+ $options['ssl']['verify_peer'] = false;
+ return;
+ } else {
+ throw new \InvalidArgumentException('Invalid verify request option');
+ }
+
+ $options['ssl']['verify_peer'] = true;
+ $options['ssl']['allow_self_signed'] = true;
+ }
+
+ private function add_cert(RequestInterface $request, &$options, $value, &$params)
+ {
+ if (is_array($value)) {
+ $options['ssl']['passphrase'] = $value[1];
+ $value = $value[0];
+ }
+
+ if (!file_exists($value)) {
+ throw new \RuntimeException("SSL certificate not found: {$value}");
+ }
+
+ $options['ssl']['local_cert'] = $value;
+ }
+
+ private function add_progress(RequestInterface $request, &$options, $value, &$params)
+ {
+ $this->addNotification(
+ $params,
+ function ($code, $_, $_, $_, $transferred, $total) use ($value) {
+ if ($code == STREAM_NOTIFY_PROGRESS) {
+ $value($total, $transferred, null, null);
+ }
+ }
+ );
+ }
+
+ private function add_debug(RequestInterface $request, &$options, $value, &$params)
+ {
+ if ($value === false) {
+ return;
+ }
+
+ static $map = [
+ STREAM_NOTIFY_CONNECT => 'CONNECT',
+ STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
+ STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT',
+ STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS',
+ STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS',
+ STREAM_NOTIFY_REDIRECTED => 'REDIRECTED',
+ STREAM_NOTIFY_PROGRESS => 'PROGRESS',
+ STREAM_NOTIFY_FAILURE => 'FAILURE',
+ STREAM_NOTIFY_COMPLETED => 'COMPLETED',
+ STREAM_NOTIFY_RESOLVE => 'RESOLVE',
+ ];
+ static $args = ['severity', 'message', 'message_code',
+ 'bytes_transferred', 'bytes_max'];
+
+ $value = \GuzzleHttp\get_debug_resource($value);
+ $ident = $request->getMethod() . ' ' . $request->getUri();
+ $this->addNotification(
+ $params,
+ function () use ($ident, $value, $map, $args) {
+ $passed = func_get_args();
+ $code = array_shift($passed);
+ fprintf($value, '<%s> [%s] ', $ident, $map[$code]);
+ foreach (array_filter($passed) as $i => $v) {
+ fwrite($value, $args[$i] . ': "' . $v . '" ');
+ }
+ fwrite($value, "\n");
+ }
+ );
+ }
+
+ private function addNotification(array &$params, callable $notify)
+ {
+ // Wrap the existing function if needed.
+ if (!isset($params['notification'])) {
+ $params['notification'] = $notify;
+ } else {
+ $params['notification'] = $this->callArray([
+ $params['notification'],
+ $notify
+ ]);
+ }
+ }
+
+ private function callArray(array $functions)
+ {
+ return function () use ($functions) {
+ $args = func_get_args();
+ foreach ($functions as $fn) {
+ call_user_func_array($fn, $args);
+ }
+ };
+ }
+}
diff --git a/src/HandlerBuilder.php b/src/HandlerBuilder.php
new file mode 100644
index 000000000..8ed826d94
--- /dev/null
+++ b/src/HandlerBuilder.php
@@ -0,0 +1,88 @@
+handler = $handler;
+ $this->stack[-1] = $this->stack[1] = [];
+ $this->stack[0] = $middleware;
+ }
+
+ public function setHandler(callable $handler)
+ {
+ $this->handler = $handler;
+ return $this;
+ }
+
+ public function hasHandler()
+ {
+ return (bool) $this->handler;
+ }
+
+ public function prepend(callable $middleware, $sticky = false)
+ {
+ array_unshift($this->stack[-1 * (bool) $sticky], $middleware);
+ return $this;
+ }
+
+ public function append(callable $middleware, $sticky = false)
+ {
+ $this->stack[(bool) $sticky][] = $middleware;
+ return $this;
+ }
+
+ public function remove(callable $remove)
+ {
+ for ($i = -1; $i < 2; $i++) {
+ $this->stack[$i] = array_filter(
+ $this->stack[$i],
+ function ($f) use ($remove) {
+ return $f !== $remove;
+ }
+ );
+ }
+
+ return $this;
+ }
+
+ public function resolve()
+ {
+ if (!($prev = $this->handler)) {
+ throw new \LogicException('No handler has been specified');
+ }
+
+ foreach ($this->stack as $stack) {
+ if ($stack) {
+ /** @var callable $fn */
+ foreach (array_reverse($stack) as $fn) {
+ $prev = $fn($prev);
+ }
+ }
+ }
+
+ return $prev;
+ }
+}
diff --git a/src/HasDataTrait.php b/src/HasDataTrait.php
deleted file mode 100644
index 020dfc9ab..000000000
--- a/src/HasDataTrait.php
+++ /dev/null
@@ -1,75 +0,0 @@
-data);
- }
-
- public function offsetGet($offset)
- {
- return isset($this->data[$offset]) ? $this->data[$offset] : null;
- }
-
- public function offsetSet($offset, $value)
- {
- $this->data[$offset] = $value;
- }
-
- public function offsetExists($offset)
- {
- return isset($this->data[$offset]);
- }
-
- public function offsetUnset($offset)
- {
- unset($this->data[$offset]);
- }
-
- public function toArray()
- {
- return $this->data;
- }
-
- public function count()
- {
- return count($this->data);
- }
-
- /**
- * Get a value from the collection using a path syntax to retrieve nested
- * data.
- *
- * @param string $path Path to traverse and retrieve a value from
- *
- * @return mixed|null
- */
- public function getPath($path)
- {
- return Utils::getPath($this->data, $path);
- }
-
- /**
- * Set a value into a nested array key. Keys will be created as needed to
- * set the value.
- *
- * @param string $path Path to set
- * @param mixed $value Value to set at the key
- *
- * @throws \RuntimeException when trying to setPath using a nested path
- * that travels through a scalar value
- */
- public function setPath($path, $value)
- {
- Utils::setPath($this->data, $path, $value);
- }
-}
diff --git a/src/Message/AbstractMessage.php b/src/Message/AbstractMessage.php
deleted file mode 100644
index 0c675758d..000000000
--- a/src/Message/AbstractMessage.php
+++ /dev/null
@@ -1,253 +0,0 @@
-getBody();
- }
-
- public function getProtocolVersion()
- {
- return $this->protocolVersion;
- }
-
- public function getBody()
- {
- return $this->body;
- }
-
- public function setBody(StreamInterface $body = null)
- {
- if ($body === null) {
- // Setting a null body will remove the body of the request
- $this->removeHeader('Content-Length');
- $this->removeHeader('Transfer-Encoding');
- }
-
- $this->body = $body;
- }
-
- public function addHeader($header, $value)
- {
- if (is_array($value)) {
- $current = array_merge($this->getHeaderAsArray($header), $value);
- } else {
- $current = $this->getHeaderAsArray($header);
- $current[] = (string) $value;
- }
-
- $this->setHeader($header, $current);
- }
-
- public function addHeaders(array $headers)
- {
- foreach ($headers as $name => $header) {
- $this->addHeader($name, $header);
- }
- }
-
- public function getHeader($header)
- {
- $name = strtolower($header);
- return isset($this->headers[$name])
- ? implode(', ', $this->headers[$name])
- : '';
- }
-
- public function getHeaderAsArray($header)
- {
- $name = strtolower($header);
- return isset($this->headers[$name]) ? $this->headers[$name] : [];
- }
-
- public function getHeaders()
- {
- $headers = [];
- foreach ($this->headers as $name => $values) {
- $headers[$this->headerNames[$name]] = $values;
- }
-
- return $headers;
- }
-
- public function setHeader($header, $value)
- {
- $header = trim($header);
- $name = strtolower($header);
- $this->headerNames[$name] = $header;
-
- if (is_array($value)) {
- foreach ($value as &$v) {
- $v = trim($v);
- }
- $this->headers[$name] = $value;
- } else {
- $this->headers[$name] = [trim($value)];
- }
- }
-
- public function setHeaders(array $headers)
- {
- $this->headers = $this->headerNames = [];
- foreach ($headers as $key => $value) {
- $this->setHeader($key, $value);
- }
- }
-
- public function hasHeader($header)
- {
- return isset($this->headers[strtolower($header)]);
- }
-
- public function removeHeader($header)
- {
- $name = strtolower($header);
- unset($this->headers[$name], $this->headerNames[$name]);
- }
-
- /**
- * Parse an array of header values containing ";" separated data into an
- * array of associative arrays representing the header key value pair
- * data of the header. When a parameter does not contain a value, but just
- * contains a key, this function will inject a key with a '' string value.
- *
- * @param MessageInterface $message That contains the header
- * @param string $header Header to retrieve from the message
- *
- * @return array Returns the parsed header values.
- */
- public static function parseHeader(MessageInterface $message, $header)
- {
- static $trimmed = "\"' \n\t\r";
- $params = $matches = [];
-
- foreach (self::normalizeHeader($message, $header) as $val) {
- $part = [];
- foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
- if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
- $m = $matches[0];
- if (isset($m[1])) {
- $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
- } else {
- $part[] = trim($m[0], $trimmed);
- }
- }
- }
- if ($part) {
- $params[] = $part;
- }
- }
-
- return $params;
- }
-
- /**
- * Converts an array of header values that may contain comma separated
- * headers into an array of headers with no comma separated values.
- *
- * @param MessageInterface $message That contains the header
- * @param string $header Header to retrieve from the message
- *
- * @return array Returns the normalized header field values.
- */
- public static function normalizeHeader(MessageInterface $message, $header)
- {
- $h = $message->getHeaderAsArray($header);
- for ($i = 0, $total = count($h); $i < $total; $i++) {
- if (strpos($h[$i], ',') === false) {
- continue;
- }
- foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $h[$i]) as $v) {
- $h[] = trim($v);
- }
- unset($h[$i]);
- }
-
- return $h;
- }
-
- /**
- * Gets the start-line and headers of a message as a string
- *
- * @param MessageInterface $message
- *
- * @return string
- */
- public static function getStartLineAndHeaders(MessageInterface $message)
- {
- return static::getStartLine($message)
- . self::getHeadersAsString($message);
- }
-
- /**
- * Gets the headers of a message as a string
- *
- * @param MessageInterface $message
- *
- * @return string
- */
- public static function getHeadersAsString(MessageInterface $message)
- {
- $result = '';
- foreach ($message->getHeaders() as $name => $values) {
- $result .= "\r\n{$name}: " . implode(', ', $values);
- }
-
- return $result;
- }
-
- /**
- * Gets the start line of a message
- *
- * @param MessageInterface $message
- *
- * @return string
- * @throws \InvalidArgumentException
- */
- public static function getStartLine(MessageInterface $message)
- {
- if ($message instanceof RequestInterface) {
- return trim($message->getMethod() . ' '
- . $message->getResource())
- . ' HTTP/' . $message->getProtocolVersion();
- } elseif ($message instanceof ResponseInterface) {
- return 'HTTP/' . $message->getProtocolVersion() . ' '
- . $message->getStatusCode() . ' '
- . $message->getReasonPhrase();
- } else {
- throw new \InvalidArgumentException('Unknown message type');
- }
- }
-
- /**
- * Accepts and modifies the options provided to the message in the
- * constructor.
- *
- * Can be overridden in subclasses as necessary.
- *
- * @param array $options Options array passed by reference.
- */
- protected function handleOptions(array &$options)
- {
- if (isset($options['protocol_version'])) {
- $this->protocolVersion = $options['protocol_version'];
- }
- }
-}
diff --git a/src/Message/AppliesHeadersInterface.php b/src/Message/AppliesHeadersInterface.php
deleted file mode 100644
index ca42f20f3..000000000
--- a/src/Message/AppliesHeadersInterface.php
+++ /dev/null
@@ -1,24 +0,0 @@
-then($onFulfilled, $onRejected, $onProgress),
- [$future, 'wait'],
- [$future, 'cancel']
- );
- }
-
- public function getStatusCode()
- {
- return $this->_value->getStatusCode();
- }
-
- public function setStatusCode($code)
- {
- $this->_value->setStatusCode($code);
- }
-
- public function getReasonPhrase()
- {
- return $this->_value->getReasonPhrase();
- }
-
- public function setReasonPhrase($phrase)
- {
- $this->_value->setReasonPhrase($phrase);
- }
-
- public function getEffectiveUrl()
- {
- return $this->_value->getEffectiveUrl();
- }
-
- public function setEffectiveUrl($url)
- {
- $this->_value->setEffectiveUrl($url);
- }
-
- public function json(array $config = [])
- {
- return $this->_value->json($config);
- }
-
- public function xml(array $config = [])
- {
- return $this->_value->xml($config);
- }
-
- public function __toString()
- {
- try {
- return $this->_value->__toString();
- } catch (\Exception $e) {
- trigger_error($e->getMessage(), E_USER_WARNING);
- return '';
- }
- }
-
- public function getProtocolVersion()
- {
- return $this->_value->getProtocolVersion();
- }
-
- public function setBody(StreamInterface $body = null)
- {
- $this->_value->setBody($body);
- }
-
- public function getBody()
- {
- return $this->_value->getBody();
- }
-
- public function getHeaders()
- {
- return $this->_value->getHeaders();
- }
-
- public function getHeader($header)
- {
- return $this->_value->getHeader($header);
- }
-
- public function getHeaderAsArray($header)
- {
- return $this->_value->getHeaderAsArray($header);
- }
-
- public function hasHeader($header)
- {
- return $this->_value->hasHeader($header);
- }
-
- public function removeHeader($header)
- {
- $this->_value->removeHeader($header);
- }
-
- public function addHeader($header, $value)
- {
- $this->_value->addHeader($header, $value);
- }
-
- public function addHeaders(array $headers)
- {
- $this->_value->addHeaders($headers);
- }
-
- public function setHeader($header, $value)
- {
- $this->_value->setHeader($header, $value);
- }
-
- public function setHeaders(array $headers)
- {
- $this->_value->setHeaders($headers);
- }
-}
diff --git a/src/Message/MessageFactory.php b/src/Message/MessageFactory.php
deleted file mode 100644
index 85984e2dd..000000000
--- a/src/Message/MessageFactory.php
+++ /dev/null
@@ -1,364 +0,0 @@
- 1, 'timeout' => 1, 'verify' => 1, 'ssl_key' => 1,
- 'cert' => 1, 'proxy' => 1, 'debug' => 1, 'save_to' => 1, 'stream' => 1,
- 'expect' => 1, 'future' => 1
- ];
-
- /** @var array Default allow_redirects request option settings */
- private static $defaultRedirect = [
- 'max' => 5,
- 'strict' => false,
- 'referer' => false,
- 'protocols' => ['http', 'https']
- ];
-
- /**
- * @param array $customOptions Associative array of custom request option
- * names mapping to functions used to apply
- * the option. The function accepts the request
- * and the option value to apply.
- */
- public function __construct(array $customOptions = [])
- {
- $this->errorPlugin = new HttpError();
- $this->redirectPlugin = new Redirect();
- $this->customOptions = $customOptions;
- }
-
- public function createResponse(
- $statusCode,
- array $headers = [],
- $body = null,
- array $options = []
- ) {
- if (null !== $body) {
- $body = Stream::factory($body);
- }
-
- return new Response($statusCode, $headers, $body, $options);
- }
-
- public function createRequest($method, $url, array $options = [])
- {
- // Handle the request protocol version option that needs to be
- // specified in the request constructor.
- if (isset($options['version'])) {
- $options['config']['protocol_version'] = $options['version'];
- unset($options['version']);
- }
-
- $request = new Request($method, $url, [], null,
- isset($options['config']) ? $options['config'] : []);
-
- unset($options['config']);
-
- // Use a POST body by default
- if ($method == 'POST'
- && !isset($options['body'])
- && !isset($options['json'])
- ) {
- $options['body'] = [];
- }
-
- if ($options) {
- $this->applyOptions($request, $options);
- }
-
- return $request;
- }
-
- /**
- * Create a request or response object from an HTTP message string
- *
- * @param string $message Message to parse
- *
- * @return RequestInterface|ResponseInterface
- * @throws \InvalidArgumentException if unable to parse a message
- */
- public function fromMessage($message)
- {
- static $parser;
- if (!$parser) {
- $parser = new MessageParser();
- }
-
- // Parse a response
- if (strtoupper(substr($message, 0, 4)) == 'HTTP') {
- $data = $parser->parseResponse($message);
- return $this->createResponse(
- $data['code'],
- $data['headers'],
- $data['body'] === '' ? null : $data['body'],
- $data
- );
- }
-
- // Parse a request
- if (!($data = ($parser->parseRequest($message)))) {
- throw new \InvalidArgumentException('Unable to parse request');
- }
-
- return $this->createRequest(
- $data['method'],
- Url::buildUrl($data['request_url']),
- [
- 'headers' => $data['headers'],
- 'body' => $data['body'] === '' ? null : $data['body'],
- 'config' => [
- 'protocol_version' => $data['protocol_version']
- ]
- ]
- );
- }
-
- /**
- * Apply POST fields and files to a request to attempt to give an accurate
- * representation.
- *
- * @param RequestInterface $request Request to update
- * @param array $body Body to apply
- */
- protected function addPostData(RequestInterface $request, array $body)
- {
- static $fields = ['string' => true, 'array' => true, 'NULL' => true,
- 'boolean' => true, 'double' => true, 'integer' => true];
-
- $post = new PostBody();
- foreach ($body as $key => $value) {
- if (isset($fields[gettype($value)])) {
- $post->setField($key, $value);
- } elseif ($value instanceof PostFileInterface) {
- $post->addFile($value);
- } else {
- $post->addFile(new PostFile($key, $value));
- }
- }
-
- if ($request->getHeader('Content-Type') == 'multipart/form-data') {
- $post->forceMultipartUpload(true);
- }
-
- $request->setBody($post);
- }
-
- protected function applyOptions(
- RequestInterface $request,
- array $options = []
- ) {
- $config = $request->getConfig();
- $emitter = $request->getEmitter();
-
- foreach ($options as $key => $value) {
-
- if (isset(self::$configMap[$key])) {
- $config[$key] = $value;
- continue;
- }
-
- switch ($key) {
-
- case 'allow_redirects':
-
- if ($value === false) {
- continue;
- }
-
- if ($value === true) {
- $value = self::$defaultRedirect;
- } elseif (!is_array($value)) {
- throw new Iae('allow_redirects must be true, false, or array');
- } else {
- // Merge the default settings with the provided settings
- $value += self::$defaultRedirect;
- }
-
- $config['redirect'] = $value;
- $emitter->attach($this->redirectPlugin);
- break;
-
- case 'decode_content':
-
- if ($value === false) {
- continue;
- }
-
- $config['decode_content'] = true;
- if ($value !== true) {
- $request->setHeader('Accept-Encoding', $value);
- }
- break;
-
- case 'headers':
-
- if (!is_array($value)) {
- throw new Iae('header value must be an array');
- }
- foreach ($value as $k => $v) {
- $request->setHeader($k, $v);
- }
- break;
-
- case 'exceptions':
-
- if ($value === true) {
- $emitter->attach($this->errorPlugin);
- }
- break;
-
- case 'body':
-
- if (is_array($value)) {
- $this->addPostData($request, $value);
- } elseif ($value !== null) {
- $request->setBody(Stream::factory($value));
- }
- break;
-
- case 'auth':
-
- if (!$value) {
- continue;
- }
-
- if (is_array($value)) {
- $type = isset($value[2]) ? strtolower($value[2]) : 'basic';
- } else {
- $type = strtolower($value);
- }
-
- $config['auth'] = $value;
-
- if ($type == 'basic') {
- $request->setHeader(
- 'Authorization',
- 'Basic ' . base64_encode("$value[0]:$value[1]")
- );
- } elseif ($type == 'digest') {
- // @todo: Do not rely on curl
- $config->setPath('curl/' . CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
- $config->setPath('curl/' . CURLOPT_USERPWD, "$value[0]:$value[1]");
- }
- break;
-
- case 'query':
-
- if ($value instanceof Query) {
- $original = $request->getQuery();
- // Do not overwrite existing query string variables by
- // overwriting the object with the query string data passed
- // in the URL
- $value->overwriteWith($original->toArray());
- $request->setQuery($value);
- } elseif (is_array($value)) {
- // Do not overwrite existing query string variables
- $query = $request->getQuery();
- foreach ($value as $k => $v) {
- if (!isset($query[$k])) {
- $query[$k] = $v;
- }
- }
- } else {
- throw new Iae('query must be an array or Query object');
- }
- break;
-
- case 'cookies':
-
- if ($value === true) {
- static $cookie = null;
- if (!$cookie) {
- $cookie = new Cookie();
- }
- $emitter->attach($cookie);
- } elseif (is_array($value)) {
- $emitter->attach(
- new Cookie(CookieJar::fromArray($value, $request->getHost()))
- );
- } elseif ($value instanceof CookieJarInterface) {
- $emitter->attach(new Cookie($value));
- } elseif ($value !== false) {
- throw new Iae('cookies must be an array, true, or CookieJarInterface');
- }
- break;
-
- case 'events':
-
- if (!is_array($value)) {
- throw new Iae('events must be an array');
- }
-
- $this->attachListeners($request,
- $this->prepareListeners(
- $value,
- ['before', 'complete', 'error', 'progress', 'end']
- )
- );
- break;
-
- case 'subscribers':
-
- if (!is_array($value)) {
- throw new Iae('subscribers must be an array');
- }
-
- foreach ($value as $subscribers) {
- $emitter->attach($subscribers);
- }
- break;
-
- case 'json':
-
- $request->setBody(Stream::factory(json_encode($value)));
- if (!$request->hasHeader('Content-Type')) {
- $request->setHeader('Content-Type', 'application/json');
- }
- break;
-
- default:
-
- // Check for custom handler functions.
- if (isset($this->customOptions[$key])) {
- $fn = $this->customOptions[$key];
- $fn($request, $value);
- continue;
- }
-
- throw new Iae("No method can handle the {$key} config key");
- }
- }
- }
-}
diff --git a/src/Message/MessageFactoryInterface.php b/src/Message/MessageFactoryInterface.php
deleted file mode 100644
index 57c43e5cd..000000000
--- a/src/Message/MessageFactoryInterface.php
+++ /dev/null
@@ -1,71 +0,0 @@
-getHeaders() as $name => $values) {
- * echo $name . ": " . implode(", ", $values);
- * }
- *
- * @return array Returns an associative array of the message's headers.
- */
- public function getHeaders();
-
- /**
- * Retrieve a header by the given case-insensitive name.
- *
- * @param string $header Case-insensitive header name.
- *
- * @return string
- */
- public function getHeader($header);
-
- /**
- * Retrieves a header by the given case-insensitive name as an array of strings.
- *
- * @param string $header Case-insensitive header name.
- *
- * @return string[]
- */
- public function getHeaderAsArray($header);
-
- /**
- * Checks if a header exists by the given case-insensitive name.
- *
- * @param string $header Case-insensitive header name.
- *
- * @return bool Returns true if any header names match the given header
- * name using a case-insensitive string comparison. Returns false if
- * no matching header name is found in the message.
- */
- public function hasHeader($header);
-
- /**
- * Remove a specific header by case-insensitive name.
- *
- * @param string $header Case-insensitive header name.
- */
- public function removeHeader($header);
-
- /**
- * Appends a header value to any existing values associated with the
- * given header name.
- *
- * @param string $header Header name to add
- * @param string $value Value of the header
- */
- public function addHeader($header, $value);
-
- /**
- * Merges in an associative array of headers.
- *
- * Each array key MUST be a string representing the case-insensitive name
- * of a header. Each value MUST be either a string or an array of strings.
- * For each value, the value is appended to any existing header of the same
- * name, or, if a header does not already exist by the given name, then the
- * header is added.
- *
- * @param array $headers Associative array of headers to add to the message
- */
- public function addHeaders(array $headers);
-
- /**
- * Sets a header, replacing any existing values of any headers with the
- * same case-insensitive name.
- *
- * The header values MUST be a string or an array of strings.
- *
- * @param string $header Header name
- * @param string|array $value Header value(s)
- */
- public function setHeader($header, $value);
-
- /**
- * Sets headers, replacing any headers that have already been set on the
- * message.
- *
- * The array keys MUST be a string. The array values must be either a
- * string or an array of strings.
- *
- * @param array $headers Headers to set.
- */
- public function setHeaders(array $headers);
-}
diff --git a/src/Message/Request.php b/src/Message/Request.php
deleted file mode 100644
index 4dbe32e64..000000000
--- a/src/Message/Request.php
+++ /dev/null
@@ -1,195 +0,0 @@
-setUrl($url);
- $this->method = strtoupper($method);
- $this->handleOptions($options);
- $this->transferOptions = new Collection($options);
- $this->addPrepareEvent();
-
- if ($body !== null) {
- $this->setBody($body);
- }
-
- if ($headers) {
- foreach ($headers as $key => $value) {
- $this->setHeader($key, $value);
- }
- }
- }
-
- public function __clone()
- {
- if ($this->emitter) {
- $this->emitter = clone $this->emitter;
- }
- $this->transferOptions = clone $this->transferOptions;
- $this->url = clone $this->url;
- }
-
- public function setUrl($url)
- {
- $this->url = $url instanceof Url ? $url : Url::fromString($url);
- $this->updateHostHeaderFromUrl();
- }
-
- public function getUrl()
- {
- return (string) $this->url;
- }
-
- public function setQuery($query)
- {
- $this->url->setQuery($query);
- }
-
- public function getQuery()
- {
- return $this->url->getQuery();
- }
-
- public function setMethod($method)
- {
- $this->method = strtoupper($method);
- }
-
- public function getMethod()
- {
- return $this->method;
- }
-
- public function getScheme()
- {
- return $this->url->getScheme();
- }
-
- public function setScheme($scheme)
- {
- $this->url->setScheme($scheme);
- }
-
- public function getPort()
- {
- return $this->url->getPort();
- }
-
- public function setPort($port)
- {
- $this->url->setPort($port);
- $this->updateHostHeaderFromUrl();
- }
-
- public function getHost()
- {
- return $this->url->getHost();
- }
-
- public function setHost($host)
- {
- $this->url->setHost($host);
- $this->updateHostHeaderFromUrl();
- }
-
- public function getPath()
- {
- return '/' . ltrim($this->url->getPath(), '/');
- }
-
- public function setPath($path)
- {
- $this->url->setPath($path);
- }
-
- public function getResource()
- {
- $resource = $this->getPath();
- if ($query = (string) $this->url->getQuery()) {
- $resource .= '?' . $query;
- }
-
- return $resource;
- }
-
- public function getConfig()
- {
- return $this->transferOptions;
- }
-
- protected function handleOptions(array &$options)
- {
- parent::handleOptions($options);
- // Use a custom emitter if one is specified, and remove it from
- // options that are exposed through getConfig()
- if (isset($options['emitter'])) {
- $this->emitter = $options['emitter'];
- unset($options['emitter']);
- }
- }
-
- /**
- * Adds a subscriber that ensures a request's body is prepared before
- * sending.
- */
- private function addPrepareEvent()
- {
- static $subscriber;
- if (!$subscriber) {
- $subscriber = new Prepare();
- }
-
- $this->getEmitter()->attach($subscriber);
- }
-
- private function updateHostHeaderFromUrl()
- {
- $port = $this->url->getPort();
- $scheme = $this->url->getScheme();
- if ($host = $this->url->getHost()) {
- if (($port == 80 && $scheme == 'http') ||
- ($port == 443 && $scheme == 'https')
- ) {
- $this->setHeader('Host', $host);
- } else {
- $this->setHeader('Host', "{$host}:{$port}");
- }
- }
- }
-}
diff --git a/src/Message/RequestInterface.php b/src/Message/RequestInterface.php
deleted file mode 100644
index f6a69d1e1..000000000
--- a/src/Message/RequestInterface.php
+++ /dev/null
@@ -1,136 +0,0 @@
- 'Continue',
- 101 => 'Switching Protocols',
- 102 => 'Processing',
- 200 => 'OK',
- 201 => 'Created',
- 202 => 'Accepted',
- 203 => 'Non-Authoritative Information',
- 204 => 'No Content',
- 205 => 'Reset Content',
- 206 => 'Partial Content',
- 207 => 'Multi-Status',
- 208 => 'Already Reported',
- 226 => 'IM Used',
- 300 => 'Multiple Choices',
- 301 => 'Moved Permanently',
- 302 => 'Found',
- 303 => 'See Other',
- 304 => 'Not Modified',
- 305 => 'Use Proxy',
- 307 => 'Temporary Redirect',
- 308 => 'Permanent Redirect',
- 400 => 'Bad Request',
- 401 => 'Unauthorized',
- 402 => 'Payment Required',
- 403 => 'Forbidden',
- 404 => 'Not Found',
- 405 => 'Method Not Allowed',
- 406 => 'Not Acceptable',
- 407 => 'Proxy Authentication Required',
- 408 => 'Request Timeout',
- 409 => 'Conflict',
- 410 => 'Gone',
- 411 => 'Length Required',
- 412 => 'Precondition Failed',
- 413 => 'Request Entity Too Large',
- 414 => 'Request-URI Too Long',
- 415 => 'Unsupported Media Type',
- 416 => 'Requested Range Not Satisfiable',
- 417 => 'Expectation Failed',
- 422 => 'Unprocessable Entity',
- 423 => 'Locked',
- 424 => 'Failed Dependency',
- 425 => 'Reserved for WebDAV advanced collections expired proposal',
- 426 => 'Upgrade required',
- 428 => 'Precondition Required',
- 429 => 'Too Many Requests',
- 431 => 'Request Header Fields Too Large',
- 500 => 'Internal Server Error',
- 501 => 'Not Implemented',
- 502 => 'Bad Gateway',
- 503 => 'Service Unavailable',
- 504 => 'Gateway Timeout',
- 505 => 'HTTP Version Not Supported',
- 506 => 'Variant Also Negotiates (Experimental)',
- 507 => 'Insufficient Storage',
- 508 => 'Loop Detected',
- 510 => 'Not Extended',
- 511 => 'Network Authentication Required',
- ];
-
- /** @var string The reason phrase of the response (human readable code) */
- private $reasonPhrase;
-
- /** @var string The status code of the response */
- private $statusCode;
-
- /** @var string The effective URL that returned this response */
- private $effectiveUrl;
-
- /**
- * @param int|string $statusCode The response status code (e.g. 200)
- * @param array $headers The response headers
- * @param StreamInterface $body The body of the response
- * @param array $options Response message options
- * - reason_phrase: Set a custom reason phrase
- * - protocol_version: Set a custom protocol version
- */
- public function __construct(
- $statusCode,
- array $headers = [],
- StreamInterface $body = null,
- array $options = []
- ) {
- $this->statusCode = (int) $statusCode;
- $this->handleOptions($options);
-
- // Assume a reason phrase if one was not applied as an option
- if (!$this->reasonPhrase &&
- isset(self::$statusTexts[$this->statusCode])
- ) {
- $this->reasonPhrase = self::$statusTexts[$this->statusCode];
- }
-
- if ($headers) {
- $this->setHeaders($headers);
- }
-
- if ($body) {
- $this->setBody($body);
- }
- }
-
- public function getStatusCode()
- {
- return $this->statusCode;
- }
-
- public function setStatusCode($code)
- {
- return $this->statusCode = (int) $code;
- }
-
- public function getReasonPhrase()
- {
- return $this->reasonPhrase;
- }
-
- public function setReasonPhrase($phrase)
- {
- return $this->reasonPhrase = $phrase;
- }
-
- public function json(array $config = [])
- {
- try {
- return Utils::jsonDecode(
- (string) $this->getBody(),
- isset($config['object']) ? !$config['object'] : true,
- 512,
- isset($config['big_int_strings']) ? JSON_BIGINT_AS_STRING : 0
- );
- } catch (\InvalidArgumentException $e) {
- throw new ParseException(
- $e->getMessage(),
- $this
- );
- }
- }
-
- public function xml(array $config = [])
- {
- $disableEntities = libxml_disable_entity_loader(true);
- $internalErrors = libxml_use_internal_errors(true);
-
- try {
- // Allow XML to be retrieved even if there is no response body
- $xml = new \SimpleXMLElement(
- (string) $this->getBody() ?: '',
- isset($config['libxml_options']) ? $config['libxml_options'] : LIBXML_NONET,
- false,
- isset($config['ns']) ? $config['ns'] : '',
- isset($config['ns_is_prefix']) ? $config['ns_is_prefix'] : false
- );
- libxml_disable_entity_loader($disableEntities);
- libxml_use_internal_errors($internalErrors);
- } catch (\Exception $e) {
- libxml_disable_entity_loader($disableEntities);
- libxml_use_internal_errors($internalErrors);
- throw new XmlParseException(
- 'Unable to parse response body into XML: ' . $e->getMessage(),
- $this,
- $e,
- (libxml_get_last_error()) ?: null
- );
- }
-
- return $xml;
- }
-
- public function getEffectiveUrl()
- {
- return $this->effectiveUrl;
- }
-
- public function setEffectiveUrl($url)
- {
- $this->effectiveUrl = $url;
- }
-
- /**
- * Accepts and modifies the options provided to the response in the
- * constructor.
- *
- * @param array $options Options array passed by reference.
- */
- protected function handleOptions(array &$options = [])
- {
- parent::handleOptions($options);
- if (isset($options['reason_phrase'])) {
- $this->reasonPhrase = $options['reason_phrase'];
- }
- }
-}
diff --git a/src/Message/ResponseInterface.php b/src/Message/ResponseInterface.php
deleted file mode 100644
index c0ae9be93..000000000
--- a/src/Message/ResponseInterface.php
+++ /dev/null
@@ -1,111 +0,0 @@
-withCookieHeader($request);
+ return $handler($request, $options)
+ ->then(function ($response) use ($cookieJar, $request) {
+ $cookieJar->extractCookies($request, $response);
+ return $response;
+ }
+ );
+ };
+ };
+ }
+
+ /**
+ * Middleware that throws exceptions for 4xx or 5xx responses.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function httpError()
+ {
+ return function (callable $handler) {
+ return $fn = function ($request, array $options) use ($handler) {
+ return $handler($request, $options)->then(
+ function (ResponseInterface $response) use ($request, $handler) {
+ $code = $response->getStatusCode();
+ if ($code < 400) {
+ return $response;
+ }
+ throw $code > 499
+ ? new ServerException("Server error: $code", $request, $response)
+ : new ClientException("Client error: $code", $request, $response);
+ }
+ );
+ };
+ };
+ }
+
+ /**
+ * Middleware that pushes history data to an ArrayAccess container.
+ *
+ * @param array $container Container to hold the history (by reference).
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function history(array &$container)
+ {
+ return function (callable $handler) use (&$container) {
+ return function ($request, array $options) use ($handler, &$container) {
+ $response = $handler($request, $options);
+ $response->then(function ($value) use ($request, &$container, $options) {
+ $container[] = [
+ 'request' => $request,
+ 'response' => $value,
+ 'options' => $options
+ ];
+ });
+ return $response;
+ };
+ };
+ }
+
+ /**
+ * Middleware that invokes a callback before and after sending a request.
+ *
+ * The provided listener cannot modify or alter the response. It simply
+ * "taps" into the chain to be notified before returning the promise. The
+ * before listener accepts a request and options array, and the after
+ * listener accepts a request, options array, and response promise.
+ *
+ * @param callable $before Function to invoke before forwarding the request.
+ * @param callable $after Function invoked after forwarding.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function tap(callable $before = null, callable $after = null)
+ {
+ return function (callable $handler) use ($before, $after) {
+ return function ($request, array $options) use ($handler, $before, $after) {
+ if ($before) {
+ $before($request, $options);
+ }
+ $response = $handler($request, $options);
+ if ($after) {
+ $after($request, $options, $response);
+ }
+ return $response;
+ };
+ };
+ }
+
+ /**
+ * Middleware that handles request redirects.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function redirect()
+ {
+ return function (callable $handler) {
+ return new RedirectMiddleware($handler);
+ };
+ }
+
+ /**
+ * Middleware that retries requests based on the boolean result of
+ * invoking the provided "decider" function.
+ *
+ * If no delay function is provided, a simple implementation of exponential
+ * backoff will be utilized.
+ *
+ * @param callable $decider Function that accepts the number of retries,
+ * a request, [response], and [exception] and
+ * returns true if the request is to be retried.
+ * @param callable $delay Function that accepts the number of retries and
+ * returns the number of milliseconds to delay.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function retry(callable $decider, callable $delay = null)
+ {
+ /** @var callable $delay */
+ $delay = $delay ?: [__CLASS__, 'exponentialBackoffDelay'];
+ return function (callable $handler) use ($decider, $delay) {
+ return $f = function ($request, array $options) use ($handler, $decider, $delay, &$f) {
+ if (!isset($options['retries'])) {
+ $options['retries'] = 0;
+ }
+ // Then function used for both onFulfilled and onRejected.
+ $g = function ($value) use ($handler, $request, $options, $decider, $delay, &$f) {
+ if ($value instanceof \Exception) {
+ $response = null;
+ $error = $value;
+ } else {
+ $response = $value;
+ $error = null;
+ }
+ if (!$decider($options['retries'], $request, $response, $error)) {
+ return $response;
+ }
+ $options['delay'] = $delay(++$options['retries']);
+ return $f($request, $options);
+ };
+ return $handler($request, $options)->then($g, $g);
+ };
+ };
+ }
+
+ /**
+ * Default exponential backoff delay function.
+ *
+ * @param $retries
+ *
+ * @return int
+ */
+ public static function exponentialBackoffDelay($retries)
+ {
+ return (int) pow(2, $retries - 1);
+ }
+}
diff --git a/src/MultipartPostBody.php b/src/MultipartPostBody.php
new file mode 100644
index 000000000..8cb08aa93
--- /dev/null
+++ b/src/MultipartPostBody.php
@@ -0,0 +1,169 @@
+boundary = $boundary ?: uniqid();
+ $this->stream = $this->createStream($fields, $files);
+ }
+
+ /**
+ * Get the boundary
+ *
+ * @return string
+ */
+ public function getBoundary()
+ {
+ return $this->boundary;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ /**
+ * Get the string needed to transfer a POST field
+ */
+ private function getFieldString($name, $value)
+ {
+ return sprintf(
+ "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n",
+ $this->boundary,
+ $name,
+ $value
+ );
+ }
+
+ /**
+ * Get the headers needed before transferring the content of a POST file
+ */
+ private function getFileHeaders(array $headers)
+ {
+ $str = '';
+ foreach ($headers as $key => $value) {
+ $str .= "{$key}: {$value}\r\n";
+ }
+
+ return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n";
+ }
+
+ /**
+ * Create the aggregate stream that will be used to upload the POST data
+ */
+ protected function createStream(array $fields, array $files)
+ {
+ $stream = new AppendStream();
+
+ foreach ($fields as $name => $fieldValues) {
+ foreach ((array) $fieldValues as $value) {
+ $stream->addStream(
+ Stream::factory($this->getFieldString($name, $value))
+ );
+ }
+ }
+
+ foreach ($files as $name => $file) {
+ if ($file instanceof StreamableInterface || is_resource($file)) {
+ $file = $this->createPostFile($name, $file);
+ } elseif (is_array($file)) {
+ $file = $this->createPostFile($name, $file[0], $file[1]);
+ } else {
+ throw new \InvalidArgumentException('All POST files must be '
+ . 'an array or StreamableInterface');
+ }
+ $stream->addStream(Stream::factory($this->getFileHeaders($file[1])));
+ $stream->addStream($file[0]);
+ $stream->addStream(Stream::factory("\r\n"));
+ }
+
+ // Add the trailing boundary with CRLF
+ $stream->addStream(Stream::factory("--{$this->boundary}--\r\n"));
+
+ return $stream;
+ }
+
+ /**
+ * @return array
+ */
+ private function createPostFile($name, $stream, array $headers = [])
+ {
+ $stream = Stream::factory($stream);
+ $filename = $name;
+
+ if ($uri = $stream->getMetadata('uri')) {
+ if (substr($uri, 0, 6) !== 'php://') {
+ $filename = $uri;
+ }
+ }
+
+ // Set a default content-disposition header if one was no provided
+ $disposition = $this->getHeader($headers, 'content-disposition');
+ if (!$disposition) {
+ $headers['Content-Disposition'] = sprintf(
+ 'form-data; name="%s"; filename="%s"',
+ $name,
+ basename($filename)
+ );
+ }
+
+ // Set a default content-length header if one was no provided
+ $length = $this->getHeader($headers, 'content-length');
+ if (!$length) {
+ if ($length = $stream->getSize()) {
+ $headers['Content-Length'] = (string) $length;
+ }
+ }
+
+ // Set a default Content-Type if one was not supplied
+ $type = $this->getHeader($headers, 'content-type');
+ if (!$type) {
+ $mimes = Mimetypes::getInstance();
+ $type = $mimes->fromFilename($filename);
+ if ($type) {
+ $headers['Content-Type'] = $type;
+ }
+ }
+
+ return [$stream, $headers];
+ }
+
+ private function getHeader(array $headers, $key)
+ {
+ foreach ($headers as $k => $v) {
+ if ($k === $key) {
+ return $v;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/Pool.php b/src/Pool.php
index 49d9940fe..09cfe76d6 100644
--- a/src/Pool.php
+++ b/src/Pool.php
@@ -1,77 +1,43 @@
client = $client;
$this->iter = $this->coerceIterable($requests);
- $this->deferred = new Deferred();
- $this->promise = $this->deferred->promise();
$this->poolSize = isset($options['pool_size'])
? $options['pool_size'] : 25;
- $this->eventListeners = $this->prepareListeners(
- $options,
- ['before', 'complete', 'error', 'end']
- );
+ $this->requestOptions = isset($options['request_options'])
+ ? $options['request_options']
+ : [];
+
+ parent::__construct(function () {
+ // Seed the pool with N number of requests.
+ $this->addNextRequests();
+ while ($this->pending) {
+ array_pop($this->pending)->wait(false);
+ $this->addNextRequests();
+ }
+ $this->resolve(true);
+ });
}
/**
- * Sends multiple requests in parallel and returns an array of responses
- * and exceptions that uses the same ordering as the provided requests.
- *
- * IMPORTANT: This method keeps every request and response in memory, and
- * as such, is NOT recommended when sending a large number or an
- * indeterminate number of requests concurrently.
- *
- * @param ClientInterface $client Client used to send the requests
- * @param array|\Iterator $requests Requests to send in parallel
- * @param array $options Passes through the options available in
- * {@see GuzzleHttp\Pool::__construct}
- *
- * @return BatchResults Returns a container for the results.
- * @throws \InvalidArgumentException if the event format is incorrect.
+ * @param $requests
+ * @return \Iterator
*/
- public static function batch(
- ClientInterface $client,
- $requests,
- array $options = []
- ) {
- $hash = new \SplObjectStorage();
- foreach ($requests as $request) {
- $hash->attach($request);
+ private function coerceIterable($requests)
+ {
+ if ($requests instanceof \Iterator) {
+ return $requests;
+ } elseif (is_array($requests)) {
+ return new \ArrayIterator($requests);
}
- // In addition to the normally run events when requests complete, add
- // and event to continuously track the results of transfers in the hash.
- (new self($client, $requests, RequestEvents::convertEventArray(
- $options,
- ['end'],
- [
- 'priority' => RequestEvents::LATE,
- 'fn' => function (EndEvent $e) use ($hash) {
- $hash[$e->getRequest()] = $e->getException()
- ? $e->getException()
- : $e->getResponse();
- }
- ]
- )))->wait();
-
- return new BatchResults($hash);
- }
-
- /**
- * Creates a Pool and immediately sends the requests.
- *
- * @param ClientInterface $client Client used to send the requests
- * @param array|\Iterator $requests Requests to send in parallel
- * @param array $options Passes through the options available in
- * {@see GuzzleHttp\Pool::__construct}
- */
- public static function send(
- ClientInterface $client,
- $requests,
- array $options = []
- ) {
- $pool = new self($client, $requests, $options);
- $pool->wait();
+ throw new \InvalidArgumentException('Expected Iterator or array.'
+ . 'Found ' . describe_type($requests));
}
private function getPoolSize()
{
return is_callable($this->poolSize)
- ? call_user_func($this->poolSize, count($this->waitQueue))
+ ? call_user_func($this->poolSize, count($this->pending))
: $this->poolSize;
}
@@ -163,7 +91,7 @@ private function getPoolSize()
*/
private function addNextRequests()
{
- $limit = max($this->getPoolSize() - count($this->waitQueue), 0);
+ $limit = max($this->getPoolSize() - count($this->pending), 0);
while ($limit--) {
if (!$this->addNextRequest()) {
break;
@@ -171,95 +99,6 @@ private function addNextRequests()
}
}
- public function wait()
- {
- if ($this->isRealized) {
- return false;
- }
-
- // Seed the pool with N number of requests.
- $this->addNextRequests();
-
- // Stop if the pool was cancelled while transferring requests.
- if ($this->isRealized) {
- return false;
- }
-
- // Wait on any outstanding FutureResponse objects.
- while ($response = array_pop($this->waitQueue)) {
- try {
- $response->wait();
- } catch (\Exception $e) {
- // Eat exceptions because they should be handled asynchronously
- }
- $this->addNextRequests();
- }
-
- // Clean up no longer needed state.
- $this->isRealized = true;
- $this->waitQueue = $this->eventListeners = [];
- $this->client = $this->iter = null;
- $this->deferred->resolve(true);
-
- return true;
- }
-
- /**
- * {@inheritdoc}
- *
- * Attempt to cancel all outstanding requests (requests that are queued for
- * dereferencing). Returns true if all outstanding requests can be
- * cancelled.
- *
- * @return bool
- */
- public function cancel()
- {
- if ($this->isRealized) {
- return false;
- }
-
- $success = $this->isRealized = true;
- foreach ($this->waitQueue as $response) {
- if (!$response->cancel()) {
- $success = false;
- }
- }
-
- return $success;
- }
-
- /**
- * Returns a promise that is invoked when the pool completed. There will be
- * no passed value.
- *
- * {@inheritdoc}
- */
- public function then(
- callable $onFulfilled = null,
- callable $onRejected = null,
- callable $onProgress = null
- ) {
- return $this->promise->then($onFulfilled, $onRejected, $onProgress);
- }
-
- public function promise()
- {
- return $this->promise;
- }
-
- private function coerceIterable($requests)
- {
- if ($requests instanceof \Iterator) {
- return $requests;
- } elseif (is_array($requests)) {
- return new \ArrayIterator($requests);
- }
-
- throw new \InvalidArgumentException('Expected Iterator or array. '
- . 'Found ' . Core::describeType($requests));
- }
-
/**
* Adds the next request to pool and tracks what requests need to be
* dereferenced when completing the pool.
@@ -267,67 +106,37 @@ private function coerceIterable($requests)
private function addNextRequest()
{
add_next:
-
- if ($this->isRealized || !$this->iter || !$this->iter->valid()) {
+ if ($this->getState() !== 'pending' || !$this->iter->valid()) {
return false;
}
$request = $this->iter->current();
$this->iter->next();
- if (!($request instanceof RequestInterface)) {
+ if (is_callable($request)) {
+ $response = $request($this->requestOptions);
+ } elseif (!($request instanceof RequestInterface)) {
throw new \InvalidArgumentException(sprintf(
'All requests in the provided iterator must implement '
. 'RequestInterface. Found %s',
- Core::describeType($request)
+ describe_type($request)
));
+ } else {
+ $response = $this->client->send($request, $this->requestOptions);
}
- // Be sure to use "lazy" futures, meaning they do not send right away.
- $request->getConfig()->set('future', 'lazy');
- $hash = spl_object_hash($request);
- $this->attachListeners($request, $this->eventListeners);
- $request->getEmitter()->on('before', [$this, '_trackRetries'], RequestEvents::EARLY);
- $response = $this->client->send($request);
- $this->waitQueue[$hash] = $response;
- $promise = $response->promise();
-
- // Don't recursively call itself for completed or rejected responses.
- if ($promise instanceof FulfilledPromise
- || $promise instanceof RejectedPromise
- ) {
- try {
- $this->finishResponse($request, $response->wait(), $hash);
- } catch (\Exception $e) {
- $this->finishResponse($request, $e, $hash);
- }
+ if ($response->getState() !== 'pending') {
goto add_next;
}
- // Use this function for both resolution and rejection.
- $thenFn = function ($value) use ($request, $hash) {
- $this->finishResponse($request, $value, $hash);
- if (!$request->getConfig()->get('_pool_retries')) {
- $this->addNextRequests();
- }
+ $this->pending[spl_object_hash($response)] = $response;
+ $fn = function () use ($response) {
+ unset($this->pending[spl_object_hash($response)]);
+ $this->addNextRequests();
};
- $promise->then($thenFn, $thenFn);
+ $response->then($fn, $fn);
return true;
}
-
- public function _trackRetries(BeforeEvent $e)
- {
- $e->getRequest()->getConfig()->set('_pool_retries', $e->getRetryCount());
- }
-
- private function finishResponse($request, $value, $hash)
- {
- unset($this->waitQueue[$hash]);
- $result = $value instanceof ResponseInterface
- ? ['request' => $request, 'response' => $value, 'error' => null]
- : ['request' => $request, 'response' => null, 'error' => $value];
- $this->deferred->progress($result);
- }
}
diff --git a/src/Post/MultipartBody.php b/src/Post/MultipartBody.php
deleted file mode 100644
index 1149e6235..000000000
--- a/src/Post/MultipartBody.php
+++ /dev/null
@@ -1,109 +0,0 @@
-boundary = $boundary ?: uniqid();
- $this->stream = $this->createStream($fields, $files);
- }
-
- /**
- * Get the boundary
- *
- * @return string
- */
- public function getBoundary()
- {
- return $this->boundary;
- }
-
- public function isWritable()
- {
- return false;
- }
-
- /**
- * Get the string needed to transfer a POST field
- */
- private function getFieldString($name, $value)
- {
- return sprintf(
- "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n",
- $this->boundary,
- $name,
- $value
- );
- }
-
- /**
- * Get the headers needed before transferring the content of a POST file
- */
- private function getFileHeaders(PostFileInterface $file)
- {
- $headers = '';
- foreach ($file->getHeaders() as $key => $value) {
- $headers .= "{$key}: {$value}\r\n";
- }
-
- return "--{$this->boundary}\r\n" . trim($headers) . "\r\n\r\n";
- }
-
- /**
- * Create the aggregate stream that will be used to upload the POST data
- */
- protected function createStream(array $fields, array $files)
- {
- $stream = new AppendStream();
-
- foreach ($fields as $name => $fieldValues) {
- foreach ((array) $fieldValues as $value) {
- $stream->addStream(
- Stream::factory($this->getFieldString($name, $value))
- );
- }
- }
-
- foreach ($files as $file) {
-
- if (!$file instanceof PostFileInterface) {
- throw new \InvalidArgumentException('All POST fields must '
- . 'implement PostFieldInterface');
- }
-
- $stream->addStream(
- Stream::factory($this->getFileHeaders($file))
- );
- $stream->addStream($file->getContent());
- $stream->addStream(Stream::factory("\r\n"));
- }
-
- // Add the trailing boundary with CRLF
- $stream->addStream(Stream::factory("--{$this->boundary}--\r\n"));
-
- return $stream;
- }
-}
diff --git a/src/Post/PostBody.php b/src/Post/PostBody.php
deleted file mode 100644
index ed14d1f70..000000000
--- a/src/Post/PostBody.php
+++ /dev/null
@@ -1,287 +0,0 @@
-files || $this->forceMultipart) {
- $request->setHeader(
- 'Content-Type',
- 'multipart/form-data; boundary=' . $this->getBody()->getBoundary()
- );
- } elseif ($this->fields && !$request->hasHeader('Content-Type')) {
- $request->setHeader(
- 'Content-Type',
- 'application/x-www-form-urlencoded'
- );
- }
-
- if ($size = $this->getSize()) {
- $request->setHeader('Content-Length', $size);
- }
- }
-
- public function forceMultipartUpload($force)
- {
- $this->forceMultipart = $force;
- }
-
- public function setAggregator(callable $aggregator)
- {
- $this->aggregator = $aggregator;
- }
-
- public function setField($name, $value)
- {
- $this->fields[$name] = $value;
- $this->mutate();
- }
-
- public function replaceFields(array $fields)
- {
- $this->fields = $fields;
- $this->mutate();
- }
-
- public function getField($name)
- {
- return isset($this->fields[$name]) ? $this->fields[$name] : null;
- }
-
- public function removeField($name)
- {
- unset($this->fields[$name]);
- $this->mutate();
- }
-
- public function getFields($asString = false)
- {
- if (!$asString) {
- return $this->fields;
- }
-
- $query = new Query($this->fields);
- $query->setEncodingType(Query::RFC1738);
- $query->setAggregator($this->getAggregator());
-
- return (string) $query;
- }
-
- public function hasField($name)
- {
- return isset($this->fields[$name]);
- }
-
- public function getFile($name)
- {
- foreach ($this->files as $file) {
- if ($file->getName() == $name) {
- return $file;
- }
- }
-
- return null;
- }
-
- public function getFiles()
- {
- return $this->files;
- }
-
- public function addFile(PostFileInterface $file)
- {
- $this->files[] = $file;
- $this->mutate();
- }
-
- public function clearFiles()
- {
- $this->files = [];
- $this->mutate();
- }
-
- /**
- * Returns the numbers of fields + files
- *
- * @return int
- */
- public function count()
- {
- return count($this->files) + count($this->fields);
- }
-
- public function __toString()
- {
- return (string) $this->getBody();
- }
-
- public function getContents($maxLength = -1)
- {
- return $this->getBody()->getContents();
- }
-
- public function close()
- {
- $this->detach();
- }
-
- public function detach()
- {
- $this->detached = true;
- $this->fields = $this->files = [];
-
- if ($this->body) {
- $this->body->close();
- $this->body = null;
- }
- }
-
- public function attach($stream)
- {
- throw new CannotAttachException();
- }
-
- public function eof()
- {
- return $this->getBody()->eof();
- }
-
- public function tell()
- {
- return $this->body ? $this->body->tell() : 0;
- }
-
- public function isSeekable()
- {
- return true;
- }
-
- public function isReadable()
- {
- return true;
- }
-
- public function isWritable()
- {
- return false;
- }
-
- public function getSize()
- {
- return $this->getBody()->getSize();
- }
-
- public function seek($offset, $whence = SEEK_SET)
- {
- return $this->getBody()->seek($offset, $whence);
- }
-
- public function read($length)
- {
- return $this->getBody()->read($length);
- }
-
- public function write($string)
- {
- return false;
- }
-
- public function getMetadata($key = null)
- {
- return $key ? null : [];
- }
-
- /**
- * Return a stream object that is built from the POST fields and files.
- *
- * If one has already been created, the previously created stream will be
- * returned.
- */
- private function getBody()
- {
- if ($this->body) {
- return $this->body;
- } elseif ($this->files || $this->forceMultipart) {
- return $this->body = $this->createMultipart();
- } elseif ($this->fields) {
- return $this->body = $this->createUrlEncoded();
- } else {
- return $this->body = Stream::factory();
- }
- }
-
- /**
- * Get the aggregator used to join multi-valued field parameters
- *
- * @return callable
- */
- final protected function getAggregator()
- {
- if (!$this->aggregator) {
- $this->aggregator = Query::phpAggregator();
- }
-
- return $this->aggregator;
- }
-
- /**
- * Creates a multipart/form-data body stream
- *
- * @return MultipartBody
- */
- private function createMultipart()
- {
- // Flatten the nested query string values using the correct aggregator
- return new MultipartBody(
- call_user_func($this->getAggregator(), $this->fields),
- $this->files
- );
- }
-
- /**
- * Creates an application/x-www-form-urlencoded stream body
- *
- * @return StreamInterface
- */
- private function createUrlEncoded()
- {
- return Stream::factory($this->getFields(true));
- }
-
- /**
- * Get rid of any cached data
- */
- private function mutate()
- {
- $this->body = null;
- }
-}
diff --git a/src/Post/PostBodyInterface.php b/src/Post/PostBodyInterface.php
deleted file mode 100644
index c2ec9a62c..000000000
--- a/src/Post/PostBodyInterface.php
+++ /dev/null
@@ -1,109 +0,0 @@
-headers = $headers;
- $this->name = $name;
- $this->prepareContent($content);
- $this->prepareFilename($filename);
- $this->prepareDefaultHeaders();
- }
-
- public function getName()
- {
- return $this->name;
- }
-
- public function getFilename()
- {
- return $this->filename;
- }
-
- public function getContent()
- {
- return $this->content;
- }
-
- public function getHeaders()
- {
- return $this->headers;
- }
-
- /**
- * Prepares the contents of a POST file.
- *
- * @param mixed $content Content of the POST file
- */
- private function prepareContent($content)
- {
- $this->content = $content;
-
- if (!($this->content instanceof StreamInterface)) {
- $this->content = Stream::factory($this->content);
- } elseif ($this->content instanceof MultipartBody) {
- if (!$this->hasHeader('Content-Disposition')) {
- $disposition = 'form-data; name="' . $this->name .'"';
- $this->headers['Content-Disposition'] = $disposition;
- }
-
- if (!$this->hasHeader('Content-Type')) {
- $this->headers['Content-Type'] = sprintf(
- "multipart/form-data; boundary=%s",
- $this->content->getBoundary()
- );
- }
- }
- }
-
- /**
- * Applies a file name to the POST file based on various checks.
- *
- * @param string|null $filename Filename to apply (or null to guess)
- */
- private function prepareFilename($filename)
- {
- $this->filename = $filename;
-
- if (!$this->filename) {
- $this->filename = $this->content->getMetadata('uri');
- }
-
- if (!$this->filename || substr($this->filename, 0, 6) === 'php://') {
- $this->filename = $this->name;
- }
- }
-
- /**
- * Applies default Content-Disposition and Content-Type headers if needed.
- */
- private function prepareDefaultHeaders()
- {
- // Set a default content-disposition header if one was no provided
- if (!$this->hasHeader('Content-Disposition')) {
- $this->headers['Content-Disposition'] = sprintf(
- 'form-data; name="%s"; filename="%s"',
- $this->name,
- basename($this->filename)
- );
- }
-
- // Set a default Content-Type if one was not supplied
- if (!$this->hasHeader('Content-Type')) {
- $this->headers['Content-Type'] = Mimetypes::getInstance()
- ->fromFilename($this->filename) ?: 'text/plain';
- }
- }
-
- /**
- * Check if a specific header exists on the POST file by name.
- *
- * @param string $name Case-insensitive header to check
- *
- * @return bool
- */
- private function hasHeader($name)
- {
- return isset(array_change_key_case($this->headers)[strtolower($name)]);
- }
-}
diff --git a/src/Post/PostFileInterface.php b/src/Post/PostFileInterface.php
deleted file mode 100644
index 2e816c088..000000000
--- a/src/Post/PostFileInterface.php
+++ /dev/null
@@ -1,41 +0,0 @@
-setEncodingType($urlEncoding);
- }
-
- $qp->parseInto($q, $query, $urlEncoding);
-
- return $q;
- }
-
- /**
- * Convert the query string parameters to a query string string
- *
- * @return string
- */
- public function __toString()
- {
- if (!$this->data) {
- return '';
- }
-
- // The default aggregator is statically cached
- static $defaultAggregator;
-
- if (!$this->aggregator) {
- if (!$defaultAggregator) {
- $defaultAggregator = self::phpAggregator();
- }
- $this->aggregator = $defaultAggregator;
- }
-
- $result = '';
- $aggregator = $this->aggregator;
- $encoder = $this->encoding;
-
- foreach ($aggregator($this->data) as $key => $values) {
- foreach ($values as $value) {
- if ($result) {
- $result .= '&';
- }
- $result .= $encoder($key);
- if ($value !== null) {
- $result .= '=' . $encoder($value);
- }
- }
- }
-
- return $result;
- }
-
- /**
- * Controls how multi-valued query string parameters are aggregated into a
- * string.
- *
- * $query->setAggregator($query::duplicateAggregator());
- *
- * @param callable $aggregator Callable used to convert a deeply nested
- * array of query string variables into a flattened array of key value
- * pairs. The callable accepts an array of query data and returns a
- * flattened array of key value pairs where each value is an array of
- * strings.
- */
- public function setAggregator(callable $aggregator)
- {
- $this->aggregator = $aggregator;
- }
-
- /**
- * Specify how values are URL encoded
- *
- * @param string|bool $type One of 'RFC1738', 'RFC3986', or false to disable encoding
- *
- * @throws \InvalidArgumentException
- */
- public function setEncodingType($type)
- {
- switch ($type) {
- case self::RFC3986:
- $this->encoding = 'rawurlencode';
- break;
- case self::RFC1738:
- $this->encoding = 'urlencode';
- break;
- case false:
- $this->encoding = function ($v) { return $v; };
- break;
- default:
- throw new \InvalidArgumentException('Invalid URL encoding type');
- }
- }
-
- /**
- * Query string aggregator that does not aggregate nested query string
- * values and allows duplicates in the resulting array.
- *
- * Example: http://test.com?q=1&q=2
- *
- * @return callable
- */
- public static function duplicateAggregator()
- {
- return function (array $data) {
- return self::walkQuery($data, '', function ($key, $prefix) {
- return is_int($key) ? $prefix : "{$prefix}[{$key}]";
- });
- };
- }
-
- /**
- * Aggregates nested query string variables using the same technique as
- * ``http_build_query()``.
- *
- * @param bool $numericIndices Pass false to not include numeric indices
- * when multi-values query string parameters are present.
- *
- * @return callable
- */
- public static function phpAggregator($numericIndices = true)
- {
- return function (array $data) use ($numericIndices) {
- return self::walkQuery(
- $data,
- '',
- function ($key, $prefix) use ($numericIndices) {
- return !$numericIndices && is_int($key)
- ? "{$prefix}[]"
- : "{$prefix}[{$key}]";
- }
- );
- };
- }
-
- /**
- * Easily create query aggregation functions by providing a key prefix
- * function to this query string array walker.
- *
- * @param array $query Query string to walk
- * @param string $keyPrefix Key prefix (start with '')
- * @param callable $prefixer Function used to create a key prefix
- *
- * @return array
- */
- public static function walkQuery(array $query, $keyPrefix, callable $prefixer)
- {
- $result = [];
- foreach ($query as $key => $value) {
- if ($keyPrefix) {
- $key = $prefixer($key, $keyPrefix);
- }
- if (is_array($value)) {
- $result += self::walkQuery($value, $key, $prefixer);
- } elseif (isset($result[$key])) {
- $result[$key][] = $value;
- } else {
- $result[$key] = array($value);
- }
- }
-
- return $result;
- }
-}
diff --git a/src/QueryParser.php b/src/QueryParser.php
deleted file mode 100644
index 90727cc6c..000000000
--- a/src/QueryParser.php
+++ /dev/null
@@ -1,163 +0,0 @@
-duplicates = false;
- $this->numericIndices = true;
- $decoder = self::getDecoder($urlEncoding);
-
- foreach (explode('&', $str) as $kvp) {
-
- $parts = explode('=', $kvp, 2);
- $key = $decoder($parts[0]);
- $value = isset($parts[1]) ? $decoder($parts[1]) : null;
-
- // Special handling needs to be taken for PHP nested array syntax
- if (strpos($key, '[') !== false) {
- $this->parsePhpValue($key, $value, $result);
- continue;
- }
-
- if (!isset($result[$key])) {
- $result[$key] = $value;
- } else {
- $this->duplicates = true;
- if (!is_array($result[$key])) {
- $result[$key] = [$result[$key]];
- }
- $result[$key][] = $value;
- }
- }
-
- $query->replace($result);
-
- if (!$this->numericIndices) {
- $query->setAggregator(Query::phpAggregator(false));
- } elseif ($this->duplicates) {
- $query->setAggregator(Query::duplicateAggregator());
- }
- }
-
- /**
- * Returns a callable that is used to URL decode query keys and values.
- *
- * @param string|bool $type One of true, false, RFC3986, and RFC1738
- *
- * @return callable|string
- */
- private static function getDecoder($type)
- {
- if ($type === true) {
- return function ($value) {
- return rawurldecode(str_replace('+', ' ', $value));
- };
- } elseif ($type == Query::RFC3986) {
- return 'rawurldecode';
- } elseif ($type == Query::RFC1738) {
- return 'urldecode';
- } else {
- return function ($str) { return $str; };
- }
- }
-
- /**
- * Parses a PHP style key value pair.
- *
- * @param string $key Key to parse (e.g., "foo[a][b]")
- * @param string|null $value Value to set
- * @param array $result Result to modify by reference
- */
- private function parsePhpValue($key, $value, array &$result)
- {
- $node =& $result;
- $keyBuffer = '';
-
- for ($i = 0, $t = strlen($key); $i < $t; $i++) {
- switch ($key[$i]) {
- case '[':
- if ($keyBuffer) {
- $this->prepareNode($node, $keyBuffer);
- $node =& $node[$keyBuffer];
- $keyBuffer = '';
- }
- break;
- case ']':
- $k = $this->cleanKey($node, $keyBuffer);
- $this->prepareNode($node, $k);
- $node =& $node[$k];
- $keyBuffer = '';
- break;
- default:
- $keyBuffer .= $key[$i];
- break;
- }
- }
-
- if (isset($node)) {
- $this->duplicates = true;
- $node[] = $value;
- } else {
- $node = $value;
- }
- }
-
- /**
- * Prepares a value in the array at the given key.
- *
- * If the key already exists, the key value is converted into an array.
- *
- * @param array $node Result node to modify
- * @param string $key Key to add or modify in the node
- */
- private function prepareNode(&$node, $key)
- {
- if (!isset($node[$key])) {
- $node[$key] = null;
- } elseif (!is_array($node[$key])) {
- $node[$key] = [$node[$key]];
- }
- }
-
- /**
- * Returns the appropriate key based on the node and key.
- */
- private function cleanKey($node, $key)
- {
- if ($key === '') {
- $key = $node ? (string) count($node) : 0;
- // Found a [] key, so track this to ensure that we disable numeric
- // indexing of keys in the resolved query aggregator.
- $this->numericIndices = false;
- }
-
- return $key;
- }
-}
diff --git a/src/RedirectMiddleware.php b/src/RedirectMiddleware.php
new file mode 100644
index 000000000..2addf1886
--- /dev/null
+++ b/src/RedirectMiddleware.php
@@ -0,0 +1,185 @@
+nextHandler = $nextHandler;
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return PromiseInterface
+ */
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ $fn = $this->nextHandler;
+ if (empty($options['allow_redirects'])) {
+ return $fn($request, $options);
+ }
+
+ $options['allow_redirects'] += [
+ 'max' => 5,
+ 'protocols' => ['http', 'https'],
+ 'strict' => false
+ ];
+
+ return $fn($request, $options)
+ ->then(function (ResponseInterface $response) use ($request, $options) {
+ return $this->checkRedirect($request, $options, $response);
+ });
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ * @param ResponseInterface|PromiseInterface $response
+ *
+ * @return ResponseInterface|PromiseInterface
+ */
+ public function checkRedirect(
+ RequestInterface $request,
+ array $options,
+ ResponseInterface $response
+ ) {
+ if (substr($response->getStatusCode(), 0, 1) != '3'
+ || !$response->hasHeader('Location')
+ ) {
+ return $response;
+ }
+
+ $this->guardMax($request, $options);
+ $nextRequest = $this->modifyRequest($request, $options, $response);
+
+ return $this($nextRequest, $options);
+ }
+
+ private function guardMax(RequestInterface $request, array &$options)
+ {
+ $current = isset($options['__redirect_count'])
+ ? $options['__redirect_count']
+ : 0;
+ $options['__redirect_count'] = $current + 1;
+
+ if (!isset($options['__redirect_scheme'])) {
+ $options['__redirect_scheme'] = $request->getUri()->getScheme();
+ }
+
+ $max = $options['allow_redirects']['max'];
+
+ if ($options['__redirect_count'] > $max) {
+ throw new TooManyRedirectsException(
+ "Will not follow more than {$max} redirects",
+ $request
+ );
+ }
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ * @param ResponseInterface $response
+ *
+ * @return RequestInterface
+ */
+ public function modifyRequest(
+ RequestInterface $request,
+ array $options,
+ ResponseInterface $response
+ ) {
+ // Request modifications to apply.
+ $modify = [];
+ $protocols = $options['allow_redirects']['protocols'];
+
+ // Use a GET request if this is an entity enclosing request and we are
+ // not forcing RFC compliance, but rather emulating what all browsers
+ // would do.
+ $statusCode = $response->getStatusCode();
+ if ($statusCode == 303 ||
+ ($statusCode <= 302 && $request->getBody() && !$options['allow_redirects']['strict'])
+ ) {
+ $modify['method'] = 'GET';
+ $modify['body'] = '';
+ }
+
+ $modify['uri'] = $this->redirectUri($request, $response, $protocols);
+ rewind_body($request);
+
+ // Add the Referer header if it is told to do so and only
+ // add the header if we are not redirecting from https to http.
+ $scheme = $request->getUri()->getScheme();
+ if ($options['allow_redirects']['referer']
+ && ($scheme == 'https' || $scheme == $options['__redirect_scheme'])
+ ) {
+ $uri = $request->getUri()->withUserInfo('', '');
+ $modify['set_headers']['Referer'] = (string) $uri;
+ } else {
+ $modify['remove_headers'][] = 'Referer';
+ }
+
+ return modify_request($request, $modify);
+ }
+
+ /**
+ * Set the appropriate URL on the request based on the location header
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @param array $protocols
+ *
+ * @return UriInterface
+ */
+ private function redirectUri(
+ RequestInterface $request,
+ ResponseInterface $response,
+ array $protocols
+ ) {
+ $location = new Uri($response->getHeader('Location'));
+
+ // Combine location with the original URL if it is not absolute.
+ if (!$location->getScheme()) {
+ // Remove query string parameters and just take what is present on
+ // the redirect Location header
+ $base = $request->getUri()->withQuery('');
+ $location = Uri::resolve($base, $location);
+ }
+
+ // Ensure that the redirect URL is allowed based on the protocols.
+ if (!in_array($location->getScheme(), $protocols)) {
+ throw new BadResponseException(
+ sprintf(
+ 'Redirect URL, %s, does not use one of the allowed redirect protocols: %s',
+ $location,
+ implode(', ', $protocols)
+ ),
+ $request,
+ $response
+ );
+ }
+
+ return $location;
+ }
+}
diff --git a/src/RejectedResponse.php b/src/RejectedResponse.php
new file mode 100644
index 000000000..204e819bf
--- /dev/null
+++ b/src/RejectedResponse.php
@@ -0,0 +1,89 @@
+e = $e;
+ parent::__construct($e);
+ }
+
+ public function getStatusCode()
+ {
+ throw $this->e;
+ }
+
+ public function withStatus($code, $reasonPhrase = null)
+ {
+ throw $this->e;
+ }
+
+ public function getReasonPhrase()
+ {
+ throw $this->e;
+ }
+
+ public function getProtocolVersion()
+ {
+ throw $this->e;
+ }
+
+ public function withProtocolVersion($version)
+ {
+ throw $this->e;
+ }
+
+ public function getHeaders()
+ {
+ throw $this->e;
+ }
+
+ public function hasHeader($name)
+ {
+ throw $this->e;
+ }
+
+ public function getHeader($name)
+ {
+ throw $this->e;
+ }
+
+ public function getHeaderLines($name)
+ {
+ throw $this->e;
+ }
+
+ public function withHeader($name, $value)
+ {
+ throw $this->e;
+ }
+
+ public function withAddedHeader($name, $value)
+ {
+ throw $this->e;
+ }
+
+ public function withoutHeader($name)
+ {
+ throw $this->e;
+ }
+
+ public function getBody()
+ {
+ throw $this->e;
+ }
+
+ public function withBody(StreamableInterface $body)
+ {
+ throw $this->e;
+ }
+}
diff --git a/src/RequestFsm.php b/src/RequestFsm.php
deleted file mode 100644
index b37c190d4..000000000
--- a/src/RequestFsm.php
+++ /dev/null
@@ -1,153 +0,0 @@
-mf = $messageFactory;
- $this->maxTransitions = $maxTransitions;
- $this->handler = $handler;
- }
-
- /**
- * Runs the state machine until a terminal state is entered or the
- * optionally supplied $finalState is entered.
- *
- * @param Transaction $trans Transaction being transitioned.
- *
- * @throws \Exception if a terminal state throws an exception.
- */
- public function __invoke(Transaction $trans)
- {
- $trans->_transitionCount = 0;
-
- if (!$trans->state) {
- $trans->state = 'before';
- }
-
- transition:
-
- if (++$trans->_transitionCount > $this->maxTransitions) {
- throw new StateException("Too many state transitions were "
- . "encountered ({$trans->_transitionCount}). This likely "
- . "means that a combination of event listeners are in an "
- . "infinite loop.");
- }
-
- switch ($trans->state) {
- case 'before': goto before;
- case 'complete': goto complete;
- case 'error': goto error;
- case 'retry': goto retry;
- case 'send': goto send;
- case 'end': goto end;
- default: throw new StateException("Invalid state: {$trans->state}");
- }
-
- before: {
- try {
- $trans->request->getEmitter()->emit('before', new BeforeEvent($trans));
- $trans->state = 'send';
- if ((bool) $trans->response) {
- $trans->state = 'complete';
- }
- } catch (\Exception $e) {
- $trans->state = 'error';
- $trans->exception = $e;
- }
- goto transition;
- }
-
- complete: {
- try {
- if ($trans->response instanceof FutureInterface) {
- // Futures will have their own end events emitted when
- // dereferenced.
- return;
- }
- $trans->state = 'end';
- $trans->response->setEffectiveUrl($trans->request->getUrl());
- $trans->request->getEmitter()->emit('complete', new CompleteEvent($trans));
- } catch (\Exception $e) {
- $trans->state = 'error';
- $trans->exception = $e;
- }
- goto transition;
- }
-
- error: {
- try {
- // Convert non-request exception to a wrapped exception
- $trans->exception = RequestException::wrapException(
- $trans->request, $trans->exception
- );
- $trans->state = 'end';
- $trans->request->getEmitter()->emit('error', new ErrorEvent($trans));
- // An intercepted request (not retried) transitions to complete
- if (!$trans->exception && $trans->state !== 'retry') {
- $trans->state = 'complete';
- }
- } catch (\Exception $e) {
- $trans->state = 'end';
- $trans->exception = $e;
- }
- goto transition;
- }
-
- retry: {
- $trans->retries++;
- $trans->response = null;
- $trans->exception = null;
- $trans->state = 'before';
- goto transition;
- }
-
- send: {
- $fn = $this->handler;
- $trans->response = FutureResponse::proxy(
- $fn(RingBridge::prepareRingRequest($trans)),
- function ($value) use ($trans) {
- RingBridge::completeRingResponse($trans, $value, $this->mf, $this);
- $this($trans);
- return $trans->response;
- }
- );
- return;
- }
-
- end: {
- $trans->request->getEmitter()->emit('end', new EndEvent($trans));
- // Throw exceptions in the terminal event if the exception
- // was not handled by an "end" event listener.
- if ($trans->exception) {
- if (!($trans->exception instanceof RequestException)) {
- $trans->exception = RequestException::wrapException(
- $trans->request, $trans->exception
- );
- }
- throw $trans->exception;
- }
- }
- }
-}
diff --git a/src/ResponsePromise.php b/src/ResponsePromise.php
new file mode 100644
index 000000000..98711ab68
--- /dev/null
+++ b/src/ResponsePromise.php
@@ -0,0 +1,135 @@
+getState();
+ if ($state === 'pending') {
+ $next = new ResponsePromise([$promise, 'wait'], [$promise, 'cancel']);
+ $promise->then([$next, 'resolve'], [$next, 'reject']);
+ return $next;
+ } elseif ($state === 'fulfilled') {
+ return new FulfilledResponse($promise->wait());
+ } elseif ($state === 'rejected' || $state === 'cancelled') {
+ try {
+ $promise->wait();
+ } catch (\Exception $e) {
+ return new RejectedResponse($e);
+ }
+ }
+
+ throw new \UnexpectedValueException("Invalid promise state: {$state}");
+ }
+
+ public function __get($name)
+ {
+ if ($name == '_response') {
+ return $this->_response = $this->wait();
+ }
+
+ throw new \BadMethodCallException("Unknown property {$name}");
+ }
+
+ public function getStatusCode()
+ {
+ return $this->_response->getStatusCode();
+ }
+
+ public function withStatus($code, $reasonPhrase = null)
+ {
+ return $this->_response->withStatus($code, $reasonPhrase);
+ }
+
+ public function getReasonPhrase()
+ {
+ return $this->_response->getReasonPhrase();
+ }
+
+ public function getProtocolVersion()
+ {
+ return $this->_response->getProtocolVersion();
+ }
+
+ public function withProtocolVersion($version)
+ {
+ return $this->_response->withProtocolVersion($version);
+ }
+
+ public function getHeaders()
+ {
+ return $this->_response->getHeaders();
+ }
+
+ public function hasHeader($name)
+ {
+ return $this->_response->hasHeader($name);
+ }
+
+ public function getHeader($name)
+ {
+ return $this->_response->getHeader($name);
+ }
+
+ public function getHeaderLines($name)
+ {
+ return $this->_response->getHeaderLines($name);
+ }
+
+ public function withHeader($name, $value)
+ {
+ return $this->_response->withHeader($name, $value);
+ }
+
+ public function withAddedHeader($name, $value)
+ {
+ return $this->_response->withAddedHeader($name, $value);
+ }
+
+ public function withoutHeader($name)
+ {
+ return $this->_response->withoutHeader($name);
+ }
+
+ public function getBody()
+ {
+ return $this->_response->getBody();
+ }
+
+ public function withBody(StreamableInterface $body)
+ {
+ return $this->_response->withBody($body);
+ }
+
+ public function resolve($value)
+ {
+ if ($value instanceof ResponseInterface
+ || $value instanceof RejectedPromise
+ ) {
+ parent::resolve($value);
+ return;
+ }
+
+ throw new \InvalidArgumentException('A response promise must be '
+ . 'resolved with a Psr\Http\Message\ResponseInterface or a '
+ . 'GuzzleHttp\RejectedPromise. Found ' . \GuzzleHttp\describe_type($value));
+ }
+}
diff --git a/src/ResponsePromiseInterface.php b/src/ResponsePromiseInterface.php
new file mode 100644
index 000000000..74662c717
--- /dev/null
+++ b/src/ResponsePromiseInterface.php
@@ -0,0 +1,9 @@
+getConfig()->toArray();
- $url = $request->getUrl();
- // No need to calculate the query string twice (in URL and query).
- $qs = ($pos = strpos($url, '?')) ? substr($url, $pos + 1) : null;
-
- return [
- 'scheme' => $request->getScheme(),
- 'http_method' => $request->getMethod(),
- 'url' => $url,
- 'uri' => $request->getPath(),
- 'headers' => $request->getHeaders(),
- 'body' => $request->getBody(),
- 'version' => $request->getProtocolVersion(),
- 'client' => $options,
- 'query_string' => $qs,
- 'future' => isset($options['future']) ? $options['future'] : false
- ];
- }
-
- /**
- * Creates a Ring request from a request object AND prepares the callbacks.
- *
- * @param Transaction $trans Transaction to update.
- *
- * @return array Converted Guzzle Ring request.
- */
- public static function prepareRingRequest(Transaction $trans)
- {
- // Clear out the transaction state when initiating.
- $trans->exception = null;
- $request = self::createRingRequest($trans->request);
-
- // Emit progress events if any progress listeners are registered.
- if ($trans->request->getEmitter()->hasListeners('progress')) {
- $emitter = $trans->request->getEmitter();
- $request['client']['progress'] = function ($a, $b, $c, $d) use ($trans, $emitter) {
- $emitter->emit('progress', new ProgressEvent($trans, $a, $b, $c, $d));
- };
- }
-
- return $request;
- }
-
- /**
- * Handles the process of processing a response received from a ring
- * handler. The created response is added to the transaction, and the
- * transaction stat is set appropriately.
- *
- * @param Transaction $trans Owns request and response.
- * @param array $response Ring response array
- * @param MessageFactoryInterface $messageFactory Creates response objects.
- */
- public static function completeRingResponse(
- Transaction $trans,
- array $response,
- MessageFactoryInterface $messageFactory
- ) {
- $trans->state = 'complete';
- $trans->transferInfo = isset($response['transfer_stats'])
- ? $response['transfer_stats'] : [];
-
- if (!empty($response['status'])) {
- $options = [];
- if (isset($response['version'])) {
- $options['protocol_version'] = $response['version'];
- }
- if (isset($response['reason'])) {
- $options['reason_phrase'] = $response['reason'];
- }
- $trans->response = $messageFactory->createResponse(
- $response['status'],
- isset($response['headers']) ? $response['headers'] : [],
- isset($response['body']) ? $response['body'] : null,
- $options
- );
- if (isset($response['effective_url'])) {
- $trans->response->setEffectiveUrl($response['effective_url']);
- }
- } elseif (empty($response['error'])) {
- // When nothing was returned, then we need to add an error.
- $response['error'] = self::getNoRingResponseException($trans->request);
- }
-
- if (isset($response['error'])) {
- $trans->state = 'error';
- $trans->exception = $response['error'];
- }
- }
-
- /**
- * Creates a Guzzle request object using a ring request array.
- *
- * @param array $request Ring request
- *
- * @return Request
- * @throws \InvalidArgumentException for incomplete requests.
- */
- public static function fromRingRequest(array $request)
- {
- $options = [];
- if (isset($request['version'])) {
- $options['protocol_version'] = $request['version'];
- }
-
- if (!isset($request['http_method'])) {
- throw new \InvalidArgumentException('No http_method');
- }
-
- return new Request(
- $request['http_method'],
- Core::url($request),
- isset($request['headers']) ? $request['headers'] : [],
- isset($request['body']) ? Stream::factory($request['body']) : null,
- $options
- );
- }
-
- /**
- * Get an exception that can be used when a RingPHP handler does not
- * populate a response.
- *
- * @param RequestInterface $request
- *
- * @return RequestException
- */
- public static function getNoRingResponseException(RequestInterface $request)
- {
- $message = <<cookieJar = $cookieJar ?: new CookieJar();
- }
-
- public function getEvents()
- {
- // Fire the cookie plugin complete event before redirecting
- return [
- 'before' => ['onBefore'],
- 'complete' => ['onComplete', RequestEvents::REDIRECT_RESPONSE + 10]
- ];
- }
-
- /**
- * Get the cookie cookieJar
- *
- * @return CookieJarInterface
- */
- public function getCookieJar()
- {
- return $this->cookieJar;
- }
-
- public function onBefore(BeforeEvent $event)
- {
- $this->cookieJar->addCookieHeader($event->getRequest());
- }
-
- public function onComplete(CompleteEvent $event)
- {
- $this->cookieJar->extractCookies(
- $event->getRequest(),
- $event->getResponse()
- );
- }
-}
diff --git a/src/Subscriber/History.php b/src/Subscriber/History.php
deleted file mode 100644
index 5cf06119f..000000000
--- a/src/Subscriber/History.php
+++ /dev/null
@@ -1,172 +0,0 @@
-limit = $limit;
- }
-
- public function getEvents()
- {
- return [
- 'complete' => ['onComplete', RequestEvents::EARLY],
- 'error' => ['onError', RequestEvents::EARLY],
- ];
- }
-
- /**
- * Convert to a string that contains all request and response headers
- *
- * @return string
- */
- public function __toString()
- {
- $lines = array();
- foreach ($this->transactions as $entry) {
- $response = isset($entry['response']) ? $entry['response'] : '';
- $lines[] = '> ' . trim($entry['sent_request'])
- . "\n\n< " . trim($response) . "\n";
- }
-
- return implode("\n", $lines);
- }
-
- public function onComplete(CompleteEvent $event)
- {
- $this->add($event->getRequest(), $event->getResponse());
- }
-
- public function onError(ErrorEvent $event)
- {
- // Only track when no response is present, meaning this didn't ever
- // emit a complete event
- if (!$event->getResponse()) {
- $this->add($event->getRequest());
- }
- }
-
- /**
- * Returns an Iterator that yields associative array values where each
- * associative array contains the following key value pairs:
- *
- * - request: Representing the actual request that was received.
- * - sent_request: A clone of the request that will not be mutated.
- * - response: The response that was received (if available).
- *
- * @return \Iterator
- */
- public function getIterator()
- {
- return new \ArrayIterator($this->transactions);
- }
-
- /**
- * Get all of the requests sent through the plugin.
- *
- * Requests can be modified after they are logged by the history
- * subscriber. By default this method will return the actual request
- * instances that were received. Pass true to this method if you wish to
- * get copies of the requests that represent the request state when it was
- * initially logged by the history subscriber.
- *
- * @param bool $asSent Set to true to get clones of the requests that have
- * not been mutated since the request was received by
- * the history subscriber.
- *
- * @return RequestInterface[]
- */
- public function getRequests($asSent = false)
- {
- return array_map(function ($t) use ($asSent) {
- return $asSent ? $t['sent_request'] : $t['request'];
- }, $this->transactions);
- }
-
- /**
- * Get the number of requests in the history
- *
- * @return int
- */
- public function count()
- {
- return count($this->transactions);
- }
-
- /**
- * Get the last request sent.
- *
- * Requests can be modified after they are logged by the history
- * subscriber. By default this method will return the actual request
- * instance that was received. Pass true to this method if you wish to get
- * a copy of the request that represents the request state when it was
- * initially logged by the history subscriber.
- *
- * @param bool $asSent Set to true to get a clone of the last request that
- * has not been mutated since the request was received
- * by the history subscriber.
- *
- * @return RequestInterface
- */
- public function getLastRequest($asSent = false)
- {
- return $asSent
- ? end($this->transactions)['sent_request']
- : end($this->transactions)['request'];
- }
-
- /**
- * Get the last response in the history
- *
- * @return ResponseInterface|null
- */
- public function getLastResponse()
- {
- return end($this->transactions)['response'];
- }
-
- /**
- * Clears the history
- */
- public function clear()
- {
- $this->transactions = array();
- }
-
- /**
- * Add a request to the history
- *
- * @param RequestInterface $request Request to add
- * @param ResponseInterface $response Response of the request
- */
- private function add(
- RequestInterface $request,
- ResponseInterface $response = null
- ) {
- $this->transactions[] = [
- 'request' => $request,
- 'sent_request' => clone $request,
- 'response' => $response
- ];
- if (count($this->transactions) > $this->limit) {
- array_shift($this->transactions);
- }
- }
-}
diff --git a/src/Subscriber/HttpError.php b/src/Subscriber/HttpError.php
deleted file mode 100644
index ed9de5bcc..000000000
--- a/src/Subscriber/HttpError.php
+++ /dev/null
@@ -1,36 +0,0 @@
- ['onComplete', RequestEvents::VERIFY_RESPONSE]];
- }
-
- /**
- * Throw a RequestException on an HTTP protocol error
- *
- * @param CompleteEvent $event Emitted event
- * @throws RequestException
- */
- public function onComplete(CompleteEvent $event)
- {
- $code = (string) $event->getResponse()->getStatusCode();
- // Throw an exception for an unsuccessful response
- if ($code[0] >= 4) {
- throw RequestException::create(
- $event->getRequest(),
- $event->getResponse()
- );
- }
- }
-}
diff --git a/src/Subscriber/Mock.php b/src/Subscriber/Mock.php
deleted file mode 100644
index 39a3c442d..000000000
--- a/src/Subscriber/Mock.php
+++ /dev/null
@@ -1,132 +0,0 @@
-factory = new MessageFactory();
- $this->readBodies = $readBodies;
- $this->addMultiple($items);
- }
-
- public function getEvents()
- {
- // Fire the event last, after signing
- return ['before' => ['onBefore', RequestEvents::SIGN_REQUEST - 10]];
- }
-
- /**
- * @throws \OutOfBoundsException|\Exception
- */
- public function onBefore(BeforeEvent $event)
- {
- if (!$item = array_shift($this->queue)) {
- throw new \OutOfBoundsException('Mock queue is empty');
- } elseif ($item instanceof RequestException) {
- throw $item;
- }
-
- // Emulate reading a response body
- $request = $event->getRequest();
- if ($this->readBodies && $request->getBody()) {
- while (!$request->getBody()->eof()) {
- $request->getBody()->read(8096);
- }
- }
-
- $event->intercept($item);
- }
-
- public function count()
- {
- return count($this->queue);
- }
-
- /**
- * Add a response to the end of the queue
- *
- * @param string|ResponseInterface $response Response or path to response file
- *
- * @return self
- * @throws \InvalidArgumentException if a string or Response is not passed
- */
- public function addResponse($response)
- {
- if (is_string($response)) {
- $response = file_exists($response)
- ? $this->factory->fromMessage(file_get_contents($response))
- : $this->factory->fromMessage($response);
- } elseif (!($response instanceof ResponseInterface)) {
- throw new \InvalidArgumentException('Response must a message '
- . 'string, response object, or path to a file');
- }
-
- $this->queue[] = $response;
-
- return $this;
- }
-
- /**
- * Add an exception to the end of the queue
- *
- * @param RequestException $e Exception to throw when the request is executed
- *
- * @return self
- */
- public function addException(RequestException $e)
- {
- $this->queue[] = $e;
-
- return $this;
- }
-
- /**
- * Add multiple items to the queue
- *
- * @param array $items Items to add
- */
- public function addMultiple(array $items)
- {
- foreach ($items as $item) {
- if ($item instanceof RequestException) {
- $this->addException($item);
- } else {
- $this->addResponse($item);
- }
- }
- }
-
- /**
- * Clear the queue
- */
- public function clearQueue()
- {
- $this->queue = [];
- }
-}
diff --git a/src/Subscriber/Prepare.php b/src/Subscriber/Prepare.php
deleted file mode 100644
index b5ed4e260..000000000
--- a/src/Subscriber/Prepare.php
+++ /dev/null
@@ -1,130 +0,0 @@
- ['onBefore', RequestEvents::PREPARE_REQUEST]];
- }
-
- public function onBefore(BeforeEvent $event)
- {
- $request = $event->getRequest();
-
- // Set the appropriate Content-Type for a request if one is not set and
- // there are form fields
- if (!($body = $request->getBody())) {
- return;
- }
-
- $this->addContentLength($request, $body);
-
- if ($body instanceof AppliesHeadersInterface) {
- // Synchronize the body with the request headers
- $body->applyRequestHeaders($request);
- } elseif (!$request->hasHeader('Content-Type')) {
- $this->addContentType($request, $body);
- }
-
- $this->addExpectHeader($request, $body);
- }
-
- private function addContentType(
- RequestInterface $request,
- StreamInterface $body
- ) {
- if (!($uri = $body->getMetadata('uri'))) {
- return;
- }
-
- // Guess the content-type based on the stream's "uri" metadata value.
- // The file extension is used to determine the appropriate mime-type.
- if ($contentType = Mimetypes::getInstance()->fromFilename($uri)) {
- $request->setHeader('Content-Type', $contentType);
- }
- }
-
- private function addContentLength(
- RequestInterface $request,
- StreamInterface $body
- ) {
- // Set the Content-Length header if it can be determined, and never
- // send a Transfer-Encoding: chunked and Content-Length header in
- // the same request.
- if ($request->hasHeader('Content-Length')) {
- // Remove transfer-encoding if content-length is set.
- $request->removeHeader('Transfer-Encoding');
- return;
- }
-
- if ($request->hasHeader('Transfer-Encoding')) {
- return;
- }
-
- if (null !== ($size = $body->getSize())) {
- $request->setHeader('Content-Length', $size);
- $request->removeHeader('Transfer-Encoding');
- } elseif ('1.1' == $request->getProtocolVersion()) {
- // Use chunked Transfer-Encoding if there is no determinable
- // content-length header and we're using HTTP/1.1.
- $request->setHeader('Transfer-Encoding', 'chunked');
- $request->removeHeader('Content-Length');
- }
- }
-
- private function addExpectHeader(
- RequestInterface $request,
- StreamInterface $body
- ) {
- // Determine if the Expect header should be used
- if ($request->hasHeader('Expect')) {
- return;
- }
-
- $expect = $request->getConfig()['expect'];
-
- // Return if disabled or if you're not using HTTP/1.1
- if ($expect === false || $request->getProtocolVersion() !== '1.1') {
- return;
- }
-
- // The expect header is unconditionally enabled
- if ($expect === true) {
- $request->setHeader('Expect', '100-Continue');
- return;
- }
-
- // By default, send the expect header when the payload is > 1mb
- if ($expect === null) {
- $expect = 1048576;
- }
-
- // Always add if the body cannot be rewound, the size cannot be
- // determined, or the size is greater than the cutoff threshold
- $size = $body->getSize();
- if ($size === null || $size >= (int) $expect || !$body->isSeekable()) {
- $request->setHeader('Expect', '100-Continue');
- }
- }
-}
diff --git a/src/Subscriber/Redirect.php b/src/Subscriber/Redirect.php
deleted file mode 100644
index ff992268b..000000000
--- a/src/Subscriber/Redirect.php
+++ /dev/null
@@ -1,176 +0,0 @@
- ['onComplete', RequestEvents::REDIRECT_RESPONSE]];
- }
-
- /**
- * Rewind the entity body of the request if needed
- *
- * @param RequestInterface $redirectRequest
- * @throws CouldNotRewindStreamException
- */
- public static function rewindEntityBody(RequestInterface $redirectRequest)
- {
- // Rewind the entity body of the request if needed
- if ($body = $redirectRequest->getBody()) {
- // Only rewind the body if some of it has been read already, and
- // throw an exception if the rewind fails
- if ($body->tell() && !$body->seek(0)) {
- throw new CouldNotRewindStreamException(
- 'Unable to rewind the non-seekable request body after redirecting',
- $redirectRequest
- );
- }
- }
- }
-
- /**
- * Called when a request receives a redirect response
- *
- * @param CompleteEvent $event Event emitted
- * @throws TooManyRedirectsException
- */
- public function onComplete(CompleteEvent $event)
- {
- $response = $event->getResponse();
-
- if (substr($response->getStatusCode(), 0, 1) != '3'
- || !$response->hasHeader('Location')
- ) {
- return;
- }
-
- $request = $event->getRequest();
- $config = $request->getConfig();
-
- // Increment the redirect and initialize the redirect state.
- if ($redirectCount = $config['redirect_count']) {
- $config['redirect_count'] = ++$redirectCount;
- } else {
- $config['redirect_scheme'] = $request->getScheme();
- $config['redirect_count'] = $redirectCount = 1;
- }
-
- $max = $config->getPath('redirect/max') ?: 5;
-
- if ($redirectCount > $max) {
- throw new TooManyRedirectsException(
- "Will not follow more than {$redirectCount} redirects",
- $request
- );
- }
-
- $this->modifyRedirectRequest($request, $response);
- $event->retry();
- }
-
- private function modifyRedirectRequest(
- RequestInterface $request,
- ResponseInterface $response
- ) {
- $config = $request->getConfig();
- $protocols = $config->getPath('redirect/protocols') ?: ['http', 'https'];
-
- // Use a GET request if this is an entity enclosing request and we are
- // not forcing RFC compliance, but rather emulating what all browsers
- // would do.
- $statusCode = $response->getStatusCode();
- if ($statusCode == 303 ||
- ($statusCode <= 302 && $request->getBody() && !$config->getPath('redirect/strict'))
- ) {
- $request->setMethod('GET');
- $request->setBody(null);
- }
-
- $previousUrl = $request->getUrl();
- $this->setRedirectUrl($request, $response, $protocols);
- $this->rewindEntityBody($request);
-
- // Add the Referer header if it is told to do so and only
- // add the header if we are not redirecting from https to http.
- if ($config->getPath('redirect/referer')
- && ($request->getScheme() == 'https' || $request->getScheme() == $config['redirect_scheme'])
- ) {
- $url = Url::fromString($previousUrl);
- $url->setUsername(null);
- $url->setPassword(null);
- $request->setHeader('Referer', (string) $url);
- } else {
- $request->removeHeader('Referer');
- }
- }
-
- /**
- * Set the appropriate URL on the request based on the location header
- *
- * @param RequestInterface $request
- * @param ResponseInterface $response
- * @param array $protocols
- */
- private function setRedirectUrl(
- RequestInterface $request,
- ResponseInterface $response,
- array $protocols
- ) {
- $location = $response->getHeader('Location');
- $location = Url::fromString($location);
-
- // Combine location with the original URL if it is not absolute.
- if (!$location->isAbsolute()) {
- $originalUrl = Url::fromString($request->getUrl());
- // Remove query string parameters and just take what is present on
- // the redirect Location header
- $originalUrl->getQuery()->clear();
- $location = $originalUrl->combine($location);
- }
-
- // Ensure that the redirect URL is allowed based on the protocols.
- if (!in_array($location->getScheme(), $protocols)) {
- throw new BadResponseException(
- sprintf(
- 'Redirect URL, %s, does not use one of the allowed redirect protocols: %s',
- $location,
- implode(', ', $protocols)
- ),
- $request,
- $response
- );
- }
-
- $request->setUrl($location);
- }
-}
diff --git a/src/ToArrayInterface.php b/src/ToArrayInterface.php
deleted file mode 100644
index d57c0229a..000000000
--- a/src/ToArrayInterface.php
+++ /dev/null
@@ -1,15 +0,0 @@
-client = $client;
- $this->request = $request;
- $this->_future = $future;
- }
-}
diff --git a/src/Url.php b/src/Url.php
deleted file mode 100644
index a81bad2f0..000000000
--- a/src/Url.php
+++ /dev/null
@@ -1,595 +0,0 @@
- 80, 'https' => 443, 'ftp' => 21];
- private static $pathPattern = '/[^a-zA-Z0-9\-\._~!\$&\'\(\)\*\+,;=%:@\/]+|%(?![A-Fa-f0-9]{2})/';
- private static $queryPattern = '/[^a-zA-Z0-9\-\._~!\$\'\(\)\*\+,;%:@\/\?=&]+|%(?![A-Fa-f0-9]{2})/';
- /** @var Query|string Query part of the URL */
- private $query;
-
- /**
- * Factory method to create a new URL from a URL string
- *
- * @param string $url Full URL used to create a Url object
- *
- * @return Url
- * @throws \InvalidArgumentException
- */
- public static function fromString($url)
- {
- static $defaults = ['scheme' => null, 'host' => null,
- 'path' => null, 'port' => null, 'query' => null,
- 'user' => null, 'pass' => null, 'fragment' => null];
-
- if (false === ($parts = parse_url($url))) {
- throw new \InvalidArgumentException('Unable to parse malformed '
- . 'url: ' . $url);
- }
-
- $parts += $defaults;
-
- // Convert the query string into a Query object
- if ($parts['query'] || 0 !== strlen($parts['query'])) {
- $parts['query'] = Query::fromString($parts['query']);
- }
-
- return new static($parts['scheme'], $parts['host'], $parts['user'],
- $parts['pass'], $parts['port'], $parts['path'], $parts['query'],
- $parts['fragment']);
- }
-
- /**
- * Build a URL from parse_url parts. The generated URL will be a relative
- * URL if a scheme or host are not provided.
- *
- * @param array $parts Array of parse_url parts
- *
- * @return string
- */
- public static function buildUrl(array $parts)
- {
- $url = $scheme = '';
-
- if (!empty($parts['scheme'])) {
- $scheme = $parts['scheme'];
- $url .= $scheme . ':';
- }
-
- if (!empty($parts['host'])) {
- $url .= '//';
- if (isset($parts['user'])) {
- $url .= $parts['user'];
- if (isset($parts['pass'])) {
- $url .= ':' . $parts['pass'];
- }
- $url .= '@';
- }
-
- $url .= $parts['host'];
-
- // Only include the port if it is not the default port of the scheme
- if (isset($parts['port']) &&
- (!isset(self::$defaultPorts[$scheme]) ||
- $parts['port'] != self::$defaultPorts[$scheme])
- ) {
- $url .= ':' . $parts['port'];
- }
- }
-
- // Add the path component if present
- if (isset($parts['path']) && strlen($parts['path'])) {
- // Always ensure that the path begins with '/' if set and something
- // is before the path
- if (!empty($parts['host']) && $parts['path'][0] != '/') {
- $url .= '/';
- }
- $url .= $parts['path'];
- }
-
- // Add the query string if present
- if (isset($parts['query'])) {
- $queryStr = (string) $parts['query'];
- if ($queryStr || $queryStr === '0') {
- $url .= '?' . $queryStr;
- }
- }
-
- // Ensure that # is only added to the url if fragment contains anything.
- if (isset($parts['fragment'])) {
- $url .= '#' . $parts['fragment'];
- }
-
- return $url;
- }
-
- /**
- * Create a new URL from URL parts
- *
- * @param string $scheme Scheme of the URL
- * @param string $host Host of the URL
- * @param string $username Username of the URL
- * @param string $password Password of the URL
- * @param int $port Port of the URL
- * @param string $path Path of the URL
- * @param Query|array|string $query Query string of the URL
- * @param string $fragment Fragment of the URL
- */
- public function __construct(
- $scheme,
- $host,
- $username = null,
- $password = null,
- $port = null,
- $path = null,
- $query = null,
- $fragment = null
- ) {
- $this->scheme = $scheme;
- $this->host = $host;
- $this->port = $port;
- $this->username = $username;
- $this->password = $password;
- $this->fragment = $fragment;
-
- if ($query) {
- $this->setQuery($query);
- }
-
- $this->setPath($path);
- }
-
- /**
- * Clone the URL
- */
- public function __clone()
- {
- if ($this->query instanceof Query) {
- $this->query = clone $this->query;
- }
- }
-
- /**
- * Returns the URL as a URL string
- *
- * @return string
- */
- public function __toString()
- {
- return static::buildUrl($this->getParts());
- }
-
- /**
- * Get the parts of the URL as an array
- *
- * @return array
- */
- public function getParts()
- {
- return array(
- 'scheme' => $this->scheme,
- 'user' => $this->username,
- 'pass' => $this->password,
- 'host' => $this->host,
- 'port' => $this->port,
- 'path' => $this->path,
- 'query' => $this->query,
- 'fragment' => $this->fragment,
- );
- }
-
- /**
- * Set the host of the request.
- *
- * @param string $host Host to set (e.g. www.yahoo.com, yahoo.com)
- *
- * @return Url
- */
- public function setHost($host)
- {
- if (strpos($host, ':') === false) {
- $this->host = $host;
- } else {
- list($host, $port) = explode(':', $host);
- $this->host = $host;
- $this->setPort($port);
- }
- }
-
- /**
- * Get the host part of the URL
- *
- * @return string
- */
- public function getHost()
- {
- return $this->host;
- }
-
- /**
- * Set the scheme part of the URL (http, https, ftp, etc.)
- *
- * @param string $scheme Scheme to set
- */
- public function setScheme($scheme)
- {
- // Remove the default port if one is specified
- if ($this->port
- && isset(self::$defaultPorts[$this->scheme])
- && self::$defaultPorts[$this->scheme] == $this->port
- ) {
- $this->port = null;
- }
-
- $this->scheme = $scheme;
- }
-
- /**
- * Get the scheme part of the URL
- *
- * @return string
- */
- public function getScheme()
- {
- return $this->scheme;
- }
-
- /**
- * Set the port part of the URL
- *
- * @param int $port Port to set
- */
- public function setPort($port)
- {
- $this->port = $port;
- }
-
- /**
- * Get the port part of the URl.
- *
- * If no port was set, this method will return the default port for the
- * scheme of the URI.
- *
- * @return int|null
- */
- public function getPort()
- {
- if ($this->port) {
- return $this->port;
- } elseif (isset(self::$defaultPorts[$this->scheme])) {
- return self::$defaultPorts[$this->scheme];
- }
-
- return null;
- }
-
- /**
- * Set the path part of the URL.
- *
- * The provided URL is URL encoded as necessary.
- *
- * @param string $path Path string to set
- */
- public function setPath($path)
- {
- $this->path = self::encodePath($path);
- }
-
- /**
- * Removes dot segments from a URL
- * @link http://tools.ietf.org/html/rfc3986#section-5.2.4
- */
- public function removeDotSegments()
- {
- static $noopPaths = ['' => true, '/' => true, '*' => true];
- static $ignoreSegments = ['.' => true, '..' => true];
-
- if (isset($noopPaths[$this->path])) {
- return;
- }
-
- $results = [];
- $segments = $this->getPathSegments();
- foreach ($segments as $segment) {
- if ($segment == '..') {
- array_pop($results);
- } elseif (!isset($ignoreSegments[$segment])) {
- $results[] = $segment;
- }
- }
-
- $newPath = implode('/', $results);
-
- // Add the leading slash if necessary
- if (substr($this->path, 0, 1) === '/' &&
- substr($newPath, 0, 1) !== '/'
- ) {
- $newPath = '/' . $newPath;
- }
-
- // Add the trailing slash if necessary
- if ($newPath != '/' && isset($ignoreSegments[end($segments)])) {
- $newPath .= '/';
- }
-
- $this->path = $newPath;
- }
-
- /**
- * Add a relative path to the currently set path.
- *
- * @param string $relativePath Relative path to add
- */
- public function addPath($relativePath)
- {
- if ($relativePath != '/' &&
- is_string($relativePath) &&
- strlen($relativePath) > 0
- ) {
- // Add a leading slash if needed
- if ($relativePath[0] !== '/' &&
- substr($this->path, -1, 1) !== '/'
- ) {
- $relativePath = '/' . $relativePath;
- }
-
- $this->setPath($this->path . $relativePath);
- }
- }
-
- /**
- * Get the path part of the URL
- *
- * @return string
- */
- public function getPath()
- {
- return $this->path;
- }
-
- /**
- * Get the path segments of the URL as an array
- *
- * @return array
- */
- public function getPathSegments()
- {
- return explode('/', $this->path);
- }
-
- /**
- * Set the password part of the URL
- *
- * @param string $password Password to set
- */
- public function setPassword($password)
- {
- $this->password = $password;
- }
-
- /**
- * Get the password part of the URL
- *
- * @return null|string
- */
- public function getPassword()
- {
- return $this->password;
- }
-
- /**
- * Set the username part of the URL
- *
- * @param string $username Username to set
- */
- public function setUsername($username)
- {
- $this->username = $username;
- }
-
- /**
- * Get the username part of the URl
- *
- * @return null|string
- */
- public function getUsername()
- {
- return $this->username;
- }
-
- /**
- * Get the query part of the URL as a Query object
- *
- * @return Query
- */
- public function getQuery()
- {
- // Convert the query string to a query object if not already done.
- if (!$this->query instanceof Query) {
- $this->query = $this->query === null
- ? new Query()
- : Query::fromString($this->query);
- }
-
- return $this->query;
- }
-
- /**
- * Set the query part of the URL.
- *
- * You may provide a query string as a string and pass $rawString as true
- * to provide a query string that is not parsed until a call to getQuery()
- * is made. Setting a raw query string will still encode invalid characters
- * in a query string.
- *
- * @param Query|string|array $query Query string value to set. Can
- * be a string that will be parsed into a Query object, an array
- * of key value pairs, or a Query object.
- * @param bool $rawString Set to true when providing a raw query string.
- *
- * @throws \InvalidArgumentException
- */
- public function setQuery($query, $rawString = false)
- {
- if ($query instanceof Query) {
- $this->query = $query;
- } elseif (is_string($query)) {
- if (!$rawString) {
- $this->query = Query::fromString($query);
- } else {
- // Ensure the query does not have illegal characters.
- $this->query = preg_replace_callback(
- self::$queryPattern,
- [__CLASS__, 'encodeMatch'],
- $query
- );
- }
-
- } elseif (is_array($query)) {
- $this->query = new Query($query);
- } else {
- throw new \InvalidArgumentException('Query must be a Query, '
- . 'array, or string. Got ' . Core::describeType($query));
- }
- }
-
- /**
- * Get the fragment part of the URL
- *
- * @return null|string
- */
- public function getFragment()
- {
- return $this->fragment;
- }
-
- /**
- * Set the fragment part of the URL
- *
- * @param string $fragment Fragment to set
- */
- public function setFragment($fragment)
- {
- $this->fragment = $fragment;
- }
-
- /**
- * Check if this is an absolute URL
- *
- * @return bool
- */
- public function isAbsolute()
- {
- return $this->scheme && $this->host;
- }
-
- /**
- * Combine the URL with another URL and return a new URL instance.
- *
- * Follows the rules specific in RFC 3986 section 5.4.
- *
- * @param string $url Relative URL to combine with
- *
- * @return Url
- * @throws \InvalidArgumentException
- * @link http://tools.ietf.org/html/rfc3986#section-5.4
- */
- public function combine($url)
- {
- $url = static::fromString($url);
-
- // Use the more absolute URL as the base URL
- if (!$this->isAbsolute() && $url->isAbsolute()) {
- $url = $url->combine($this);
- }
-
- $parts = $url->getParts();
-
- // Passing a URL with a scheme overrides everything
- if ($parts['scheme']) {
- return clone $url;
- }
-
- // Setting a host overrides the entire rest of the URL
- if ($parts['host']) {
- return new static(
- $this->scheme,
- $parts['host'],
- $parts['user'],
- $parts['pass'],
- $parts['port'],
- $parts['path'],
- $parts['query'] instanceof Query
- ? clone $parts['query']
- : $parts['query'],
- $parts['fragment']
- );
- }
-
- if (!$parts['path'] && $parts['path'] !== '0') {
- // The relative URL has no path, so check if it is just a query
- $path = $this->path ?: '';
- $query = $parts['query'] ?: $this->query;
- } else {
- $query = $parts['query'];
- if ($parts['path'][0] == '/' || !$this->path) {
- // Overwrite the existing path if the rel path starts with "/"
- $path = $parts['path'];
- } else {
- // If the relative URL does not have a path or the base URL
- // path does not end in a "/" then overwrite the existing path
- // up to the last "/"
- $path = substr($this->path, 0, strrpos($this->path, '/') + 1) . $parts['path'];
- }
- }
-
- $result = new self(
- $this->scheme,
- $this->host,
- $this->username,
- $this->password,
- $this->port,
- $path,
- $query instanceof Query ? clone $query : $query,
- $parts['fragment']
- );
-
- if ($path) {
- $result->removeDotSegments();
- }
-
- return $result;
- }
-
- /**
- * Encodes the path part of a URL without double-encoding percent-encoded
- * key value pairs.
- *
- * @param string $path Path to encode
- *
- * @return string
- */
- public static function encodePath($path)
- {
- static $cb = [__CLASS__, 'encodeMatch'];
- return preg_replace_callback(self::$pathPattern, $cb, $path);
- }
-
- private static function encodeMatch(array $match)
- {
- return rawurlencode($match[0]);
- }
-}
diff --git a/src/Utils.php b/src/Utils.php
deleted file mode 100644
index 8b6de0b46..000000000
--- a/src/Utils.php
+++ /dev/null
@@ -1,151 +0,0 @@
-expand($template, $variables);
- }
-
- /**
- * Wrapper for JSON decode that implements error detection with helpful
- * error messages.
- *
- * @param string $json JSON data to parse
- * @param bool $assoc When true, returned objects will be converted
- * into associative arrays.
- * @param int $depth User specified recursion depth.
- * @param int $options Bitmask of JSON decode options.
- *
- * @return mixed
- * @throws \InvalidArgumentException if the JSON cannot be parsed.
- * @link http://www.php.net/manual/en/function.json-decode.php
- */
- public static function jsonDecode($json, $assoc = false, $depth = 512, $options = 0)
- {
- static $jsonErrors = [
- JSON_ERROR_DEPTH => 'JSON_ERROR_DEPTH - Maximum stack depth exceeded',
- JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH - Underflow or the modes mismatch',
- JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR - Unexpected control character found',
- JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX - Syntax error, malformed JSON',
- JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8 - Malformed UTF-8 characters, possibly incorrectly encoded'
- ];
-
- $data = \json_decode($json, $assoc, $depth, $options);
-
- if (JSON_ERROR_NONE !== json_last_error()) {
- $last = json_last_error();
- throw new \InvalidArgumentException(
- 'Unable to parse JSON data: '
- . (isset($jsonErrors[$last])
- ? $jsonErrors[$last]
- : 'Unknown error')
- );
- }
-
- return $data;
- }
-}
diff --git a/src/functions.php b/src/functions.php
new file mode 100644
index 000000000..298192c17
--- /dev/null
+++ b/src/functions.php
@@ -0,0 +1,520 @@
+expand($template, $variables);
+}
+
+/**
+ * Wrapper for JSON decode that implements error detection with helpful
+ * error messages.
+ *
+ * @param string $json JSON data to parse
+ * @param bool $assoc When true, returned objects will be converted
+ * into associative arrays.
+ * @param int $depth User specified recursion depth.
+ * @param int $options Bitmask of JSON decode options.
+ *
+ * @return mixed
+ * @throws \InvalidArgumentException if the JSON cannot be parsed.
+ * @link http://www.php.net/manual/en/function.json-decode.php
+ */
+function json_decode($json, $assoc = false, $depth = 512, $options = 0)
+{
+ static $jsonErrors = [
+ JSON_ERROR_DEPTH => 'JSON_ERROR_DEPTH - Maximum stack depth exceeded',
+ JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH - Underflow or the modes mismatch',
+ JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR - Unexpected control character found',
+ JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX - Syntax error, malformed JSON',
+ JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8 - Malformed UTF-8 characters, possibly incorrectly encoded'
+ ];
+
+ $data = \json_decode($json, $assoc, $depth, $options);
+
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ $last = json_last_error();
+ throw new \InvalidArgumentException(
+ 'Unable to parse JSON data: '
+ . (isset($jsonErrors[$last])
+ ? $jsonErrors[$last]
+ : 'Unknown error')
+ );
+ }
+
+ return $data;
+}
+
+/**
+ * Returns the default cacert bundle for the current system.
+ *
+ * First, the openssl.cafile and curl.cainfo php.ini settings are checked.
+ * If those settings are not configured, then the common locations for
+ * bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X
+ * and Windows are checked. If any of these file locations are found on
+ * disk, they will be utilized.
+ *
+ * Note: the result of this function is cached for subsequent calls.
+ *
+ * @return string
+ * @throws \RuntimeException if no bundle can be found.
+ */
+function default_ca_bundle()
+{
+ static $cached = null;
+ static $cafiles = [
+ // Red Hat, CentOS, Fedora (provided by the ca-certificates package)
+ '/etc/pki/tls/certs/ca-bundle.crt',
+ // Ubuntu, Debian (provided by the ca-certificates package)
+ '/etc/ssl/certs/ca-certificates.crt',
+ // FreeBSD (provided by the ca_root_nss package)
+ '/usr/local/share/certs/ca-root-nss.crt',
+ // OS X provided by homebrew (using the default path)
+ '/usr/local/etc/openssl/cert.pem',
+ // Windows?
+ 'C:\\windows\\system32\\curl-ca-bundle.crt',
+ 'C:\\windows\\curl-ca-bundle.crt',
+ ];
+
+ if ($cached) {
+ return $cached;
+ }
+
+ if ($ca = ini_get('openssl.cafile')) {
+ return $cached = $ca;
+ }
+
+ if ($ca = ini_get('curl.cainfo')) {
+ return $cached = $ca;
+ }
+
+ foreach ($cafiles as $filename) {
+ if (file_exists($filename)) {
+ return $cached = $filename;
+ }
+ }
+
+ throw new \RuntimeException(<<< EOT
+No system CA bundle could be found in any of the the common system locations.
+PHP versions earlier than 5.6 are not properly configured to use the system's
+CA bundle by default. In order to verify peer certificates, you will need to
+supply the path on disk to a certificate bundle to the 'verify' request
+option: http://docs.guzzlephp.org/en/latest/clients.html#verify. If you do not
+need a specific certificate bundle, then Mozilla provides a commonly used CA
+bundle which can be downloaded here (provided by the maintainer of cURL):
+https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt. Once
+you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP
+ini setting to point to the path to the file, allowing you to omit the 'verify'
+request option. See http://curl.haxx.se/docs/sslcerts.html for more
+information.
+EOT
+ );
+}
+
+/**
+ * Returns the string representation of an HTTP message.
+ *
+ * @param MessageInterface|PromiseInterface $message Message to convert to a string.
+ *
+ * @return string
+ */
+function str($message)
+{
+ if ($message instanceof PromiseInterface) {
+ $message = $message->wait();
+ }
+
+ if ($message instanceof RequestInterface) {
+ $msg = trim($message->getMethod() . ' '
+ . $message->getRequestTarget())
+ . ' HTTP/' . $message->getProtocolVersion();
+ if (!$message->hasHeader('host')) {
+ $msg .= "\r\nHost: " . $message->getUri()->getHost();
+ }
+ } elseif ($message instanceof ResponseInterface) {
+ $msg = 'HTTP/' . $message->getProtocolVersion() . ' '
+ . $message->getStatusCode() . ' '
+ . $message->getReasonPhrase();
+ } else {
+ throw new \InvalidArgumentException('Unknown message type');
+ }
+
+ foreach ($message->getHeaders() as $name => $values) {
+ $msg .= "\r\n{$name}: " . implode(', ', $values);
+ }
+
+ return "{$msg}\r\n\r\n" . $message->getBody();
+}
+
+/**
+ * Parse an array of header values containing ";" separated data into an
+ * array of associative arrays representing the header key value pair
+ * data of the header. When a parameter does not contain a value, but just
+ * contains a key, this function will inject a key with a '' string value.
+ *
+ * @param string|array $header Header to parse into components.
+ *
+ * @return array Returns the parsed header values.
+ */
+function parse_header($header)
+{
+ static $trimmed = "\"' \n\t\r";
+ $params = $matches = [];
+
+ foreach (normalize_header($header) as $val) {
+ $part = [];
+ foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
+ if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
+ $m = $matches[0];
+ if (isset($m[1])) {
+ $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
+ } else {
+ $part[] = trim($m[0], $trimmed);
+ }
+ }
+ }
+ if ($part) {
+ $params[] = $part;
+ }
+ }
+
+ return $params;
+}
+
+/**
+ * Converts an array of header values that may contain comma separated
+ * headers into an array of headers with no comma separated values.
+ *
+ * @param string|array $header Header to normalize.
+ *
+ * @return array Returns the normalized header field values.
+ */
+function normalize_header($header)
+{
+ if (!is_array($header)) {
+ return array_map('trim', explode(',', $header));
+ }
+
+ $result = [];
+ foreach ($header as $value) {
+ foreach ((array) $value as $v) {
+ if (strpos($v, ',') === false) {
+ $result[] = $v;
+ continue;
+ }
+ foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) {
+ $result[] = trim($vv);
+ }
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Debug function used to describe the provided value type and class.
+ *
+ * @param mixed $input
+ *
+ * @return string Returns a string containing the type of the variable and
+ * if a class is provided, the class name.
+ */
+function describe_type($input)
+{
+ switch (gettype($input)) {
+ case 'object':
+ return 'object(' . get_class($input) . ')';
+ case 'array':
+ return 'array(' . count($input) . ')';
+ default:
+ ob_start();
+ var_dump($input);
+ // normalize float vs double
+ return str_replace('double(', 'float(', rtrim(ob_get_clean()));
+ }
+}
+
+/**
+ * Parses an array of header lines into an associative array of headers.
+ *
+ * @param array $lines Header lines array of strings in the following
+ * format: "Name: Value"
+ * @return array
+ */
+function headers_from_lines($lines)
+{
+ $headers = [];
+
+ foreach ($lines as $line) {
+ $parts = explode(':', $line, 2);
+ $headers[trim($parts[0])][] = isset($parts[1])
+ ? trim($parts[1])
+ : null;
+ }
+
+ return $headers;
+}
+
+/**
+ * Returns a debug stream based on the provided variable.
+ *
+ * @param mixed $value Optional value
+ *
+ * @return resource
+ */
+function get_debug_resource($value = null)
+{
+ if (is_resource($value)) {
+ return $value;
+ } elseif (defined('STDOUT')) {
+ return STDOUT;
+ }
+
+ return fopen('php://output', 'w');
+}
+
+/**
+ * Clone and modify a request with the given changes.
+ *
+ * The changes can be one of:
+ * - method: (string) Changes the HTTP method.
+ * - set_headers: (array) Sets the given headers.
+ * - remove_headers: (array) Remove the given headers.
+ * - body: (mixed) Sets the given body.
+ * - uri: (UriInterface) Set the URI.
+ * - query: (string) Set the query string value of the URI.
+ * - version: (string) Set the protocol version.
+ *
+ * @param RequestInterface $request Request to clone and modify.
+ * @param array $changes Changes to apply.
+ *
+ * @return RequestInterface
+ */
+function modify_request(RequestInterface $request, array $changes)
+{
+ if (!$changes) {
+ return $request;
+ }
+
+ $headers = $request->getHeaders();
+ if (isset($changes['remove_headers'])) {
+ foreach ($changes['remove_headers'] as $header) {
+ unset($headers[$header]);
+ }
+ }
+
+ if (isset($changes['set_headers'])) {
+ $headers = $changes['set_headers'] + $headers;
+ }
+
+ $uri = isset($changes['uri']) ? $changes['uri'] : $request->getUri();
+ if (isset($changes['query'])) {
+ $uri = $uri->withQuery($changes['query']);
+ }
+
+ return new Request(
+ isset($changes['method']) ? $changes['method'] : $request->getMethod(),
+ $uri,
+ $headers,
+ isset($changes['body']) ? $changes['body'] : $request->getBody(),
+ isset($changes['version'])
+ ? $changes['version']
+ : $request->getProtocolVersion()
+ );
+}
+
+/**
+ * Create a default handler to use based on the environment
+ *
+ * @throws \RuntimeException if no viable Handler is available.
+ * @return callable Returns the best handler for the given system.
+ */
+function default_handler()
+{
+ $handler = null;
+ if (extension_loaded('curl')) {
+ $config = [];
+ if ($maxHandles = getenv('MUZZLE_CURL_MAX_HANDLES')) {
+ $config['max_handles'] = $maxHandles;
+ }
+ $handler = new CurlMultiHandler($config);
+ if (function_exists('curl_reset')) {
+ $handler = Proxy::wrapSync($handler, new CurlHandler());
+ }
+ }
+
+ if (ini_get('allow_url_fopen')) {
+ if ($handler) {
+ $handler = Proxy::wrapStreaming($handler, new StreamHandler());
+ } else {
+ $handler = new StreamHandler();
+ }
+ } elseif (!$handler) {
+ throw new \RuntimeException('GuzzleHttp requires cURL, the '
+ . 'allow_url_fopen ini setting, or a custom HTTP handler.');
+ }
+
+ return $handler;
+}
+
+/**
+ * Get the default User-Agent string to use with Guzzle
+ *
+ * @return string
+ */
+function default_user_agent()
+{
+ static $defaultAgent = '';
+
+ if (!$defaultAgent) {
+ $defaultAgent = 'GuzzleHttp/' . Client::VERSION;
+ if (extension_loaded('curi')) {
+ $defaultAgent .= ' curi/' . \curl_version()['version'];
+ }
+ $defaultAgent .= ' PHP/' . PHP_VERSION;
+ }
+
+ return $defaultAgent;
+}
+
+/**
+ * Wait on multiple promises
+ *
+ * @param PromiseInterface[] $promises Promise to await.
+ *
+ * @return array Returns the responses
+ */
+function wait_all(array $promises)
+{
+ $results = [];
+ foreach ($promises as $promise) {
+ $results[] = $promise->wait();
+ }
+
+ return $results;
+}
+
+/**
+ * Attempts to rewind a message body and throws an exception on failure.
+ *
+ * @param MessageInterface $message Message to rewind
+ *
+ * @throws SeekException
+ */
+function rewind_body(MessageInterface $message)
+{
+ $body = $message->getBody();
+ if ($body->tell() && !$body->rewind()) {
+ throw new SeekException($body, 0);
+ }
+}
diff --git a/tests/BatchResultsTest.php b/tests/BatchResultsTest.php
deleted file mode 100644
index 080d44c01..000000000
--- a/tests/BatchResultsTest.php
+++ /dev/null
@@ -1,58 +0,0 @@
-assertCount(3, $batch);
- $this->assertEquals([$a, $b, $c], $batch->getKeys());
- $this->assertEquals([$hash[$c]], $batch->getFailures());
- $this->assertEquals(['1', '2'], $batch->getSuccessful());
- $this->assertEquals('1', $batch->getResult($a));
- $this->assertNull($batch->getResult(new \stdClass()));
- $this->assertTrue(isset($batch[0]));
- $this->assertFalse(isset($batch[10]));
- $this->assertEquals('1', $batch[0]);
- $this->assertEquals('2', $batch[1]);
- $this->assertNull($batch[100]);
- $this->assertInstanceOf('Exception', $batch[2]);
-
- $results = iterator_to_array($batch);
- $this->assertEquals(['1', '2', $hash[$c]], $results);
- }
-
- /**
- * @expectedException \RuntimeException
- */
- public function testCannotSetByIndex()
- {
- $hash = new \SplObjectStorage();
- $batch = new BatchResults($hash);
- $batch[10] = 'foo';
- }
-
- /**
- * @expectedException \RuntimeException
- */
- public function testCannotUnsetByIndex()
- {
- $hash = new \SplObjectStorage();
- $batch = new BatchResults($hash);
- unset($batch[10]);
- }
-}
diff --git a/tests/ClientTest.php b/tests/ClientTest.php
deleted file mode 100644
index 913e535b8..000000000
--- a/tests/ClientTest.php
+++ /dev/null
@@ -1,624 +0,0 @@
-ma = function () {
- throw new \RuntimeException('Should not have been called.');
- };
- }
-
- public function testProvidesDefaultUserAgent()
- {
- $ua = Client::getDefaultUserAgent();
- $this->assertEquals(1, preg_match('#^Guzzle/.+ curl/.+ PHP/.+$#', $ua));
- }
-
- public function testUsesDefaultDefaultOptions()
- {
- $client = new Client();
- $this->assertTrue($client->getDefaultOption('allow_redirects'));
- $this->assertTrue($client->getDefaultOption('exceptions'));
- $this->assertTrue($client->getDefaultOption('verify'));
- }
-
- public function testUsesProvidedDefaultOptions()
- {
- $client = new Client([
- 'defaults' => [
- 'allow_redirects' => false,
- 'query' => ['foo' => 'bar']
- ]
- ]);
- $this->assertFalse($client->getDefaultOption('allow_redirects'));
- $this->assertTrue($client->getDefaultOption('exceptions'));
- $this->assertTrue($client->getDefaultOption('verify'));
- $this->assertEquals(['foo' => 'bar'], $client->getDefaultOption('query'));
- }
-
- public function testCanSpecifyBaseUrl()
- {
- $this->assertSame('', (new Client())->getBaseUrl());
- $this->assertEquals('http://foo', (new Client([
- 'base_url' => 'http://foo'
- ]))->getBaseUrl());
- }
-
- public function testCanSpecifyBaseUrlUriTemplate()
- {
- $client = new Client(['base_url' => ['http://foo.com/{var}/', ['var' => 'baz']]]);
- $this->assertEquals('http://foo.com/baz/', $client->getBaseUrl());
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testValidatesUriTemplateValue()
- {
- new Client(['base_url' => ['http://foo.com/']]);
- }
-
- /**
- * @expectedException \Exception
- * @expectedExceptionMessage Foo
- */
- public function testCanSpecifyHandler()
- {
- $client = new Client(['handler' => function () {
- throw new \Exception('Foo');
- }]);
- $client->get('http://httpbin.org');
- }
-
- /**
- * @expectedException \Exception
- * @expectedExceptionMessage Foo
- */
- public function testCanSpecifyHandlerAsAdapter()
- {
- $client = new Client(['adapter' => function () {
- throw new \Exception('Foo');
- }]);
- $client->get('http://httpbin.org');
- }
-
- /**
- * @expectedException \Exception
- * @expectedExceptionMessage Foo
- */
- public function testCanSpecifyMessageFactory()
- {
- $factory = $this->getMockBuilder('GuzzleHttp\Message\MessageFactoryInterface')
- ->setMethods(['createRequest'])
- ->getMockForAbstractClass();
- $factory->expects($this->once())
- ->method('createRequest')
- ->will($this->throwException(new \Exception('Foo')));
- $client = new Client(['message_factory' => $factory]);
- $client->get();
- }
-
- public function testCanSpecifyEmitter()
- {
- $emitter = $this->getMockBuilder('GuzzleHttp\Event\EmitterInterface')
- ->setMethods(['listeners'])
- ->getMockForAbstractClass();
- $emitter->expects($this->once())
- ->method('listeners')
- ->will($this->returnValue('foo'));
-
- $client = new Client(['emitter' => $emitter]);
- $this->assertEquals('foo', $client->getEmitter()->listeners());
- }
-
- public function testAddsDefaultUserAgentHeaderWithDefaultOptions()
- {
- $client = new Client(['defaults' => ['allow_redirects' => false]]);
- $this->assertFalse($client->getDefaultOption('allow_redirects'));
- $this->assertEquals(
- ['User-Agent' => Client::getDefaultUserAgent()],
- $client->getDefaultOption('headers')
- );
- }
-
- public function testAddsDefaultUserAgentHeaderWithoutDefaultOptions()
- {
- $client = new Client();
- $this->assertEquals(
- ['User-Agent' => Client::getDefaultUserAgent()],
- $client->getDefaultOption('headers')
- );
- }
-
- private function getRequestClient()
- {
- $client = $this->getMockBuilder('GuzzleHttp\Client')
- ->setMethods(['send'])
- ->getMock();
- $client->expects($this->once())
- ->method('send')
- ->will($this->returnArgument(0));
-
- return $client;
- }
-
- public function requestMethodProvider()
- {
- return [
- ['GET', false],
- ['HEAD', false],
- ['DELETE', false],
- ['OPTIONS', false],
- ['POST', 'foo'],
- ['PUT', 'foo'],
- ['PATCH', 'foo']
- ];
- }
-
- /**
- * @dataProvider requestMethodProvider
- */
- public function testClientProvidesMethodShortcut($method, $body)
- {
- $client = $this->getRequestClient();
- if ($body) {
- $request = $client->{$method}('http://foo.com', [
- 'headers' => ['X-Baz' => 'Bar'],
- 'body' => $body,
- 'query' => ['a' => 'b']
- ]);
- } else {
- $request = $client->{$method}('http://foo.com', [
- 'headers' => ['X-Baz' => 'Bar'],
- 'query' => ['a' => 'b']
- ]);
- }
- $this->assertEquals($method, $request->getMethod());
- $this->assertEquals('Bar', $request->getHeader('X-Baz'));
- $this->assertEquals('a=b', $request->getQuery());
- if ($body) {
- $this->assertEquals($body, $request->getBody());
- }
- }
-
- public function testClientMergesDefaultOptionsWithRequestOptions()
- {
- $f = $this->getMockBuilder('GuzzleHttp\Message\MessageFactoryInterface')
- ->setMethods(array('createRequest'))
- ->getMockForAbstractClass();
-
- $o = null;
- // Intercept the creation
- $f->expects($this->once())
- ->method('createRequest')
- ->will($this->returnCallback(
- function ($method, $url, array $options = []) use (&$o) {
- $o = $options;
- return (new MessageFactory())->createRequest($method, $url, $options);
- }
- ));
-
- $client = new Client([
- 'message_factory' => $f,
- 'defaults' => [
- 'headers' => ['Foo' => 'Bar'],
- 'query' => ['baz' => 'bam'],
- 'exceptions' => false
- ]
- ]);
-
- $request = $client->createRequest('GET', 'http://foo.com?a=b', [
- 'headers' => ['Hi' => 'there', '1' => 'one'],
- 'allow_redirects' => false,
- 'query' => ['t' => 1]
- ]);
-
- $this->assertFalse($o['allow_redirects']);
- $this->assertFalse($o['exceptions']);
- $this->assertEquals('Bar', $request->getHeader('Foo'));
- $this->assertEquals('there', $request->getHeader('Hi'));
- $this->assertEquals('one', $request->getHeader('1'));
- $this->assertEquals('a=b&baz=bam&t=1', $request->getQuery());
- }
-
- public function testClientMergesDefaultHeadersCaseInsensitively()
- {
- $client = new Client(['defaults' => ['headers' => ['Foo' => 'Bar']]]);
- $request = $client->createRequest('GET', 'http://foo.com?a=b', [
- 'headers' => ['foo' => 'custom', 'user-agent' => 'test']
- ]);
- $this->assertEquals('test', $request->getHeader('User-Agent'));
- $this->assertEquals('custom', $request->getHeader('Foo'));
- }
-
- public function testCanOverrideDefaultOptionWithNull()
- {
- $client = new Client(['defaults' => ['proxy' => 'invalid!']]);
- $request = $client->createRequest('GET', 'http://foo.com?a=b', [
- 'proxy' => null
- ]);
- $this->assertFalse($request->getConfig()->hasKey('proxy'));
- }
-
- public function testDoesNotOverwriteExistingUA()
- {
- $client = new Client(['defaults' => [
- 'headers' => ['User-Agent' => 'test']
- ]]);
- $this->assertEquals(
- ['User-Agent' => 'test'],
- $client->getDefaultOption('headers')
- );
- }
-
- public function testUsesBaseUrlWhenNoUrlIsSet()
- {
- $client = new Client(['base_url' => 'http://www.foo.com/baz?bam=bar']);
- $this->assertEquals(
- 'http://www.foo.com/baz?bam=bar',
- $client->createRequest('GET')->getUrl()
- );
- }
-
- public function testUsesBaseUrlCombinedWithProvidedUrl()
- {
- $client = new Client(['base_url' => 'http://www.foo.com/baz?bam=bar']);
- $this->assertEquals(
- 'http://www.foo.com/bar/bam',
- $client->createRequest('GET', 'bar/bam')->getUrl()
- );
- }
-
- public function testFalsyPathsAreCombinedWithBaseUrl()
- {
- $client = new Client(['base_url' => 'http://www.foo.com/baz?bam=bar']);
- $this->assertEquals(
- 'http://www.foo.com/0',
- $client->createRequest('GET', '0')->getUrl()
- );
- }
-
- public function testUsesBaseUrlCombinedWithProvidedUrlViaUriTemplate()
- {
- $client = new Client(['base_url' => 'http://www.foo.com/baz?bam=bar']);
- $this->assertEquals(
- 'http://www.foo.com/bar/123',
- $client->createRequest('GET', ['bar/{bam}', ['bam' => '123']])->getUrl()
- );
- }
-
- public function testSettingAbsoluteUrlOverridesBaseUrl()
- {
- $client = new Client(['base_url' => 'http://www.foo.com/baz?bam=bar']);
- $this->assertEquals(
- 'http://www.foo.com/foo',
- $client->createRequest('GET', '/foo')->getUrl()
- );
- }
-
- public function testSettingAbsoluteUriTemplateOverridesBaseUrl()
- {
- $client = new Client(['base_url' => 'http://www.foo.com/baz?bam=bar']);
- $this->assertEquals(
- 'http://goo.com/1',
- $client->createRequest(
- 'GET',
- ['http://goo.com/{bar}', ['bar' => '1']]
- )->getUrl()
- );
- }
-
- public function testCanSetRelativeUrlStartingWithHttp()
- {
- $client = new Client(['base_url' => 'http://www.foo.com']);
- $this->assertEquals(
- 'http://www.foo.com/httpfoo',
- $client->createRequest('GET', 'httpfoo')->getUrl()
- );
- }
-
- public function testClientSendsRequests()
- {
- $mock = new MockHandler(['status' => 200, 'headers' => []]);
- $client = new Client(['handler' => $mock]);
- $response = $client->get('http://test.com');
- $this->assertEquals(200, $response->getStatusCode());
- $this->assertEquals('http://test.com', $response->getEffectiveUrl());
- }
-
- public function testSendingRequestCanBeIntercepted()
- {
- $response = new Response(200);
- $client = new Client(['handler' => $this->ma]);
- $client->getEmitter()->on(
- 'before',
- function (BeforeEvent $e) use ($response) {
- $e->intercept($response);
- }
- );
- $this->assertSame($response, $client->get('http://test.com'));
- $this->assertEquals('http://test.com', $response->getEffectiveUrl());
- }
-
- /**
- * @expectedException \GuzzleHttp\Exception\RequestException
- * @expectedExceptionMessage Argument 1 passed to GuzzleHttp\Message\FutureResponse::proxy() must implement interface GuzzleHttp\Ring\Future\FutureInterface
- */
- public function testEnsuresResponseIsPresentAfterSending()
- {
- $handler = function () {};
- $client = new Client(['handler' => $handler]);
- $client->get('http://httpbin.org');
- }
-
- /**
- * @expectedException \GuzzleHttp\Exception\RequestException
- * @expectedExceptionMessage Waiting did not resolve future
- */
- public function testEnsuresResponseIsPresentAfterDereferencing()
- {
- $deferred = new Deferred();
- $handler = new MockHandler(function () use ($deferred) {
- return new FutureArray(
- $deferred->promise(),
- function () {}
- );
- });
- $client = new Client(['handler' => $handler]);
- $response = $client->get('http://httpbin.org');
- $response->wait();
- }
-
- public function testClientHandlesErrorsDuringBeforeSend()
- {
- $client = new Client();
- $client->getEmitter()->on('before', function ($e) {
- throw new \Exception('foo');
- });
- $client->getEmitter()->on('error', function (ErrorEvent $e) {
- $e->intercept(new Response(200));
- });
- $this->assertEquals(
- 200,
- $client->get('http://test.com')->getStatusCode()
- );
- }
-
- /**
- * @expectedException \GuzzleHttp\Exception\RequestException
- * @expectedExceptionMessage foo
- */
- public function testClientHandlesErrorsDuringBeforeSendAndThrowsIfUnhandled()
- {
- $client = new Client();
- $client->getEmitter()->on('before', function (BeforeEvent $e) {
- throw new RequestException('foo', $e->getRequest());
- });
- $client->get('http://httpbin.org');
- }
-
- /**
- * @expectedException \GuzzleHttp\Exception\RequestException
- * @expectedExceptionMessage foo
- */
- public function testClientWrapsExceptions()
- {
- $client = new Client();
- $client->getEmitter()->on('before', function (BeforeEvent $e) {
- throw new \Exception('foo');
- });
- $client->get('http://httpbin.org');
- }
-
- public function testCanInjectResponseForFutureError()
- {
- $calledFuture = false;
- $deferred = new Deferred();
- $future = new FutureArray(
- $deferred->promise(),
- function () use ($deferred, &$calledFuture) {
- $calledFuture = true;
- $deferred->resolve(['error' => new \Exception('Noo!')]);
- }
- );
- $mock = new MockHandler($future);
- $client = new Client(['handler' => $mock]);
- $called = 0;
- $response = $client->get('http://localhost:123/foo', [
- 'future' => true,
- 'events' => [
- 'error' => function (ErrorEvent $e) use (&$called) {
- $called++;
- $e->intercept(new Response(200));
- }
- ]
- ]);
- $this->assertEquals(0, $called);
- $this->assertInstanceOf('GuzzleHttp\Message\FutureResponse', $response);
- $this->assertEquals(200, $response->getStatusCode());
- $this->assertTrue($calledFuture);
- $this->assertEquals(1, $called);
- }
-
- public function testCanReturnFutureResults()
- {
- $called = false;
- $deferred = new Deferred();
- $future = new FutureArray(
- $deferred->promise(),
- function () use ($deferred, &$called) {
- $called = true;
- $deferred->resolve(['status' => 201, 'headers' => []]);
- }
- );
- $mock = new MockHandler($future);
- $client = new Client(['handler' => $mock]);
- $response = $client->get('http://localhost:123/foo', ['future' => true]);
- $this->assertFalse($called);
- $this->assertInstanceOf('GuzzleHttp\Message\FutureResponse', $response);
- $this->assertEquals(201, $response->getStatusCode());
- $this->assertTrue($called);
- }
-
- public function testThrowsExceptionsWhenDereferenced()
- {
- $calledFuture = false;
- $deferred = new Deferred();
- $future = new FutureArray(
- $deferred->promise(),
- function () use ($deferred, &$calledFuture) {
- $calledFuture = true;
- $deferred->resolve(['error' => new \Exception('Noop!')]);
- }
- );
- $client = new Client(['handler' => new MockHandler($future)]);
- try {
- $res = $client->get('http://localhost:123/foo', ['future' => true]);
- $res->wait();
- $this->fail('Did not throw');
- } catch (RequestException $e) {
- $this->assertEquals(1, $calledFuture);
- }
- }
-
- /**
- * @expectedExceptionMessage Noo!
- * @expectedException \GuzzleHttp\Exception\RequestException
- */
- public function testThrowsExceptionsSynchronously()
- {
- $client = new Client([
- 'handler' => new MockHandler(['error' => new \Exception('Noo!')])
- ]);
- $client->get('http://localhost:123/foo');
- }
-
- public function testCanSetDefaultValues()
- {
- $client = new Client(['foo' => 'bar']);
- $client->setDefaultOption('headers/foo', 'bar');
- $this->assertNull($client->getDefaultOption('foo'));
- $this->assertEquals('bar', $client->getDefaultOption('headers/foo'));
- }
-
- public function testSendsAllInParallel()
- {
- $client = new Client();
- $client->getEmitter()->attach(new Mock([
- new Response(200),
- new Response(201),
- new Response(202),
- ]));
- $history = new History();
- $client->getEmitter()->attach($history);
-
- $requests = [
- $client->createRequest('GET', 'http://test.com'),
- $client->createRequest('POST', 'http://test.com'),
- $client->createRequest('PUT', 'http://test.com')
- ];
-
- $client->sendAll($requests);
- $requests = array_map(function($r) {
- return $r->getMethod();
- }, $history->getRequests());
- $this->assertContains('GET', $requests);
- $this->assertContains('POST', $requests);
- $this->assertContains('PUT', $requests);
- }
-
- public function testCanDisableAuthPerRequest()
- {
- $client = new Client(['defaults' => ['auth' => 'foo']]);
- $request = $client->createRequest('GET', 'http://test.com');
- $this->assertEquals('foo', $request->getConfig()['auth']);
- $request = $client->createRequest('GET', 'http://test.com', ['auth' => null]);
- $this->assertFalse($request->getConfig()->hasKey('auth'));
- }
-
- public function testUsesProxyEnvironmentVariables()
- {
- $http = getenv('HTTP_PROXY');
- $https = getenv('HTTPS_PROXY');
-
- $client = new Client();
- $this->assertNull($client->getDefaultOption('proxy'));
-
- putenv('HTTP_PROXY=127.0.0.1');
- $client = new Client();
- $this->assertEquals(
- ['http' => '127.0.0.1'],
- $client->getDefaultOption('proxy')
- );
-
- putenv('HTTPS_PROXY=127.0.0.2');
- $client = new Client();
- $this->assertEquals(
- ['http' => '127.0.0.1', 'https' => '127.0.0.2'],
- $client->getDefaultOption('proxy')
- );
-
- putenv("HTTP_PROXY=$http");
- putenv("HTTPS_PROXY=$https");
- }
-
- public function testReturnsFutureForErrorWhenRequested()
- {
- $client = new Client(['handler' => new MockHandler(['status' => 404])]);
- $request = $client->createRequest('GET', 'http://localhost:123/foo', [
- 'future' => true
- ]);
- $res = $client->send($request);
- $this->assertInstanceOf('GuzzleHttp\Message\FutureResponse', $res);
- try {
- $res->wait();
- $this->fail('did not throw');
- } catch (RequestException $e) {
- $this->assertContains('404', $e->getMessage());
- }
- }
-
- public function testReturnsFutureForResponseWhenRequested()
- {
- $client = new Client(['handler' => new MockHandler(['status' => 200])]);
- $request = $client->createRequest('GET', 'http://localhost:123/foo', [
- 'future' => true
- ]);
- $res = $client->send($request);
- $this->assertInstanceOf('GuzzleHttp\Message\FutureResponse', $res);
- $this->assertEquals(200, $res->getStatusCode());
- }
-
- public function testCanUseUrlWithCustomQuery()
- {
- $client = new Client();
- $url = Url::fromString('http://foo.com/bar');
- $query = new Query(['baz' => '123%20']);
- $query->setEncodingType(false);
- $url->setQuery($query);
- $r = $client->createRequest('GET', $url);
- $this->assertEquals('http://foo.com/bar?baz=123%20', $r->getUrl());
- }
-}
diff --git a/tests/CollectionTest.php b/tests/CollectionTest.php
deleted file mode 100644
index d137947db..000000000
--- a/tests/CollectionTest.php
+++ /dev/null
@@ -1,416 +0,0 @@
-coll = new Collection();
- }
-
- public function testConstructorCanBeCalledWithNoParams()
- {
- $this->coll = new Collection();
- $p = $this->coll->toArray();
- $this->assertEmpty($p, '-> Collection must be empty when no data is passed');
- }
-
- public function testConstructorCanBeCalledWithParams()
- {
- $testData = array(
- 'test' => 'value',
- 'test_2' => 'value2'
- );
- $this->coll = new Collection($testData);
- $this->assertEquals($this->coll->toArray(), $testData);
- $this->assertEquals($this->coll->toArray(), $this->coll->toArray());
- }
-
- public function testImplementsIteratorAggregate()
- {
- $this->coll->set('key', 'value');
- $this->assertInstanceOf('ArrayIterator', $this->coll->getIterator());
- $this->assertEquals(1, count($this->coll));
- $total = 0;
- foreach ($this->coll as $key => $value) {
- $this->assertEquals('key', $key);
- $this->assertEquals('value', $value);
- $total++;
- }
- $this->assertEquals(1, $total);
- }
-
- public function testCanAddValuesToExistingKeysByUsingArray()
- {
- $this->coll->add('test', 'value1');
- $this->assertEquals($this->coll->toArray(), array('test' => 'value1'));
- $this->coll->add('test', 'value2');
- $this->assertEquals($this->coll->toArray(), array('test' => array('value1', 'value2')));
- $this->coll->add('test', 'value3');
- $this->assertEquals($this->coll->toArray(), array('test' => array('value1', 'value2', 'value3')));
- }
-
- public function testHandlesMergingInDisparateDataSources()
- {
- $params = array(
- 'test' => 'value1',
- 'test2' => 'value2',
- 'test3' => array('value3', 'value4')
- );
- $this->coll->merge($params);
- $this->assertEquals($this->coll->toArray(), $params);
- $this->coll->merge(new Collection(['test4' => 'hi']));
- $this->assertEquals(
- $this->coll->toArray(),
- $params + ['test4' => 'hi']
- );
- }
-
- public function testCanClearAllDataOrSpecificKeys()
- {
- $this->coll->merge(array(
- 'test' => 'value1',
- 'test2' => 'value2'
- ));
-
- // Clear a specific parameter by name
- $this->coll->remove('test');
-
- $this->assertEquals($this->coll->toArray(), array(
- 'test2' => 'value2'
- ));
-
- // Clear all parameters
- $this->coll->clear();
-
- $this->assertEquals($this->coll->toArray(), array());
- }
-
- public function testProvidesKeys()
- {
- $this->assertEquals(array(), $this->coll->getKeys());
- $this->coll->merge(array(
- 'test1' => 'value1',
- 'test2' => 'value2'
- ));
- $this->assertEquals(array('test1', 'test2'), $this->coll->getKeys());
- // Returns the cached array previously returned
- $this->assertEquals(array('test1', 'test2'), $this->coll->getKeys());
- $this->coll->remove('test1');
- $this->assertEquals(array('test2'), $this->coll->getKeys());
- $this->coll->add('test3', 'value3');
- $this->assertEquals(array('test2', 'test3'), $this->coll->getKeys());
- }
-
- public function testChecksIfHasKey()
- {
- $this->assertFalse($this->coll->hasKey('test'));
- $this->coll->add('test', 'value');
- $this->assertEquals(true, $this->coll->hasKey('test'));
- $this->coll->add('test2', 'value2');
- $this->assertEquals(true, $this->coll->hasKey('test'));
- $this->assertEquals(true, $this->coll->hasKey('test2'));
- $this->assertFalse($this->coll->hasKey('testing'));
- $this->assertEquals(false, $this->coll->hasKey('AB-C', 'junk'));
- }
-
- public function testChecksIfHasValue()
- {
- $this->assertFalse($this->coll->hasValue('value'));
- $this->coll->add('test', 'value');
- $this->assertEquals('test', $this->coll->hasValue('value'));
- $this->coll->add('test2', 'value2');
- $this->assertEquals('test', $this->coll->hasValue('value'));
- $this->assertEquals('test2', $this->coll->hasValue('value2'));
- $this->assertFalse($this->coll->hasValue('val'));
- }
-
- public function testImplementsCount()
- {
- $data = new Collection();
- $this->assertEquals(0, $data->count());
- $data->add('key', 'value');
- $this->assertEquals(1, count($data));
- $data->add('key', 'value2');
- $this->assertEquals(1, count($data));
- $data->add('key_2', 'value3');
- $this->assertEquals(2, count($data));
- }
-
- public function testAddParamsByMerging()
- {
- $params = array(
- 'test' => 'value1',
- 'test2' => 'value2',
- 'test3' => array('value3', 'value4')
- );
-
- // Add some parameters
- $this->coll->merge($params);
-
- // Add more parameters by merging them in
- $this->coll->merge(array(
- 'test' => 'another',
- 'different_key' => 'new value'
- ));
-
- $this->assertEquals(array(
- 'test' => array('value1', 'another'),
- 'test2' => 'value2',
- 'test3' => array('value3', 'value4'),
- 'different_key' => 'new value'
- ), $this->coll->toArray());
- }
-
- public function testAllowsFunctionalFilter()
- {
- $this->coll->merge(array(
- 'fruit' => 'apple',
- 'number' => 'ten',
- 'prepositions' => array('about', 'above', 'across', 'after'),
- 'same_number' => 'ten'
- ));
-
- $filtered = $this->coll->filter(function ($key, $value) {
- return $value == 'ten';
- });
-
- $this->assertNotSame($filtered, $this->coll);
-
- $this->assertEquals(array(
- 'number' => 'ten',
- 'same_number' => 'ten'
- ), $filtered->toArray());
- }
-
- public function testAllowsFunctionalMapping()
- {
- $this->coll->merge(array(
- 'number_1' => 1,
- 'number_2' => 2,
- 'number_3' => 3
- ));
-
- $mapped = $this->coll->map(function ($key, $value) {
- return $value * $value;
- });
-
- $this->assertNotSame($mapped, $this->coll);
-
- $this->assertEquals(array(
- 'number_1' => 1,
- 'number_2' => 4,
- 'number_3' => 9
- ), $mapped->toArray());
- }
-
- public function testImplementsArrayAccess()
- {
- $this->coll->merge(array(
- 'k1' => 'v1',
- 'k2' => 'v2'
- ));
-
- $this->assertTrue($this->coll->offsetExists('k1'));
- $this->assertFalse($this->coll->offsetExists('Krull'));
-
- $this->coll->offsetSet('k3', 'v3');
- $this->assertEquals('v3', $this->coll->offsetGet('k3'));
- $this->assertEquals('v3', $this->coll->get('k3'));
-
- $this->coll->offsetUnset('k1');
- $this->assertFalse($this->coll->offsetExists('k1'));
- }
-
- public function testCanReplaceAllData()
- {
- $this->coll->replace(array('a' => '123'));
- $this->assertEquals(array('a' => '123'), $this->coll->toArray());
- }
-
- public function testPreparesFromConfig()
- {
- $c = Collection::fromConfig(array(
- 'a' => '123',
- 'base_url' => 'http://www.test.com/'
- ), array(
- 'a' => 'xyz',
- 'b' => 'lol'
- ), array('a'));
-
- $this->assertInstanceOf('GuzzleHttp\Collection', $c);
- $this->assertEquals(array(
- 'a' => '123',
- 'b' => 'lol',
- 'base_url' => 'http://www.test.com/'
- ), $c->toArray());
-
- try {
- $c = Collection::fromConfig(array(), array(), array('a'));
- $this->fail('Exception not throw when missing config');
- } catch (\InvalidArgumentException $e) {
- }
- }
-
- function falseyDataProvider()
- {
- return array(
- array(false, false),
- array(null, null),
- array('', ''),
- array(array(), array()),
- array(0, 0),
- );
- }
-
- /**
- * @dataProvider falseyDataProvider
- */
- public function testReturnsCorrectData($a, $b)
- {
- $c = new Collection(array('value' => $a));
- $this->assertSame($b, $c->get('value'));
- }
-
- public function testRetrievesNestedKeysUsingPath()
- {
- $data = array(
- 'foo' => 'bar',
- 'baz' => array(
- 'mesa' => array(
- 'jar' => 'jar'
- )
- )
- );
- $collection = new Collection($data);
- $this->assertEquals('bar', $collection->getPath('foo'));
- $this->assertEquals('jar', $collection->getPath('baz/mesa/jar'));
- $this->assertNull($collection->getPath('wewewf'));
- $this->assertNull($collection->getPath('baz/mesa/jar/jar'));
- }
-
- public function testFalseyKeysStillDescend()
- {
- $collection = new Collection(array(
- '0' => array(
- 'a' => 'jar'
- ),
- 1 => 'other'
- ));
- $this->assertEquals('jar', $collection->getPath('0/a'));
- $this->assertEquals('other', $collection->getPath('1'));
- }
-
- public function getPathProvider()
- {
- $data = array(
- 'foo' => 'bar',
- 'baz' => array(
- 'mesa' => array(
- 'jar' => 'jar',
- 'array' => array('a', 'b', 'c')
- ),
- 'bar' => array(
- 'baz' => 'bam',
- 'array' => array('d', 'e', 'f')
- )
- ),
- 'bam' => array(
- array('foo' => 1),
- array('foo' => 2),
- array('array' => array('h', 'i'))
- )
- );
- $c = new Collection($data);
-
- return array(
- // Simple path selectors
- array($c, 'foo', 'bar'),
- array($c, 'baz', $data['baz']),
- array($c, 'bam', $data['bam']),
- array($c, 'baz/mesa', $data['baz']['mesa']),
- array($c, 'baz/mesa/jar', 'jar'),
- // Does not barf on missing keys
- array($c, 'fefwfw', null),
- array($c, 'baz/mesa/array', $data['baz']['mesa']['array'])
- );
- }
-
- /**
- * @dataProvider getPathProvider
- */
- public function testGetPath(Collection $c, $path, $expected, $separator = '/')
- {
- $this->assertEquals($expected, $c->getPath($path, $separator));
- }
-
- public function testOverridesSettings()
- {
- $c = new Collection(array('foo' => 1, 'baz' => 2, 'bar' => 3));
- $c->overwriteWith(array('foo' => 10, 'bar' => 300));
- $this->assertEquals(array('foo' => 10, 'baz' => 2, 'bar' => 300), $c->toArray());
- }
-
- public function testOverwriteWithCollection()
- {
- $c = new Collection(array('foo' => 1, 'baz' => 2, 'bar' => 3));
- $b = new Collection(array('foo' => 10, 'bar' => 300));
- $c->overwriteWith($b);
- $this->assertEquals(array('foo' => 10, 'baz' => 2, 'bar' => 300), $c->toArray());
- }
-
- public function testOverwriteWithTraversable()
- {
- $c = new Collection(array('foo' => 1, 'baz' => 2, 'bar' => 3));
- $b = new Collection(array('foo' => 10, 'bar' => 300));
- $c->overwriteWith($b->getIterator());
- $this->assertEquals(array('foo' => 10, 'baz' => 2, 'bar' => 300), $c->toArray());
- }
-
- public function testCanSetNestedPathValueThatDoesNotExist()
- {
- $c = new Collection(array());
- $c->setPath('foo/bar/baz/123', 'hi');
- $this->assertEquals('hi', $c['foo']['bar']['baz']['123']);
- }
-
- public function testCanSetNestedPathValueThatExists()
- {
- $c = new Collection(array('foo' => array('bar' => 'test')));
- $c->setPath('foo/bar', 'hi');
- $this->assertEquals('hi', $c['foo']['bar']);
- }
-
- /**
- * @expectedException \RuntimeException
- */
- public function testVerifiesNestedPathIsValidAtExactLevel()
- {
- $c = new Collection(array('foo' => 'bar'));
- $c->setPath('foo/bar', 'hi');
- $this->assertEquals('hi', $c['foo']['bar']);
- }
-
- /**
- * @expectedException \RuntimeException
- */
- public function testVerifiesThatNestedPathIsValidAtAnyLevel()
- {
- $c = new Collection(array('foo' => 'bar'));
- $c->setPath('foo/bar/baz', 'test');
- }
-
- public function testCanAppendToNestedPathValues()
- {
- $c = new Collection();
- $c->setPath('foo/bar/[]', 'a');
- $c->setPath('foo/bar/[]', 'b');
- $this->assertEquals(['a', 'b'], $c['foo']['bar']);
- }
-}
diff --git a/tests/Cookie/CookieJarTest.php b/tests/Cookie/CookieJarTest.php
index 1360419d9..2cf96c634 100644
--- a/tests/Cookie/CookieJarTest.php
+++ b/tests/Cookie/CookieJarTest.php
@@ -1,11 +1,10 @@
jar->addCookieHeader($request);
+ $request = $this->jar->withCookieHeader($request);
$this->assertEquals($cookies, $request->getHeader('Cookie'));
}
diff --git a/tests/Event/AbstractEventTest.php b/tests/Event/AbstractEventTest.php
deleted file mode 100644
index b8c06f152..000000000
--- a/tests/Event/AbstractEventTest.php
+++ /dev/null
@@ -1,14 +0,0 @@
-getMockBuilder('GuzzleHttp\Event\AbstractEvent')
- ->getMockForAbstractClass();
- $this->assertFalse($e->isPropagationStopped());
- $e->stopPropagation();
- $this->assertTrue($e->isPropagationStopped());
- }
-}
diff --git a/tests/Event/AbstractRequestEventTest.php b/tests/Event/AbstractRequestEventTest.php
deleted file mode 100644
index 50536c582..000000000
--- a/tests/Event/AbstractRequestEventTest.php
+++ /dev/null
@@ -1,33 +0,0 @@
-getMockBuilder('GuzzleHttp\Event\AbstractRequestEvent')
- ->setConstructorArgs([$t])
- ->getMockForAbstractClass();
- $this->assertSame($t->client, $e->getClient());
- $this->assertSame($t->request, $e->getRequest());
- }
-
- public function testHasTransaction()
- {
- $t = new Transaction(new Client(), new Request('GET', '/'));
- $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractRequestEvent')
- ->setConstructorArgs([$t])
- ->getMockForAbstractClass();
- $r = new \ReflectionMethod($e, 'getTransaction');
- $r->setAccessible(true);
- $this->assertSame($t, $r->invoke($e));
- }
-}
diff --git a/tests/Event/AbstractRetryableEventTest.php b/tests/Event/AbstractRetryableEventTest.php
deleted file mode 100644
index 6a39d8bb0..000000000
--- a/tests/Event/AbstractRetryableEventTest.php
+++ /dev/null
@@ -1,37 +0,0 @@
-transferInfo = ['foo' => 'bar'];
- $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractRetryableEvent')
- ->setConstructorArgs([$t])
- ->getMockForAbstractClass();
- $e->retry();
- $this->assertTrue($e->isPropagationStopped());
- $this->assertEquals('retry', $t->state);
- }
-
- public function testCanRetryAfterDelay()
- {
- $t = new Transaction(new Client(), new Request('GET', '/'));
- $t->transferInfo = ['foo' => 'bar'];
- $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractRetryableEvent')
- ->setConstructorArgs([$t])
- ->getMockForAbstractClass();
- $e->retry(10);
- $this->assertTrue($e->isPropagationStopped());
- $this->assertEquals('retry', $t->state);
- $this->assertEquals(10, $t->request->getConfig()->get('delay'));
- }
-}
diff --git a/tests/Event/AbstractTransferEventTest.php b/tests/Event/AbstractTransferEventTest.php
deleted file mode 100644
index 5313c8e7f..000000000
--- a/tests/Event/AbstractTransferEventTest.php
+++ /dev/null
@@ -1,59 +0,0 @@
-transferInfo = ['foo' => 'bar'];
- $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractTransferEvent')
- ->setConstructorArgs([$t])
- ->getMockForAbstractClass();
- $this->assertNull($e->getTransferInfo('baz'));
- $this->assertEquals('bar', $e->getTransferInfo('foo'));
- $this->assertEquals($t->transferInfo, $e->getTransferInfo());
- }
-
- public function testHasResponse()
- {
- $t = new Transaction(new Client(), new Request('GET', '/'));
- $t->response = new Response(200);
- $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractTransferEvent')
- ->setConstructorArgs([$t])
- ->getMockForAbstractClass();
- $this->assertTrue($e->hasResponse());
- $this->assertSame($t->response, $e->getResponse());
- }
-
- public function testCanInterceptWithResponse()
- {
- $t = new Transaction(new Client(), new Request('GET', '/'));
- $r = new Response(200);
- $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractTransferEvent')
- ->setConstructorArgs([$t])
- ->getMockForAbstractClass();
- $e->intercept($r);
- $this->assertSame($t->response, $r);
- $this->assertSame($t->response, $e->getResponse());
- $this->assertTrue($e->isPropagationStopped());
- }
-
- public function testReturnsNumberOfRetries()
- {
- $t = new Transaction(new Client(), new Request('GET', '/'));
- $t->retries = 2;
- $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractTransferEvent')
- ->setConstructorArgs([$t])
- ->getMockForAbstractClass();
- $this->assertEquals(2, $e->getRetryCount());
- }
-}
diff --git a/tests/Event/BeforeEventTest.php b/tests/Event/BeforeEventTest.php
deleted file mode 100644
index 469e4e251..000000000
--- a/tests/Event/BeforeEventTest.php
+++ /dev/null
@@ -1,26 +0,0 @@
-exception = new \Exception('foo');
- $e = new BeforeEvent($t);
- $response = new Response(200);
- $e->intercept($response);
- $this->assertTrue($e->isPropagationStopped());
- $this->assertSame($t->response, $response);
- $this->assertNull($t->exception);
- }
-}
diff --git a/tests/Event/EmitterTest.php b/tests/Event/EmitterTest.php
deleted file mode 100644
index 5b7061bc6..000000000
--- a/tests/Event/EmitterTest.php
+++ /dev/null
@@ -1,363 +0,0 @@
-emitter = new Emitter();
- $this->listener = new TestEventListener();
- }
-
- protected function tearDown()
- {
- $this->emitter = null;
- $this->listener = null;
- }
-
- public function testInitialState()
- {
- $this->assertEquals(array(), $this->emitter->listeners());
- }
-
- public function testAddListener()
- {
- $this->emitter->on('pre.foo', array($this->listener, 'preFoo'));
- $this->emitter->on('post.foo', array($this->listener, 'postFoo'));
- $this->assertTrue($this->emitter->hasListeners(self::preFoo));
- $this->assertTrue($this->emitter->hasListeners(self::preFoo));
- $this->assertCount(1, $this->emitter->listeners(self::postFoo));
- $this->assertCount(1, $this->emitter->listeners(self::postFoo));
- $this->assertCount(2, $this->emitter->listeners());
- }
-
- public function testGetListenersSortsByPriority()
- {
- $listener1 = new TestEventListener();
- $listener2 = new TestEventListener();
- $listener3 = new TestEventListener();
- $listener1->name = '1';
- $listener2->name = '2';
- $listener3->name = '3';
-
- $this->emitter->on('pre.foo', array($listener1, 'preFoo'), -10);
- $this->emitter->on('pre.foo', array($listener2, 'preFoo'), 10);
- $this->emitter->on('pre.foo', array($listener3, 'preFoo'));
-
- $expected = array(
- array($listener2, 'preFoo'),
- array($listener3, 'preFoo'),
- array($listener1, 'preFoo'),
- );
-
- $this->assertSame($expected, $this->emitter->listeners('pre.foo'));
- }
-
- public function testGetAllListenersSortsByPriority()
- {
- $listener1 = new TestEventListener();
- $listener2 = new TestEventListener();
- $listener3 = new TestEventListener();
- $listener4 = new TestEventListener();
- $listener5 = new TestEventListener();
- $listener6 = new TestEventListener();
-
- $this->emitter->on('pre.foo', [$listener1, 'preFoo'], -10);
- $this->emitter->on('pre.foo', [$listener2, 'preFoo']);
- $this->emitter->on('pre.foo', [$listener3, 'preFoo'], 10);
- $this->emitter->on('post.foo', [$listener4, 'preFoo'], -10);
- $this->emitter->on('post.foo', [$listener5, 'preFoo']);
- $this->emitter->on('post.foo', [$listener6, 'preFoo'], 10);
-
- $expected = [
- 'pre.foo' => [[$listener3, 'preFoo'], [$listener2, 'preFoo'], [$listener1, 'preFoo']],
- 'post.foo' => [[$listener6, 'preFoo'], [$listener5, 'preFoo'], [$listener4, 'preFoo']],
- ];
-
- $this->assertSame($expected, $this->emitter->listeners());
- }
-
- public function testDispatch()
- {
- $this->emitter->on('pre.foo', array($this->listener, 'preFoo'));
- $this->emitter->on('post.foo', array($this->listener, 'postFoo'));
- $this->emitter->emit(self::preFoo, $this->getEvent());
- $this->assertTrue($this->listener->preFooInvoked);
- $this->assertFalse($this->listener->postFooInvoked);
- $this->assertInstanceOf('GuzzleHttp\Event\EventInterface', $this->emitter->emit(self::preFoo, $this->getEvent()));
- $event = $this->getEvent();
- $return = $this->emitter->emit(self::preFoo, $event);
- $this->assertSame($event, $return);
- }
-
- public function testDispatchForClosure()
- {
- $invoked = 0;
- $listener = function () use (&$invoked) {
- $invoked++;
- };
- $this->emitter->on('pre.foo', $listener);
- $this->emitter->on('post.foo', $listener);
- $this->emitter->emit(self::preFoo, $this->getEvent());
- $this->assertEquals(1, $invoked);
- }
-
- public function testStopEventPropagation()
- {
- $otherListener = new TestEventListener();
-
- // postFoo() stops the propagation, so only one listener should
- // be executed
- // Manually set priority to enforce $this->listener to be called first
- $this->emitter->on('post.foo', array($this->listener, 'postFoo'), 10);
- $this->emitter->on('post.foo', array($otherListener, 'preFoo'));
- $this->emitter->emit(self::postFoo, $this->getEvent());
- $this->assertTrue($this->listener->postFooInvoked);
- $this->assertFalse($otherListener->postFooInvoked);
- }
-
- public function testDispatchByPriority()
- {
- $invoked = array();
- $listener1 = function () use (&$invoked) {
- $invoked[] = '1';
- };
- $listener2 = function () use (&$invoked) {
- $invoked[] = '2';
- };
- $listener3 = function () use (&$invoked) {
- $invoked[] = '3';
- };
- $this->emitter->on('pre.foo', $listener1, -10);
- $this->emitter->on('pre.foo', $listener2);
- $this->emitter->on('pre.foo', $listener3, 10);
- $this->emitter->emit(self::preFoo, $this->getEvent());
- $this->assertEquals(array('3', '2', '1'), $invoked);
- }
-
- public function testRemoveListener()
- {
- $this->emitter->on('pre.bar', [$this->listener, 'preFoo']);
- $this->assertNotEmpty($this->emitter->listeners(self::preBar));
- $this->emitter->removeListener('pre.bar', [$this->listener, 'preFoo']);
- $this->assertEmpty($this->emitter->listeners(self::preBar));
- $this->emitter->removeListener('notExists', [$this->listener, 'preFoo']);
- }
-
- public function testAddSubscriber()
- {
- $eventSubscriber = new TestEventSubscriber();
- $this->emitter->attach($eventSubscriber);
- $this->assertNotEmpty($this->emitter->listeners(self::preFoo));
- $this->assertNotEmpty($this->emitter->listeners(self::postFoo));
- }
-
- public function testAddSubscriberWithMultiple()
- {
- $eventSubscriber = new TestEventSubscriberWithMultiple();
- $this->emitter->attach($eventSubscriber);
- $listeners = $this->emitter->listeners('pre.foo');
- $this->assertNotEmpty($this->emitter->listeners(self::preFoo));
- $this->assertCount(2, $listeners);
- }
-
- public function testAddSubscriberWithPriorities()
- {
- $eventSubscriber = new TestEventSubscriber();
- $this->emitter->attach($eventSubscriber);
-
- $eventSubscriber = new TestEventSubscriberWithPriorities();
- $this->emitter->attach($eventSubscriber);
-
- $listeners = $this->emitter->listeners('pre.foo');
- $this->assertNotEmpty($this->emitter->listeners(self::preFoo));
- $this->assertCount(2, $listeners);
- $this->assertInstanceOf('GuzzleHttp\Tests\Event\TestEventSubscriberWithPriorities', $listeners[0][0]);
- }
-
- public function testdetach()
- {
- $eventSubscriber = new TestEventSubscriber();
- $this->emitter->attach($eventSubscriber);
- $this->assertNotEmpty($this->emitter->listeners(self::preFoo));
- $this->assertNotEmpty($this->emitter->listeners(self::postFoo));
- $this->emitter->detach($eventSubscriber);
- $this->assertEmpty($this->emitter->listeners(self::preFoo));
- $this->assertEmpty($this->emitter->listeners(self::postFoo));
- }
-
- public function testdetachWithPriorities()
- {
- $eventSubscriber = new TestEventSubscriberWithPriorities();
- $this->emitter->attach($eventSubscriber);
- $this->assertNotEmpty($this->emitter->listeners(self::preFoo));
- $this->assertNotEmpty($this->emitter->listeners(self::postFoo));
- $this->emitter->detach($eventSubscriber);
- $this->assertEmpty($this->emitter->listeners(self::preFoo));
- $this->assertEmpty($this->emitter->listeners(self::postFoo));
- }
-
- public function testEventReceivesEventNameAsArgument()
- {
- $listener = new TestWithDispatcher();
- $this->emitter->on('test', array($listener, 'foo'));
- $this->assertNull($listener->name);
- $this->emitter->emit('test', $this->getEvent());
- $this->assertEquals('test', $listener->name);
- }
-
- /**
- * @see https://bugs.php.net/bug.php?id=62976
- *
- * This bug affects:
- * - The PHP 5.3 branch for versions < 5.3.18
- * - The PHP 5.4 branch for versions < 5.4.8
- * - The PHP 5.5 branch is not affected
- */
- public function testWorkaroundForPhpBug62976()
- {
- $dispatcher = new Emitter();
- $dispatcher->on('bug.62976', new CallableClass());
- $dispatcher->removeListener('bug.62976', function () {});
- $this->assertNotEmpty($dispatcher->listeners('bug.62976'));
- }
-
- public function testRegistersEventsOnce()
- {
- $this->emitter->once('pre.foo', array($this->listener, 'preFoo'));
- $this->emitter->on('pre.foo', array($this->listener, 'preFoo'));
- $this->assertCount(2, $this->emitter->listeners(self::preFoo));
- $this->emitter->emit(self::preFoo, $this->getEvent());
- $this->assertTrue($this->listener->preFooInvoked);
- $this->assertCount(1, $this->emitter->listeners(self::preFoo));
- }
-
- public function testReturnsEmptyArrayForNonExistentEvent()
- {
- $this->assertEquals([], $this->emitter->listeners('doesnotexist'));
- }
-
- public function testCanAddFirstAndLastListeners()
- {
- $b = '';
- $this->emitter->on('foo', function () use (&$b) { $b .= 'a'; }, 'first'); // 1
- $this->emitter->on('foo', function () use (&$b) { $b .= 'b'; }, 'last'); // 0
- $this->emitter->on('foo', function () use (&$b) { $b .= 'c'; }, 'first'); // 2
- $this->emitter->on('foo', function () use (&$b) { $b .= 'd'; }, 'first'); // 3
- $this->emitter->on('foo', function () use (&$b) { $b .= 'e'; }, 'first'); // 4
- $this->emitter->on('foo', function () use (&$b) { $b .= 'f'; }); // 0
- $this->emitter->emit('foo', $this->getEvent());
- $this->assertEquals('edcabf', $b);
- }
-
- /**
- * @return \GuzzleHttp\Event\EventInterface
- */
- private function getEvent()
- {
- return $this->getMockBuilder('GuzzleHttp\Event\AbstractEvent')
- ->getMockForAbstractClass();
- }
-}
-
-class CallableClass
-{
- public function __invoke()
- {
- }
-}
-
-class TestEventListener
-{
- public $preFooInvoked = false;
- public $postFooInvoked = false;
-
- /* Listener methods */
-
- public function preFoo(EventInterface $e)
- {
- $this->preFooInvoked = true;
- }
-
- public function postFoo(EventInterface $e)
- {
- $this->postFooInvoked = true;
-
- $e->stopPropagation();
- }
-
- /**
- * @expectedException \PHPUnit_Framework_Error_Deprecated
- */
- public function testHasDeprecatedAddListener()
- {
- $emitter = new Emitter();
- $emitter->addListener('foo', function () {});
- }
-
- /**
- * @expectedException \PHPUnit_Framework_Error_Deprecated
- */
- public function testHasDeprecatedAddSubscriber()
- {
- $emitter = new Emitter();
- $emitter->addSubscriber('foo', new TestEventSubscriber());
- }
-}
-
-class TestWithDispatcher
-{
- public $name;
-
- public function foo(EventInterface $e, $name)
- {
- $this->name = $name;
- }
-}
-
-class TestEventSubscriber extends TestEventListener implements SubscriberInterface
-{
- public function getEvents()
- {
- return [
- 'pre.foo' => ['preFoo'],
- 'post.foo' => ['postFoo']
- ];
- }
-}
-
-class TestEventSubscriberWithPriorities extends TestEventListener implements SubscriberInterface
-{
- public function getEvents()
- {
- return [
- 'pre.foo' => ['preFoo', 10],
- 'post.foo' => ['postFoo']
- ];
- }
-}
-
-class TestEventSubscriberWithMultiple extends TestEventListener implements SubscriberInterface
-{
- public function getEvents()
- {
- return ['pre.foo' => [['preFoo', 10],['preFoo', 20]]];
- }
-}
diff --git a/tests/Event/ErrorEventTest.php b/tests/Event/ErrorEventTest.php
deleted file mode 100644
index e91b7f0c3..000000000
--- a/tests/Event/ErrorEventTest.php
+++ /dev/null
@@ -1,23 +0,0 @@
-request);
- $t->exception = $except;
- $e = new ErrorEvent($t);
- $this->assertSame($e->getException(), $t->exception);
- }
-}
diff --git a/tests/Event/HasEmitterTraitTest.php b/tests/Event/HasEmitterTraitTest.php
deleted file mode 100644
index 470991871..000000000
--- a/tests/Event/HasEmitterTraitTest.php
+++ /dev/null
@@ -1,27 +0,0 @@
-getMockBuilder('GuzzleHttp\Tests\Event\AbstractHasEmitter')
- ->getMockForAbstractClass();
-
- $result = $mock->getEmitter();
- $this->assertInstanceOf('GuzzleHttp\Event\EmitterInterface', $result);
- $result2 = $mock->getEmitter();
- $this->assertSame($result, $result2);
- }
-}
diff --git a/tests/Event/ListenerAttacherTraitTest.php b/tests/Event/ListenerAttacherTraitTest.php
deleted file mode 100644
index c066788bc..000000000
--- a/tests/Event/ListenerAttacherTraitTest.php
+++ /dev/null
@@ -1,92 +0,0 @@
-listeners = $this->prepareListeners($args, ['foo', 'bar']);
- $this->attachListeners($this, $this->listeners);
- }
-}
-
-class ListenerAttacherTraitTest extends \PHPUnit_Framework_TestCase
-{
- public function testRegistersEvents()
- {
- $fn = function () {};
- $o = new ObjectWithEvents([
- 'foo' => $fn,
- 'bar' => $fn,
- ]);
-
- $this->assertEquals([
- ['name' => 'foo', 'fn' => $fn, 'priority' => 0, 'once' => false],
- ['name' => 'bar', 'fn' => $fn, 'priority' => 0, 'once' => false],
- ], $o->listeners);
-
- $this->assertCount(1, $o->getEmitter()->listeners('foo'));
- $this->assertCount(1, $o->getEmitter()->listeners('bar'));
- }
-
- public function testRegistersEventsWithPriorities()
- {
- $fn = function () {};
- $o = new ObjectWithEvents([
- 'foo' => ['fn' => $fn, 'priority' => 99, 'once' => true],
- 'bar' => ['fn' => $fn, 'priority' => 50],
- ]);
-
- $this->assertEquals([
- ['name' => 'foo', 'fn' => $fn, 'priority' => 99, 'once' => true],
- ['name' => 'bar', 'fn' => $fn, 'priority' => 50, 'once' => false],
- ], $o->listeners);
- }
-
- public function testRegistersMultipleEvents()
- {
- $fn = function () {};
- $eventArray = [['fn' => $fn], ['fn' => $fn]];
- $o = new ObjectWithEvents([
- 'foo' => $eventArray,
- 'bar' => $eventArray,
- ]);
-
- $this->assertEquals([
- ['name' => 'foo', 'fn' => $fn, 'priority' => 0, 'once' => false],
- ['name' => 'foo', 'fn' => $fn, 'priority' => 0, 'once' => false],
- ['name' => 'bar', 'fn' => $fn, 'priority' => 0, 'once' => false],
- ['name' => 'bar', 'fn' => $fn, 'priority' => 0, 'once' => false],
- ], $o->listeners);
-
- $this->assertCount(2, $o->getEmitter()->listeners('foo'));
- $this->assertCount(2, $o->getEmitter()->listeners('bar'));
- }
-
- public function testRegistersEventsWithOnce()
- {
- $called = 0;
- $fn = function () use (&$called) { $called++; };
- $o = new ObjectWithEvents(['foo' => ['fn' => $fn, 'once' => true]]);
- $ev = $this->getMock('GuzzleHttp\Event\EventInterface');
- $o->getEmitter()->emit('foo', $ev);
- $o->getEmitter()->emit('foo', $ev);
- $this->assertEquals(1, $called);
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testValidatesEvents()
- {
- $o = new ObjectWithEvents(['foo' => 'bar']);
- }
-}
diff --git a/tests/Event/ProgressEventTest.php b/tests/Event/ProgressEventTest.php
deleted file mode 100644
index 664f8b6bb..000000000
--- a/tests/Event/ProgressEventTest.php
+++ /dev/null
@@ -1,25 +0,0 @@
-assertSame($t->request, $p->getRequest());
- $this->assertSame($t->client, $p->getClient());
- $this->assertEquals(2, $p->downloadSize);
- $this->assertEquals(1, $p->downloaded);
- $this->assertEquals(3, $p->uploadSize);
- $this->assertEquals(0, $p->uploaded);
- }
-}
diff --git a/tests/Event/RequestEventsTest.php b/tests/Event/RequestEventsTest.php
deleted file mode 100644
index b3b96660f..000000000
--- a/tests/Event/RequestEventsTest.php
+++ /dev/null
@@ -1,74 +0,0 @@
- [$cb]]],
- [
- ['complete' => $cb],
- ['complete'],
- $cb,
- ['complete' => [$cb, $cb]]
- ],
- [
- ['prepare' => []],
- ['error', 'foo'],
- $cb,
- [
- 'prepare' => [],
- 'error' => [$cb],
- 'foo' => [$cb]
- ]
- ],
- [
- ['prepare' => []],
- ['prepare'],
- $cb,
- [
- 'prepare' => [$cb]
- ]
- ],
- [
- ['prepare' => ['fn' => $cb]],
- ['prepare'], $cb,
- [
- 'prepare' => [
- ['fn' => $cb],
- $cb
- ]
- ]
- ],
- ];
- }
-
- /**
- * @dataProvider prepareEventProvider
- */
- public function testConvertsEventArrays(
- array $in,
- array $events,
- $add,
- array $out
- ) {
- $result = RequestEvents::convertEventArray($in, $events, $add);
- $this->assertEquals($out, $result);
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testValidatesEventFormat()
- {
- RequestEvents::convertEventArray(['foo' => false], ['foo'], []);
- }
-}
diff --git a/tests/Exception/ParseExceptionTest.php b/tests/Exception/ParseExceptionTest.php
deleted file mode 100644
index 4ff9bfb6c..000000000
--- a/tests/Exception/ParseExceptionTest.php
+++ /dev/null
@@ -1,20 +0,0 @@
-assertSame($res, $e->getResponse());
- $this->assertEquals('foo', $e->getMessage());
- }
-}
diff --git a/tests/Exception/RequestExceptionTest.php b/tests/Exception/RequestExceptionTest.php
index bea9077bf..e8bfad5f4 100644
--- a/tests/Exception/RequestExceptionTest.php
+++ b/tests/Exception/RequestExceptionTest.php
@@ -2,9 +2,8 @@
namespace GuzzleHttp\Tests\Event;
use GuzzleHttp\Exception\RequestException;
-use GuzzleHttp\Message\Request;
-use GuzzleHttp\Message\Response;
-use GuzzleHttp\Ring\Exception\ConnectException;
+use GuzzleHttp\Psr7\Request;
+use GuzzleHttp\Psr7\Response;
/**
* @covers GuzzleHttp\Exception\RequestException
@@ -73,11 +72,11 @@ public function testWrapsRequestExceptions()
$this->assertSame($e, $ex->getPrevious());
}
- public function testWrapsConnectExceptions()
+ public function testDoesNotWrapExistingRequestExceptions()
{
- $e = new ConnectException('foo');
$r = new Request('GET', 'http://www.oo.com');
- $ex = RequestException::wrapException($r, $e);
- $this->assertInstanceOf('GuzzleHttp\Exception\ConnectException', $ex);
+ $e = new RequestException('foo', $r);
+ $e2 = RequestException::wrapException($r, $e);
+ $this->assertSame($e, $e2);
}
}
diff --git a/tests/Exception/SeekExceptionTest.php b/tests/Exception/SeekExceptionTest.php
new file mode 100644
index 000000000..42c0b95b4
--- /dev/null
+++ b/tests/Exception/SeekExceptionTest.php
@@ -0,0 +1,16 @@
+assertSame($s, $e->getStream());
+ $this->assertContains('10', $e->getMessage());
+ }
+}
diff --git a/tests/Exception/XmlParseExceptionTest.php b/tests/Exception/XmlParseExceptionTest.php
deleted file mode 100644
index 51b97425e..000000000
--- a/tests/Exception/XmlParseExceptionTest.php
+++ /dev/null
@@ -1,19 +0,0 @@
-assertSame($error, $e->getError());
- $this->assertEquals('foo', $e->getMessage());
- }
-}
diff --git a/tests/FulfilledResponseTest.php b/tests/FulfilledResponseTest.php
new file mode 100644
index 000000000..9c0cedc3d
--- /dev/null
+++ b/tests/FulfilledResponseTest.php
@@ -0,0 +1,36 @@
+ 'bar'], 'baz', 'bam');
+ $p = new FulfilledResponse($r);
+ $this->assertEquals('fulfilled', $p->getState());
+ $this->assertEquals(200, $p->getStatusCode());
+ $this->assertEquals('bam', $p->getReasonPhrase());
+ $this->assertEquals(['foo' => ['bar']], $p->getHeaders());
+ $this->assertTrue($p->hasHeader('foo'));
+ $this->assertEquals('bar', $p->getHeader('foo'));
+ $this->assertEquals(['bar'], $p->getHeaderLines('foo'));
+ $this->assertEquals('baz', (string) $p->getBody());
+ $this->assertEquals('1.1', $p->getProtocolVersion());
+ $this->assertFalse($p->withoutHeader('foo')->hasHeader('foo'));
+ $this->assertTrue($p->withHeader('a', 'b')->hasHeader('a'));
+ $this->assertTrue($p->withAddedHeader('a', 'b')->hasHeader('a'));
+ $this->assertEquals('hi', (string) $p->withBody(Stream::factory('hi'))->getBody());
+ $this->assertEquals('201', $p->withStatus('201')->getStatusCode());
+ $this->assertEquals('2', $p->withProtocolVersion('2')->getProtocolVersion());
+ $this->assertEquals('test', $p->withStatus(201, 'test')->getReasonPhrase());
+ }
+}
diff --git a/tests/HandlerBuilderTest.php b/tests/HandlerBuilderTest.php
new file mode 100644
index 000000000..165161096
--- /dev/null
+++ b/tests/HandlerBuilderTest.php
@@ -0,0 +1,33 @@
+assertTrue($h->hasHandler());
+ $this->assertCount(1, $this->readAttribute($h, 'stack')[0]);
+ }
+
+ public function testCanSetDifferentHandlerAfterConstruction()
+ {
+ $f = function () {};
+ $h = new HandlerBuilder();
+ $h->setHandler($f);
+ $h->resolve();
+ }
+
+ /**
+ * @expectedException \LogicException
+ */
+ public function testEnsuresHandlerIsSet()
+ {
+ $h = new HandlerBuilder();
+ $h->resolve();
+ }
+}
diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php
deleted file mode 100644
index e26c64d9f..000000000
--- a/tests/IntegrationTest.php
+++ /dev/null
@@ -1,123 +0,0 @@
-createRequest(
- 'GET',
- Server::$url,
- [
- 'timeout' => 1,
- 'connect_timeout' => 1,
- 'proxy' => 'http://127.0.0.1:123/foo'
- ]
- );
-
- $events = [];
- $fn = function(AbstractTransferEvent $event) use (&$events) {
- $events[] = [
- get_class($event),
- $event->hasResponse(),
- $event->getResponse()
- ];
- };
-
- $pool = new Pool($c, [$r], [
- 'error' => $fn,
- 'end' => $fn
- ]);
-
- $pool->wait();
-
- $this->assertCount(2, $events);
- $this->assertEquals('GuzzleHttp\Event\ErrorEvent', $events[0][0]);
- $this->assertFalse($events[0][1]);
- $this->assertNull($events[0][2]);
-
- $this->assertEquals('GuzzleHttp\Event\EndEvent', $events[1][0]);
- $this->assertFalse($events[1][1]);
- $this->assertNull($events[1][2]);
- }
-
- /**
- * @issue https://github.com/guzzle/guzzle/issues/866
- */
- public function testProperyGetsTransferStats()
- {
- $transfer = [];
- Server::enqueue([new Response(200)]);
- $c = new Client();
- $response = $c->get(Server::$url . '/foo', [
- 'events' => [
- 'end' => function (EndEvent $e) use (&$transfer) {
- $transfer = $e->getTransferInfo();
- }
- ]
- ]);
- $this->assertEquals(Server::$url . '/foo', $response->getEffectiveUrl());
- $this->assertNotEmpty($transfer);
- $this->assertArrayHasKey('url', $transfer);
- }
-
- public function testNestedFutureResponsesAreResolvedWhenSending()
- {
- $c = new Client();
- $total = 3;
- Server::enqueue([
- new Response(200),
- new Response(201),
- new Response(202)
- ]);
- $c->getEmitter()->on(
- 'complete',
- function (CompleteEvent $e) use (&$total) {
- if (--$total) {
- $e->retry();
- }
- }
- );
- $response = $c->get(Server::$url);
- $this->assertEquals(202, $response->getStatusCode());
- $this->assertEquals('GuzzleHttp\Message\Response', get_class($response));
- }
-
- public function testNestedFutureErrorsAreResolvedWhenSending()
- {
- $c = new Client();
- $total = 3;
- Server::enqueue([
- new Response(500),
- new Response(501),
- new Response(502)
- ]);
- $c->getEmitter()->on(
- 'error',
- function (ErrorEvent $e) use (&$total) {
- if (--$total) {
- $e->retry();
- }
- }
- );
- try {
- $c->get(Server::$url);
- $this->fail('Did not throw!');
- } catch (RequestException $e) {
- $this->assertEquals(502, $e->getResponse()->getStatusCode());
- }
- }
-}
diff --git a/tests/Message/AbstractMessageTest.php b/tests/Message/AbstractMessageTest.php
deleted file mode 100644
index f02a576f5..000000000
--- a/tests/Message/AbstractMessageTest.php
+++ /dev/null
@@ -1,269 +0,0 @@
-assertEquals(1.1, $m->getProtocolVersion());
- }
-
- public function testHasHeaders()
- {
- $m = new Request('GET', 'http://foo.com');
- $this->assertFalse($m->hasHeader('foo'));
- $m->addHeader('foo', 'bar');
- $this->assertTrue($m->hasHeader('foo'));
- }
-
- public function testInitializesMessageWithProtocolVersionOption()
- {
- $m = new Request('GET', '/', [], null, [
- 'protocol_version' => '10'
- ]);
- $this->assertEquals(10, $m->getProtocolVersion());
- }
-
- public function testHasBody()
- {
- $m = new Request('GET', 'http://foo.com');
- $this->assertNull($m->getBody());
- $s = Stream::factory('test');
- $m->setBody($s);
- $this->assertSame($s, $m->getBody());
- $this->assertFalse($m->hasHeader('Content-Length'));
- }
-
- public function testCanRemoveBodyBySettingToNullAndRemovesCommonBodyHeaders()
- {
- $m = new Request('GET', 'http://foo.com');
- $m->setBody(Stream::factory('foo'));
- $m->setHeader('Content-Length', 3);
- $m->setHeader('Transfer-Encoding', 'chunked');
- $m->setBody(null);
- $this->assertNull($m->getBody());
- $this->assertFalse($m->hasHeader('Content-Length'));
- $this->assertFalse($m->hasHeader('Transfer-Encoding'));
- }
-
- public function testCastsToString()
- {
- $m = new Request('GET', 'http://foo.com');
- $m->setHeader('foo', 'bar');
- $m->setBody(Stream::factory('baz'));
- $this->assertEquals("GET / HTTP/1.1\r\nHost: foo.com\r\nfoo: bar\r\n\r\nbaz", (string) $m);
- }
-
- public function parseParamsProvider()
- {
- $res1 = array(
- array(
- '',
- 'rel' => 'front',
- 'type' => 'image/jpeg',
- ),
- array(
- '',
- 'rel' => 'back',
- 'type' => 'image/jpeg',
- ),
- );
-
- return array(
- array(
- '; rel="front"; type="image/jpeg", ; rel=back; type="image/jpeg"',
- $res1
- ),
- array(
- '; rel="front"; type="image/jpeg",; rel=back; type="image/jpeg"',
- $res1
- ),
- array(
- 'foo="baz"; bar=123, boo, test="123", foobar="foo;bar"',
- array(
- array('foo' => 'baz', 'bar' => '123'),
- array('boo'),
- array('test' => '123'),
- array('foobar' => 'foo;bar')
- )
- ),
- array(
- '; rel="side"; type="image/jpeg",; rel=side; type="image/jpeg"',
- array(
- array('', 'rel' => 'side', 'type' => 'image/jpeg'),
- array('', 'rel' => 'side', 'type' => 'image/jpeg')
- )
- ),
- array(
- '',
- array()
- )
- );
- }
-
- /**
- * @dataProvider parseParamsProvider
- */
- public function testParseParams($header, $result)
- {
- $request = new Request('GET', '/', ['foo' => $header]);
- $this->assertEquals($result, Request::parseHeader($request, 'foo'));
- }
-
- public function testAddsHeadersWhenNotPresent()
- {
- $h = new Request('GET', 'http://foo.com');
- $h->addHeader('foo', 'bar');
- $this->assertInternalType('string', $h->getHeader('foo'));
- $this->assertEquals('bar', $h->getHeader('foo'));
- }
-
- public function testAddsHeadersWhenPresentSameCase()
- {
- $h = new Request('GET', 'http://foo.com');
- $h->addHeader('foo', 'bar');
- $h->addHeader('foo', 'baz');
- $this->assertEquals('bar, baz', $h->getHeader('foo'));
- $this->assertEquals(['bar', 'baz'], $h->getHeaderAsArray('foo'));
- }
-
- public function testAddsMultipleHeaders()
- {
- $h = new Request('GET', 'http://foo.com');
- $h->addHeaders([
- 'foo' => ' bar',
- 'baz' => [' bam ', 'boo']
- ]);
- $this->assertEquals([
- 'foo' => ['bar'],
- 'baz' => ['bam', 'boo'],
- 'Host' => ['foo.com']
- ], $h->getHeaders());
- }
-
- public function testAddsHeadersWhenPresentDifferentCase()
- {
- $h = new Request('GET', 'http://foo.com');
- $h->addHeader('Foo', 'bar');
- $h->addHeader('fOO', 'baz');
- $this->assertEquals('bar, baz', $h->getHeader('foo'));
- }
-
- public function testAddsHeadersWithArray()
- {
- $h = new Request('GET', 'http://foo.com');
- $h->addHeader('Foo', ['bar', 'baz']);
- $this->assertEquals('bar, baz', $h->getHeader('foo'));
- }
-
- public function testGetHeadersReturnsAnArrayOfOverTheWireHeaderValues()
- {
- $h = new Request('GET', 'http://foo.com');
- $h->addHeader('foo', 'bar');
- $h->addHeader('Foo', 'baz');
- $h->addHeader('boO', 'test');
- $result = $h->getHeaders();
- $this->assertInternalType('array', $result);
- $this->assertArrayHasKey('Foo', $result);
- $this->assertArrayNotHasKey('foo', $result);
- $this->assertArrayHasKey('boO', $result);
- $this->assertEquals(['bar', 'baz'], $result['Foo']);
- $this->assertEquals(['test'], $result['boO']);
- }
-
- public function testSetHeaderOverwritesExistingValues()
- {
- $h = new Request('GET', 'http://foo.com');
- $h->setHeader('foo', 'bar');
- $this->assertEquals('bar', $h->getHeader('foo'));
- $h->setHeader('Foo', 'baz');
- $this->assertEquals('baz', $h->getHeader('foo'));
- $this->assertArrayHasKey('Foo', $h->getHeaders());
- }
-
- public function testSetHeaderOverwritesExistingValuesUsingHeaderArray()
- {
- $h = new Request('GET', 'http://foo.com');
- $h->setHeader('foo', ['bar']);
- $this->assertEquals('bar', $h->getHeader('foo'));
- }
-
- public function testSetHeaderOverwritesExistingValuesUsingArray()
- {
- $h = new Request('GET', 'http://foo.com');
- $h->setHeader('foo', ['bar']);
- $this->assertEquals('bar', $h->getHeader('foo'));
- }
-
- public function testSetHeadersOverwritesAllHeaders()
- {
- $h = new Request('GET', 'http://foo.com');
- $h->setHeader('foo', 'bar');
- $h->setHeaders(['foo' => 'a', 'boo' => 'b']);
- $this->assertEquals(['foo' => ['a'], 'boo' => ['b']], $h->getHeaders());
- }
-
- public function testChecksIfCaseInsensitiveHeaderIsPresent()
- {
- $h = new Request('GET', 'http://foo.com');
- $h->setHeader('foo', 'bar');
- $this->assertTrue($h->hasHeader('foo'));
- $this->assertTrue($h->hasHeader('Foo'));
- $h->setHeader('fOo', 'bar');
- $this->assertTrue($h->hasHeader('Foo'));
- }
-
- public function testRemovesHeaders()
- {
- $h = new Request('GET', 'http://foo.com');
- $h->setHeader('foo', 'bar');
- $h->removeHeader('foo');
- $this->assertFalse($h->hasHeader('foo'));
- $h->setHeader('Foo', 'bar');
- $h->removeHeader('FOO');
- $this->assertFalse($h->hasHeader('foo'));
- }
-
- public function testReturnsCorrectTypeWhenMissing()
- {
- $h = new Request('GET', 'http://foo.com');
- $this->assertInternalType('string', $h->getHeader('foo'));
- $this->assertInternalType('array', $h->getHeaderAsArray('foo'));
- }
-
- public function testSetsIntegersAndFloatsAsHeaders()
- {
- $h = new Request('GET', 'http://foo.com');
- $h->setHeader('foo', 10);
- $h->setHeader('bar', 10.5);
- $h->addHeader('foo', 10);
- $h->addHeader('bar', 10.5);
- $this->assertSame('10, 10', $h->getHeader('foo'));
- $this->assertSame('10.5, 10.5', $h->getHeader('bar'));
- }
-
- public function testGetsResponseStartLine()
- {
- $m = new Response(200);
- $this->assertEquals('HTTP/1.1 200 OK', Response::getStartLine($m));
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testThrowsWhenMessageIsUnknown()
- {
- $m = $this->getMockBuilder('GuzzleHttp\Message\AbstractMessage')
- ->getMockForAbstractClass();
- AbstractMessage::getStartLine($m);
- }
-}
diff --git a/tests/Message/FutureResponseTest.php b/tests/Message/FutureResponseTest.php
deleted file mode 100644
index 771631d56..000000000
--- a/tests/Message/FutureResponseTest.php
+++ /dev/null
@@ -1,160 +0,0 @@
-foo;
- }
-
- public function testDoesTheSameAsResponseWhenDereferenced()
- {
- $str = Stream::factory('foo');
- $response = new Response(200, ['Foo' => 'bar'], $str);
- $future = MockTest::createFuture(function () use ($response) {
- return $response;
- });
- $this->assertFalse($this->readAttribute($future, 'isRealized'));
- $this->assertEquals(200, $future->getStatusCode());
- $this->assertTrue($this->readAttribute($future, 'isRealized'));
- // Deref again does nothing.
- $future->wait();
- $this->assertTrue($this->readAttribute($future, 'isRealized'));
- $this->assertEquals('bar', $future->getHeader('Foo'));
- $this->assertEquals(['bar'], $future->getHeaderAsarray('Foo'));
- $this->assertSame($response->getHeaders(), $future->getHeaders());
- $this->assertSame(
- $response->getBody(),
- $future->getBody()
- );
- $this->assertSame(
- $response->getProtocolVersion(),
- $future->getProtocolVersion()
- );
- $this->assertSame(
- $response->getEffectiveUrl(),
- $future->getEffectiveUrl()
- );
- $future->setEffectiveUrl('foo');
- $this->assertEquals('foo', $response->getEffectiveUrl());
- $this->assertSame(
- $response->getReasonPhrase(),
- $future->getReasonPhrase()
- );
-
- $this->assertTrue($future->hasHeader('foo'));
-
- $future->removeHeader('Foo');
- $this->assertFalse($future->hasHeader('foo'));
- $this->assertFalse($response->hasHeader('foo'));
-
- $future->setBody(Stream::factory('true'));
- $this->assertEquals('true', (string) $response->getBody());
- $this->assertTrue($future->json());
- $this->assertSame((string) $response, (string) $future);
-
- $future->setBody(Stream::factory('c'));
- $this->assertEquals('c', (string) $future->xml()->b);
-
- $future->addHeader('a', 'b');
- $this->assertEquals('b', $future->getHeader('a'));
-
- $future->addHeaders(['a' => '2']);
- $this->assertEquals('b, 2', $future->getHeader('a'));
-
- $future->setHeader('a', '2');
- $this->assertEquals('2', $future->getHeader('a'));
-
- $future->setHeaders(['a' => '3']);
- $this->assertEquals(['a' => ['3']], $future->getHeaders());
- }
-
- public function testCanDereferenceManually()
- {
- $response = new Response(200, ['Foo' => 'bar']);
- $future = MockTest::createFuture(function () use ($response) {
- return $response;
- });
- $this->assertSame($response, $future->wait());
- $this->assertTrue($this->readAttribute($future, 'isRealized'));
- }
-
- public function testCanCancel()
- {
- $c = false;
- $deferred = new Deferred();
- $future = new FutureResponse(
- $deferred->promise(),
- function () {},
- function () use (&$c) {
- $c = true;
- return true;
- }
- );
-
- $this->assertFalse($this->readAttribute($future, 'isRealized'));
- $future->cancel();
- $this->assertTrue($this->readAttribute($future, 'isRealized'));
- $future->cancel();
- }
-
- public function testCanCancelButReturnsFalseForNoCancelFunction()
- {
- $future = MockTest::createFuture(function () {});
- $future->cancel();
- $this->assertTrue($this->readAttribute($future, 'isRealized'));
- }
-
- /**
- * @expectedException \GuzzleHttp\Ring\Exception\CancelledFutureAccessException
- */
- public function testAccessingCancelledResponseThrows()
- {
- $future = MockTest::createFuture(function () {});
- $future->cancel();
- $future->getStatusCode();
- }
-
- public function testExceptionInToStringTriggersError()
- {
- $future = MockTest::createFuture(function () {
- throw new \Exception('foo');
- });
- $err = '';
- set_error_handler(function () use (&$err) {
- $err = func_get_args()[1];
- });
- echo $future;
- restore_error_handler();
- $this->assertContains('foo', $err);
- }
-
- public function testProxiesSetters()
- {
- $str = Stream::factory('foo');
- $response = new Response(200, ['Foo' => 'bar'], $str);
- $future = MockTest::createFuture(function () use ($response) {
- return $response;
- });
-
- $future->setStatusCode(202);
- $this->assertEquals(202, $future->getStatusCode());
- $this->assertEquals(202, $response->getStatusCode());
-
- $future->setReasonPhrase('foo');
- $this->assertEquals('foo', $future->getReasonPhrase());
- $this->assertEquals('foo', $response->getReasonPhrase());
- }
-}
diff --git a/tests/Message/MessageFactoryTest.php b/tests/Message/MessageFactoryTest.php
deleted file mode 100644
index aa2e45e02..000000000
--- a/tests/Message/MessageFactoryTest.php
+++ /dev/null
@@ -1,601 +0,0 @@
-createResponse(200, ['foo' => 'bar'], 'test', [
- 'protocol_version' => 1.0
- ]);
- $this->assertEquals(200, $response->getStatusCode());
- $this->assertEquals(['foo' => ['bar']], $response->getHeaders());
- $this->assertEquals('test', $response->getBody());
- $this->assertEquals(1.0, $response->getProtocolVersion());
- }
-
- public function testCreatesRequestFromMessage()
- {
- $f = new MessageFactory();
- $req = $f->fromMessage("GET / HTTP/1.1\r\nBaz: foo\r\n\r\n");
- $this->assertEquals('GET', $req->getMethod());
- $this->assertEquals('/', $req->getPath());
- $this->assertEquals('foo', $req->getHeader('Baz'));
- $this->assertNull($req->getBody());
- }
-
- public function testCreatesRequestFromMessageWithBody()
- {
- $req = (new MessageFactory())->fromMessage("GET / HTTP/1.1\r\nBaz: foo\r\n\r\ntest");
- $this->assertEquals('test', $req->getBody());
- }
-
- public function testCreatesRequestWithPostBody()
- {
- $req = (new MessageFactory())->createRequest('GET', 'http://www.foo.com', ['body' => ['abc' => '123']]);
- $this->assertEquals('abc=123', $req->getBody());
- }
-
- public function testCreatesRequestWithPostBodyScalars()
- {
- $req = (new MessageFactory())->createRequest(
- 'GET',
- 'http://www.foo.com',
- ['body' => [
- 'abc' => true,
- '123' => false,
- 'foo' => null,
- 'baz' => 10,
- 'bam' => 1.5,
- 'boo' => [1]]
- ]
- );
- $this->assertEquals(
- 'abc=1&123=&foo&baz=10&bam=1.5&boo%5B0%5D=1',
- (string) $req->getBody()
- );
- }
-
- public function testCreatesRequestWithPostBodyAndPostFiles()
- {
- $pf = fopen(__FILE__, 'r');
- $pfi = new PostFile('ghi', 'abc', __FILE__);
- $req = (new MessageFactory())->createRequest('GET', 'http://www.foo.com', [
- 'body' => [
- 'abc' => '123',
- 'def' => $pf,
- 'ghi' => $pfi
- ]
- ]);
- $this->assertInstanceOf('GuzzleHttp\Post\PostBody', $req->getBody());
- $s = (string) $req;
- $this->assertContains('testCreatesRequestWithPostBodyAndPostFiles', $s);
- $this->assertContains('multipart/form-data', $s);
- $this->assertTrue(in_array($pfi, $req->getBody()->getFiles(), true));
- }
-
- public function testCreatesResponseFromMessage()
- {
- $response = (new MessageFactory())->fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest");
- $this->assertEquals(200, $response->getStatusCode());
- $this->assertEquals('OK', $response->getReasonPhrase());
- $this->assertEquals('4', $response->getHeader('Content-Length'));
- $this->assertEquals('test', $response->getBody(true));
- }
-
- public function testCanCreateHeadResponses()
- {
- $response = (new MessageFactory())->fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\n");
- $this->assertEquals(200, $response->getStatusCode());
- $this->assertEquals('OK', $response->getReasonPhrase());
- $this->assertEquals(null, $response->getBody());
- $this->assertEquals('4', $response->getHeader('Content-Length'));
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testFactoryRequiresMessageForRequest()
- {
- (new MessageFactory())->fromMessage('');
- }
-
- /**
- * @expectedException \InvalidArgumentException
- * @expectedExceptionMessage foo
- */
- public function testValidatesOptionsAreImplemented()
- {
- (new MessageFactory())->createRequest('GET', 'http://test.com', ['foo' => 'bar']);
- }
-
- public function testOptionsAddsRequestOptions()
- {
- $request = (new MessageFactory())->createRequest(
- 'GET', 'http://test.com', ['config' => ['baz' => 'bar']]
- );
- $this->assertEquals('bar', $request->getConfig()->get('baz'));
- }
-
- public function testCanDisableRedirects()
- {
- $request = (new MessageFactory())->createRequest('GET', '/', ['allow_redirects' => false]);
- $this->assertEmpty($request->getEmitter()->listeners('complete'));
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testValidatesRedirects()
- {
- (new MessageFactory())->createRequest('GET', '/', ['allow_redirects' => 'foo']);
- }
-
- public function testCanEnableStrictRedirectsAndSpecifyMax()
- {
- $request = (new MessageFactory())->createRequest('GET', '/', [
- 'allow_redirects' => ['max' => 10, 'strict' => true]
- ]);
- $this->assertTrue($request->getConfig()['redirect']['strict']);
- $this->assertEquals(10, $request->getConfig()['redirect']['max']);
- }
-
- public function testCanAddCookiesFromHash()
- {
- $request = (new MessageFactory())->createRequest('GET', 'http://www.test.com/', [
- 'cookies' => ['Foo' => 'Bar']
- ]);
- $cookies = null;
- foreach ($request->getEmitter()->listeners('before') as $l) {
- if ($l[0] instanceof Cookie) {
- $cookies = $l[0];
- break;
- }
- }
- if (!$cookies) {
- $this->fail('Did not add cookie listener');
- } else {
- $this->assertCount(1, $cookies->getCookieJar());
- }
- }
-
- public function testAddsCookieUsingTrue()
- {
- $factory = new MessageFactory();
- $request1 = $factory->createRequest('GET', '/', ['cookies' => true]);
- $request2 = $factory->createRequest('GET', '/', ['cookies' => true]);
- $listeners = function ($r) {
- return array_filter($r->getEmitter()->listeners('before'), function ($l) {
- return $l[0] instanceof Cookie;
- });
- };
- $this->assertSame($listeners($request1), $listeners($request2));
- }
-
- public function testAddsCookieFromCookieJar()
- {
- $jar = new CookieJar();
- $request = (new MessageFactory())->createRequest('GET', '/', ['cookies' => $jar]);
- foreach ($request->getEmitter()->listeners('before') as $l) {
- if ($l[0] instanceof Cookie) {
- $this->assertSame($jar, $l[0]->getCookieJar());
- }
- }
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testValidatesCookies()
- {
- (new MessageFactory())->createRequest('GET', '/', ['cookies' => 'baz']);
- }
-
- public function testCanAddQuery()
- {
- $request = (new MessageFactory())->createRequest('GET', 'http://foo.com', [
- 'query' => ['Foo' => 'Bar']
- ]);
- $this->assertEquals('Bar', $request->getQuery()->get('Foo'));
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testValidatesQuery()
- {
- (new MessageFactory())->createRequest('GET', 'http://foo.com', [
- 'query' => 'foo'
- ]);
- }
-
- public function testCanSetDefaultQuery()
- {
- $request = (new MessageFactory())->createRequest('GET', 'http://foo.com?test=abc', [
- 'query' => ['Foo' => 'Bar', 'test' => 'def']
- ]);
- $this->assertEquals('Bar', $request->getQuery()->get('Foo'));
- $this->assertEquals('abc', $request->getQuery()->get('test'));
- }
-
- public function testCanSetDefaultQueryWithObject()
- {
- $request = (new MessageFactory)->createRequest(
- 'GET',
- 'http://foo.com?test=abc', [
- 'query' => new Query(['Foo' => 'Bar', 'test' => 'def'])
- ]
- );
- $this->assertEquals('Bar', $request->getQuery()->get('Foo'));
- $this->assertEquals('abc', $request->getQuery()->get('test'));
- }
-
- public function testCanAddBasicAuth()
- {
- $request = (new MessageFactory())->createRequest('GET', 'http://foo.com', [
- 'auth' => ['michael', 'test']
- ]);
- $this->assertTrue($request->hasHeader('Authorization'));
- }
-
- public function testCanAddDigestAuth()
- {
- $request = (new MessageFactory())->createRequest('GET', 'http://foo.com', [
- 'auth' => ['michael', 'test', 'digest']
- ]);
- $this->assertEquals('michael:test', $request->getConfig()->getPath('curl/' . CURLOPT_USERPWD));
- $this->assertEquals(CURLAUTH_DIGEST, $request->getConfig()->getPath('curl/' . CURLOPT_HTTPAUTH));
- }
-
- public function testCanDisableAuth()
- {
- $request = (new MessageFactory())->createRequest('GET', 'http://foo.com', [
- 'auth' => false
- ]);
- $this->assertFalse($request->hasHeader('Authorization'));
- }
-
- public function testCanSetCustomAuth()
- {
- $request = (new MessageFactory())->createRequest('GET', 'http://foo.com', [
- 'auth' => 'foo'
- ]);
- $this->assertEquals('foo', $request->getConfig()['auth']);
- }
-
- public function testCanAddEvents()
- {
- $foo = null;
- $client = new Client();
- $client->getEmitter()->attach(new Mock([new Response(200)]));
- $client->get('http://test.com', [
- 'events' => [
- 'before' => function () use (&$foo) { $foo = true; }
- ]
- ]);
- $this->assertTrue($foo);
- }
-
- public function testCanAddEventsWithPriority()
- {
- $foo = null;
- $client = new Client();
- $client->getEmitter()->attach(new Mock(array(new Response(200))));
- $request = $client->createRequest('GET', 'http://test.com', [
- 'events' => [
- 'before' => [
- 'fn' => function () use (&$foo) { $foo = true; },
- 'priority' => 123
- ]
- ]
- ]);
- $client->send($request);
- $this->assertTrue($foo);
- $l = $this->readAttribute($request->getEmitter(), 'listeners');
- $this->assertArrayHasKey(123, $l['before']);
- }
-
- public function testCanAddEventsOnce()
- {
- $foo = 0;
- $client = new Client();
- $client->getEmitter()->attach(new Mock([
- new Response(200),
- new Response(200),
- ]));
- $fn = function () use (&$foo) { ++$foo; };
- $request = $client->createRequest('GET', 'http://test.com', [
- 'events' => ['before' => ['fn' => $fn, 'once' => true]]
- ]);
- $client->send($request);
- $this->assertEquals(1, $foo);
- $client->send($request);
- $this->assertEquals(1, $foo);
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testValidatesEventContainsFn()
- {
- $client = new Client(['base_url' => 'http://test.com']);
- $client->createRequest('GET', '/', ['events' => ['before' => ['foo' => 'bar']]]);
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testValidatesEventIsArray()
- {
- $client = new Client(['base_url' => 'http://test.com']);
- $client->createRequest('GET', '/', ['events' => ['before' => '123']]);
- }
-
- public function testCanAddSubscribers()
- {
- $mock = new Mock([new Response(200)]);
- $client = new Client();
- $client->getEmitter()->attach($mock);
- $request = $client->get('http://test.com', ['subscribers' => [$mock]]);
- }
-
- public function testCanDisableExceptions()
- {
- $client = new Client();
- $this->assertEquals(500, $client->get('http://test.com', [
- 'subscribers' => [new Mock([new Response(500)])],
- 'exceptions' => false
- ])->getStatusCode());
- }
-
- public function testCanChangeSaveToLocation()
- {
- $saveTo = Stream::factory();
- $request = (new MessageFactory())->createRequest('GET', '/', ['save_to' => $saveTo]);
- $this->assertSame($saveTo, $request->getConfig()->get('save_to'));
- }
-
- public function testCanSetProxy()
- {
- $request = (new MessageFactory())->createRequest('GET', '/', ['proxy' => '192.168.16.121']);
- $this->assertEquals('192.168.16.121', $request->getConfig()->get('proxy'));
- }
-
- public function testCanSetHeadersOption()
- {
- $request = (new MessageFactory())->createRequest('GET', '/', ['headers' => ['Foo' => 'Bar']]);
- $this->assertEquals('Bar', (string) $request->getHeader('Foo'));
- }
-
- public function testCanSetHeaders()
- {
- $request = (new MessageFactory())->createRequest('GET', '/', [
- 'headers' => ['Foo' => ['Baz', 'Bar'], 'Test' => '123']
- ]);
- $this->assertEquals('Baz, Bar', $request->getHeader('Foo'));
- $this->assertEquals('123', $request->getHeader('Test'));
- }
-
- public function testCanSetTimeoutOption()
- {
- $request = (new MessageFactory())->createRequest('GET', '/', ['timeout' => 1.5]);
- $this->assertEquals(1.5, $request->getConfig()->get('timeout'));
- }
-
- public function testCanSetConnectTimeoutOption()
- {
- $request = (new MessageFactory())->createRequest('GET', '/', ['connect_timeout' => 1.5]);
- $this->assertEquals(1.5, $request->getConfig()->get('connect_timeout'));
- }
-
- public function testCanSetDebug()
- {
- $request = (new MessageFactory())->createRequest('GET', '/', ['debug' => true]);
- $this->assertTrue($request->getConfig()->get('debug'));
- }
-
- public function testCanSetVerifyToOff()
- {
- $request = (new MessageFactory())->createRequest('GET', '/', ['verify' => false]);
- $this->assertFalse($request->getConfig()->get('verify'));
- }
-
- public function testCanSetVerifyToOn()
- {
- $request = (new MessageFactory())->createRequest('GET', '/', ['verify' => true]);
- $this->assertTrue($request->getConfig()->get('verify'));
- }
-
- public function testCanSetVerifyToPath()
- {
- $request = (new MessageFactory())->createRequest('GET', '/', ['verify' => '/foo.pem']);
- $this->assertEquals('/foo.pem', $request->getConfig()->get('verify'));
- }
-
- public function inputValidation()
- {
- return array_map(function ($option) { return array($option); }, array(
- 'headers', 'events', 'subscribers', 'params'
- ));
- }
-
- /**
- * @dataProvider inputValidation
- * @expectedException \InvalidArgumentException
- */
- public function testValidatesInput($option)
- {
- (new MessageFactory())->createRequest('GET', '/', [$option => 'foo']);
- }
-
- public function testCanAddSslKey()
- {
- $request = (new MessageFactory())->createRequest('GET', '/', ['ssl_key' => '/foo.pem']);
- $this->assertEquals('/foo.pem', $request->getConfig()->get('ssl_key'));
- }
-
- public function testCanAddSslKeyPassword()
- {
- $request = (new MessageFactory())->createRequest('GET', '/', ['ssl_key' => ['/foo.pem', 'bar']]);
- $this->assertEquals(['/foo.pem', 'bar'], $request->getConfig()->get('ssl_key'));
- }
-
- public function testCanAddSslCert()
- {
- $request = (new MessageFactory())->createRequest('GET', '/', ['cert' => '/foo.pem']);
- $this->assertEquals('/foo.pem', $request->getConfig()->get('cert'));
- }
-
- public function testCanAddSslCertPassword()
- {
- $request = (new MessageFactory())->createRequest('GET', '/', ['cert' => ['/foo.pem', 'bar']]);
- $this->assertEquals(['/foo.pem', 'bar'], $request->getConfig()->get('cert'));
- }
-
- public function testCreatesBodyWithoutZeroString()
- {
- $request = (new MessageFactory())->createRequest('PUT', 'http://test.com', ['body' => '0']);
- $this->assertSame('0', (string) $request->getBody());
- }
-
- public function testCanSetProtocolVersion()
- {
- $request = (new MessageFactory())->createRequest('GET', 'http://t.com', ['version' => 1.0]);
- $this->assertEquals(1.0, $request->getProtocolVersion());
- }
-
- public function testCanAddJsonData()
- {
- $request = (new MessageFactory())->createRequest('PUT', 'http://f.com', [
- 'json' => ['foo' => 'bar']
- ]);
- $this->assertEquals(
- 'application/json',
- $request->getHeader('Content-Type')
- );
- $this->assertEquals('{"foo":"bar"}', (string) $request->getBody());
- }
-
- public function testCanAddJsonDataToAPostRequest()
- {
- $request = (new MessageFactory())->createRequest('POST', 'http://f.com', [
- 'json' => ['foo' => 'bar']
- ]);
- $this->assertEquals(
- 'application/json',
- $request->getHeader('Content-Type')
- );
- $this->assertEquals('{"foo":"bar"}', (string) $request->getBody());
- }
-
- public function testCanAddJsonDataAndNotOverwriteContentType()
- {
- $request = (new MessageFactory())->createRequest('PUT', 'http://f.com', [
- 'headers' => ['Content-Type' => 'foo'],
- 'json' => null
- ]);
- $this->assertEquals('foo', $request->getHeader('Content-Type'));
- $this->assertEquals('null', (string) $request->getBody());
- }
-
- public function testCanUseCustomRequestOptions()
- {
- $c = false;
- $f = new MessageFactory([
- 'foo' => function (RequestInterface $request, $value) use (&$c) {
- $c = true;
- $this->assertEquals('bar', $value);
- }
- ]);
-
- $f->createRequest('PUT', 'http://f.com', [
- 'headers' => ['Content-Type' => 'foo'],
- 'foo' => 'bar'
- ]);
-
- $this->assertTrue($c);
- }
-
- /**
- * @ticket https://github.com/guzzle/guzzle/issues/706
- */
- public function testDoesNotApplyPostBodyRightAway()
- {
- $request = (new MessageFactory())->createRequest('POST', 'http://f.cn', [
- 'body' => ['foo' => ['bar', 'baz']]
- ]);
- $this->assertEquals('', $request->getHeader('Content-Type'));
- $this->assertEquals('', $request->getHeader('Content-Length'));
- $request->getBody()->setAggregator(Query::duplicateAggregator());
- $request->getBody()->applyRequestHeaders($request);
- $this->assertEquals('foo=bar&foo=baz', $request->getBody());
- }
-
- public function testCanForceMultipartUploadWithContentType()
- {
- $client = new Client();
- $client->getEmitter()->attach(new Mock([new Response(200)]));
- $history = new History();
- $client->getEmitter()->attach($history);
- $client->post('http://foo.com', [
- 'headers' => ['Content-Type' => 'multipart/form-data'],
- 'body' => ['foo' => 'bar']
- ]);
- $this->assertContains(
- 'multipart/form-data; boundary=',
- $history->getLastRequest()->getHeader('Content-Type')
- );
- $this->assertContains(
- "Content-Disposition: form-data; name=\"foo\"\r\n\r\nbar",
- (string) $history->getLastRequest()->getBody()
- );
- }
-
- public function testDecodeDoesNotForceAcceptHeader()
- {
- $request = (new MessageFactory())->createRequest('POST', 'http://f.cn', [
- 'decode_content' => true
- ]);
- $this->assertEquals('', $request->getHeader('Accept-Encoding'));
- $this->assertTrue($request->getConfig()->get('decode_content'));
- }
-
- public function testDecodeCanAddAcceptHeader()
- {
- $request = (new MessageFactory())->createRequest('POST', 'http://f.cn', [
- 'decode_content' => 'gzip'
- ]);
- $this->assertEquals('gzip', $request->getHeader('Accept-Encoding'));
- $this->assertTrue($request->getConfig()->get('decode_content'));
- }
-
- public function testCanDisableDecoding()
- {
- $request = (new MessageFactory())->createRequest('POST', 'http://f.cn', [
- 'decode_content' => false
- ]);
- $this->assertEquals('', $request->getHeader('Accept-Encoding'));
- $this->assertNull($request->getConfig()->get('decode_content'));
- }
-}
-
-class ExtendedFactory extends MessageFactory
-{
- protected function add_foo() {}
-}
diff --git a/tests/Message/RequestTest.php b/tests/Message/RequestTest.php
deleted file mode 100644
index a6241a429..000000000
--- a/tests/Message/RequestTest.php
+++ /dev/null
@@ -1,132 +0,0 @@
- '123'], Stream::factory('foo'));
- $this->assertEquals('PUT', $r->getMethod());
- $this->assertEquals('/test', $r->getUrl());
- $this->assertEquals('123', $r->getHeader('test'));
- $this->assertEquals('foo', $r->getBody());
- }
-
- public function testConstructorInitializesMessageWithProtocolVersion()
- {
- $r = new Request('GET', '', [], null, ['protocol_version' => 10]);
- $this->assertEquals(10, $r->getProtocolVersion());
- }
-
- public function testConstructorInitializesMessageWithEmitter()
- {
- $e = new Emitter();
- $r = new Request('GET', '', [], null, ['emitter' => $e]);
- $this->assertSame($r->getEmitter(), $e);
- }
-
- public function testCloneIsDeep()
- {
- $r = new Request('GET', '/test', ['foo' => 'baz'], Stream::factory('foo'));
- $r2 = clone $r;
-
- $this->assertNotSame($r->getEmitter(), $r2->getEmitter());
- $this->assertEquals('foo', $r2->getBody());
-
- $r->getConfig()->set('test', 123);
- $this->assertFalse($r2->getConfig()->hasKey('test'));
-
- $r->setPath('/abc');
- $this->assertEquals('/test', $r2->getPath());
- }
-
- public function testCastsToString()
- {
- $r = new Request('GET', 'http://test.com/test', ['foo' => 'baz'], Stream::factory('body'));
- $s = explode("\r\n", (string) $r);
- $this->assertEquals("GET /test HTTP/1.1", $s[0]);
- $this->assertContains('Host: test.com', $s);
- $this->assertContains('foo: baz', $s);
- $this->assertContains('', $s);
- $this->assertContains('body', $s);
- }
-
- public function testSettingUrlOverridesHostHeaders()
- {
- $r = new Request('GET', 'http://test.com/test');
- $r->setUrl('https://baz.com/bar');
- $this->assertEquals('baz.com', $r->getHost());
- $this->assertEquals('baz.com', $r->getHeader('Host'));
- $this->assertEquals('/bar', $r->getPath());
- $this->assertEquals('https', $r->getScheme());
- }
-
- public function testQueryIsMutable()
- {
- $r = new Request('GET', 'http://www.foo.com?baz=bar');
- $this->assertEquals('baz=bar', $r->getQuery());
- $this->assertInstanceOf('GuzzleHttp\Query', $r->getQuery());
- $r->getQuery()->set('hi', 'there');
- $this->assertEquals('/?baz=bar&hi=there', $r->getResource());
- }
-
- public function testQueryCanChange()
- {
- $r = new Request('GET', 'http://www.foo.com?baz=bar');
- $r->setQuery(new Query(['foo' => 'bar']));
- $this->assertEquals('foo=bar', $r->getQuery());
- }
-
- public function testCanChangeMethod()
- {
- $r = new Request('GET', 'http://www.foo.com');
- $r->setMethod('put');
- $this->assertEquals('PUT', $r->getMethod());
- }
-
- public function testCanChangeSchemeWithPort()
- {
- $r = new Request('GET', 'http://www.foo.com:80');
- $r->setScheme('https');
- $this->assertEquals('https://www.foo.com', $r->getUrl());
- }
-
- public function testCanChangeScheme()
- {
- $r = new Request('GET', 'http://www.foo.com');
- $r->setScheme('https');
- $this->assertEquals('https://www.foo.com', $r->getUrl());
- }
-
- public function testCanChangeHost()
- {
- $r = new Request('GET', 'http://www.foo.com:222');
- $r->setHost('goo');
- $this->assertEquals('http://goo:222', $r->getUrl());
- $this->assertEquals('goo:222', $r->getHeader('host'));
- $r->setHost('goo:80');
- $this->assertEquals('http://goo', $r->getUrl());
- $this->assertEquals('goo', $r->getHeader('host'));
- }
-
- public function testCanChangePort()
- {
- $r = new Request('GET', 'http://www.foo.com:222');
- $this->assertSame(222, $r->getPort());
- $this->assertEquals('www.foo.com', $r->getHost());
- $this->assertEquals('www.foo.com:222', $r->getHeader('host'));
- $r->setPort(80);
- $this->assertSame(80, $r->getPort());
- $this->assertEquals('www.foo.com', $r->getHost());
- $this->assertEquals('www.foo.com', $r->getHeader('host'));
- }
-}
diff --git a/tests/Message/ResponseTest.php b/tests/Message/ResponseTest.php
deleted file mode 100644
index bbae24a17..000000000
--- a/tests/Message/ResponseTest.php
+++ /dev/null
@@ -1,120 +0,0 @@
- 'hi!']);
- $this->assertEquals(999, $response->getStatusCode());
- $this->assertEquals('hi!', $response->getReasonPhrase());
- }
-
- public function testConvertsToString()
- {
- $response = new Response(200);
- $this->assertEquals("HTTP/1.1 200 OK\r\n\r\n", (string) $response);
- // Add another header
- $response = new Response(200, ['X-Test' => 'Guzzle']);
- $this->assertEquals("HTTP/1.1 200 OK\r\nX-Test: Guzzle\r\n\r\n", (string) $response);
- $response = new Response(200, ['Content-Length' => 4], Stream::factory('test'));
- $this->assertEquals("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest", (string) $response);
- }
-
- public function testConvertsToStringAndSeeksToByteZero()
- {
- $response = new Response(200);
- $s = Stream::factory('foo');
- $s->read(1);
- $response->setBody($s);
- $this->assertEquals("HTTP/1.1 200 OK\r\n\r\nfoo", (string) $response);
- }
-
- public function testParsesJsonResponses()
- {
- $json = '{"foo": "bar"}';
- $response = new Response(200, [], Stream::factory($json));
- $this->assertEquals(['foo' => 'bar'], $response->json());
- $this->assertEquals(json_decode($json), $response->json(['object' => true]));
-
- $response = new Response(200);
- $this->assertEquals(null, $response->json());
- }
-
- /**
- * @expectedException \GuzzleHttp\Exception\ParseException
- * @expectedExceptionMessage Unable to parse JSON data: JSON_ERROR_SYNTAX - Syntax error, malformed JSON
- */
- public function testThrowsExceptionWhenFailsToParseJsonResponse()
- {
- $response = new Response(200, [], Stream::factory('{"foo": "'));
- $response->json();
- }
-
- public function testParsesXmlResponses()
- {
- $response = new Response(200, [], Stream::factory('bar'));
- $this->assertEquals('bar', (string) $response->xml()->foo);
- // Always return a SimpleXMLElement from the xml method
- $response = new Response(200);
- $this->assertEmpty((string) $response->xml()->foo);
- }
-
- /**
- * @expectedException \GuzzleHttp\Exception\XmlParseException
- * @expectedExceptionMessage Unable to parse response body into XML: String could not be parsed as XML
- */
- public function testThrowsExceptionWhenFailsToParseXmlResponse()
- {
- $response = new Response(200, [], Stream::factory('xml();
- } catch (XmlParseException $e) {
- $xmlParseError = $e->getError();
- $this->assertInstanceOf('\LibXMLError', $xmlParseError);
- $this->assertContains("Couldn't find end of Start Tag abc line 1", $xmlParseError->message);
- throw $e;
- }
- }
-
- public function testHasEffectiveUrl()
- {
- $r = new Response(200);
- $this->assertNull($r->getEffectiveUrl());
- $r->setEffectiveUrl('http://www.test.com');
- $this->assertEquals('http://www.test.com', $r->getEffectiveUrl());
- }
-
- public function testPreventsComplexExternalEntities()
- {
- $xml = ']>&test;';
- $response = new Response(200, [], Stream::factory($xml));
-
- $oldCwd = getcwd();
- chdir(__DIR__);
- try {
- $xml = $response->xml();
- chdir($oldCwd);
- $this->markTestIncomplete('Did not throw the expected exception! XML resolved as: ' . $xml->asXML());
- } catch (\Exception $e) {
- chdir($oldCwd);
- }
- }
-
- public function testStatusAndReasonAreMutable()
- {
- $response = new Response(200);
- $response->setStatusCode(201);
- $this->assertEquals(201, $response->getStatusCode());
- $response->setReasonPhrase('Foo');
- $this->assertEquals('Foo', $response->getReasonPhrase());
- }
-}
diff --git a/tests/Message/MessageParserTest.php b/tests/MessageParserTest.php
similarity index 99%
rename from tests/Message/MessageParserTest.php
rename to tests/MessageParserTest.php
index 0bcc9430f..d8d9c241a 100644
--- a/tests/Message/MessageParserTest.php
+++ b/tests/MessageParserTest.php
@@ -1,11 +1,10 @@
10]);
- $this->assertSame($c, $this->readAttribute($p, 'client'));
- $this->assertEquals(10, $this->readAttribute($p, 'poolSize'));
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testValidatesEachElement()
- {
- $c = new Client();
- $requests = ['foo'];
- $p = new Pool($c, new \ArrayIterator($requests));
- $p->wait();
- }
-
- public function testSendsAndRealizesFuture()
- {
- $c = $this->getClient();
- $p = new Pool($c, [$c->createRequest('GET', 'http://foo.com')]);
- $this->assertTrue($p->wait());
- $this->assertFalse($p->wait());
- $this->assertTrue($this->readAttribute($p, 'isRealized'));
- $this->assertFalse($p->cancel());
- }
-
- public function testSendsManyRequestsInCappedPool()
- {
- $c = $this->getClient();
- $p = new Pool($c, [$c->createRequest('GET', 'http://foo.com')]);
- $this->assertTrue($p->wait());
- $this->assertFalse($p->wait());
- }
-
- public function testSendsRequestsThatHaveNotBeenRealized()
- {
- $c = $this->getClient();
- $p = new Pool($c, [$c->createRequest('GET', 'http://foo.com')]);
- $this->assertTrue($p->wait());
- $this->assertFalse($p->wait());
- $this->assertFalse($p->cancel());
- }
-
- public function testCancelsInFlightRequests()
- {
- $c = $this->getClient();
- $h = new History();
- $c->getEmitter()->attach($h);
- $p = new Pool($c, [
- $c->createRequest('GET', 'http://foo.com'),
- $c->createRequest('GET', 'http://foo.com', [
- 'events' => [
- 'before' => [
- 'fn' => function () use (&$p) {
- $this->assertTrue($p->cancel());
- },
- 'priority' => RequestEvents::EARLY
- ]
- ]
- ])
- ]);
- ob_start();
- $p->wait();
- $contents = ob_get_clean();
- $this->assertEquals(1, count($h));
- $this->assertEquals('Cancelling', $contents);
- }
-
- private function getClient()
- {
- $deferred = new Deferred();
- $future = new FutureArray(
- $deferred->promise(),
- function() use ($deferred) {
- $deferred->resolve(['status' => 200, 'headers' => []]);
- }, function () {
- echo 'Cancelling';
- }
- );
-
- return new Client(['handler' => new MockHandler($future)]);
- }
-
- public function testBatchesRequests()
- {
- $client = new Client(['handler' => function () {
- throw new \RuntimeException('No network access');
- }]);
-
- $responses = [
- new Response(301, ['Location' => 'http://foo.com/bar']),
- new Response(200),
- new Response(200),
- new Response(404)
- ];
-
- $client->getEmitter()->attach(new Mock($responses));
- $requests = [
- $client->createRequest('GET', 'http://foo.com/baz'),
- $client->createRequest('HEAD', 'http://httpbin.org/get'),
- $client->createRequest('PUT', 'http://httpbin.org/put'),
- ];
-
- $a = $b = $c = $d = 0;
- $result = Pool::batch($client, $requests, [
- 'before' => function (BeforeEvent $e) use (&$a) { $a++; },
- 'complete' => function (CompleteEvent $e) use (&$b) { $b++; },
- 'error' => function (ErrorEvent $e) use (&$c) { $c++; },
- 'end' => function (EndEvent $e) use (&$d) { $d++; }
- ]);
-
- $this->assertEquals(4, $a);
- $this->assertEquals(2, $b);
- $this->assertEquals(1, $c);
- $this->assertEquals(3, $d);
- $this->assertCount(3, $result);
- $this->assertInstanceOf('GuzzleHttp\BatchResults', $result);
-
- // The first result is actually the second (redirect) response.
- $this->assertSame($responses[1], $result[0]);
- // The second result is a 1:1 request:response map
- $this->assertSame($responses[2], $result[1]);
- // The third entry is the 404 RequestException
- $this->assertSame($responses[3], $result[2]->getResponse());
- }
-
- public function testBatchesRequestsWithDynamicPoolSize()
- {
- $client = new Client(['handler' => function () {
- throw new \RuntimeException('No network access');
- }]);
-
- $responses = [
- new Response(301, ['Location' => 'http://foo.com/bar']),
- new Response(200),
- new Response(200),
- new Response(404)
- ];
-
- $client->getEmitter()->attach(new Mock($responses));
- $requests = [
- $client->createRequest('GET', 'http://foo.com/baz'),
- $client->createRequest('HEAD', 'http://httpbin.org/get'),
- $client->createRequest('PUT', 'http://httpbin.org/put'),
- ];
-
- $a = $b = $c = $d = 0;
- $result = Pool::batch($client, $requests, [
- 'before' => function (BeforeEvent $e) use (&$a) { $a++; },
- 'complete' => function (CompleteEvent $e) use (&$b) { $b++; },
- 'error' => function (ErrorEvent $e) use (&$c) { $c++; },
- 'end' => function (EndEvent $e) use (&$d) { $d++; },
- 'pool_size' => function ($queueSize) {
- static $options = [1, 2, 1];
- static $queued = 0;
-
- $this->assertEquals(
- $queued,
- $queueSize,
- 'The number of queued requests should be equal to the sum of pool sizes so far.'
- );
-
- $next = array_shift($options);
- $queued += $next;
-
- return $next;
- }
- ]);
-
- $this->assertEquals(4, $a);
- $this->assertEquals(2, $b);
- $this->assertEquals(1, $c);
- $this->assertEquals(3, $d);
- $this->assertCount(3, $result);
- $this->assertInstanceOf('GuzzleHttp\BatchResults', $result);
-
- // The first result is actually the second (redirect) response.
- $this->assertSame($responses[1], $result[0]);
- // The second result is a 1:1 request:response map
- $this->assertSame($responses[2], $result[1]);
- // The third entry is the 404 RequestException
- $this->assertSame($responses[3], $result[2]->getResponse());
- }
-
- /**
- * @expectedException \InvalidArgumentException
- * @expectedExceptionMessage Each event listener must be a callable or
- */
- public function testBatchValidatesTheEventFormat()
- {
- $client = new Client();
- $requests = [$client->createRequest('GET', 'http://foo.com/baz')];
- Pool::batch($client, $requests, ['complete' => 'foo']);
- }
-
- public function testEmitsProgress()
- {
- $client = new Client(['handler' => function () {
- throw new \RuntimeException('No network access');
- }]);
-
- $responses = [new Response(200), new Response(404)];
- $client->getEmitter()->attach(new Mock($responses));
- $requests = [
- $client->createRequest('GET', 'http://foo.com/baz'),
- $client->createRequest('HEAD', 'http://httpbin.org/get')
- ];
-
- $pool = new Pool($client, $requests);
- $count = 0;
- $thenned = null;
- $pool->then(
- function ($value) use (&$thenned) {
- $thenned = $value;
- },
- null,
- function ($result) use (&$count, $requests) {
- $this->assertSame($requests[$count], $result['request']);
- if ($count == 0) {
- $this->assertNull($result['error']);
- $this->assertEquals(200, $result['response']->getStatusCode());
- } else {
- $this->assertInstanceOf(
- 'GuzzleHttp\Exception\ClientException',
- $result['error']
- );
- }
- $count++;
- }
- );
-
- $pool->wait();
- $this->assertEquals(2, $count);
- $this->assertEquals(true, $thenned);
- }
-
- public function testDoesNotThrowInErrorEvent()
- {
- $client = new Client();
- $responses = [new Response(404)];
- $client->getEmitter()->attach(new Mock($responses));
- $requests = [$client->createRequest('GET', 'http://foo.com/baz')];
- $result = Pool::batch($client, $requests);
- $this->assertCount(1, $result);
- $this->assertInstanceOf('GuzzleHttp\Exception\ClientException', $result[0]);
- }
-
- public function testHasSendMethod()
- {
- $client = new Client();
- $responses = [new Response(404)];
- $history = new History();
- $client->getEmitter()->attach($history);
- $client->getEmitter()->attach(new Mock($responses));
- $requests = [$client->createRequest('GET', 'http://foo.com/baz')];
- Pool::send($client, $requests);
- $this->assertCount(1, $history);
- }
-
- public function testDoesNotInfinitelyRecurse()
- {
- $client = new Client(['handler' => function () {
- throw new \RuntimeException('No network access');
- }]);
-
- $last = null;
- $client->getEmitter()->on(
- 'before',
- function (BeforeEvent $e) use (&$last) {
- $e->intercept(new Response(200));
- if (function_exists('xdebug_get_stack_depth')) {
- if ($last) {
- $this->assertEquals($last, xdebug_get_stack_depth());
- } else {
- $last = xdebug_get_stack_depth();
- }
- }
- }
- );
-
- $requests = [];
- for ($i = 0; $i < 100; $i++) {
- $requests[] = $client->createRequest('GET', 'http://foo.com');
- }
-
- $pool = new Pool($client, $requests);
- $pool->wait();
- }
-}
diff --git a/tests/Post/MultipartBodyTest.php b/tests/Post/MultipartBodyTest.php
deleted file mode 100644
index 4b3b39164..000000000
--- a/tests/Post/MultipartBodyTest.php
+++ /dev/null
@@ -1,120 +0,0 @@
- 'bar'], [
- new PostFile('foo', 'abc', 'foo.txt')
- ], 'abcdef');
- }
-
- public function testConstructorAddsFieldsAndFiles()
- {
- $b = $this->getTestBody();
- $this->assertEquals('abcdef', $b->getBoundary());
- $c = (string) $b;
- $this->assertContains("--abcdef\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\nbar\r\n", $c);
- $this->assertContains("--abcdef\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"foo.txt\"\r\n"
- . "Content-Type: text/plain\r\n\r\nabc\r\n--abcdef--", $c);
- }
-
- public function testDoesNotModifyFieldFormat()
- {
- $m = new MultipartBody(['foo+baz' => 'bar+bam %20 boo'], [
- new PostFile('foo+bar', 'abc %20 123', 'foo.txt')
- ], 'abcdef');
- $this->assertContains('name="foo+baz"', (string) $m);
- $this->assertContains('name="foo+bar"', (string) $m);
- $this->assertContains('bar+bam %20 boo', (string) $m);
- $this->assertContains('abc %20 123', (string) $m);
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testConstructorValidatesFiles()
- {
- new MultipartBody([], ['bar']);
- }
-
- public function testConstructorCanCreateBoundary()
- {
- $b = new MultipartBody();
- $this->assertNotNull($b->getBoundary());
- }
-
- public function testWrapsStreamMethods()
- {
- $b = $this->getTestBody();
- $this->assertFalse($b->write('foo'));
- $this->assertFalse($b->isWritable());
- $this->assertTrue($b->isReadable());
- $this->assertTrue($b->isSeekable());
- $this->assertEquals(0, $b->tell());
- }
-
- public function testCanDetachFieldsAndFiles()
- {
- $b = $this->getTestBody();
- $b->detach();
- $b->close();
- $this->assertEquals('', (string) $b);
- }
-
- public function testIsSeekableReturnsTrueIfAllAreSeekable()
- {
- $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface')
- ->setMethods(['isSeekable', 'isReadable'])
- ->getMockForAbstractClass();
- $s->expects($this->once())
- ->method('isSeekable')
- ->will($this->returnValue(false));
- $s->expects($this->once())
- ->method('isReadable')
- ->will($this->returnValue(true));
- $p = new PostFile('foo', $s, 'foo.php');
- $b = new MultipartBody([], [$p]);
- $this->assertFalse($b->isSeekable());
- $this->assertFalse($b->seek(10));
- }
-
- public function testReadsFromBuffer()
- {
- $b = $this->getTestBody();
- $c = $b->read(1);
- $c .= $b->read(1);
- $c .= $b->read(1);
- $c .= $b->read(1);
- $c .= $b->read(1);
- $this->assertEquals('--abc', $c);
- }
-
- public function testCalculatesSize()
- {
- $b = $this->getTestBody();
- $this->assertEquals(strlen($b), $b->getSize());
- }
-
- public function testCalculatesSizeAndReturnsNullForUnknown()
- {
- $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface')
- ->setMethods(['getSize', 'isReadable'])
- ->getMockForAbstractClass();
- $s->expects($this->once())
- ->method('getSize')
- ->will($this->returnValue(null));
- $s->expects($this->once())
- ->method('isReadable')
- ->will($this->returnValue(true));
- $b = new MultipartBody([], [new PostFile('foo', $s, 'foo.php')]);
- $this->assertNull($b->getSize());
- }
-}
diff --git a/tests/Post/PostBodyTest.php b/tests/Post/PostBodyTest.php
deleted file mode 100644
index 0283a5ebf..000000000
--- a/tests/Post/PostBodyTest.php
+++ /dev/null
@@ -1,255 +0,0 @@
-assertTrue($b->isSeekable());
- $this->assertTrue($b->isReadable());
- $this->assertFalse($b->isWritable());
- $this->assertFalse($b->write('foo'));
- }
-
- public function testApplyingWithNothingDoesNothing()
- {
- $b = new PostBody();
- $m = new Request('POST', '/');
- $b->applyRequestHeaders($m);
- $this->assertFalse($m->hasHeader('Content-Length'));
- $this->assertFalse($m->hasHeader('Content-Type'));
- }
-
- public function testCanForceMultipartUploadsWhenApplying()
- {
- $b = new PostBody();
- $b->forceMultipartUpload(true);
- $m = new Request('POST', '/');
- $b->applyRequestHeaders($m);
- $this->assertContains(
- 'multipart/form-data',
- $m->getHeader('Content-Type')
- );
- }
-
- public function testApplyingWithFilesAddsMultipartUpload()
- {
- $b = new PostBody();
- $p = new PostFile('foo', fopen(__FILE__, 'r'));
- $b->addFile($p);
- $this->assertEquals([$p], $b->getFiles());
- $this->assertNull($b->getFile('missing'));
- $this->assertSame($p, $b->getFile('foo'));
- $m = new Request('POST', '/');
- $b->applyRequestHeaders($m);
- $this->assertContains(
- 'multipart/form-data',
- $m->getHeader('Content-Type')
- );
- $this->assertTrue($m->hasHeader('Content-Length'));
- }
-
- public function testApplyingWithFieldsAddsMultipartUpload()
- {
- $b = new PostBody();
- $b->setField('foo', 'bar');
- $this->assertEquals(['foo' => 'bar'], $b->getFields());
- $m = new Request('POST', '/');
- $b->applyRequestHeaders($m);
- $this->assertContains(
- 'application/x-www-form',
- $m->getHeader('Content-Type')
- );
- $this->assertTrue($m->hasHeader('Content-Length'));
- }
-
- public function testMultipartWithNestedFields()
- {
- $b = new PostBody();
- $b->setField('foo', ['bar' => 'baz']);
- $b->forceMultipartUpload(true);
- $this->assertEquals(['foo' => ['bar' => 'baz']], $b->getFields());
- $m = new Request('POST', '/');
- $b->applyRequestHeaders($m);
- $this->assertContains(
- 'multipart/form-data',
- $m->getHeader('Content-Type')
- );
- $this->assertTrue($m->hasHeader('Content-Length'));
- $contents = $b->getContents();
- $this->assertContains('name="foo[bar]"', $contents);
- $this->assertNotContains('name="foo"', $contents);
- }
-
- public function testCountProvidesFieldsAndFiles()
- {
- $b = new PostBody();
- $b->setField('foo', 'bar');
- $b->addFile(new PostFile('foo', fopen(__FILE__, 'r')));
- $this->assertEquals(2, count($b));
- $b->clearFiles();
- $b->removeField('foo');
- $this->assertEquals(0, count($b));
- $this->assertEquals([], $b->getFiles());
- $this->assertEquals([], $b->getFields());
- }
-
- public function testHasFields()
- {
- $b = new PostBody();
- $b->setField('foo', 'bar');
- $b->setField('baz', '123');
- $this->assertEquals('bar', $b->getField('foo'));
- $this->assertEquals('123', $b->getField('baz'));
- $this->assertNull($b->getField('ahh'));
- $this->assertTrue($b->hasField('foo'));
- $this->assertFalse($b->hasField('test'));
- $b->replaceFields(['abc' => '123']);
- $this->assertFalse($b->hasField('foo'));
- $this->assertTrue($b->hasField('abc'));
- }
-
- public function testConvertsFieldsToQueryStyleBody()
- {
- $b = new PostBody();
- $b->setField('foo', 'bar');
- $b->setField('baz', '123');
- $this->assertEquals('foo=bar&baz=123', $b);
- $this->assertEquals(15, $b->getSize());
- $b->seek(0);
- $this->assertEquals('foo=bar&baz=123', $b->getContents());
- $b->seek(0);
- $this->assertEquals('foo=bar&baz=123', $b->read(1000));
- $this->assertEquals(15, $b->tell());
- }
-
- public function testCanSpecifyQueryAggregator()
- {
- $b = new PostBody();
- $b->setField('foo', ['baz', 'bar']);
- $this->assertEquals('foo%5B0%5D=baz&foo%5B1%5D=bar', (string) $b);
- $b = new PostBody();
- $b->setField('foo', ['baz', 'bar']);
- $agg = Query::duplicateAggregator();
- $b->setAggregator($agg);
- $this->assertEquals('foo=baz&foo=bar', (string) $b);
- }
-
- public function testDetachesAndCloses()
- {
- $b = new PostBody();
- $b->setField('foo', 'bar');
- $b->detach();
- $b->close();
- $this->assertEquals('', $b->read(10));
- }
-
- public function testDetachesWhenBodyIsPresent()
- {
- $b = new PostBody();
- $b->setField('foo', 'bar');
- $b->getContents();
- $b->detach();
- }
-
- public function testFlushAndMetadataPlaceholders()
- {
- $b = new PostBody();
- $this->assertEquals([], $b->getMetadata());
- $this->assertNull($b->getMetadata('foo'));
- }
-
- public function testCreatesMultipartUploadWithMultiFields()
- {
- $b = new PostBody();
- $b->setField('testing', ['baz', 'bar']);
- $b->setField('other', 'hi');
- $b->setField('third', 'there');
- $b->addFile(new PostFile('foo', fopen(__FILE__, 'r')));
- $s = (string) $b;
- $this->assertContains(file_get_contents(__FILE__), $s);
- $this->assertContains('testing=bar', $s);
- $this->assertContains(
- 'Content-Disposition: form-data; name="third"',
- $s
- );
- $this->assertContains(
- 'Content-Disposition: form-data; name="other"',
- $s
- );
- }
-
- public function testMultipartWithBase64Fields()
- {
- $b = new PostBody();
- $b->setField('foo64', '/xA2JhWEqPcgyLRDdir9WSRi/khpb2Lh3ooqv+5VYoc=');
- $b->forceMultipartUpload(true);
- $this->assertEquals(
- ['foo64' => '/xA2JhWEqPcgyLRDdir9WSRi/khpb2Lh3ooqv+5VYoc='],
- $b->getFields()
- );
- $m = new Request('POST', '/');
- $b->applyRequestHeaders($m);
- $this->assertContains(
- 'multipart/form-data',
- $m->getHeader('Content-Type')
- );
- $this->assertTrue($m->hasHeader('Content-Length'));
- $contents = $b->getContents();
- $this->assertContains('name="foo64"', $contents);
- $this->assertContains(
- '/xA2JhWEqPcgyLRDdir9WSRi/khpb2Lh3ooqv+5VYoc=',
- $contents
- );
- }
-
- public function testMultipartWithAmpersandInValue()
- {
- $b = new PostBody();
- $b->setField('a', 'b&c=d');
- $b->forceMultipartUpload(true);
- $this->assertEquals(['a' => 'b&c=d'], $b->getFields());
- $m = new Request('POST', '/');
- $b->applyRequestHeaders($m);
- $this->assertContains(
- 'multipart/form-data',
- $m->getHeader('Content-Type')
- );
- $this->assertTrue($m->hasHeader('Content-Length'));
- $contents = $b->getContents();
- $this->assertContains('name="a"', $contents);
- $this->assertContains('b&c=d', $contents);
- }
-
- /**
- * @expectedException \GuzzleHttp\Stream\Exception\CannotAttachException
- */
- public function testCannotAttach()
- {
- $b = new PostBody();
- $b->attach('foo');
- }
-
- public function testDoesNotOverwriteExistingHeaderForUrlencoded()
- {
- $m = new Request('POST', 'http://foo.com', [
- 'content-type' => 'application/x-www-form-urlencoded; charset=utf-8'
- ]);
- $b = new PostBody();
- $b->setField('foo', 'bar');
- $b->applyRequestHeaders($m);
- $this->assertEquals(
- 'application/x-www-form-urlencoded; charset=utf-8',
- $m->getHeader('Content-Type')
- );
- }
-}
diff --git a/tests/Post/PostFileTest.php b/tests/Post/PostFileTest.php
deleted file mode 100644
index 800cee503..000000000
--- a/tests/Post/PostFileTest.php
+++ /dev/null
@@ -1,61 +0,0 @@
-assertInstanceOf('GuzzleHttp\Post\PostFileInterface', $p);
- $this->assertEquals('hi', $p->getContent());
- $this->assertEquals('foo', $p->getName());
- $this->assertEquals('/path/to/test.php', $p->getFilename());
- $this->assertEquals(
- 'form-data; name="foo"; filename="test.php"',
- $p->getHeaders()['Content-Disposition']
- );
- }
-
- public function testGetsFilenameFromMetadata()
- {
- $p = new PostFile('foo', fopen(__FILE__, 'r'));
- $this->assertEquals(__FILE__, $p->getFilename());
- }
-
- public function testDefaultsToNameWhenNoFilenameExists()
- {
- $p = new PostFile('foo', 'bar');
- $this->assertEquals('foo', $p->getFilename());
- }
-
- public function testCreatesFromMultipartFormData()
- {
- $mp = new MultipartBody([], [], 'baz');
- $p = new PostFile('foo', $mp);
- $this->assertEquals(
- 'form-data; name="foo"',
- $p->getHeaders()['Content-Disposition']
- );
- $this->assertEquals(
- 'multipart/form-data; boundary=baz',
- $p->getHeaders()['Content-Type']
- );
- }
-
- public function testCanAddHeaders()
- {
- $p = new PostFile('foo', Stream::factory('hi'), 'test.php', [
- 'X-Foo' => '123',
- 'Content-Disposition' => 'bar'
- ]);
- $this->assertEquals('bar', $p->getHeaders()['Content-Disposition']);
- $this->assertEquals('123', $p->getHeaders()['X-Foo']);
- }
-}
diff --git a/tests/QueryParserTest.php b/tests/QueryParserTest.php
deleted file mode 100644
index e9075a80d..000000000
--- a/tests/QueryParserTest.php
+++ /dev/null
@@ -1,80 +0,0 @@
- ['a', 'b']]],
- // Can parse multi-valued items that use numeric indices
- ['q[0]=a&q[1]=b', ['q' => ['a', 'b']]],
- // Can parse duplicates and does not include numeric indices
- ['q[]=a&q[]=b', ['q' => ['a', 'b']]],
- // Ensures that the value of "q" is an array even though one value
- ['q[]=a', ['q' => ['a']]],
- // Does not modify "." to "_" like PHP's parse_str()
- ['q.a=a&q.b=b', ['q.a' => 'a', 'q.b' => 'b']],
- // Can decode %20 to " "
- ['q%20a=a%20b', ['q a' => 'a b']],
- // Can parse funky strings with no values by assigning each to null
- ['q&a', ['q' => null, 'a' => null]],
- // Does not strip trailing equal signs
- ['data=abc=', ['data' => 'abc=']],
- // Can store duplicates without affecting other values
- ['foo=a&foo=b&?µ=c', ['foo' => ['a', 'b'], '?µ' => 'c']],
- // Sets value to null when no "=" is present
- ['foo', ['foo' => null]],
- // Preserves "0" keys.
- ['0', ['0' => null]],
- // Sets the value to an empty string when "=" is present
- ['0=', ['0' => '']],
- // Preserves falsey keys
- ['var=0', ['var' => '0']],
- // Can deeply nest and store duplicate PHP values
- ['a[b][c]=1&a[b][c]=2', [
- 'a' => ['b' => ['c' => ['1', '2']]]
- ]],
- // Can parse PHP style arrays
- ['a[b]=c&a[d]=e', ['a' => ['b' => 'c', 'd' => 'e']]],
- // Ensure it doesn't leave things behind with repeated values
- // Can parse mult-values items
- ['q=a&q=b&q=c', ['q' => ['a', 'b', 'c']]],
- ];
- }
-
- /**
- * @dataProvider parseQueryProvider
- */
- public function testParsesQueries($input, $output)
- {
- $query = Query::fromString($input);
- $this->assertEquals($output, $query->toArray());
- // Normalize the input and output
- $query->setEncodingType(false);
- $this->assertEquals(rawurldecode($input), (string) $query);
- }
-
- public function testConvertsPlusSymbolsToSpacesByDefault()
- {
- $query = Query::fromString('var=foo+bar', true);
- $this->assertEquals('foo bar', $query->get('var'));
- }
-
- public function testCanControlDecodingType()
- {
- $qp = new QueryParser();
- $q = new Query();
- $qp->parseInto($q, 'var=foo+bar', Query::RFC3986);
- $this->assertEquals('foo+bar', $q->get('var'));
- $qp->parseInto($q, 'var=foo+bar', Query::RFC1738);
- $this->assertEquals('foo bar', $q->get('var'));
- }
-}
diff --git a/tests/QueryTest.php b/tests/QueryTest.php
deleted file mode 100644
index 8b9d3448f..000000000
--- a/tests/QueryTest.php
+++ /dev/null
@@ -1,171 +0,0 @@
- 'baz', 'bar' => 'bam boozle']);
- $this->assertEquals('foo=baz&bar=bam%20boozle', (string) $q);
- }
-
- public function testCanDisableUrlEncoding()
- {
- $q = new Query(['bar' => 'bam boozle']);
- $q->setEncodingType(false);
- $this->assertEquals('bar=bam boozle', (string) $q);
- }
-
- public function testCanSpecifyRfc1783UrlEncodingType()
- {
- $q = new Query(['bar abc' => 'bam boozle']);
- $q->setEncodingType(Query::RFC1738);
- $this->assertEquals('bar+abc=bam+boozle', (string) $q);
- }
-
- public function testCanSpecifyRfc3986UrlEncodingType()
- {
- $q = new Query(['bar abc' => 'bam boozle', 'ሴ' => 'hi']);
- $q->setEncodingType(Query::RFC3986);
- $this->assertEquals('bar%20abc=bam%20boozle&%E1%88%B4=hi', (string) $q);
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testValidatesEncodingType()
- {
- (new Query(['bar' => 'bam boozle']))->setEncodingType('foo');
- }
-
- public function testAggregatesMultipleValues()
- {
- $q = new Query(['foo' => ['bar', 'baz']]);
- $this->assertEquals('foo%5B0%5D=bar&foo%5B1%5D=baz', (string) $q);
- }
-
- public function testCanSetAggregator()
- {
- $q = new Query(['foo' => ['bar', 'baz']]);
- $q->setAggregator(function (array $data) {
- return ['foo' => ['barANDbaz']];
- });
- $this->assertEquals('foo=barANDbaz', (string) $q);
- }
-
- public function testAllowsMultipleValuesPerKey()
- {
- $q = new Query();
- $q->add('facet', 'size');
- $q->add('facet', 'width');
- $q->add('facet.field', 'foo');
- // Use the duplicate aggregator
- $q->setAggregator($q::duplicateAggregator());
- $this->assertEquals('facet=size&facet=width&facet.field=foo', (string) $q);
- }
-
- public function testAllowsZeroValues()
- {
- $query = new Query(array(
- 'foo' => 0,
- 'baz' => '0',
- 'bar' => null,
- 'boo' => false
- ));
- $this->assertEquals('foo=0&baz=0&bar&boo=', (string) $query);
- }
-
- private $encodeData = [
- 't' => [
- 'v1' => ['a', '1'],
- 'v2' => 'b',
- 'v3' => ['v4' => 'c', 'v5' => 'd']
- ]
- ];
-
- public function testEncodesDuplicateAggregator()
- {
- $agg = Query::duplicateAggregator();
- $result = $agg($this->encodeData);
- $this->assertEquals(array(
- 't[v1]' => ['a', '1'],
- 't[v2]' => ['b'],
- 't[v3][v4]' => ['c'],
- 't[v3][v5]' => ['d'],
- ), $result);
- }
-
- public function testDuplicateEncodesNoNumericIndices()
- {
- $agg = Query::duplicateAggregator();
- $result = $agg($this->encodeData);
- $this->assertEquals(array(
- 't[v1]' => ['a', '1'],
- 't[v2]' => ['b'],
- 't[v3][v4]' => ['c'],
- 't[v3][v5]' => ['d'],
- ), $result);
- }
-
- public function testEncodesPhpAggregator()
- {
- $agg = Query::phpAggregator();
- $result = $agg($this->encodeData);
- $this->assertEquals(array(
- 't[v1][0]' => ['a'],
- 't[v1][1]' => ['1'],
- 't[v2]' => ['b'],
- 't[v3][v4]' => ['c'],
- 't[v3][v5]' => ['d'],
- ), $result);
- }
-
- public function testPhpEncodesNoNumericIndices()
- {
- $agg = Query::phpAggregator(false);
- $result = $agg($this->encodeData);
- $this->assertEquals(array(
- 't[v1][]' => ['a', '1'],
- 't[v2]' => ['b'],
- 't[v3][v4]' => ['c'],
- 't[v3][v5]' => ['d'],
- ), $result);
- }
-
- public function testCanDisableUrlEncodingDecoding()
- {
- $q = Query::fromString('foo=bar+baz boo%20', false);
- $this->assertEquals('bar+baz boo%20', $q['foo']);
- $this->assertEquals('foo=bar+baz boo%20', (string) $q);
- }
-
- public function testCanChangeUrlEncodingDecodingToRfc1738()
- {
- $q = Query::fromString('foo=bar+baz', Query::RFC1738);
- $this->assertEquals('bar baz', $q['foo']);
- $this->assertEquals('foo=bar+baz', (string) $q);
- }
-
- public function testCanChangeUrlEncodingDecodingToRfc3986()
- {
- $q = Query::fromString('foo=bar%20baz', Query::RFC3986);
- $this->assertEquals('bar baz', $q['foo']);
- $this->assertEquals('foo=bar%20baz', (string) $q);
- }
-
- public function testQueryStringsAllowSlashButDoesNotDecodeWhenDisable()
- {
- $q = Query::fromString('foo=bar%2Fbaz&bam=boo%20boo', Query::RFC3986);
- $q->setEncodingType(false);
- $this->assertEquals('foo=bar/baz&bam=boo boo', (string) $q);
- }
-
- public function testQueryStringsAllowDecodingEncodingCompletelyDisabled()
- {
- $q = Query::fromString('foo=bar%2Fbaz&bam=boo boo!', false);
- $this->assertEquals('foo=bar%2Fbaz&bam=boo boo!', (string) $q);
- }
-}
diff --git a/tests/RejectedResponseTest.php b/tests/RejectedResponseTest.php
new file mode 100644
index 000000000..49909b04f
--- /dev/null
+++ b/tests/RejectedResponseTest.php
@@ -0,0 +1,40 @@
+assertEquals('rejected', $p->getState());
+ /** @var callable $f */
+ $f = [$this, 'check'];
+ $f($p, 'getStatusCode');
+ $f($p, 'getReasonPhrase');
+ $f($p, 'getHeaders');
+ $f($p, 'getHeaderLines', ['foo']);
+ $f($p, 'hasHeader', ['foo']);
+ $f($p, 'getHeader', ['foo']);
+ $f($p, 'withAddedHeader', ['foo', 'bar']);
+ $f($p, 'withHeader', ['foo', 'bar']);
+ $f($p, 'withoutHeader', ['foo']);
+ $f($p, 'getBody');
+ $f($p, 'withBody', [Stream::factory('test')]);
+ $f($p, 'getProtocolVersion');
+ $f($p, 'withProtocolVersion', ['2']);
+ $f($p, 'withStatus', ['202']);
+ }
+
+ private function check($m, $f, array $args = [])
+ {
+ try {
+ call_user_func_array([$m, $f], $args);
+ $this->fail('Should have thrown');
+ } catch (\UnexpectedValueException $e) {
+ $this->assertEquals('test', $e->getMessage());
+ }
+ }
+}
diff --git a/tests/RequestFsmTest.php b/tests/RequestFsmTest.php
deleted file mode 100644
index dd6768405..000000000
--- a/tests/RequestFsmTest.php
+++ /dev/null
@@ -1,187 +0,0 @@
-mf = new MessageFactory();
- }
-
- public function testEmitsBeforeEventInTransition()
- {
- $fsm = new RequestFsm(function () {
- return new CompletedFutureArray(['status' => 200]);
- }, $this->mf);
- $t = new Transaction(new Client(), new Request('GET', 'http://foo.com'));
- $c = false;
- $t->request->getEmitter()->on('before', function (BeforeEvent $e) use (&$c) {
- $c = true;
- });
- $fsm($t);
- $this->assertTrue($c);
- }
-
- public function testEmitsCompleteEventInTransition()
- {
- $fsm = new RequestFsm(function () {
- return new CompletedFutureArray(['status' => 200]);
- }, $this->mf);
- $t = new Transaction(new Client(), new Request('GET', 'http://foo.com'));
- $t->response = new Response(200);
- $t->state = 'complete';
- $c = false;
- $t->request->getEmitter()->on('complete', function (CompleteEvent $e) use (&$c) {
- $c = true;
- });
- $fsm($t);
- $this->assertTrue($c);
- }
-
- public function testDoesNotEmitCompleteForFuture()
- {
- $fsm = new RequestFsm(function () {
- return new CompletedFutureArray(['status' => 200]);
- }, $this->mf);
- $t = new Transaction(new Client(), new Request('GET', 'http://foo.com'));
- $deferred = new Deferred();
- $t->response = new FutureResponse($deferred->promise());
- $t->state = 'complete';
- $c = false;
- $t->request->getEmitter()->on('complete', function (CompleteEvent $e) use (&$c) {
- $c = true;
- });
- $fsm($t);
- $this->assertFalse($c);
- }
-
- public function testTransitionsThroughSuccessfulTransfer()
- {
- $client = new Client();
- $client->getEmitter()->attach(new Mock([new Response(200)]));
- $request = $client->createRequest('GET', 'http://ewfewwef.com');
- $this->addListeners($request, $calls);
- $client->send($request);
- $this->assertEquals(['before', 'complete', 'end'], $calls);
- }
-
- public function testTransitionsThroughErrorsInBefore()
- {
- $fsm = new RequestFsm(function () {
- return new CompletedFutureArray(['status' => 200]);
- }, $this->mf);
- $client = new Client();
- $request = $client->createRequest('GET', 'http://ewfewwef.com');
- $t = new Transaction($client, $request);
- $calls = [];
- $this->addListeners($t->request, $calls);
- $t->request->getEmitter()->on('before', function (BeforeEvent $e) {
- throw new \Exception('foo');
- });
- try {
- $fsm($t);
- $this->fail('did not throw');
- } catch (RequestException $e) {
- $this->assertContains('foo', $t->exception->getMessage());
- $this->assertEquals(['before', 'error', 'end'], $calls);
- }
- }
-
- public function testTransitionsThroughErrorsInComplete()
- {
- $client = new Client();
- $client->getEmitter()->attach(new Mock([new Response(200)]));
- $request = $client->createRequest('GET', 'http://ewfewwef.com');
- $this->addListeners($request, $calls);
- $request->getEmitter()->once('complete', function (CompleteEvent $e) {
- throw new \Exception('foo');
- });
- try {
- $client->send($request);
- $this->fail('did not throw');
- } catch (RequestException $e) {
- $this->assertContains('foo', $e->getMessage());
- $this->assertEquals(['before', 'complete', 'error', 'end'], $calls);
- }
- }
-
- public function testTransitionsThroughErrorInterception()
- {
- $fsm = new RequestFsm(function () {
- return new CompletedFutureArray(['status' => 404]);
- }, $this->mf);
- $client = new Client();
- $request = $client->createRequest('GET', 'http://ewfewwef.com');
- $t = new Transaction($client, $request);
- $calls = [];
- $this->addListeners($t->request, $calls);
- $t->request->getEmitter()->on('error', function (ErrorEvent $e) {
- $e->intercept(new Response(200));
- });
- $fsm($t);
- $this->assertEquals(200, $t->response->getStatusCode());
- $this->assertNull($t->exception);
- $this->assertEquals(['before', 'complete', 'error', 'complete', 'end'], $calls);
- }
-
- private function addListeners(RequestInterface $request, &$calls)
- {
- $request->getEmitter()->on('before', function (BeforeEvent $e) use (&$calls) {
- $calls[] = 'before';
- }, RequestEvents::EARLY);
- $request->getEmitter()->on('complete', function (CompleteEvent $e) use (&$calls) {
- $calls[] = 'complete';
- }, RequestEvents::EARLY);
- $request->getEmitter()->on('error', function (ErrorEvent $e) use (&$calls) {
- $calls[] = 'error';
- }, RequestEvents::EARLY);
- $request->getEmitter()->on('end', function (EndEvent $e) use (&$calls) {
- $calls[] = 'end';
- }, RequestEvents::EARLY);
- }
-
- /**
- * @expectedException \GuzzleHttp\Exception\RequestException
- * @expectedExceptionMessage Too many state transitions
- */
- public function testDetectsInfiniteLoops()
- {
- $client = new Client([
- 'fsm' => $fsm = new RequestFsm(
- function () {
- return new CompletedFutureArray(['status' => 200]);
- },
- new MessageFactory(),
- 3
- )
- ]);
- $request = $client->createRequest('GET', 'http://foo.com:123');
- $request->getEmitter()->on('before', function () {
- throw new \Exception('foo');
- });
- $request->getEmitter()->on('error', function ($e) {
- $e->retry();
- });
- $client->send($request);
- }
-}
diff --git a/tests/ResponsePromiseTest.php b/tests/ResponsePromiseTest.php
new file mode 100644
index 000000000..7611d2535
--- /dev/null
+++ b/tests/ResponsePromiseTest.php
@@ -0,0 +1,129 @@
+not_there;
+ }
+
+ public function testUsingAsResponseWaits()
+ {
+ $r = new Response(200, ['foo' => 'bar'], 'baz');
+ $p = new ResponsePromise(function () use (&$p, $r) { $p->resolve($r); });
+ $this->assertEquals('pending', $p->getState());
+ $this->assertEquals(200, $p->getStatusCode());
+ $this->assertEquals('fulfilled', $p->getState());
+ $this->assertEquals('OK', $p->getReasonPhrase());
+ $this->assertEquals(['foo' => ['bar']], $p->getHeaders());
+ $this->assertTrue($p->hasHeader('foo'));
+ $this->assertEquals('bar', $p->getHeader('foo'));
+ $this->assertEquals(['bar'], $p->getHeaderLines('foo'));
+ $this->assertEquals('baz', (string) $p->getBody());
+ $this->assertEquals('1.1', $p->getProtocolVersion());
+ $this->assertFalse($p->withoutHeader('foo')->hasHeader('foo'));
+ $this->assertTrue($p->withHeader('a', 'b')->hasHeader('a'));
+ $this->assertTrue($p->withAddedHeader('a', 'b')->hasHeader('a'));
+ $this->assertEquals('hi', (string) $p->withBody(Stream::factory('hi'))->getBody());
+ $this->assertEquals('201', $p->withStatus('201')->getStatusCode());
+ $this->assertEquals('2', $p->withProtocolVersion('2')->getProtocolVersion());
+ $this->assertEquals('test', $p->withStatus(201, 'test')->getReasonPhrase());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage A response promise must be resolved with a
+ */
+ public function testMustResponseWithResponseOrRejectedPromise()
+ {
+ $p = new ResponsePromise();
+ $p->resolve('whoops');
+ }
+
+ public function testCreatesFromFulfilledPromise()
+ {
+ $r = new Response();
+ $p = new Promise();
+ $p->resolve($r);
+ $p2 = ResponsePromise::fromPromise($p);
+ $this->assertInstanceOf('GuzzleHttp\FulfilledResponse', $p2);
+ $this->assertEquals(200, $p2->getStatusCode());
+ }
+
+ public function testCreatesFromPendingPromise()
+ {
+ $r = new Response();
+ $p = new Promise();
+ $p2 = ResponsePromise::fromPromise($p);
+ $this->assertInstanceOf('GuzzleHttp\ResponsePromise', $p2);
+ $p->resolve($r);
+ $this->assertEquals(200, $p2->getStatusCode());
+ }
+
+ public function testCreatesFromPendingPromiseAndForwardsWait()
+ {
+ $r = new Response();
+ $p = new Promise(function () use (&$p, $r) { $p->resolve($r); });
+ $p2 = ResponsePromise::fromPromise($p);
+ $this->assertInstanceOf('GuzzleHttp\ResponsePromise', $p2);
+ $this->assertEquals(200, $p2->getStatusCode());
+ }
+
+ public function testCreatesByExtractingValueFromRejectedPromise()
+ {
+ $p = new Promise();
+ $e = new \Exception('foo');
+ $p->reject($e);
+ $p2 = ResponsePromise::fromPromise($p);
+ $this->assertInstanceOf('GuzzleHttp\RejectedResponse', $p2);
+ try {
+ $p2->getStatusCode();
+ $this->fail();
+ } catch (\Exception $e2) {
+ $this->assertSame($e2, $e);
+ }
+ }
+
+ /**
+ * @expectedException \UnexpectedValueException
+ */
+ public function testFailsWhenInvalidState()
+ {
+ $p = $this->getMockBuilder('GuzzleHttp\Promise\Promise')
+ ->setMethods(['getState'])
+ ->getMock();
+ $p->expects($this->any())
+ ->method('getState')
+ ->will($this->returnValue('foo'));
+ ResponsePromise::fromPromise($p);
+ }
+
+ /**
+ * @expectedException \UnexpectedValueException
+ */
+ public function testFailsWhenWaitingOnRejectedDoesNotThrow()
+ {
+ $p = $this->getMockBuilder('GuzzleHttp\Promise\Promise')
+ ->setMethods(['wait', 'getState'])
+ ->getMock();
+ $p->expects($this->any())
+ ->method('getState')
+ ->will($this->returnValue('rejected'));
+ $p->expects($this->any())
+ ->method('wait');
+ ResponsePromise::fromPromise($p);
+ }
+}
diff --git a/tests/RingBridgeTest.php b/tests/RingBridgeTest.php
deleted file mode 100644
index dc26a42aa..000000000
--- a/tests/RingBridgeTest.php
+++ /dev/null
@@ -1,195 +0,0 @@
- 'hello'
- ], $stream);
- $request->getConfig()->set('foo', 'bar');
- $trans = new Transaction(new Client(), $request);
- $factory = new MessageFactory();
- $fsm = new RequestFsm(function () {}, new MessageFactory());
- $r = RingBridge::prepareRingRequest($trans, $factory, $fsm);
- $this->assertEquals('http', $r['scheme']);
- $this->assertEquals('1.1', $r['version']);
- $this->assertEquals('GET', $r['http_method']);
- $this->assertEquals('http://httpbin.org/get?a=b', $r['url']);
- $this->assertEquals('/get', $r['uri']);
- $this->assertEquals('a=b', $r['query_string']);
- $this->assertEquals([
- 'Host' => ['httpbin.org'],
- 'test' => ['hello']
- ], $r['headers']);
- $this->assertSame($stream, $r['body']);
- $this->assertEquals(['foo' => 'bar'], $r['client']);
- $this->assertFalse($r['future']);
- }
-
- public function testCreatesRingRequestsWithNullQueryString()
- {
- $request = new Request('GET', 'http://httpbin.org');
- $trans = new Transaction(new Client(), $request);
- $factory = new MessageFactory();
- $fsm = new RequestFsm(function () {}, new MessageFactory());
- $r = RingBridge::prepareRingRequest($trans, $factory, $fsm);
- $this->assertNull($r['query_string']);
- $this->assertEquals('/', $r['uri']);
- $this->assertEquals(['Host' => ['httpbin.org']], $r['headers']);
- $this->assertNull($r['body']);
- $this->assertEquals([], $r['client']);
- }
-
- public function testAddsProgress()
- {
- Server::enqueue([new Response(200)]);
- $client = new Client(['base_url' => Server::$url]);
- $request = $client->createRequest('GET');
- $called = false;
- $request->getEmitter()->on(
- 'progress',
- function (ProgressEvent $e) use (&$called) {
- $called = true;
- }
- );
- $this->assertEquals(200, $client->send($request)->getStatusCode());
- $this->assertTrue($called);
- }
-
- public function testGetsResponseProtocolVersionAndEffectiveUrlAndReason()
- {
- $client = new Client([
- 'handler' => new MockHandler([
- 'status' => 200,
- 'reason' => 'test',
- 'headers' => [],
- 'version' => '1.0',
- 'effective_url' => 'http://foo.com'
- ])
- ]);
- $request = $client->createRequest('GET', 'http://foo.com');
- $response = $client->send($request);
- $this->assertEquals('1.0', $response->getProtocolVersion());
- $this->assertEquals('http://foo.com', $response->getEffectiveUrl());
- $this->assertEquals('test', $response->getReasonPhrase());
- }
-
- public function testGetsStreamFromResponse()
- {
- $res = fopen('php://temp', 'r+');
- fwrite($res, 'foo');
- rewind($res);
- $client = new Client([
- 'handler' => new MockHandler([
- 'status' => 200,
- 'headers' => [],
- 'body' => $res
- ])
- ]);
- $request = $client->createRequest('GET', 'http://foo.com');
- $response = $client->send($request);
- $this->assertEquals('foo', (string) $response->getBody());
- }
-
- public function testEmitsErrorEventOnError()
- {
- $client = new Client(['base_url' => 'http://127.0.0.1:123']);
- $request = $client->createRequest('GET');
- $called = false;
- $request->getEmitter()->on('error', function () use (&$called) {
- $called = true;
- });
- $request->getConfig()['timeout'] = 0.001;
- $request->getConfig()['connect_timeout'] = 0.001;
- try {
- $client->send($request);
- $this->fail('did not throw');
- } catch (RequestException $e) {
- $this->assertSame($request, $e->getRequest());
- $this->assertContains('cURL error', $e->getMessage());
- $this->assertTrue($called);
- }
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testValidatesRingRequest()
- {
- RingBridge::fromRingRequest([]);
- }
-
- public function testCreatesRequestFromRing()
- {
- $request = RingBridge::fromRingRequest([
- 'http_method' => 'GET',
- 'uri' => '/',
- 'headers' => [
- 'foo' => ['bar'],
- 'host' => ['foo.com']
- ],
- 'body' => 'test',
- 'version' => '1.0'
- ]);
- $this->assertEquals('GET', $request->getMethod());
- $this->assertEquals('http://foo.com/', $request->getUrl());
- $this->assertEquals('1.0', $request->getProtocolVersion());
- $this->assertEquals('test', (string) $request->getBody());
- $this->assertEquals('bar', $request->getHeader('foo'));
- }
-
- public function testCanInterceptException()
- {
- $client = new Client(['base_url' => 'http://127.0.0.1:123']);
- $request = $client->createRequest('GET');
- $called = false;
- $request->getEmitter()->on(
- 'error',
- function (ErrorEvent $e) use (&$called) {
- $called = true;
- $e->intercept(new Response(200));
- }
- );
- $request->getConfig()['timeout'] = 0.001;
- $request->getConfig()['connect_timeout'] = 0.001;
- $this->assertEquals(200, $client->send($request)->getStatusCode());
- $this->assertTrue($called);
- }
-
- public function testCreatesLongException()
- {
- $r = new Request('GET', 'http://www.google.com');
- $e = RingBridge::getNoRingResponseException($r);
- $this->assertInstanceOf('GuzzleHttp\Exception\RequestException', $e);
- $this->assertSame($r, $e->getRequest());
- }
-
- public function testEnsuresResponseOrExceptionWhenCompletingResponse()
- {
- $trans = new Transaction(new Client(), new Request('GET', 'http://f.co'));
- $f = new MessageFactory();
- $fsm = new RequestFsm(function () {}, new MessageFactory());
- try {
- RingBridge::completeRingResponse($trans, [], $f, $fsm);
- } catch (RequestException $e) {
- $this->assertSame($trans->request, $e->getRequest());
- $this->assertContains('RingPHP', $e->getMessage());
- }
- }
-}
diff --git a/tests/Server.php b/tests/Server.php
index 1de20e38b..6c2ec6c63 100644
--- a/tests/Server.php
+++ b/tests/Server.php
@@ -2,19 +2,45 @@
namespace GuzzleHttp\Tests;
use GuzzleHttp\Client;
-use GuzzleHttp\Message\MessageFactory;
-use GuzzleHttp\Message\Response;
-use GuzzleHttp\Message\ResponseInterface;
-use GuzzleHttp\RingBridge;
-use GuzzleHttp\Tests\Ring\Client\Server as TestServer;
+use GuzzleHttp\MessageParser;
+use GuzzleHttp\Psr7\Request;
+use GuzzleHttp\Psr7\Uri;
+use Psr\Http\Message\ResponseInterface;
/**
- * Placeholder for the RingPHP-Client server that makes it easier to use.
+ * The Server class is used to control a scripted webserver using node.js that
+ * will respond to HTTP requests with queued responses.
+ *
+ * Queued responses will be served to requests using a FIFO order. All requests
+ * received by the server are stored on the node.js server and can be retrieved
+ * by calling {@see Server::received()}.
+ *
+ * Mock responses that don't require data to be transmitted over HTTP a great
+ * for testing. Mock response, however, cannot test the actual sending of an
+ * HTTP request using cURL. This test server allows the simulation of any
+ * number of HTTP request response transactions to test the actual sending of
+ * requests over the wire without having to leave an internal network.
*/
class Server
{
- public static $url = 'http://127.0.0.1:8125/';
- public static $port = 8125;
+ const REQUEST_DELIMITER = "\n----[request]\n";
+
+ /** @var Client */
+ private static $client;
+ private static $started = false;
+ public static $url = 'http://127.0.0.1:8126/';
+ public static $port = 8126;
+
+ /**
+ * Flush the received requests from the server
+ * @throws \RuntimeException
+ */
+ public static function flush()
+ {
+ self::start();
+
+ return self::$client->request('DELETE', 'guzzle-server/requests')->wait();
+ }
/**
* Queue an array of responses or a single response on the server.
@@ -22,28 +48,34 @@ class Server
* Any currently queued responses will be overwritten. Subsequent requests
* on the server will return queued responses in FIFO order.
*
- * @param array $responses Responses to queue.
+ * @param array|ResponseInterface $responses A single or array of Responses
+ * to queue.
* @throws \Exception
*/
- public static function enqueue(array $responses)
+ public static function enqueue($responses)
{
- static $factory;
- if (!$factory) {
- $factory = new MessageFactory();
- }
+ self::start();
$data = [];
- foreach ($responses as $response) {
- // Create the response object from a string
- if (is_string($response)) {
- $response = $factory->fromMessage($response);
- } elseif (!($response instanceof ResponseInterface)) {
- throw new \Exception('Responses must be strings or Responses');
+ foreach ((array) $responses as $response) {
+ if (!($response instanceof ResponseInterface)) {
+ throw new \Exception('Invalid response given.');
}
- $data[] = self::convertResponse($response);
+ $headers = array_map(function ($h) {
+ return implode(' ,', $h);
+ }, $response->getHeaders());
+
+ $data[] = [
+ 'statusCode' => $response->getStatusCode(),
+ 'reasonPhrase' => $response->getReasonPhrase(),
+ 'headers' => $headers,
+ 'body' => base64_encode((string) $response->getBody())
+ ];
}
- TestServer::enqueue($data);
+ self::getClient()->request('PUT', 'guzzle-server/responses', [
+ 'json' => json_encode($data)
+ ])->wait();
}
/**
@@ -58,50 +90,90 @@ public static function enqueue(array $responses)
*/
public static function received($hydrate = false)
{
- $response = TestServer::received();
+ if (!self::$started) {
+ return [];
+ }
+ $response = self::getClient()->request('GET', 'guzzle-server/requests')->wait();
+ $data = array_filter(explode(self::REQUEST_DELIMITER, (string) $response->getBody()));
if ($hydrate) {
- $c = new Client();
- $factory = new MessageFactory();
- $response = array_map(function($message) use ($factory, $c) {
- return RingBridge::fromRingRequest($message);
- }, $response);
+ $parser = new MessageParser();
+ $data = array_map(
+ function ($message) use ($parser) {
+ $parts = $parser->parseRequest($message);
+ return new Request(
+ $parts['method'],
+ Uri::fromParts($parts['request_url']),
+ $parts['headers'],
+ $parts['body'],
+ $parts['protocol_version']
+ );
+ },
+ $data
+ );
}
- return $response;
- }
-
- public static function flush()
- {
- TestServer::flush();
+ return $data;
}
+ /**
+ * Stop running the node.js server
+ */
public static function stop()
{
- TestServer::stop();
+ if (self::$started) {
+ self::getClient()->request('DELETE', 'guzzle-server')->wait();
+ }
+
+ self::$started = false;
}
public static function wait($maxTries = 5)
{
- TestServer::wait($maxTries);
+ $tries = 0;
+ while (!self::isListening() && ++$tries < $maxTries) {
+ usleep(100000);
+ }
+
+ if (!self::isListening()) {
+ throw new \RuntimeException('Unable to contact node.js server');
+ }
}
public static function start()
{
- TestServer::start();
+ if (self::$started) {
+ return;
+ }
+
+ if (!self::isListening()) {
+ exec('node ' . __DIR__ . '/server.js '
+ . self::$port . ' >> /tmp/server.log 2>&1 &');
+ self::wait();
+ }
+
+ self::$started = true;
+ }
+
+ private static function isListening()
+ {
+ try {
+ self::getClient()->request('GET', 'guzzle-server/perf', [
+ 'connect_timeout' => 5,
+ 'timeout' => 5
+ ])->wait();
+ return true;
+ } catch (\Exception $e) {
+ return false;
+ }
}
- private static function convertResponse(Response $response)
+ private static function getClient()
{
- $headers = array_map(function ($h) {
- return implode(', ', $h);
- }, $response->getHeaders());
-
- return [
- 'status' => $response->getStatusCode(),
- 'reason' => $response->getReasonPhrase(),
- 'headers' => $headers,
- 'body' => base64_encode((string) $response->getBody())
- ];
+ if (!self::$client) {
+ self::$client = new Client(['base_uri' => self::$url]);
+ }
+
+ return self::$client;
}
}
diff --git a/tests/Subscriber/CookieTest.php b/tests/Subscriber/CookieTest.php
deleted file mode 100644
index bc17e2dc2..000000000
--- a/tests/Subscriber/CookieTest.php
+++ /dev/null
@@ -1,74 +0,0 @@
-getMockBuilder('GuzzleHttp\Cookie\CookieJar')
- ->setMethods(array('extractCookies'))
- ->getMock();
-
- $mock->expects($this->exactly(1))
- ->method('extractCookies')
- ->with($request, $response);
-
- $plugin = new Cookie($mock);
- $t = new Transaction(new Client(), $request);
- $t->response = $response;
- $plugin->onComplete(new CompleteEvent($t));
- }
-
- public function testProvidesCookieJar()
- {
- $jar = new CookieJar();
- $plugin = new Cookie($jar);
- $this->assertSame($jar, $plugin->getCookieJar());
- }
-
- public function testCookiesAreExtractedFromRedirectResponses()
- {
- $jar = new CookieJar();
- $cookie = new Cookie($jar);
- $history = new History();
- $mock = new Mock([
- "HTTP/1.1 302 Moved Temporarily\r\n" .
- "Set-Cookie: test=583551; Domain=www.foo.com; Expires=Wednesday, 23-Mar-2050 19:49:45 GMT; Path=/\r\n" .
- "Location: /redirect\r\n\r\n",
- "HTTP/1.1 200 OK\r\n" .
- "Content-Length: 0\r\n\r\n",
- "HTTP/1.1 200 OK\r\n" .
- "Content-Length: 0\r\n\r\n"
- ]);
- $client = new Client(['base_url' => 'http://www.foo.com']);
- $client->getEmitter()->attach($cookie);
- $client->getEmitter()->attach($mock);
- $client->getEmitter()->attach($history);
-
- $client->get();
- $request = $client->createRequest('GET', '/');
- $client->send($request);
-
- $this->assertEquals('test=583551', $request->getHeader('Cookie'));
- $requests = $history->getRequests();
- // Confirm subsequent requests have the cookie.
- $this->assertEquals('test=583551', $requests[2]->getHeader('Cookie'));
- // Confirm the redirected request has the cookie.
- $this->assertEquals('test=583551', $requests[1]->getHeader('Cookie'));
- }
-}
diff --git a/tests/Subscriber/HistoryTest.php b/tests/Subscriber/HistoryTest.php
deleted file mode 100644
index d28e301cd..000000000
--- a/tests/Subscriber/HistoryTest.php
+++ /dev/null
@@ -1,140 +0,0 @@
-response = $response;
- $e = new RequestException('foo', $request, $response);
- $ev = new ErrorEvent($t, $e);
- $h = new History(2);
- $h->onError($ev);
- // Only tracks when no response is present
- $this->assertEquals([], $h->getRequests());
- }
-
- public function testLogsConnectionErrors()
- {
- $request = new Request('GET', '/');
- $t = new Transaction(new Client(), $request);
- $e = new RequestException('foo', $request);
- $ev = new ErrorEvent($t, $e);
- $h = new History();
- $h->onError($ev);
- $this->assertEquals([$request], $h->getRequests());
- }
-
- public function testMaintainsLimitValue()
- {
- $request = new Request('GET', '/');
- $response = new Response(200);
- $t = new Transaction(new Client(), $request);
- $t->response = $response;
- $ev = new CompleteEvent($t);
- $h = new History(2);
- $h->onComplete($ev);
- $h->onComplete($ev);
- $h->onComplete($ev);
- $this->assertEquals(2, count($h));
- $this->assertSame($request, $h->getLastRequest());
- $this->assertSame($response, $h->getLastResponse());
- foreach ($h as $trans) {
- $this->assertInstanceOf('GuzzleHttp\Message\RequestInterface', $trans['request']);
- $this->assertInstanceOf('GuzzleHttp\Message\ResponseInterface', $trans['response']);
- }
- return $h;
- }
-
- /**
- * @depends testMaintainsLimitValue
- */
- public function testClearsHistory($h)
- {
- $this->assertEquals(2, count($h));
- $h->clear();
- $this->assertEquals(0, count($h));
- }
-
- public function testWorksWithMock()
- {
- $client = new Client(['base_url' => 'http://localhost/']);
- $h = new History();
- $client->getEmitter()->attach($h);
- $mock = new Mock([new Response(200), new Response(201), new Response(202)]);
- $client->getEmitter()->attach($mock);
- $request = $client->createRequest('GET', '/');
- $client->send($request);
- $request->setMethod('PUT');
- $client->send($request);
- $request->setMethod('POST');
- $client->send($request);
- $this->assertEquals(3, count($h));
-
- $result = implode("\n", array_map(function ($line) {
- return strpos($line, 'User-Agent') === 0
- ? 'User-Agent:'
- : trim($line);
- }, explode("\n", $h)));
-
- $this->assertEquals("> GET / HTTP/1.1
-Host: localhost
-User-Agent:
-
-< HTTP/1.1 200 OK
-
-> PUT / HTTP/1.1
-Host: localhost
-User-Agent:
-
-< HTTP/1.1 201 Created
-
-> POST / HTTP/1.1
-Host: localhost
-User-Agent:
-
-< HTTP/1.1 202 Accepted
-", $result);
- }
-
- public function testCanCastToString()
- {
- $client = new Client(['base_url' => 'http://localhost/']);
- $h = new History();
- $client->getEmitter()->attach($h);
-
- $mock = new Mock(array(
- new Response(301, array('Location' => '/redirect1', 'Content-Length' => 0)),
- new Response(307, array('Location' => '/redirect2', 'Content-Length' => 0)),
- new Response(200, array('Content-Length' => '2'), Stream::factory('HI'))
- ));
-
- $client->getEmitter()->attach($mock);
- $request = $client->createRequest('GET', '/');
- $client->send($request);
- $this->assertEquals(3, count($h));
-
- $h = str_replace("\r", '', $h);
- $this->assertContains("> GET / HTTP/1.1\nHost: localhost\nUser-Agent:", $h);
- $this->assertContains("< HTTP/1.1 301 Moved Permanently\nLocation: /redirect1", $h);
- $this->assertContains("< HTTP/1.1 307 Temporary Redirect\nLocation: /redirect2", $h);
- $this->assertContains("< HTTP/1.1 200 OK\nContent-Length: 2\n\nHI", $h);
- }
-}
diff --git a/tests/Subscriber/HttpErrorTest.php b/tests/Subscriber/HttpErrorTest.php
deleted file mode 100644
index b0266340c..000000000
--- a/tests/Subscriber/HttpErrorTest.php
+++ /dev/null
@@ -1,60 +0,0 @@
-getEvent();
- $event->intercept(new Response(200));
- (new HttpError())->onComplete($event);
- }
-
- /**
- * @expectedException \GuzzleHttp\Exception\ClientException
- */
- public function testThrowsClientExceptionOnFailure()
- {
- $event = $this->getEvent();
- $event->intercept(new Response(403));
- (new HttpError())->onComplete($event);
- }
-
- /**
- * @expectedException \GuzzleHttp\Exception\ServerException
- */
- public function testThrowsServerExceptionOnFailure()
- {
- $event = $this->getEvent();
- $event->intercept(new Response(500));
- (new HttpError())->onComplete($event);
- }
-
- private function getEvent()
- {
- return new CompleteEvent(new Transaction(new Client(), new Request('PUT', '/')));
- }
-
- /**
- * @expectedException \GuzzleHttp\Exception\ClientException
- */
- public function testFullTransaction()
- {
- $client = new Client();
- $client->getEmitter()->attach(new Mock([
- new Response(403)
- ]));
- $client->get('http://httpbin.org');
- }
-}
diff --git a/tests/Subscriber/MockTest.php b/tests/Subscriber/MockTest.php
deleted file mode 100644
index 5e8209396..000000000
--- a/tests/Subscriber/MockTest.php
+++ /dev/null
@@ -1,192 +0,0 @@
-promise(),
- function () use ($deferred, $wait) {
- $deferred->resolve($wait());
- },
- $cancel
- );
- }
-
- public function testDescribesSubscribedEvents()
- {
- $mock = new Mock();
- $this->assertInternalType('array', $mock->getEvents());
- }
-
- public function testIsCountable()
- {
- $plugin = new Mock();
- $plugin->addResponse((new MessageFactory())->fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"));
- $this->assertEquals(1, count($plugin));
- }
-
- public function testCanClearQueue()
- {
- $plugin = new Mock();
- $plugin->addResponse((new MessageFactory())->fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"));
- $plugin->clearQueue();
- $this->assertEquals(0, count($plugin));
- }
-
- public function testRetrievesResponsesFromFiles()
- {
- $tmp = tempnam('/tmp', 'tfile');
- file_put_contents($tmp, "HTTP/1.1 201 OK\r\nContent-Length: 0\r\n\r\n");
- $plugin = new Mock();
- $plugin->addResponse($tmp);
- unlink($tmp);
- $this->assertEquals(1, count($plugin));
- $q = $this->readAttribute($plugin, 'queue');
- $this->assertEquals(201, $q[0]->getStatusCode());
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testThrowsExceptionWhenInvalidResponse()
- {
- (new Mock())->addResponse(false);
- }
-
- public function testAddsMockResponseToRequestFromClient()
- {
- $response = new Response(200);
- $t = new Transaction(new Client(), new Request('GET', '/'));
- $m = new Mock([$response]);
- $ev = new BeforeEvent($t);
- $m->onBefore($ev);
- $this->assertSame($response, $t->response);
- }
-
- /**
- * @expectedException \OutOfBoundsException
- */
- public function testUpdateThrowsExceptionWhenEmpty()
- {
- $p = new Mock();
- $ev = new BeforeEvent(new Transaction(new Client(), new Request('GET', '/')));
- $p->onBefore($ev);
- }
-
- public function testReadsBodiesFromMockedRequests()
- {
- $m = new Mock([new Response(200)]);
- $client = new Client(['base_url' => 'http://test.com']);
- $client->getEmitter()->attach($m);
- $body = Stream::factory('foo');
- $client->put('/', ['body' => $body]);
- $this->assertEquals(3, $body->tell());
- }
-
- public function testCanMockBadRequestExceptions()
- {
- $client = new Client(['base_url' => 'http://test.com']);
- $request = $client->createRequest('GET', '/');
- $ex = new RequestException('foo', $request);
- $mock = new Mock([$ex]);
- $this->assertCount(1, $mock);
- $request->getEmitter()->attach($mock);
-
- try {
- $client->send($request);
- $this->fail('Did not dequeue an exception');
- } catch (RequestException $e) {
- $this->assertSame($e, $ex);
- $this->assertSame($request, $ex->getRequest());
- }
- }
-
- public function testCanMockFutureResponses()
- {
- $client = new Client(['base_url' => 'http://test.com']);
- $request = $client->createRequest('GET', '/', ['future' => true]);
- $response = new Response(200);
- $future = self::createFuture(function () use ($response) {
- return $response;
- });
- $mock = new Mock([$future]);
- $this->assertCount(1, $mock);
- $request->getEmitter()->attach($mock);
- $res = $client->send($request);
- $this->assertSame($future, $res);
- $this->assertFalse($this->readAttribute($res, 'isRealized'));
- $this->assertSame($response, $res->wait());
- }
-
- public function testCanMockExceptionFutureResponses()
- {
- $client = new Client(['base_url' => 'http://test.com']);
- $request = $client->createRequest('GET', '/', ['future' => true]);
- $future = self::createFuture(function () use ($request) {
- throw new RequestException('foo', $request);
- });
-
- $mock = new Mock([$future]);
- $request->getEmitter()->attach($mock);
- $response = $client->send($request);
- $this->assertSame($future, $response);
- $this->assertFalse($this->readAttribute($response, 'isRealized'));
-
- try {
- $response->wait();
- $this->fail('Did not throw');
- } catch (RequestException $e) {
- $this->assertContains('foo', $e->getMessage());
- }
- }
-
- public function testCanMockFailedFutureResponses()
- {
- $client = new Client(['base_url' => 'http://test.com']);
- $request = $client->createRequest('GET', '/', ['future' => true]);
-
- // The first mock will be a mocked future response.
- $future = self::createFuture(function () use ($client) {
- // When dereferenced, we will set a mocked response and send
- // another request.
- $client->get('http://httpbin.org', ['events' => [
- 'before' => function (BeforeEvent $e) {
- $e->intercept(new Response(404));
- }
- ]]);
- });
-
- $mock = new Mock([$future]);
- $request->getEmitter()->attach($mock);
- $response = $client->send($request);
- $this->assertSame($future, $response);
- $this->assertFalse($this->readAttribute($response, 'isRealized'));
-
- try {
- $response->wait();
- $this->fail('Did not throw');
- } catch (RequestException $e) {
- $this->assertEquals(404, $e->getResponse()->getStatusCode());
- }
- }
-}
diff --git a/tests/Subscriber/PrepareTest.php b/tests/Subscriber/PrepareTest.php
deleted file mode 100644
index d07fdb44c..000000000
--- a/tests/Subscriber/PrepareTest.php
+++ /dev/null
@@ -1,213 +0,0 @@
-getTrans();
- $s->onBefore(new BeforeEvent($t));
- $this->assertFalse($t->request->hasHeader('Expect'));
- }
-
- public function testAppliesPostBody()
- {
- $s = new Prepare();
- $t = $this->getTrans();
- $p = $this->getMockBuilder('GuzzleHttp\Post\PostBody')
- ->setMethods(['applyRequestHeaders'])
- ->getMockForAbstractClass();
- $p->expects($this->once())
- ->method('applyRequestHeaders');
- $t->request->setBody($p);
- $s->onBefore(new BeforeEvent($t));
- }
-
- public function testAddsExpectHeaderWithTrue()
- {
- $s = new Prepare();
- $t = $this->getTrans();
- $t->request->getConfig()->set('expect', true);
- $t->request->setBody(Stream::factory('foo'));
- $s->onBefore(new BeforeEvent($t));
- $this->assertEquals('100-Continue', $t->request->getHeader('Expect'));
- }
-
- public function testAddsExpectHeaderBySize()
- {
- $s = new Prepare();
- $t = $this->getTrans();
- $t->request->getConfig()->set('expect', 2);
- $t->request->setBody(Stream::factory('foo'));
- $s->onBefore(new BeforeEvent($t));
- $this->assertTrue($t->request->hasHeader('Expect'));
- }
-
- public function testDoesNotModifyExpectHeaderIfPresent()
- {
- $s = new Prepare();
- $t = $this->getTrans();
- $t->request->setHeader('Expect', 'foo');
- $t->request->setBody(Stream::factory('foo'));
- $s->onBefore(new BeforeEvent($t));
- $this->assertEquals('foo', $t->request->getHeader('Expect'));
- }
-
- public function testDoesAddExpectHeaderWhenSetToFalse()
- {
- $s = new Prepare();
- $t = $this->getTrans();
- $t->request->getConfig()->set('expect', false);
- $t->request->setBody(Stream::factory('foo'));
- $s->onBefore(new BeforeEvent($t));
- $this->assertFalse($t->request->hasHeader('Expect'));
- }
-
- public function testDoesNotAddExpectHeaderBySize()
- {
- $s = new Prepare();
- $t = $this->getTrans();
- $t->request->getConfig()->set('expect', 10);
- $t->request->setBody(Stream::factory('foo'));
- $s->onBefore(new BeforeEvent($t));
- $this->assertFalse($t->request->hasHeader('Expect'));
- }
-
- public function testAddsExpectHeaderForNonSeekable()
- {
- $s = new Prepare();
- $t = $this->getTrans();
- $t->request->setBody(new NoSeekStream(Stream::factory('foo')));
- $s->onBefore(new BeforeEvent($t));
- $this->assertTrue($t->request->hasHeader('Expect'));
- }
-
- public function testRemovesContentLengthWhenSendingWithChunked()
- {
- $s = new Prepare();
- $t = $this->getTrans();
- $t->request->setBody(Stream::factory('foo'));
- $t->request->setHeader('Transfer-Encoding', 'chunked');
- $s->onBefore(new BeforeEvent($t));
- $this->assertFalse($t->request->hasHeader('Content-Length'));
- }
-
- public function testUsesProvidedContentLengthAndRemovesXferEncoding()
- {
- $s = new Prepare();
- $t = $this->getTrans();
- $t->request->setBody(Stream::factory('foo'));
- $t->request->setHeader('Content-Length', '3');
- $t->request->setHeader('Transfer-Encoding', 'chunked');
- $s->onBefore(new BeforeEvent($t));
- $this->assertEquals(3, $t->request->getHeader('Content-Length'));
- $this->assertFalse($t->request->hasHeader('Transfer-Encoding'));
- }
-
- public function testSetsContentTypeIfPossibleFromStream()
- {
- $body = $this->getMockBody();
- $sub = new Prepare();
- $t = $this->getTrans();
- $t->request->setBody($body);
- $sub->onBefore(new BeforeEvent($t));
- $this->assertEquals(
- 'image/jpeg',
- $t->request->getHeader('Content-Type')
- );
- $this->assertEquals(4, $t->request->getHeader('Content-Length'));
- }
-
- public function testDoesNotOverwriteExistingContentType()
- {
- $s = new Prepare();
- $t = $this->getTrans();
- $t->request->setBody($this->getMockBody());
- $t->request->setHeader('Content-Type', 'foo/baz');
- $s->onBefore(new BeforeEvent($t));
- $this->assertEquals(
- 'foo/baz',
- $t->request->getHeader('Content-Type')
- );
- }
-
- public function testSetsContentLengthIfPossible()
- {
- $s = new Prepare();
- $t = $this->getTrans();
- $t->request->setBody($this->getMockBody());
- $s->onBefore(new BeforeEvent($t));
- $this->assertEquals(4, $t->request->getHeader('Content-Length'));
- }
-
- public function testSetsTransferEncodingChunkedIfNeeded()
- {
- $r = new Request('PUT', '/');
- $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface')
- ->setMethods(['getSize'])
- ->getMockForAbstractClass();
- $s->expects($this->exactly(2))
- ->method('getSize')
- ->will($this->returnValue(null));
- $r->setBody($s);
- $t = $this->getTrans($r);
- $s = new Prepare();
- $s->onBefore(new BeforeEvent($t));
- $this->assertEquals('chunked', $r->getHeader('Transfer-Encoding'));
- }
-
- public function testContentLengthIntegrationTest()
- {
- Server::flush();
- Server::enqueue([new Response(200)]);
- $client = new Client(['base_url' => Server::$url]);
- $this->assertEquals(200, $client->put('/', [
- 'body' => 'test'
- ])->getStatusCode());
- $request = Server::received(true)[0];
- $this->assertEquals('PUT', $request->getMethod());
- $this->assertEquals('4', $request->getHeader('Content-Length'));
- $this->assertEquals('test', (string) $request->getBody());
- }
-
- private function getTrans($request = null)
- {
- return new Transaction(
- new Client(),
- $request ?: new Request('PUT', '/')
- );
- }
-
- /**
- * @return \GuzzleHttp\Stream\StreamInterface
- */
- private function getMockBody()
- {
- $s = $this->getMockBuilder('GuzzleHttp\Stream\MetadataStreamInterface')
- ->setMethods(['getMetadata', 'getSize'])
- ->getMockForAbstractClass();
- $s->expects($this->any())
- ->method('getMetadata')
- ->with('uri')
- ->will($this->returnValue('/foo/baz/bar.jpg'));
- $s->expects($this->exactly(2))
- ->method('getSize')
- ->will($this->returnValue(4));
-
- return $s;
- }
-}
diff --git a/tests/Subscriber/RedirectTest.php b/tests/Subscriber/RedirectTest.php
deleted file mode 100644
index 293cfc217..000000000
--- a/tests/Subscriber/RedirectTest.php
+++ /dev/null
@@ -1,288 +0,0 @@
-addMultiple([
- "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect1\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect2\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
- ]);
-
- $client = new Client(['base_url' => 'http://test.com']);
- $client->getEmitter()->attach($history);
- $client->getEmitter()->attach($mock);
-
- $request = $client->createRequest('GET', '/foo');
- // Ensure "end" is called only once
- $called = 0;
- $request->getEmitter()->on('end', function () use (&$called) {
- $called++;
- });
- $response = $client->send($request);
-
- $this->assertEquals(200, $response->getStatusCode());
- $this->assertContains('/redirect2', $response->getEffectiveUrl());
-
- // Ensure that two requests were sent
- $requests = $history->getRequests(true);
-
- $this->assertEquals('/foo', $requests[0]->getPath());
- $this->assertEquals('GET', $requests[0]->getMethod());
- $this->assertEquals('/redirect1', $requests[1]->getPath());
- $this->assertEquals('GET', $requests[1]->getMethod());
- $this->assertEquals('/redirect2', $requests[2]->getPath());
- $this->assertEquals('GET', $requests[2]->getMethod());
-
- $this->assertEquals(1, $called);
- }
-
- /**
- * @expectedException \GuzzleHttp\Exception\TooManyRedirectsException
- * @expectedExceptionMessage Will not follow more than
- */
- public function testCanLimitNumberOfRedirects()
- {
- $mock = new Mock([
- "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect1\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect2\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect3\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect4\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect5\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect6\r\nContent-Length: 0\r\n\r\n"
- ]);
- $client = new Client();
- $client->getEmitter()->attach($mock);
- $client->get('http://www.example.com/foo');
- }
-
- public function testDefaultBehaviorIsToRedirectWithGetForEntityEnclosingRequests()
- {
- $h = new History();
- $mock = new Mock([
- "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
- ]);
- $client = new Client();
- $client->getEmitter()->attach($mock);
- $client->getEmitter()->attach($h);
- $client->post('http://test.com/foo', [
- 'headers' => ['X-Baz' => 'bar'],
- 'body' => 'testing'
- ]);
-
- $requests = $h->getRequests(true);
- $this->assertEquals('POST', $requests[0]->getMethod());
- $this->assertEquals('GET', $requests[1]->getMethod());
- $this->assertEquals('bar', (string) $requests[1]->getHeader('X-Baz'));
- $this->assertEquals('GET', $requests[2]->getMethod());
- }
-
- public function testCanRedirectWithStrictRfcCompliance()
- {
- $h = new History();
- $mock = new Mock([
- "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
- ]);
- $client = new Client(['base_url' => 'http://test.com']);
- $client->getEmitter()->attach($mock);
- $client->getEmitter()->attach($h);
- $client->post('/foo', [
- 'headers' => ['X-Baz' => 'bar'],
- 'body' => 'testing',
- 'allow_redirects' => ['max' => 10, 'strict' => true]
- ]);
-
- $requests = $h->getRequests(true);
- $this->assertEquals('POST', $requests[0]->getMethod());
- $this->assertEquals('POST', $requests[1]->getMethod());
- $this->assertEquals('bar', (string) $requests[1]->getHeader('X-Baz'));
- $this->assertEquals('POST', $requests[2]->getMethod());
- }
-
- public function testRewindsStreamWhenRedirectingIfNeeded()
- {
- $h = new History();
- $mock = new Mock([
- "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
- ]);
- $client = new Client(['base_url' => 'http://test.com']);
- $client->getEmitter()->attach($mock);
- $client->getEmitter()->attach($h);
-
- $body = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface')
- ->setMethods(['seek', 'read', 'eof', 'tell'])
- ->getMockForAbstractClass();
- $body->expects($this->once())->method('tell')->will($this->returnValue(1));
- $body->expects($this->once())->method('seek')->will($this->returnValue(true));
- $body->expects($this->any())->method('eof')->will($this->returnValue(true));
- $body->expects($this->any())->method('read')->will($this->returnValue('foo'));
- $client->post('/foo', [
- 'body' => $body,
- 'allow_redirects' => ['max' => 5, 'strict' => true]
- ]);
- }
-
- /**
- * @expectedException \GuzzleHttp\Exception\CouldNotRewindStreamException
- * @expectedExceptionMessage Unable to rewind the non-seekable request body after redirecting
- */
- public function testThrowsExceptionWhenStreamCannotBeRewound()
- {
- $h = new History();
- $mock = new Mock([
- "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
- ]);
- $client = new Client();
- $client->getEmitter()->attach($mock);
- $client->getEmitter()->attach($h);
-
- $body = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface')
- ->setMethods(['seek', 'read', 'eof', 'tell'])
- ->getMockForAbstractClass();
- $body->expects($this->once())->method('tell')->will($this->returnValue(1));
- $body->expects($this->once())->method('seek')->will($this->returnValue(false));
- $body->expects($this->any())->method('eof')->will($this->returnValue(true));
- $body->expects($this->any())->method('read')->will($this->returnValue('foo'));
- $client->post('http://example.com/foo', [
- 'body' => $body,
- 'allow_redirects' => ['max' => 10, 'strict' => true]
- ]);
- }
-
- public function testRedirectsCanBeDisabledPerRequest()
- {
- $client = new Client(['base_url' => 'http://test.com']);
- $client->getEmitter()->attach(new Mock([
- "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
- ]));
- $response = $client->put('/', ['body' => 'test', 'allow_redirects' => false]);
- $this->assertEquals(301, $response->getStatusCode());
- }
-
- public function testCanRedirectWithNoLeadingSlashAndQuery()
- {
- $h = new History();
- $client = new Client(['base_url' => 'http://www.foo.com']);
- $client->getEmitter()->attach(new Mock([
- "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect?foo=bar\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
- ]));
- $client->getEmitter()->attach($h);
- $client->get('?foo=bar');
- $requests = $h->getRequests(true);
- $this->assertEquals('http://www.foo.com?foo=bar', $requests[0]->getUrl());
- $this->assertEquals('http://www.foo.com/redirect?foo=bar', $requests[1]->getUrl());
- }
-
- public function testHandlesRedirectsWithSpacesProperly()
- {
- $client = new Client(['base_url' => 'http://www.foo.com']);
- $client->getEmitter()->attach(new Mock([
- "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect 1\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
- ]));
- $h = new History();
- $client->getEmitter()->attach($h);
- $client->get('/foo');
- $reqs = $h->getRequests(true);
- $this->assertEquals('/redirect%201', $reqs[1]->getResource());
- }
-
- public function testAddsRefererWhenPossible()
- {
- $client = new Client(['base_url' => 'http://www.foo.com']);
- $client->getEmitter()->attach(new Mock([
- "HTTP/1.1 301 Moved Permanently\r\nLocation: /bar\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
- ]));
- $h = new History();
- $client->getEmitter()->attach($h);
- $client->get('/foo', ['allow_redirects' => ['max' => 5, 'referer' => true]]);
- $reqs = $h->getRequests(true);
- $this->assertEquals('http://www.foo.com/foo', $reqs[1]->getHeader('Referer'));
- }
-
- public function testDoesNotAddRefererWhenChangingProtocols()
- {
- $client = new Client(['base_url' => 'https://www.foo.com']);
- $client->getEmitter()->attach(new Mock([
- "HTTP/1.1 301 Moved Permanently\r\n"
- . "Location: http://www.foo.com/foo\r\n"
- . "Content-Length: 0\r\n\r\n",
- "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
- ]));
- $h = new History();
- $client->getEmitter()->attach($h);
- $client->get('/foo', ['allow_redirects' => ['max' => 5, 'referer' => true]]);
- $reqs = $h->getRequests(true);
- $this->assertFalse($reqs[1]->hasHeader('Referer'));
- }
-
- public function testRedirectsWithGetOn303()
- {
- $h = new History();
- $mock = new Mock([
- "HTTP/1.1 303 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
- ]);
- $client = new Client();
- $client->getEmitter()->attach($mock);
- $client->getEmitter()->attach($h);
- $client->post('http://test.com/foo', ['body' => 'testing']);
- $requests = $h->getRequests(true);
- $this->assertEquals('POST', $requests[0]->getMethod());
- $this->assertEquals('GET', $requests[1]->getMethod());
- }
-
- public function testRelativeLinkBasedLatestRequest()
- {
- $client = new Client(['base_url' => 'http://www.foo.com']);
- $client->getEmitter()->attach(new Mock([
- "HTTP/1.1 301 Moved Permanently\r\nLocation: http://www.bar.com\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
- ]));
- $response = $client->get('/');
- $this->assertEquals(
- 'http://www.bar.com/redirect',
- $response->getEffectiveUrl()
- );
- }
-
- /**
- * @expectedException \GuzzleHttp\Exception\BadResponseException
- * @expectedExceptionMessage Redirect URL, https://foo.com/redirect2, does not use one of the allowed redirect protocols: http
- */
- public function testThrowsWhenRedirectingToInvalidUrlProtocol()
- {
- $mock = new Mock([
- "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect1\r\nContent-Length: 0\r\n\r\n",
- "HTTP/1.1 301 Moved Permanently\r\nLocation: https://foo.com/redirect2\r\nContent-Length: 0\r\n\r\n"
- ]);
- $client = new Client();
- $client->getEmitter()->attach($mock);
- $client->get('http://www.example.com/foo', [
- 'allow_redirects' => [
- 'protocols' => ['http']
- ]
- ]);
- }
-}
diff --git a/tests/TransactionTest.php b/tests/TransactionTest.php
deleted file mode 100644
index 42965b1b5..000000000
--- a/tests/TransactionTest.php
+++ /dev/null
@@ -1,22 +0,0 @@
-assertSame($client, $t->client);
- $this->assertSame($request, $t->request);
- $response = new Response(200);
- $t->response = $response;
- $this->assertSame($response, $t->response);
- }
-}
diff --git a/tests/UriTemplateTest.php b/tests/UriTemplateTest.php
index 3f7a7f063..1968cc837 100644
--- a/tests/UriTemplateTest.php
+++ b/tests/UriTemplateTest.php
@@ -1,5 +1,4 @@
assertEquals('', (string) $url);
- }
-
- public function testPortIsDeterminedFromScheme()
- {
- $this->assertEquals(80, Url::fromString('http://www.test.com/')->getPort());
- $this->assertEquals(443, Url::fromString('https://www.test.com/')->getPort());
- $this->assertEquals(21, Url::fromString('ftp://www.test.com/')->getPort());
- $this->assertEquals(8192, Url::fromString('http://www.test.com:8192/')->getPort());
- $this->assertEquals(null, Url::fromString('foo://www.test.com/')->getPort());
- }
-
- public function testRemovesDefaultPortWhenSettingScheme()
- {
- $url = Url::fromString('http://www.test.com/');
- $url->setPort(80);
- $url->setScheme('https');
- $this->assertEquals(443, $url->getPort());
- }
-
- public function testCloneCreatesNewInternalObjects()
- {
- $u1 = Url::fromString('http://www.test.com/');
- $u2 = clone $u1;
- $this->assertNotSame($u1->getQuery(), $u2->getQuery());
- }
-
- public function testValidatesUrlPartsInFactory()
- {
- $url = Url::fromString('/index.php');
- $this->assertEquals('/index.php', (string) $url);
- $this->assertFalse($url->isAbsolute());
-
- $url = 'http://michael:test@test.com:80/path/123?q=abc#test';
- $u = Url::fromString($url);
- $this->assertEquals('http://michael:test@test.com/path/123?q=abc#test', (string) $u);
- $this->assertTrue($u->isAbsolute());
- }
-
- public function testAllowsFalsyUrlParts()
- {
- $url = Url::fromString('http://a:50/0?0#0');
- $this->assertSame('a', $url->getHost());
- $this->assertEquals(50, $url->getPort());
- $this->assertSame('/0', $url->getPath());
- $this->assertEquals('0', (string) $url->getQuery());
- $this->assertSame('0', $url->getFragment());
- $this->assertEquals('http://a:50/0?0#0', (string) $url);
-
- $url = Url::fromString('');
- $this->assertSame('', (string) $url);
-
- $url = Url::fromString('0');
- $this->assertSame('0', (string) $url);
- }
-
- public function testBuildsRelativeUrlsWithFalsyParts()
- {
- $url = Url::buildUrl(['path' => '/0']);
- $this->assertSame('/0', $url);
-
- $url = Url::buildUrl(['path' => '0']);
- $this->assertSame('0', $url);
-
- $url = Url::buildUrl(['host' => '', 'path' => '0']);
- $this->assertSame('0', $url);
- }
-
- public function testUrlStoresParts()
- {
- $url = Url::fromString('http://test:pass@www.test.com:8081/path/path2/?a=1&b=2#fragment');
- $this->assertEquals('http', $url->getScheme());
- $this->assertEquals('test', $url->getUsername());
- $this->assertEquals('pass', $url->getPassword());
- $this->assertEquals('www.test.com', $url->getHost());
- $this->assertEquals(8081, $url->getPort());
- $this->assertEquals('/path/path2/', $url->getPath());
- $this->assertEquals('fragment', $url->getFragment());
- $this->assertEquals('a=1&b=2', (string) $url->getQuery());
-
- $this->assertEquals(array(
- 'fragment' => 'fragment',
- 'host' => 'www.test.com',
- 'pass' => 'pass',
- 'path' => '/path/path2/',
- 'port' => 8081,
- 'query' => 'a=1&b=2',
- 'scheme' => 'http',
- 'user' => 'test'
- ), $url->getParts());
- }
-
- public function testHandlesPathsCorrectly()
- {
- $url = Url::fromString('http://www.test.com');
- $this->assertEquals('', $url->getPath());
- $url->setPath('test');
- $this->assertEquals('test', $url->getPath());
-
- $url->setPath('/test/123/abc');
- $this->assertEquals(array('', 'test', '123', 'abc'), $url->getPathSegments());
-
- $parts = parse_url('http://www.test.com/test');
- $parts['path'] = '';
- $this->assertEquals('http://www.test.com', Url::buildUrl($parts));
- $parts['path'] = 'test';
- $this->assertEquals('http://www.test.com/test', Url::buildUrl($parts));
- }
-
- public function testAddsQueryIfPresent()
- {
- $this->assertEquals('?foo=bar', Url::buildUrl(array(
- 'query' => 'foo=bar'
- )));
- }
-
- public function testAddsToPath()
- {
- // Does nothing here
- $url = Url::fromString('http://e.com/base?a=1');
- $url->addPath(false);
- $this->assertEquals('http://e.com/base?a=1', $url);
- $url = Url::fromString('http://e.com/base?a=1');
- $url->addPath('');
- $this->assertEquals('http://e.com/base?a=1', $url);
- $url = Url::fromString('http://e.com/base?a=1');
- $url->addPath('/');
- $this->assertEquals('http://e.com/base?a=1', $url);
- $url = Url::fromString('http://e.com/base');
- $url->addPath('0');
- $this->assertEquals('http://e.com/base/0', $url);
-
- $url = Url::fromString('http://e.com/base?a=1');
- $url->addPath('relative');
- $this->assertEquals('http://e.com/base/relative?a=1', $url);
- $url = Url::fromString('http://e.com/base?a=1');
- $url->addPath('/relative');
- $this->assertEquals('http://e.com/base/relative?a=1', $url);
- }
-
- /**
- * URL combination data provider
- *
- * @return array
- */
- public function urlCombineDataProvider()
- {
- return [
- // Specific test cases
- ['http://www.example.com/', 'http://www.example.com/', 'http://www.example.com/'],
- ['http://www.example.com/path', '/absolute', 'http://www.example.com/absolute'],
- ['http://www.example.com/path', '/absolute?q=2', 'http://www.example.com/absolute?q=2'],
- ['http://www.example.com/', '?q=1', 'http://www.example.com/?q=1'],
- ['http://www.example.com/path', 'http://test.com', 'http://test.com'],
- ['http://www.example.com:8080/path', 'http://test.com', 'http://test.com'],
- ['http://www.example.com:8080/path', '?q=2#abc', 'http://www.example.com:8080/path?q=2#abc'],
- ['http://www.example.com/path', 'http://u:a@www.example.com/', 'http://u:a@www.example.com/'],
- ['/path?q=2', 'http://www.test.com/', 'http://www.test.com/path?q=2'],
- ['http://api.flickr.com/services/', 'http://www.flickr.com/services/oauth/access_token', 'http://www.flickr.com/services/oauth/access_token'],
- ['https://www.example.com/path', '//foo.com/abc', 'https://foo.com/abc'],
- ['https://www.example.com/0/', 'relative/foo', 'https://www.example.com/0/relative/foo'],
- ['', '0', '0'],
- // RFC 3986 test cases
- [self::RFC3986_BASE, 'g:h', 'g:h'],
- [self::RFC3986_BASE, 'g', 'http://a/b/c/g'],
- [self::RFC3986_BASE, './g', 'http://a/b/c/g'],
- [self::RFC3986_BASE, 'g/', 'http://a/b/c/g/'],
- [self::RFC3986_BASE, '/g', 'http://a/g'],
- [self::RFC3986_BASE, '//g', 'http://g'],
- [self::RFC3986_BASE, '?y', 'http://a/b/c/d;p?y'],
- [self::RFC3986_BASE, 'g?y', 'http://a/b/c/g?y'],
- [self::RFC3986_BASE, '#s', 'http://a/b/c/d;p?q#s'],
- [self::RFC3986_BASE, 'g#s', 'http://a/b/c/g#s'],
- [self::RFC3986_BASE, 'g?y#s', 'http://a/b/c/g?y#s'],
- [self::RFC3986_BASE, ';x', 'http://a/b/c/;x'],
- [self::RFC3986_BASE, 'g;x', 'http://a/b/c/g;x'],
- [self::RFC3986_BASE, 'g;x?y#s', 'http://a/b/c/g;x?y#s'],
- [self::RFC3986_BASE, '', self::RFC3986_BASE],
- [self::RFC3986_BASE, '.', 'http://a/b/c/'],
- [self::RFC3986_BASE, './', 'http://a/b/c/'],
- [self::RFC3986_BASE, '..', 'http://a/b/'],
- [self::RFC3986_BASE, '../', 'http://a/b/'],
- [self::RFC3986_BASE, '../g', 'http://a/b/g'],
- [self::RFC3986_BASE, '../..', 'http://a/'],
- [self::RFC3986_BASE, '../../', 'http://a/'],
- [self::RFC3986_BASE, '../../g', 'http://a/g'],
- [self::RFC3986_BASE, '../../../g', 'http://a/g'],
- [self::RFC3986_BASE, '../../../../g', 'http://a/g'],
- [self::RFC3986_BASE, '/./g', 'http://a/g'],
- [self::RFC3986_BASE, '/../g', 'http://a/g'],
- [self::RFC3986_BASE, 'g.', 'http://a/b/c/g.'],
- [self::RFC3986_BASE, '.g', 'http://a/b/c/.g'],
- [self::RFC3986_BASE, 'g..', 'http://a/b/c/g..'],
- [self::RFC3986_BASE, '..g', 'http://a/b/c/..g'],
- [self::RFC3986_BASE, './../g', 'http://a/b/g'],
- [self::RFC3986_BASE, 'foo////g', 'http://a/b/c/foo////g'],
- [self::RFC3986_BASE, './g/.', 'http://a/b/c/g/'],
- [self::RFC3986_BASE, 'g/./h', 'http://a/b/c/g/h'],
- [self::RFC3986_BASE, 'g/../h', 'http://a/b/c/h'],
- [self::RFC3986_BASE, 'g;x=1/./y', 'http://a/b/c/g;x=1/y'],
- [self::RFC3986_BASE, 'g;x=1/../y', 'http://a/b/c/y'],
- [self::RFC3986_BASE, 'http:g', 'http:g'],
- ];
- }
-
- /**
- * @dataProvider urlCombineDataProvider
- */
- public function testCombinesUrls($a, $b, $c)
- {
- $this->assertEquals($c, (string) Url::fromString($a)->combine($b));
- }
-
- public function testHasGettersAndSetters()
- {
- $url = Url::fromString('http://www.test.com/');
- $url->setHost('example.com');
- $this->assertEquals('example.com', $url->getHost());
- $url->setPort(8080);
- $this->assertEquals('8080', $url->getPort());
- $url->setPath('/foo/bar');
- $this->assertEquals('/foo/bar', $url->getPath());
- $url->setPassword('a');
- $this->assertEquals('a', $url->getPassword());
- $url->setUsername('b');
- $this->assertEquals('b', $url->getUsername());
- $url->setFragment('abc');
- $this->assertEquals('abc', $url->getFragment());
- $url->setScheme('https');
- $this->assertEquals('https', $url->getScheme());
- $url->setQuery('a=123');
- $this->assertEquals('a=123', (string) $url->getQuery());
- $this->assertEquals(
- 'https://b:a@example.com:8080/foo/bar?a=123#abc',
- (string) $url
- );
- $url->setQuery(new Query(['b' => 'boo']));
- $this->assertEquals('b=boo', $url->getQuery());
- $this->assertEquals(
- 'https://b:a@example.com:8080/foo/bar?b=boo#abc',
- (string) $url
- );
-
- $url->setQuery('a%20=bar!', true);
- $this->assertEquals(
- 'https://b:a@example.com:8080/foo/bar?a%20=bar!#abc',
- (string) $url
- );
- }
-
- public function testSetQueryAcceptsArray()
- {
- $url = Url::fromString('http://www.test.com');
- $url->setQuery(array('a' => 'b'));
- $this->assertEquals('http://www.test.com?a=b', (string) $url);
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testQueryMustBeValid()
- {
- $url = Url::fromString('http://www.test.com');
- $url->setQuery(false);
- }
-
- public function testDefersParsingAndEncodingQueryUntilNecessary()
- {
- $url = Url::fromString('http://www.test.com');
- // Note that invalid characters are encoded.
- $url->setQuery('foo#bar/', true);
- $this->assertEquals('http://www.test.com?foo%23bar/', (string) $url);
- $this->assertInternalType('string', $this->readAttribute($url, 'query'));
- $this->assertEquals('foo%23bar%2F', (string) $url->getQuery());
- $this->assertInstanceOf('GuzzleHttp\Query', $this->readAttribute($url, 'query'));
- }
-
- public function urlProvider()
- {
- return array(
- array('/foo/..', '/'),
- array('//foo//..', '//foo/'),
- array('/foo//', '/foo//'),
- array('/foo/../..', '/'),
- array('/foo/../.', '/'),
- array('/./foo/..', '/'),
- array('/./foo', '/foo'),
- array('/./foo/', '/foo/'),
- array('*', '*'),
- array('/foo', '/foo'),
- array('/abc/123/../foo/', '/abc/foo/'),
- array('/a/b/c/./../../g', '/a/g'),
- array('/b/c/./../../g', '/g'),
- array('/b/c/./../../g', '/g'),
- array('/c/./../../g', '/g'),
- array('/./../../g', '/g'),
- array('foo', 'foo'),
- );
- }
-
- /**
- * @dataProvider urlProvider
- */
- public function testRemoveDotSegments($path, $result)
- {
- $url = Url::fromString('http://www.example.com');
- $url->setPath($path);
- $url->removeDotSegments();
- $this->assertEquals($result, $url->getPath());
- }
-
- public function testSettingHostWithPortModifiesPort()
- {
- $url = Url::fromString('http://www.example.com');
- $url->setHost('foo:8983');
- $this->assertEquals('foo', $url->getHost());
- $this->assertEquals(8983, $url->getPort());
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testValidatesUrlCanBeParsed()
- {
- Url::fromString('foo:////');
- }
-
- public function testConvertsSpecialCharsInPathWhenCastingToString()
- {
- $url = Url::fromString('http://foo.com/baz bar?a=b');
- $url->addPath('?');
- $this->assertEquals('http://foo.com/baz%20bar/%3F?a=b', (string) $url);
- }
-
- public function testCorrectlyEncodesPathWithoutDoubleEncoding()
- {
- $url = Url::fromString('http://foo.com/baz%20 bar:boo/baz!');
- $this->assertEquals('/baz%20%20bar:boo/baz!', $url->getPath());
- }
-}
diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php
deleted file mode 100644
index d9bdc071f..000000000
--- a/tests/UtilsTest.php
+++ /dev/null
@@ -1,34 +0,0 @@
-assertEquals(
- 'foo/123',
- Utils::uriTemplate('foo/{bar}', ['bar' => '123'])
- );
- }
-
- public function noBodyProvider()
- {
- return [['get'], ['head'], ['delete']];
- }
-
- public function testJsonDecodes()
- {
- $this->assertTrue(Utils::jsonDecode('true'));
- }
-
- /**
- * @expectedException \InvalidArgumentException
- * @expectedExceptionMessage Unable to parse JSON data: JSON_ERROR_SYNTAX - Syntax error, malformed JSON
- */
- public function testJsonDecodesWithErrorMessages()
- {
- Utils::jsonDecode('!narf!');
- }
-}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index 8713f9624..2de59b00b 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -1,6 +1,6 @@
Server::$url]);
-
-$t = microtime(true);
-for ($i = 0; $i < $total; $i++) {
- $client->get('/guzzle-server/perf');
-}
-$totalTime = microtime(true) - $t;
-$perRequest = ($totalTime / $total) * 1000;
-printf("Serial: %f (%f ms / request) %d total\n",
- $totalTime, $perRequest, $total);
-
-// Create a generator used to yield batches of requests
-$reqs = function () use ($client, $total) {
- for ($i = 0; $i < $total; $i++) {
- yield $client->createRequest('GET', '/guzzle-server/perf');
- }
-};
-
-$t = microtime(true);
-Pool::send($client, $reqs(), ['parallel' => $parallel]);
-$totalTime = microtime(true) - $t;
-$perRequest = ($totalTime / $total) * 1000;
-printf("Batch: %f (%f ms / request) %d total with %d in parallel\n",
- $totalTime, $perRequest, $total, $parallel);
-
-$handler = new CurlMultiHandler(['max_handles' => $parallel]);
-$client = new Client(['handler' => $handler, 'base_url' => Server::$url]);
-$t = microtime(true);
-for ($i = 0; $i < $total; $i++) {
- $client->get('/guzzle-server/perf');
-}
-unset($client);
-$totalTime = microtime(true) - $t;
-$perRequest = ($totalTime / $total) * 1000;
-printf("Future: %f (%f ms / request) %d total\n",
- $totalTime, $perRequest, $total);
diff --git a/tests/server.js b/tests/server.js
new file mode 100644
index 000000000..c2b697fa7
--- /dev/null
+++ b/tests/server.js
@@ -0,0 +1,241 @@
+/**
+ * Guzzle node.js test server to return queued responses to HTTP requests and
+ * expose a RESTful API for enqueueing responses and retrieving the requests
+ * that have been received.
+ *
+ * - Delete all requests that have been received:
+ * > DELETE /guzzle-server/requests
+ * > Host: 127.0.0.1:8126
+ *
+ * - Enqueue responses
+ * > PUT /guzzle-server/responses
+ * > Host: 127.0.0.1:8126
+ * >
+ * > [{'status': 200, 'reason': 'OK', 'headers': {}, 'body': '' }]
+ *
+ * - Get the received requests
+ * > GET /guzzle-server/requests
+ * > Host: 127.0.0.1:8126
+ *
+ * < HTTP/1.1 200 OK
+ * <
+ * < [{'http_method': 'GET', 'uri': '/', 'headers': {}, 'body': 'string'}]
+ *
+ * - Attempt access to the secure area
+ * > GET /secure/by-digest/qop-auth/guzzle-server/requests
+ * > Host: 127.0.0.1:8126
+ *
+ * < HTTP/1.1 401 Unauthorized
+ * < WWW-Authenticate: Digest realm="Digest Test", qop="auth", nonce="0796e98e1aeef43141fab2a66bf4521a", algorithm="MD5", stale="false"
+ * <
+ * < 401 Unauthorized
+ *
+ * - Shutdown the server
+ * > DELETE /guzzle-server
+ * > Host: 127.0.0.1:8126
+ *
+ * @package Guzzle PHP
+ * @license See the LICENSE file that was distributed with this source code.
+ */
+
+var http = require('http');
+var url = require('url');
+
+/**
+ * Guzzle node.js server
+ * @class
+ */
+var GuzzleServer = function(port, log) {
+
+ this.port = port;
+ this.log = log;
+ this.responses = [];
+ this.requests = [];
+ var that = this;
+
+ var md5 = function(input) {
+ var crypto = require('crypto');
+ var hasher = crypto.createHash('md5');
+ hasher.update(input);
+ return hasher.digest('hex');
+ }
+
+ /**
+ * Node.js HTTP server authentication module.
+ *
+ * It is only initialized on demand (by loadAuthentifier). This avoids
+ * requiring the dependency to http-auth on standard operations, and the
+ * performance hit at startup.
+ */
+ var auth;
+
+ /**
+ * Provides authentication handlers (Basic, Digest).
+ */
+ var loadAuthentifier = function(type, options) {
+ var typeId = type;
+ if (type == 'digest') {
+ typeId += '.'+(options && options.qop ? options.qop : 'none');
+ }
+ if (!loadAuthentifier[typeId]) {
+ if (!auth) {
+ try {
+ auth = require('http-auth');
+ } catch (e) {
+ if (e.code == 'MODULE_NOT_FOUND') {
+ return;
+ }
+ }
+ }
+ switch (type) {
+ case 'digest':
+ var digestParams = {
+ realm: 'Digest Test',
+ login: 'me',
+ password: 'test'
+ };
+ if (options && options.qop) {
+ digestParams.qop = options.qop;
+ }
+ loadAuthentifier[typeId] = auth.digest(digestParams, function(username, callback) {
+ callback(md5(digestParams.login + ':' + digestParams.realm + ':' + digestParams.password));
+ });
+ break
+ }
+ }
+ return loadAuthentifier[typeId];
+ };
+
+ var firewallRequest = function(request, req, res, requestHandlerCallback) {
+ var securedAreaUriParts = request.uri.match(/^\/secure\/by-(digest)(\/qop-([^\/]*))?(\/.*)$/);
+ if (securedAreaUriParts) {
+ var authentifier = loadAuthentifier(securedAreaUriParts[1], { qop: securedAreaUriParts[2] });
+ if (!authentifier) {
+ res.writeHead(501, 'HTTP authentication not implemented', { 'Content-Length': 0 });
+ res.end();
+ return;
+ }
+ authentifier.check(req, res, function(req, res) {
+ req.url = securedAreaUriParts[4];
+ requestHandlerCallback(request, req, res);
+ });
+ } else {
+ requestHandlerCallback(request, req, res);
+ }
+ };
+
+ var controlRequest = function(request, req, res) {
+ if (req.url == '/guzzle-server/perf') {
+ res.writeHead(200, 'OK', {'Content-Length': 16});
+ res.end('Body of response');
+ } else if (req.method == 'DELETE') {
+ if (req.url == '/guzzle-server/requests') {
+ // Clear the received requests
+ that.requests = [];
+ res.writeHead(200, 'OK', { 'Content-Length': 0 });
+ res.end();
+ if (that.log) {
+ console.log('Flushing requests');
+ }
+ } else if (req.url == '/guzzle-server') {
+ // Shutdown the server
+ res.writeHead(200, 'OK', { 'Content-Length': 0, 'Connection': 'close' });
+ res.end();
+ if (that.log) {
+ console.log('Shutting down');
+ }
+ that.server.close();
+ }
+ } else if (req.method == 'GET') {
+ if (req.url === '/guzzle-server/requests') {
+ if (that.log) {
+ console.log('Sending received requests');
+ }
+ // Get received requests
+ var body = JSON.stringify(that.requests);
+ res.writeHead(200, 'OK', { 'Content-Length': body.length });
+ res.end(body);
+ }
+ } else if (req.method == 'PUT' && req.url == '/guzzle-server/responses') {
+ if (that.log) {
+ console.log('Adding responses...');
+ }
+ if (!request.body) {
+ if (that.log) {
+ console.log('No response data was provided');
+ }
+ res.writeHead(400, 'NO RESPONSES IN REQUEST', { 'Content-Length': 0 });
+ } else {
+ that.responses = eval('(' + request.body + ')');
+ for (var i = 0; i < that.responses.length; i++) {
+ if (that.responses[i].body) {
+ that.responses[i].body = new Buffer(that.responses[i].body, 'base64');
+ }
+ }
+ if (that.log) {
+ console.log(that.responses);
+ }
+ res.writeHead(200, 'OK', { 'Content-Length': 0 });
+ }
+ res.end();
+ }
+ };
+
+ var receivedRequest = function(request, req, res) {
+ if (req.url.indexOf('/guzzle-server') === 0) {
+ controlRequest(request, req, res);
+ } else if (req.url.indexOf('/guzzle-server') == -1 && !that.responses.length) {
+ res.writeHead(500);
+ res.end('No responses in queue');
+ } else {
+ if (that.log) {
+ console.log('Returning response from queue and adding request');
+ }
+ that.requests.push(request);
+ var response = that.responses.shift();
+ res.writeHead(response.status, response.reason, response.headers);
+ res.end(response.body);
+ }
+ };
+
+ this.start = function() {
+
+ that.server = http.createServer(function(req, res) {
+
+ var parts = url.parse(req.url, false);
+ var request = {
+ http_method: req.method,
+ scheme: parts.scheme,
+ uri: parts.pathname,
+ query_string: parts.query,
+ headers: req.headers,
+ version: req.httpVersion,
+ body: ''
+ };
+
+ // Receive each chunk of the request body
+ req.addListener('data', function(chunk) {
+ request.body += chunk;
+ });
+
+ // Called when the request completes
+ req.addListener('end', function() {
+ firewallRequest(request, req, res, receivedRequest);
+ });
+ });
+
+ that.server.listen(this.port, '127.0.0.1');
+
+ if (this.log) {
+ console.log('Server running at http://127.0.0.1:8126/');
+ }
+ };
+};
+
+// Get the port from the arguments
+port = process.argv.length >= 3 ? process.argv[2] : 8126;
+log = process.argv.length >= 4 ? process.argv[3] : false;
+
+// Start the server
+server = new GuzzleServer(port, log);
+server.start();