Issue #2461985 by stefan.r: Update Guzzle to latest release
parent
5271f8f028
commit
ed89a08f25
|
@ -783,16 +783,16 @@
|
|||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "5.0.3",
|
||||
"version": "5.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/guzzle.git",
|
||||
"reference": "6c72627de1d66832e4270e36e56acdb0d1d8f282"
|
||||
"reference": "475b29ccd411f2fa8a408e64576418728c032cfa"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/6c72627de1d66832e4270e36e56acdb0d1d8f282",
|
||||
"reference": "6c72627de1d66832e4270e36e56acdb0d1d8f282",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/475b29ccd411f2fa8a408e64576418728c032cfa",
|
||||
"reference": "475b29ccd411f2fa8a408e64576418728c032cfa",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -837,20 +837,20 @@
|
|||
"rest",
|
||||
"web service"
|
||||
],
|
||||
"time": "2014-11-04 07:09:15"
|
||||
"time": "2015-01-28 01:03:29"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/ringphp",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/RingPHP.git",
|
||||
"reference": "e7c28f96c5ac12ab0e63412cfc15989756fcb964"
|
||||
"reference": "52d868f13570a9a56e5fce6614e0ec75d0f13ac2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/RingPHP/zipball/e7c28f96c5ac12ab0e63412cfc15989756fcb964",
|
||||
"reference": "e7c28f96c5ac12ab0e63412cfc15989756fcb964",
|
||||
"url": "https://api.github.com/repos/guzzle/RingPHP/zipball/52d868f13570a9a56e5fce6614e0ec75d0f13ac2",
|
||||
"reference": "52d868f13570a9a56e5fce6614e0ec75d0f13ac2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -887,7 +887,8 @@
|
|||
"homepage": "https://github.com/mtdowling"
|
||||
}
|
||||
],
|
||||
"time": "2014-11-04 07:01:14"
|
||||
"description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.",
|
||||
"time": "2015-03-30 01:43:20"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/streams",
|
||||
|
|
|
@ -1218,66 +1218,6 @@
|
|||
"templating"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "5.0.3",
|
||||
"version_normalized": "5.0.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/guzzle.git",
|
||||
"reference": "6c72627de1d66832e4270e36e56acdb0d1d8f282"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/6c72627de1d66832e4270e36e56acdb0d1d8f282",
|
||||
"reference": "6c72627de1d66832e4270e36e56acdb0d1d8f282",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"guzzlehttp/ringphp": "~1.0",
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-curl": "*",
|
||||
"phpunit/phpunit": "~4.0",
|
||||
"psr/log": "~1.0"
|
||||
},
|
||||
"time": "2014-11-04 07:09:15",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "5.0-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
}
|
||||
],
|
||||
"description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients",
|
||||
"homepage": "http://guzzlephp.org/",
|
||||
"keywords": [
|
||||
"client",
|
||||
"curl",
|
||||
"framework",
|
||||
"http",
|
||||
"http client",
|
||||
"rest",
|
||||
"web service"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "egulias/email-validator",
|
||||
"version": "1.2.5",
|
||||
|
@ -1382,58 +1322,6 @@
|
|||
"hhvm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/ringphp",
|
||||
"version": "1.0.3",
|
||||
"version_normalized": "1.0.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/RingPHP.git",
|
||||
"reference": "e7c28f96c5ac12ab0e63412cfc15989756fcb964"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/RingPHP/zipball/e7c28f96c5ac12ab0e63412cfc15989756fcb964",
|
||||
"reference": "e7c28f96c5ac12ab0e63412cfc15989756fcb964",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"guzzlehttp/streams": "~3.0",
|
||||
"php": ">=5.4.0",
|
||||
"react/promise": "~2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-curl": "*",
|
||||
"phpunit/phpunit": "~4.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "Guzzle will use specific adapters if cURL is present"
|
||||
},
|
||||
"time": "2014-11-04 07:01:14",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Ring\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "easyrdf/easyrdf",
|
||||
"version": "0.9.0",
|
||||
|
@ -3156,5 +3044,118 @@
|
|||
],
|
||||
"description": "Symfony BrowserKit Component",
|
||||
"homepage": "http://symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "5.2.0",
|
||||
"version_normalized": "5.2.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/guzzle.git",
|
||||
"reference": "475b29ccd411f2fa8a408e64576418728c032cfa"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/475b29ccd411f2fa8a408e64576418728c032cfa",
|
||||
"reference": "475b29ccd411f2fa8a408e64576418728c032cfa",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"guzzlehttp/ringphp": "~1.0",
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-curl": "*",
|
||||
"phpunit/phpunit": "~4.0",
|
||||
"psr/log": "~1.0"
|
||||
},
|
||||
"time": "2015-01-28 01:03:29",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "5.0-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
}
|
||||
],
|
||||
"description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients",
|
||||
"homepage": "http://guzzlephp.org/",
|
||||
"keywords": [
|
||||
"client",
|
||||
"curl",
|
||||
"framework",
|
||||
"http",
|
||||
"http client",
|
||||
"rest",
|
||||
"web service"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/ringphp",
|
||||
"version": "1.0.7",
|
||||
"version_normalized": "1.0.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/RingPHP.git",
|
||||
"reference": "52d868f13570a9a56e5fce6614e0ec75d0f13ac2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/RingPHP/zipball/52d868f13570a9a56e5fce6614e0ec75d0f13ac2",
|
||||
"reference": "52d868f13570a9a56e5fce6614e0ec75d0f13ac2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"guzzlehttp/streams": "~3.0",
|
||||
"php": ">=5.4.0",
|
||||
"react/promise": "~2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-curl": "*",
|
||||
"phpunit/phpunit": "~4.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "Guzzle will use specific adapters if cURL is present"
|
||||
},
|
||||
"time": "2015-03-30 01:43:20",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Ring\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
}
|
||||
],
|
||||
"description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function."
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,5 +1,35 @@
|
|||
# CHANGELOG
|
||||
|
||||
## 5.2.0 - 2015-01-27
|
||||
|
||||
* Added `AppliesHeadersInterface` to make applying headers to a request based
|
||||
on the body more generic and not specific to `PostBodyInterface`.
|
||||
* Reduced the number of stack frames needed to send requests.
|
||||
* Nested futures are now resolved in the client rather than the RequestFsm
|
||||
* Finishing state transitions is now handled in the RequestFsm rather than the
|
||||
RingBridge.
|
||||
* Added a guard in the Pool class to not use recursion for request retries.
|
||||
|
||||
## 5.1.0 - 2014-12-19
|
||||
|
||||
* Pool class no longer uses recursion when a request is intercepted.
|
||||
* The size of a Pool can now be dynamically adjusted using a callback.
|
||||
See https://github.com/guzzle/guzzle/pull/943.
|
||||
* Setting a request option to `null` when creating a request with a client will
|
||||
ensure that the option is not set. This allows you to overwrite default
|
||||
request options on a per-request basis.
|
||||
See https://github.com/guzzle/guzzle/pull/937.
|
||||
* Added the ability to limit which protocols are allowed for redirects by
|
||||
specifying a `protocols` array in the `allow_redirects` request option.
|
||||
* Nested futures due to retries are now resolved when waiting for synchronous
|
||||
responses. See https://github.com/guzzle/guzzle/pull/947.
|
||||
* `"0"` is now an allowed URI path. See
|
||||
https://github.com/guzzle/guzzle/pull/935.
|
||||
* `Query` no longer typehints on the `$query` argument in the constructor,
|
||||
allowing for strings and arrays.
|
||||
* Exceptions thrown in the `end` event are now correctly wrapped with Guzzle
|
||||
specific exceptions if necessary.
|
||||
|
||||
## 5.0.3 - 2014-11-03
|
||||
|
||||
This change updates query strings so that they are treated as un-encoded values
|
||||
|
@ -71,10 +101,10 @@ The breaking changes in this release are relatively minor. The biggest thing to
|
|||
look out for is that request and response objects no longer implement fluent
|
||||
interfaces.
|
||||
|
||||
* Removed the fluent interfaces (i.e., ``return $this``) from requests,
|
||||
responses, ``GuzzleHttp\Collection``, ``GuzzleHttp\Url``,
|
||||
``GuzzleHttp\Query``, ``GuzzleHttp\Post\PostBody``, and
|
||||
``GuzzleHttp\Cookie\SetCookie``. This blog post provides a good outline of
|
||||
* Removed the fluent interfaces (i.e., `return $this`) from requests,
|
||||
responses, `GuzzleHttp\Collection`, `GuzzleHttp\Url`,
|
||||
`GuzzleHttp\Query`, `GuzzleHttp\Post\PostBody`, and
|
||||
`GuzzleHttp\Cookie\SetCookie`. This blog post provides a good outline of
|
||||
why I did this: http://ocramius.github.io/blog/fluent-interfaces-are-evil/.
|
||||
This also makes the Guzzle message interfaces compatible with the current
|
||||
PSR-7 message proposal.
|
||||
|
@ -84,7 +114,7 @@ interfaces.
|
|||
moved to `GuzzleHttp\Utils::jsonDecode`. `GuzzleHttp\get_path` moved to
|
||||
`GuzzleHttp\Utils::getPath`. `GuzzleHttp\set_path` moved to
|
||||
`GuzzleHttp\Utils::setPath`. `GuzzleHttp\batch` should now be
|
||||
`GuzzleHttp\Pool::batch`, which returns a bjectStorage`. Using functions.php
|
||||
`GuzzleHttp\Pool::batch`, which returns an `objectStorage`. Using functions.php
|
||||
caused problems for many users: they aren't PSR-4 compliant, require an
|
||||
explicit include, and needed an if-guard to ensure that the functions are not
|
||||
declared multiple times.
|
||||
|
@ -105,10 +135,10 @@ interfaces.
|
|||
written to.
|
||||
* Removed the `asArray` parameter from
|
||||
`GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header
|
||||
value as an array, then use the newly added ``getHeaderAsArray()`` method of
|
||||
``MessageInterface``. This change makes the Guzzle interfaces compatible with
|
||||
value as an array, then use the newly added `getHeaderAsArray()` method of
|
||||
`MessageInterface`. This change makes the Guzzle interfaces compatible with
|
||||
the PSR-7 interfaces.
|
||||
* ``GuzzleHttp\Message\MessageFactory`` no longer allows subclasses to add
|
||||
* `GuzzleHttp\Message\MessageFactory` no longer allows subclasses to add
|
||||
custom request options using double-dispatch (this was an implementation
|
||||
detail). Instead, you should now provide an associative array to the
|
||||
constructor which is a mapping of the request option name mapping to a
|
||||
|
@ -121,9 +151,9 @@ interfaces.
|
|||
* `GuzzleHttp\Stream\StreamInterface::getContents()` no longer accepts a
|
||||
`maxLen` parameter. This update makes the Guzzle streams project
|
||||
compatible with the current PSR-7 proposal.
|
||||
* ``GuzzleHttp\Stream\Stream::__construct``,
|
||||
``GuzzleHttp\Stream\Stream::factory``, and
|
||||
``GuzzleHttp\Stream\Utils::create`` no longer accept a size in the second
|
||||
* `GuzzleHttp\Stream\Stream::__construct`,
|
||||
`GuzzleHttp\Stream\Stream::factory`, and
|
||||
`GuzzleHttp\Stream\Utils::create` no longer accept a size in the second
|
||||
argument. They now accept an associative array of options, including the
|
||||
"size" key and "metadata" key which can be used to provide custom metadata.
|
||||
|
||||
|
@ -359,7 +389,7 @@ interfaces.
|
|||
* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`.
|
||||
* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody()
|
||||
* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin
|
||||
* Bug fix: Visiting XML attributes first before visting XML children when serializing requests
|
||||
* Bug fix: Visiting XML attributes first before visiting XML children when serializing requests
|
||||
* Bug fix: Properly parsing headers that contain commas contained in quotes
|
||||
* Bug fix: mimetype guessing based on a filename is now case-insensitive
|
||||
|
||||
|
@ -502,7 +532,7 @@ interfaces.
|
|||
directly via interfaces
|
||||
* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
|
||||
but are a no-op until removed.
|
||||
* Most classes that used to require a ``Guzzle\Service\Command\CommandInterface` typehint now request a
|
||||
* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a
|
||||
`Guzzle\Service\Command\ArrayCommandInterface`.
|
||||
* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
|
||||
on a request while the request is still being transferred
|
||||
|
@ -517,7 +547,7 @@ interfaces.
|
|||
## 3.5.0 - 2013-05-13
|
||||
|
||||
* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times
|
||||
* Bug: Better cleanup of one-time events accross the board (when an event is meant to fire once, it will now remove
|
||||
* Bug: Better cleanup of one-time events across the board (when an event is meant to fire once, it will now remove
|
||||
itself from the EventDispatcher)
|
||||
* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values
|
||||
* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too
|
||||
|
@ -833,15 +863,15 @@ interfaces.
|
|||
* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body
|
||||
* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new
|
||||
* Added multiple inheritance to service description commands
|
||||
* Added an ApiCommandInterface and added ``getParamNames()`` and ``hasParam()``
|
||||
* Added an ApiCommandInterface and added `getParamNames()` and `hasParam()`
|
||||
* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything
|
||||
* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles
|
||||
|
||||
## 2.8.2 - 2012-07-24
|
||||
|
||||
* Bug: Query string values set to 0 are no longer dropped from the query string
|
||||
* Bug: A Collection object is no longer created each time a call is made to ``Guzzle\Service\Command\AbstractCommand::getRequestHeaders()``
|
||||
* Bug: ``+`` is now treated as an encoded space when parsing query strings
|
||||
* Bug: A Collection object is no longer created each time a call is made to `Guzzle\Service\Command\AbstractCommand::getRequestHeaders()`
|
||||
* Bug: `+` is now treated as an encoded space when parsing query strings
|
||||
* QueryString and Collection performance improvements
|
||||
* Allowing dot notation for class paths in filters attribute of a service descriptions
|
||||
|
||||
|
@ -858,7 +888,7 @@ interfaces.
|
|||
* Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool)
|
||||
* Changed the aggregation functions of QueryString to be static methods
|
||||
* Can now use fromString() with querystrings that have a leading ?
|
||||
* cURL configuration values can be specified in service descriptions using ``curl.`` prefixed parameters
|
||||
* cURL configuration values can be specified in service descriptions using `curl.` prefixed parameters
|
||||
* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body
|
||||
* Cookies are no longer URL decoded by default
|
||||
* Bug: URI template variables set to null are no longer expanded
|
||||
|
|
|
@ -503,7 +503,7 @@ allow developers to more easily extend and decorate stream behavior.
|
|||
## Metadata streams
|
||||
|
||||
`GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams
|
||||
that contain additonal metadata accessible via `getMetadata()`.
|
||||
that contain additional metadata accessible via `getMetadata()`.
|
||||
`GuzzleHttp\Stream\StreamInterface::getMetadata` and
|
||||
`GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed.
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ foreach (['README.md', 'LICENSE'] as $file) {
|
|||
|
||||
// Copy each dependency to the staging directory. Copy *.php and *.pem files.
|
||||
$packager->recursiveCopy('src', 'GuzzleHttp', ['php']);
|
||||
$packager->recursiveCopy('vendor/react/promise/src', '');
|
||||
$packager->recursiveCopy('vendor/react/promise/src', 'React/Promise');
|
||||
$packager->recursiveCopy('vendor/guzzlehttp/ringphp/src', 'GuzzleHttp/Ring');
|
||||
$packager->recursiveCopy('vendor/guzzlehttp/streams/src', 'GuzzleHttp/Stream');
|
||||
$packager->createAutoloader(['React/Promise/functions.php']);
|
||||
|
|
|
@ -173,6 +173,12 @@ response has completed.
|
|||
If an exception occurred while transferring the future response, then the
|
||||
exception encountered will be thrown when dereferencing.
|
||||
|
||||
.. note::
|
||||
|
||||
It depends on the RingPHP handler used by a client, but you typically need
|
||||
to use the same RingPHP handler in order to utilize asynchronous requests
|
||||
across multiple clients.
|
||||
|
||||
Asynchronous Error Handling
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -524,7 +530,7 @@ json
|
|||
|
||||
.. code-block:: php
|
||||
|
||||
$request = $client->createRequest('/put', ['json' => ['foo' => 'bar']]);
|
||||
$request = $client->createRequest('PUT', '/put', ['json' => ['foo' => 'bar']]);
|
||||
echo $request->getHeader('Content-Type');
|
||||
// application/json
|
||||
echo $request->getBody();
|
||||
|
@ -701,7 +707,15 @@ allow_redirects
|
|||
:Types:
|
||||
- bool
|
||||
- array
|
||||
:Default: ``['max' => 5, 'strict' => false, 'referer' => true]``
|
||||
:Default:
|
||||
::
|
||||
|
||||
[
|
||||
'max' => 5,
|
||||
'strict' => false,
|
||||
'referer' => true,
|
||||
'protocols' => ['http', 'https']
|
||||
]
|
||||
|
||||
Set to ``false`` to disable redirects.
|
||||
|
||||
|
@ -721,19 +735,22 @@ number of 5 redirects.
|
|||
// 200
|
||||
|
||||
Pass an associative array containing the 'max' key to specify the maximum
|
||||
number of redirects, optionally provide a 'strict' key value to specify
|
||||
whether or not to use strict RFC compliant redirects (meaning redirect POST
|
||||
requests with POST requests vs. doing what most browsers do which is redirect
|
||||
POST requests with GET requests), and optionally provide a 'referer' key to
|
||||
specify whether or not the "Referer" header should be added when redirecting.
|
||||
number of redirects, provide a 'strict' key value to specify whether or not to
|
||||
use strict RFC compliant redirects (meaning redirect POST requests with POST
|
||||
requests vs. doing what most browsers do which is redirect POST requests with
|
||||
GET requests), provide a 'referer' key to specify whether or not the "Referer"
|
||||
header should be added when redirecting, and provide a 'protocols' array that
|
||||
specifies which protocols are supported for redirects (defaults to
|
||||
``['http', 'https']``).
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$res = $client->get('/redirect/3', [
|
||||
'allow_redirects' => [
|
||||
'max' => 10,
|
||||
'strict' => true,
|
||||
'referer' => true
|
||||
'max' => 10, // allow at most 10 redirects.
|
||||
'strict' => true, // use "strict" RFC compliant redirects.
|
||||
'referer' => true, // add a Referer header
|
||||
'protocols' => ['https'] // only allow https URLs
|
||||
]
|
||||
]);
|
||||
echo $res->getStatusCode();
|
||||
|
|
|
@ -11,12 +11,12 @@ Event Emitters
|
|||
==============
|
||||
|
||||
Clients, requests, and any other class that implements the
|
||||
``GuzzleHttp\Common\HasEmitterInterface`` interface have a
|
||||
``GuzzleHttp\Common\EventEmitter`` object. You can add event *listeners* and
|
||||
``GuzzleHttp\Event\HasEmitterInterface`` interface have a
|
||||
``GuzzleHttp\Event\Emitter`` object. You can add event *listeners* and
|
||||
event *subscribers* to an event *emitter*.
|
||||
|
||||
emitter
|
||||
An object that implements ``GuzzleHttp\Common\EventEmitterInterface``. This
|
||||
An object that implements ``GuzzleHttp\Event\EmitterInterface``. This
|
||||
object emits named events to event listeners. You may register event
|
||||
listeners on subscribers on an emitter.
|
||||
|
||||
|
@ -58,7 +58,7 @@ propagation
|
|||
Getting an EventEmitter
|
||||
-----------------------
|
||||
|
||||
You can get the event emitter of ``GuzzleHttp\Common\HasEmitterInterface``
|
||||
You can get the event emitter of ``GuzzleHttp\Event\HasEmitterInterface``
|
||||
object using the the ``getEmitter()`` method. Here's an example of getting a
|
||||
client object's event emitter.
|
||||
|
||||
|
@ -95,7 +95,7 @@ event is triggered, and optionally provide a priority.
|
|||
});
|
||||
|
||||
When a listener is triggered, it is passed an event that implements the
|
||||
``GuzzleHttp\Common\EventInterface`` interface, the name of the event, and the
|
||||
``GuzzleHttp\Event\EventInterface`` interface, the name of the event, and the
|
||||
event emitter itself. The above example could more verbosely be written as
|
||||
follows:
|
||||
|
||||
|
@ -134,7 +134,7 @@ state. This technique is used in Guzzle extensively when intercepting error
|
|||
events with responses.
|
||||
|
||||
You can stop the propagation of an event using the ``stopPropagation()`` method
|
||||
of a ``GuzzleHttp\Common\EventInterface`` object:
|
||||
of a ``GuzzleHttp\Event\EventInterface`` object:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
|
@ -168,7 +168,7 @@ Event Subscribers
|
|||
-----------------
|
||||
|
||||
Event subscribers are classes that implement the
|
||||
``GuzzleHttp\Common\EventSubscriberInterface`` object. They are used to register
|
||||
``GuzzleHttp\Event\SubscriberInterface`` object. They are used to register
|
||||
one or more event listeners to methods of the class. Event subscribers tell
|
||||
event emitters exactly which events to listen to and what method to invoke on
|
||||
the class when the event is triggered by called the ``getEvents()`` method of
|
||||
|
@ -213,6 +213,18 @@ priority of the listener (as shown in the ``before`` listener in the example).
|
|||
}
|
||||
}
|
||||
|
||||
To register the listeners the subscriber needs to be attached to the emitter:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$client = new GuzzleHttp\Client();
|
||||
$emitter = $client->getEmitter();
|
||||
$subscriber = new SimpleSubscriber();
|
||||
$emitter->attach($subscriber);
|
||||
|
||||
//to remove the listeners
|
||||
$emitter->detach($subscriber);
|
||||
|
||||
.. note::
|
||||
|
||||
You can specify event priorities using integers or ``"first"`` and
|
||||
|
@ -315,7 +327,7 @@ a ``GuzzleHttp\Event\BeforeEvent``.
|
|||
.. code-block:: php
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Common\EmitterInterface;
|
||||
use GuzzleHttp\Event\EmitterInterface;
|
||||
use GuzzleHttp\Event\BeforeEvent;
|
||||
|
||||
$client = new Client(['base_url' => 'http://httpbin.org']);
|
||||
|
@ -480,7 +492,7 @@ end
|
|||
---
|
||||
|
||||
The ``end`` event is a terminal event, emitted once per request, that provides
|
||||
access to the repsonse that was received or the exception that was encountered.
|
||||
access to the response that was received or the exception that was encountered.
|
||||
The event emitted is a ``GuzzleHttp\Event\EndEvent``.
|
||||
|
||||
This event can be intercepted, but keep in mind that the ``complete`` event
|
||||
|
|
|
@ -64,7 +64,7 @@ an HTTP response into a more meaningful model object.
|
|||
- `Guzzle Command <https://github.com/guzzle/command>`_: Provides the building
|
||||
blocks for service description abstraction.
|
||||
- `Guzzle Services <https://github.com/guzzle/guzzle-services>`_: Provides an
|
||||
implementation of "Guzzle Command" that utlizes Guzzle's service description
|
||||
implementation of "Guzzle Command" that utilizes Guzzle's service description
|
||||
format.
|
||||
|
||||
Does Guzzle require cURL?
|
||||
|
@ -91,7 +91,7 @@ Can Guzzle send asynchronous requests?
|
|||
Yes. Pass the ``future`` true request option to a request to send it
|
||||
asynchronously. Guzzle will then return a ``GuzzleHttp\Message\FutureResponse``
|
||||
object that can be used synchronously by accessing the response object like a
|
||||
normal response, and it can be used asynchronoulsy using a promise that is
|
||||
normal response, and it can be used asynchronously using a promise that is
|
||||
notified when the response is resolved with a real response or rejected with an
|
||||
exception.
|
||||
|
||||
|
|
|
@ -351,7 +351,7 @@ method of a request.
|
|||
$request = $client->createRequest('GET', '/');
|
||||
$config = $request->getConfig();
|
||||
|
||||
The config object is a ``GuzzleHttp\Common\Collection`` object that acts like
|
||||
The config object is a ``GuzzleHttp\Collection`` object that acts like
|
||||
an associative array. You can grab values from the collection using array like
|
||||
access. You can also modify and remove values using array like access.
|
||||
|
||||
|
@ -393,7 +393,7 @@ allow customization through request configuration options.
|
|||
Event Emitter
|
||||
-------------
|
||||
|
||||
Request objects implement ``GuzzleHttp\Common\HasEmitterInterface``, so they
|
||||
Request objects implement ``GuzzleHttp\Event\HasEmitterInterface``, so they
|
||||
have a method called ``getEmitter()`` that can be used to get an event emitter
|
||||
used by the request. Any listener or subscriber attached to a request will only
|
||||
be triggered for the lifecycle events of a specific request. Conversely, adding
|
||||
|
|
|
@ -177,26 +177,14 @@ class Client implements ClientInterface
|
|||
|
||||
public function createRequest($method, $url = null, array $options = [])
|
||||
{
|
||||
$headers = $this->mergeDefaults($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;
|
||||
|
||||
$request = $this->messageFactory->createRequest(
|
||||
$method,
|
||||
$url ? (string) $this->buildUrl($url) : (string) $this->baseUrl,
|
||||
$options
|
||||
);
|
||||
|
||||
// Merge in default headers
|
||||
if ($headers) {
|
||||
foreach ($headers as $key => $value) {
|
||||
if (!$request->hasHeader($key)) {
|
||||
$request->setHeader($key, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $request;
|
||||
return $this->messageFactory->createRequest($method, $url, $options);
|
||||
}
|
||||
|
||||
public function get($url = null, $options = [])
|
||||
|
@ -236,30 +224,31 @@ class Client implements ClientInterface
|
|||
|
||||
public function send(RequestInterface $request)
|
||||
{
|
||||
$trans = new Transaction($this, $request);
|
||||
$isFuture = $request->getConfig()->get('future');
|
||||
$trans = new Transaction($this, $request, $isFuture);
|
||||
$fn = $this->fsm;
|
||||
|
||||
// Ensure a future response is returned if one was requested.
|
||||
if ($request->getConfig()->get('future')) {
|
||||
try {
|
||||
$fn($trans);
|
||||
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));
|
||||
} catch (RequestException $e) {
|
||||
// Wrap the exception in a promise if the user asked for a future.
|
||||
}
|
||||
// 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));
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$fn($trans);
|
||||
return $trans->response instanceof FutureInterface
|
||||
? $trans->response->wait()
|
||||
: $trans->response;
|
||||
} catch (\Exception $e) {
|
||||
throw RequestException::wrapException($trans->request, $e);
|
||||
}
|
||||
throw RequestException::wrapException($trans->request, $e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,9 +281,10 @@ class Client implements ClientInterface
|
|||
/**
|
||||
* Expand a URI template and inherit from the base URL if it's relative
|
||||
*
|
||||
* @param string|array $url URL or URI template to expand
|
||||
*
|
||||
* @param string|array $url URL or an array of the URI template to expand
|
||||
* followed by a hash of template varnames.
|
||||
* @return string
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
private function buildUrl($url)
|
||||
{
|
||||
|
@ -305,6 +295,11 @@ class Client implements ClientInterface
|
|||
: (string) $this->baseUrl->combine($url);
|
||||
}
|
||||
|
||||
if (!isset($url[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]);
|
||||
|
@ -320,7 +315,12 @@ class Client implements ClientInterface
|
|||
{
|
||||
if (!isset($config['base_url'])) {
|
||||
$this->baseUrl = new Url('', '');
|
||||
} elseif (is_array($config['base_url'])) {
|
||||
} elseif (!is_array($config['base_url'])) {
|
||||
$this->baseUrl = Url::fromString($config['base_url']);
|
||||
} elseif (count($config['base_url']) < 2) {
|
||||
throw new \InvalidArgumentException('You must provide a hash of '
|
||||
. 'varname options in the second element of a base_url array.');
|
||||
} else {
|
||||
$this->baseUrl = Url::fromString(
|
||||
Utils::uriTemplate(
|
||||
$config['base_url'][0],
|
||||
|
@ -328,8 +328,6 @@ class Client implements ClientInterface
|
|||
)
|
||||
);
|
||||
$config['base_url'] = (string) $this->baseUrl;
|
||||
} else {
|
||||
$this->baseUrl = Url::fromString($config['base_url']);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -356,27 +354,42 @@ class Client implements ClientInterface
|
|||
}
|
||||
|
||||
/**
|
||||
* Merges default options into the array passed by reference and returns
|
||||
* an array of headers that need to be merged in after the request is
|
||||
* created.
|
||||
* Merges default options into the array passed by reference.
|
||||
*
|
||||
* @param array $options Options to modify by reference
|
||||
*
|
||||
* @return array|null
|
||||
* @return array
|
||||
*/
|
||||
private function mergeDefaults(&$options)
|
||||
private function mergeDefaults($options)
|
||||
{
|
||||
// Merging optimization for when no headers are present
|
||||
if (!isset($options['headers']) || !isset($this->defaults['headers'])) {
|
||||
$options = array_replace_recursive($this->defaults, $options);
|
||||
return null;
|
||||
$defaults = $this->defaults;
|
||||
|
||||
// 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.
|
||||
$lkeys = [];
|
||||
foreach (array_keys($options['headers']) as $k) {
|
||||
$lkeys[strtolower($k)] = true;
|
||||
}
|
||||
// Merge in lowercase default keys when not present in above set.
|
||||
foreach ($defaults['headers'] as $key => $value) {
|
||||
if (!isset($lkeys[strtolower($key)])) {
|
||||
$options['headers'][$key] = $value;
|
||||
}
|
||||
}
|
||||
// No longer need to merge in headers.
|
||||
unset($defaults['headers']);
|
||||
}
|
||||
|
||||
$defaults = $this->defaults;
|
||||
unset($defaults['headers']);
|
||||
$options = array_replace_recursive($defaults, $options);
|
||||
$result = array_replace_recursive($defaults, $options);
|
||||
foreach ($options as $k => $v) {
|
||||
if ($v === null) {
|
||||
unset($result[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->defaults['headers'];
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -385,6 +398,6 @@ class Client implements ClientInterface
|
|||
*/
|
||||
public function sendAll($requests, array $options = [])
|
||||
{
|
||||
(new Pool($this, $requests, $options))->wait();
|
||||
Pool::send($this, $requests, $options);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use GuzzleHttp\Message\ResponseInterface;
|
|||
*/
|
||||
interface ClientInterface extends HasEmitterInterface
|
||||
{
|
||||
const VERSION = '5.0.3';
|
||||
const VERSION = '5.2.0';
|
||||
|
||||
/**
|
||||
* Create and return a new {@see RequestInterface} object.
|
||||
|
|
|
@ -41,6 +41,16 @@ abstract class AbstractRequestEvent extends AbstractEvent
|
|||
return $this->transaction->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of transaction retries.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getRetryCount()
|
||||
{
|
||||
return $this->transaction->retries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Transaction
|
||||
*/
|
||||
|
|
|
@ -27,9 +27,9 @@ class AbstractRetryableEvent extends AbstractTransferEvent
|
|||
*/
|
||||
public function retry($afterDelay = 0)
|
||||
{
|
||||
$this->transaction->response = null;
|
||||
$this->transaction->exception = null;
|
||||
$this->transaction->state = 'before';
|
||||
// Setting the transition state to 'retry' will cause the next state
|
||||
// transition of the transaction to retry the request.
|
||||
$this->transaction->state = 'retry';
|
||||
|
||||
if ($afterDelay) {
|
||||
$this->transaction->request->getConfig()->set('delay', $afterDelay);
|
||||
|
|
|
@ -20,11 +20,13 @@ abstract class AbstractTransferEvent extends AbstractRequestEvent
|
|||
*/
|
||||
public function getTransferInfo($name = null)
|
||||
{
|
||||
return !$name
|
||||
? $this->transaction->transferInfo
|
||||
: (isset($this->transaction->transferInfo[$name])
|
||||
? $this->transaction->transferInfo[$name]
|
||||
: null);
|
||||
if (!$name) {
|
||||
return $this->transaction->transferInfo;
|
||||
}
|
||||
|
||||
return isset($this->transaction->transferInfo[$name])
|
||||
? $this->transaction->transferInfo[$name]
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
namespace GuzzleHttp\Message;
|
||||
|
||||
/**
|
||||
* Applies headers to a request.
|
||||
*
|
||||
* This interface can be used with Guzzle streams to apply body specific
|
||||
* headers to a request during the PREPARE_REQUEST priority of the before event
|
||||
*
|
||||
* NOTE: a body that implements this interface will prevent a default
|
||||
* content-type from being added to a request during the before event. If you
|
||||
* want a default content-type to be added, then it will need to be done
|
||||
* manually (e.g., using {@see GuzzleHttp\Mimetypes}).
|
||||
*/
|
||||
interface AppliesHeadersInterface
|
||||
{
|
||||
/**
|
||||
* Apply headers to a request appropriate for the current state of the
|
||||
* object.
|
||||
*
|
||||
* @param RequestInterface $request Request
|
||||
*/
|
||||
public function applyRequestHeaders(RequestInterface $request);
|
||||
}
|
|
@ -40,9 +40,10 @@ class MessageFactory implements MessageFactoryInterface
|
|||
|
||||
/** @var array Default allow_redirects request option settings */
|
||||
private static $defaultRedirect = [
|
||||
'max' => 5,
|
||||
'strict' => false,
|
||||
'referer' => false
|
||||
'max' => 5,
|
||||
'strict' => false,
|
||||
'referer' => false,
|
||||
'protocols' => ['http', 'https']
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -198,9 +199,8 @@ class MessageFactory implements MessageFactoryInterface
|
|||
|
||||
if ($value === true) {
|
||||
$value = self::$defaultRedirect;
|
||||
} elseif (!isset($value['max'])) {
|
||||
throw new Iae('allow_redirects must be true, false, or an '
|
||||
. 'array that contains the \'max\' key');
|
||||
} 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;
|
||||
|
@ -227,12 +227,8 @@ class MessageFactory implements MessageFactoryInterface
|
|||
if (!is_array($value)) {
|
||||
throw new Iae('header value must be an array');
|
||||
}
|
||||
|
||||
// Do not overwrite existing headers
|
||||
foreach ($value as $k => $v) {
|
||||
if (!$request->hasHeader($k)) {
|
||||
$request->setHeader($k, $v);
|
||||
}
|
||||
$request->setHeader($k, $v);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Event\BeforeEvent;
|
||||
use GuzzleHttp\Event\RequestEvents;
|
||||
use GuzzleHttp\Message\RequestInterface;
|
||||
use GuzzleHttp\Message\ResponseInterface;
|
||||
|
@ -9,7 +10,9 @@ use GuzzleHttp\Ring\Future\FutureInterface;
|
|||
use GuzzleHttp\Event\ListenerAttacherTrait;
|
||||
use GuzzleHttp\Event\EndEvent;
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\FulfilledPromise;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\RejectedPromise;
|
||||
|
||||
/**
|
||||
* Sends and iterator of requests concurrently using a capped pool size.
|
||||
|
@ -50,9 +53,9 @@ class Pool implements FutureInterface
|
|||
private $isRealized = false;
|
||||
|
||||
/**
|
||||
* The option values for 'before', 'after', and 'error' can be a callable,
|
||||
* an associative array containing event data, or an array of event data
|
||||
* arrays. Event data arrays contain the following keys:
|
||||
* The option values for 'before', 'complete', 'error' and 'end' can be a
|
||||
* callable, an associative array containing event data, or an array of
|
||||
* event data arrays. Event data arrays contain the following keys:
|
||||
*
|
||||
* - fn: callable to invoke that receives the event
|
||||
* - priority: Optional event priority (defaults to 0)
|
||||
|
@ -61,10 +64,14 @@ class Pool implements FutureInterface
|
|||
* @param ClientInterface $client Client used to send the requests.
|
||||
* @param array|\Iterator $requests Requests to send in parallel
|
||||
* @param array $options Associative array of options
|
||||
* - pool_size: (int) Maximum number of requests to send concurrently
|
||||
* - pool_size: (callable|int) Maximum number of requests to send
|
||||
* concurrently, or a callback that receives
|
||||
* the current queue size and returns the
|
||||
* number of new requests to send
|
||||
* - before: (callable|array) Receives a BeforeEvent
|
||||
* - after: (callable|array) Receives a CompleteEvent
|
||||
* - complete: (callable|array) Receives a CompleteEvent
|
||||
* - error: (callable|array) Receives a ErrorEvent
|
||||
* - end: (callable|array) Receives an EndEvent
|
||||
*/
|
||||
public function __construct(
|
||||
ClientInterface $client,
|
||||
|
@ -140,7 +147,28 @@ class Pool implements FutureInterface
|
|||
$requests,
|
||||
array $options = []
|
||||
) {
|
||||
(new self($client, $requests, $options))->wait();
|
||||
$pool = new self($client, $requests, $options);
|
||||
$pool->wait();
|
||||
}
|
||||
|
||||
private function getPoolSize()
|
||||
{
|
||||
return is_callable($this->poolSize)
|
||||
? call_user_func($this->poolSize, count($this->waitQueue))
|
||||
: $this->poolSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add as many requests as possible up to the current pool limit.
|
||||
*/
|
||||
private function addNextRequests()
|
||||
{
|
||||
$limit = max($this->getPoolSize() - count($this->waitQueue), 0);
|
||||
while ($limit--) {
|
||||
if (!$this->addNextRequest()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function wait()
|
||||
|
@ -150,11 +178,7 @@ class Pool implements FutureInterface
|
|||
}
|
||||
|
||||
// Seed the pool with N number of requests.
|
||||
for ($i = 0; $i < $this->poolSize; $i++) {
|
||||
if (!$this->addNextRequest()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->addNextRequests();
|
||||
|
||||
// Stop if the pool was cancelled while transferring requests.
|
||||
if ($this->isRealized) {
|
||||
|
@ -168,6 +192,7 @@ class Pool implements FutureInterface
|
|||
} catch (\Exception $e) {
|
||||
// Eat exceptions because they should be handled asynchronously
|
||||
}
|
||||
$this->addNextRequests();
|
||||
}
|
||||
|
||||
// Clean up no longer needed state.
|
||||
|
@ -241,6 +266,8 @@ class Pool implements FutureInterface
|
|||
*/
|
||||
private function addNextRequest()
|
||||
{
|
||||
add_next:
|
||||
|
||||
if ($this->isRealized || !$this->iter || !$this->iter->valid()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -258,23 +285,49 @@ class Pool implements FutureInterface
|
|||
|
||||
// Be sure to use "lazy" futures, meaning they do not send right away.
|
||||
$request->getConfig()->set('future', 'lazy');
|
||||
$this->attachListeners($request, $this->eventListeners);
|
||||
$response = $this->client->send($request);
|
||||
$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);
|
||||
}
|
||||
goto add_next;
|
||||
}
|
||||
|
||||
// Use this function for both resolution and rejection.
|
||||
$fn = function ($value) use ($request, $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);
|
||||
$this->addNextRequest();
|
||||
$thenFn = function ($value) use ($request, $hash) {
|
||||
$this->finishResponse($request, $value, $hash);
|
||||
if (!$request->getConfig()->get('_pool_retries')) {
|
||||
$this->addNextRequests();
|
||||
}
|
||||
};
|
||||
|
||||
$response->then($fn, $fn);
|
||||
$promise->then($thenFn, $thenFn);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,15 @@
|
|||
<?php
|
||||
namespace GuzzleHttp\Post;
|
||||
|
||||
use GuzzleHttp\Message\RequestInterface;
|
||||
use GuzzleHttp\Message\AppliesHeadersInterface;
|
||||
use GuzzleHttp\Stream\StreamInterface;
|
||||
|
||||
/**
|
||||
* Represents a POST body that is sent as either a multipart/form-data stream
|
||||
* or application/x-www-urlencoded stream.
|
||||
*/
|
||||
interface PostBodyInterface extends StreamInterface, \Countable
|
||||
interface PostBodyInterface extends StreamInterface, \Countable, AppliesHeadersInterface
|
||||
{
|
||||
/**
|
||||
* Apply headers to the request appropriate for the current state of the object
|
||||
*
|
||||
* @param RequestInterface $request Request
|
||||
*/
|
||||
public function applyRequestHeaders(RequestInterface $request);
|
||||
|
||||
/**
|
||||
* Set a specific field
|
||||
*
|
||||
|
|
|
@ -20,30 +20,6 @@ class RequestFsm
|
|||
private $mf;
|
||||
private $maxTransitions;
|
||||
|
||||
private $states = [
|
||||
// When a mock intercepts the emitted "before" event, then we
|
||||
// transition to the "complete" intercept state.
|
||||
'before' => [
|
||||
'success' => 'send',
|
||||
'intercept' => 'complete',
|
||||
'error' => 'error'
|
||||
],
|
||||
// The complete and error events are handled using the "then" of
|
||||
// the RingPHP request, so we exit the FSM.
|
||||
'send' => ['error' => 'error'],
|
||||
'complete' => [
|
||||
'success' => 'end',
|
||||
'intercept' => 'before',
|
||||
'error' => 'error'
|
||||
],
|
||||
'error' => [
|
||||
'success' => 'complete',
|
||||
'intercept' => 'before',
|
||||
'error' => 'end'
|
||||
],
|
||||
'end' => []
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
callable $handler,
|
||||
MessageFactoryInterface $messageFactory,
|
||||
|
@ -59,156 +35,119 @@ class RequestFsm
|
|||
* optionally supplied $finalState is entered.
|
||||
*
|
||||
* @param Transaction $trans Transaction being transitioned.
|
||||
* @param string $finalState The state to stop on. If unspecified,
|
||||
* runs until a terminal state is found.
|
||||
*
|
||||
* @throws \Exception if a terminal state throws an exception.
|
||||
*/
|
||||
public function __invoke(Transaction $trans, $finalState = null)
|
||||
public function __invoke(Transaction $trans)
|
||||
{
|
||||
$trans->_transitionCount = 1;
|
||||
$trans->_transitionCount = 0;
|
||||
|
||||
if (!$trans->state) {
|
||||
$trans->state = 'before';
|
||||
}
|
||||
|
||||
while ($trans->state !== $finalState) {
|
||||
transition:
|
||||
|
||||
if (!isset($this->states[$trans->state])) {
|
||||
throw new StateException("Invalid state: {$trans->state}");
|
||||
} elseif (++$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.');
|
||||
}
|
||||
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.");
|
||||
}
|
||||
|
||||
$state = $this->states[$trans->state];
|
||||
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 {
|
||||
/** @var callable $fn */
|
||||
$fn = [$this, $trans->state];
|
||||
if ($fn($trans)) {
|
||||
// Handles transitioning to the "intercept" state.
|
||||
if (isset($state['intercept'])) {
|
||||
$trans->state = $state['intercept'];
|
||||
continue;
|
||||
}
|
||||
throw new StateException('Invalid intercept state '
|
||||
. 'transition from ' . $trans->state);
|
||||
$trans->request->getEmitter()->emit('before', new BeforeEvent($trans));
|
||||
$trans->state = 'send';
|
||||
if ((bool) $trans->response) {
|
||||
$trans->state = 'complete';
|
||||
}
|
||||
|
||||
if (isset($state['success'])) {
|
||||
// Transition to the success state
|
||||
$trans->state = $state['success'];
|
||||
} else {
|
||||
// Break: this is a terminal state with no transition.
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (StateException $e) {
|
||||
// State exceptions are thrown no matter what.
|
||||
throw $e;
|
||||
} catch (\Exception $e) {
|
||||
$trans->state = 'error';
|
||||
$trans->exception = $e;
|
||||
// Terminal error states throw the exception.
|
||||
if (!isset($state['error'])) {
|
||||
throw $e;
|
||||
}
|
||||
goto transition;
|
||||
}
|
||||
|
||||
complete: {
|
||||
try {
|
||||
if ($trans->response instanceof FutureInterface) {
|
||||
// Futures will have their own end events emitted when
|
||||
// dereferenced.
|
||||
return;
|
||||
}
|
||||
// Transition to the error state.
|
||||
$trans->state = $state['error'];
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
private function before(Transaction $trans)
|
||||
{
|
||||
$trans->request->getEmitter()->emit('before', new BeforeEvent($trans));
|
||||
|
||||
// When a response is set during the before event (i.e., a mock), then
|
||||
// we don't need to send anything. Skip ahead to the complete event
|
||||
// by returning to to go to the intercept state.
|
||||
return (bool) $trans->response;
|
||||
}
|
||||
|
||||
private function send(Transaction $trans)
|
||||
{
|
||||
$fn = $this->handler;
|
||||
$trans->response = FutureResponse::proxy(
|
||||
$fn(RingBridge::prepareRingRequest($trans)),
|
||||
function ($value) use ($trans) {
|
||||
RingBridge::completeRingResponse($trans, $value, $this->mf, $this);
|
||||
return $trans->response;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits the error event and ensures that the exception is set and is an
|
||||
* instance of RequestException. If the error event is not intercepted,
|
||||
* then the exception is thrown and we transition to the "end" event. This
|
||||
* event also allows requests to be retried, and when retried, transitions
|
||||
* to the "before" event. Otherwise, when no retries, and the exception is
|
||||
* intercepted, transition to the "complete" event.
|
||||
*/
|
||||
private function error(Transaction $trans)
|
||||
{
|
||||
// Convert non-request exception to a wrapped exception
|
||||
if (!($trans->exception instanceof RequestException)) {
|
||||
$trans->exception = RequestException::wrapException(
|
||||
$trans->request, $trans->exception
|
||||
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;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Dispatch an event and allow interception
|
||||
$event = new ErrorEvent($trans);
|
||||
$trans->request->getEmitter()->emit('error', $event);
|
||||
|
||||
if ($trans->exception) {
|
||||
throw $trans->exception;
|
||||
}
|
||||
|
||||
$trans->exception = null;
|
||||
|
||||
// Return true to transition to the 'before' state. False otherwise.
|
||||
return $trans->state === 'before';
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a complete event, and if a request is marked for a retry during
|
||||
* the complete event, then the "before" state is transitioned to.
|
||||
*/
|
||||
private function complete(Transaction $trans)
|
||||
{
|
||||
// Futures will have their own end events emitted when dereferenced.
|
||||
if ($trans->response instanceof FutureInterface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$trans->response->setEffectiveUrl($trans->request->getUrl());
|
||||
$trans->request->getEmitter()->emit('complete', new CompleteEvent($trans));
|
||||
|
||||
// Return true to transition to the 'before' state. False otherwise.
|
||||
return $trans->state === 'before';
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits the "end" event and throws an exception if one is present.
|
||||
*/
|
||||
private function end(Transaction $trans)
|
||||
{
|
||||
// Futures will have their own end events emitted when dereferenced,
|
||||
// but still emit, even for futures, when an exception is present.
|
||||
if (!$trans->exception && $trans->response instanceof FutureInterface) {
|
||||
return;
|
||||
}
|
||||
|
||||
$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) {
|
||||
throw $trans->exception;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,19 +72,17 @@ class RingBridge
|
|||
|
||||
/**
|
||||
* Handles the process of processing a response received from a ring
|
||||
* handler. The created response is added to the transaction, and any
|
||||
* necessary events are emitted based on the ring response.
|
||||
* 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.
|
||||
* @param callable $fsm Request FSM function.
|
||||
*/
|
||||
public static function completeRingResponse(
|
||||
Transaction $trans,
|
||||
array $response,
|
||||
MessageFactoryInterface $messageFactory,
|
||||
callable $fsm
|
||||
MessageFactoryInterface $messageFactory
|
||||
) {
|
||||
$trans->state = 'complete';
|
||||
$trans->transferInfo = isset($response['transfer_stats'])
|
||||
|
@ -116,9 +114,6 @@ class RingBridge
|
|||
$trans->state = 'error';
|
||||
$trans->exception = $response['error'];
|
||||
}
|
||||
|
||||
// Complete the lifecycle of the request.
|
||||
$fsm($trans);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -163,7 +158,7 @@ class RingBridge
|
|||
Sending the request did not return a response, exception, or populate the
|
||||
transaction with a response. This is most likely due to an incorrectly
|
||||
implemented RingPHP handler. If you are simply trying to mock responses,
|
||||
then it is recommneded to use the GuzzleHttp\Ring\Client\MockHandler.
|
||||
then it is recommended to use the GuzzleHttp\Ring\Client\MockHandler.
|
||||
EOT;
|
||||
return new RequestException($message, $request);
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ namespace GuzzleHttp\Subscriber;
|
|||
use GuzzleHttp\Event\BeforeEvent;
|
||||
use GuzzleHttp\Event\RequestEvents;
|
||||
use GuzzleHttp\Event\SubscriberInterface;
|
||||
use GuzzleHttp\Message\AppliesHeadersInterface;
|
||||
use GuzzleHttp\Message\RequestInterface;
|
||||
use GuzzleHttp\Mimetypes;
|
||||
use GuzzleHttp\Post\PostBodyInterface;
|
||||
use GuzzleHttp\Stream\StreamInterface;
|
||||
|
||||
/**
|
||||
|
@ -40,8 +40,8 @@ class Prepare implements SubscriberInterface
|
|||
|
||||
$this->addContentLength($request, $body);
|
||||
|
||||
if ($body instanceof PostBodyInterface) {
|
||||
// Synchronize the POST body with the request's headers
|
||||
if ($body instanceof AppliesHeadersInterface) {
|
||||
// Synchronize the body with the request headers
|
||||
$body->applyRequestHeaders($request);
|
||||
} elseif (!$request->hasHeader('Content-Type')) {
|
||||
$this->addContentType($request, $body);
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace GuzzleHttp\Subscriber;
|
|||
use GuzzleHttp\Event\CompleteEvent;
|
||||
use GuzzleHttp\Event\RequestEvents;
|
||||
use GuzzleHttp\Event\SubscriberInterface;
|
||||
use GuzzleHttp\Exception\BadResponseException;
|
||||
use GuzzleHttp\Exception\CouldNotRewindStreamException;
|
||||
use GuzzleHttp\Exception\TooManyRedirectsException;
|
||||
use GuzzleHttp\Message\RequestInterface;
|
||||
|
@ -25,6 +26,9 @@ use GuzzleHttp\Url;
|
|||
* POST request with a GET request).
|
||||
* - referer: Set to true to automatically add the "Referer" header when a
|
||||
* redirect request is sent.
|
||||
* - protocols: Array of allowed protocols. Defaults to 'http' and 'https'.
|
||||
* When a redirect attempts to utilize a protocol that is not white listed,
|
||||
* an exception is thrown.
|
||||
*/
|
||||
class Redirect implements SubscriberInterface
|
||||
{
|
||||
|
@ -99,6 +103,7 @@ class Redirect implements SubscriberInterface
|
|||
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
|
||||
|
@ -112,7 +117,7 @@ class Redirect implements SubscriberInterface
|
|||
}
|
||||
|
||||
$previousUrl = $request->getUrl();
|
||||
$this->setRedirectUrl($request, $response);
|
||||
$this->setRedirectUrl($request, $response, $protocols);
|
||||
$this->rewindEntityBody($request);
|
||||
|
||||
// Add the Referer header if it is told to do so and only
|
||||
|
@ -134,10 +139,12 @@ class Redirect implements SubscriberInterface
|
|||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @param array $protocols
|
||||
*/
|
||||
private function setRedirectUrl(
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response
|
||||
ResponseInterface $response,
|
||||
array $protocols
|
||||
) {
|
||||
$location = $response->getHeader('Location');
|
||||
$location = Url::fromString($location);
|
||||
|
@ -151,6 +158,19 @@ class Redirect implements SubscriberInterface
|
|||
$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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,30 +56,48 @@ class Transaction
|
|||
public $transferInfo = [];
|
||||
|
||||
/**
|
||||
* The transaction's state.
|
||||
* The number of transaction retries.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $retries = 0;
|
||||
|
||||
/**
|
||||
* The transaction's current state.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $state;
|
||||
|
||||
/**
|
||||
* The number of state transitions that this transactions has been through.
|
||||
* Whether or not this is a future transaction. This value should not be
|
||||
* changed after the future is constructed.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $future;
|
||||
|
||||
/**
|
||||
* The number of state transitions that this transaction has been through.
|
||||
*
|
||||
* @var int
|
||||
* @internal This is for internal use only. If you modify this, then you
|
||||
* are asking for trouble.
|
||||
*/
|
||||
public $_transitionCount;
|
||||
public $_transitionCount = 0;
|
||||
|
||||
/**
|
||||
* @param ClientInterface $client Client that is used to send the requests
|
||||
* @param RequestInterface $request Request to send
|
||||
* @param bool $future Whether or not this is a future request.
|
||||
*/
|
||||
public function __construct(
|
||||
ClientInterface $client,
|
||||
RequestInterface $request
|
||||
RequestInterface $request,
|
||||
$future = false
|
||||
) {
|
||||
$this->client = $client;
|
||||
$this->request = $request;
|
||||
$this->_future = $future;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -135,7 +135,7 @@ class Url
|
|||
$password = null,
|
||||
$port = null,
|
||||
$path = null,
|
||||
Query $query = null,
|
||||
$query = null,
|
||||
$fragment = null
|
||||
) {
|
||||
$this->scheme = $scheme;
|
||||
|
|
|
@ -7,10 +7,12 @@ use GuzzleHttp\Event\ErrorEvent;
|
|||
use GuzzleHttp\Message\MessageFactory;
|
||||
use GuzzleHttp\Message\Response;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Query;
|
||||
use GuzzleHttp\Ring\Client\MockHandler;
|
||||
use GuzzleHttp\Ring\Future\FutureArray;
|
||||
use GuzzleHttp\Subscriber\History;
|
||||
use GuzzleHttp\Subscriber\Mock;
|
||||
use GuzzleHttp\Url;
|
||||
use React\Promise\Deferred;
|
||||
|
||||
/**
|
||||
|
@ -70,6 +72,14 @@ class ClientTest extends \PHPUnit_Framework_TestCase
|
|||
$this->assertEquals('http://foo.com/baz/', $client->getBaseUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \InvalidArgumentException
|
||||
*/
|
||||
public function testValidatesUriTemplateValue()
|
||||
{
|
||||
new Client(['base_url' => ['http://foo.com/']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Exception
|
||||
* @expectedExceptionMessage Foo
|
||||
|
@ -243,6 +253,15 @@ class ClientTest extends \PHPUnit_Framework_TestCase
|
|||
$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' => [
|
||||
|
@ -272,6 +291,15 @@ class ClientTest extends \PHPUnit_Framework_TestCase
|
|||
);
|
||||
}
|
||||
|
||||
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']);
|
||||
|
@ -582,4 +610,15 @@ class ClientTest extends \PHPUnit_Framework_TestCase
|
|||
$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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ class AbstractRetryableEventTest extends \PHPUnit_Framework_TestCase
|
|||
->getMockForAbstractClass();
|
||||
$e->retry();
|
||||
$this->assertTrue($e->isPropagationStopped());
|
||||
$this->assertEquals('before', $t->state);
|
||||
$this->assertEquals('retry', $t->state);
|
||||
}
|
||||
|
||||
public function testCanRetryAfterDelay()
|
||||
|
@ -31,7 +31,7 @@ class AbstractRetryableEventTest extends \PHPUnit_Framework_TestCase
|
|||
->getMockForAbstractClass();
|
||||
$e->retry(10);
|
||||
$this->assertTrue($e->isPropagationStopped());
|
||||
$this->assertEquals('before', $t->state);
|
||||
$this->assertEquals('retry', $t->state);
|
||||
$this->assertEquals(10, $t->request->getConfig()->get('delay'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,4 +46,14 @@ class AbstractTransferEventTest extends \PHPUnit_Framework_TestCase
|
|||
$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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,10 @@ namespace GuzzleHttp\Tests;
|
|||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Event\AbstractTransferEvent;
|
||||
use GuzzleHttp\Event\CompleteEvent;
|
||||
use GuzzleHttp\Event\EndEvent;
|
||||
use GuzzleHttp\Event\ErrorEvent;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Message\Response;
|
||||
use GuzzleHttp\Pool;
|
||||
|
||||
|
@ -70,4 +73,51 @@ class IntegrationTest extends \PHPUnit_Framework_TestCase
|
|||
$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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -144,7 +144,7 @@ class MessageFactoryTest extends \PHPUnit_Framework_TestCase
|
|||
*/
|
||||
public function testValidatesRedirects()
|
||||
{
|
||||
(new MessageFactory())->createRequest('GET', '/', ['allow_redirects' => []]);
|
||||
(new MessageFactory())->createRequest('GET', '/', ['allow_redirects' => 'foo']);
|
||||
}
|
||||
|
||||
public function testCanEnableStrictRedirectsAndSpecifyMax()
|
||||
|
|
|
@ -154,6 +154,64 @@ class PoolTest extends \PHPUnit_Framework_TestCase
|
|||
$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
|
||||
|
@ -228,4 +286,34 @@ class PoolTest extends \PHPUnit_Framework_TestCase
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,7 +130,6 @@ class PostBodyTest extends \PHPUnit_Framework_TestCase
|
|||
$b->seek(0);
|
||||
$this->assertEquals('foo=bar&baz=123', $b->read(1000));
|
||||
$this->assertEquals(15, $b->tell());
|
||||
$this->assertTrue($b->eof());
|
||||
}
|
||||
|
||||
public function testCanSpecifyQueryAggregator()
|
||||
|
|
|
@ -5,6 +5,7 @@ use GuzzleHttp\Exception\RequestException;
|
|||
use GuzzleHttp\Message\MessageFactory;
|
||||
use GuzzleHttp\Message\Response;
|
||||
use GuzzleHttp\RequestFsm;
|
||||
use GuzzleHttp\Ring\Future\CompletedFutureArray;
|
||||
use GuzzleHttp\Subscriber\Mock;
|
||||
use GuzzleHttp\Transaction;
|
||||
use GuzzleHttp\Client;
|
||||
|
@ -29,19 +30,23 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testEmitsBeforeEventInTransition()
|
||||
{
|
||||
$fsm = new RequestFsm(function () {}, $this->mf);
|
||||
$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, 'send');
|
||||
$fsm($t);
|
||||
$this->assertTrue($c);
|
||||
}
|
||||
|
||||
public function testEmitsCompleteEventInTransition()
|
||||
{
|
||||
$fsm = new RequestFsm(function () {}, $this->mf);
|
||||
$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';
|
||||
|
@ -49,13 +54,15 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
|||
$t->request->getEmitter()->on('complete', function (CompleteEvent $e) use (&$c) {
|
||||
$c = true;
|
||||
});
|
||||
$fsm($t, 'end');
|
||||
$fsm($t);
|
||||
$this->assertTrue($c);
|
||||
}
|
||||
|
||||
public function testDoesNotEmitCompleteForFuture()
|
||||
{
|
||||
$fsm = new RequestFsm(function () {}, $this->mf);
|
||||
$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());
|
||||
|
@ -64,21 +71,6 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
|||
$t->request->getEmitter()->on('complete', function (CompleteEvent $e) use (&$c) {
|
||||
$c = true;
|
||||
});
|
||||
$fsm($t, 'end');
|
||||
$this->assertFalse($c);
|
||||
}
|
||||
|
||||
public function testDoesNotEmitEndForFuture()
|
||||
{
|
||||
$fsm = new RequestFsm(function () {}, $this->mf);
|
||||
$t = new Transaction(new Client(), new Request('GET', 'http://foo.com'));
|
||||
$deferred = new Deferred();
|
||||
$t->response = new FutureResponse($deferred->promise());
|
||||
$t->state = 'end';
|
||||
$c = false;
|
||||
$t->request->getEmitter()->on('end', function (EndEvent $e) use (&$c) {
|
||||
$c = true;
|
||||
});
|
||||
$fsm($t);
|
||||
$this->assertFalse($c);
|
||||
}
|
||||
|
@ -95,7 +87,9 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testTransitionsThroughErrorsInBefore()
|
||||
{
|
||||
$fsm = new RequestFsm(function () {}, $this->mf);
|
||||
$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);
|
||||
|
@ -105,7 +99,7 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
|||
throw new \Exception('foo');
|
||||
});
|
||||
try {
|
||||
$fsm($t, 'send');
|
||||
$fsm($t);
|
||||
$this->fail('did not throw');
|
||||
} catch (RequestException $e) {
|
||||
$this->assertContains('foo', $t->exception->getMessage());
|
||||
|
@ -133,7 +127,9 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testTransitionsThroughErrorInterception()
|
||||
{
|
||||
$fsm = new RequestFsm(function () {}, $this->mf);
|
||||
$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);
|
||||
|
@ -142,9 +138,6 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
|||
$t->request->getEmitter()->on('error', function (ErrorEvent $e) {
|
||||
$e->intercept(new Response(200));
|
||||
});
|
||||
$fsm($t, 'send');
|
||||
$t->response = new Response(404);
|
||||
$t->state = 'complete';
|
||||
$fsm($t);
|
||||
$this->assertEquals(200, $t->response->getStatusCode());
|
||||
$this->assertNull($t->exception);
|
||||
|
@ -175,10 +168,12 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
|||
{
|
||||
$client = new Client([
|
||||
'fsm' => $fsm = new RequestFsm(
|
||||
function () {},
|
||||
new MessageFactory(),
|
||||
3
|
||||
)
|
||||
function () {
|
||||
return new CompletedFutureArray(['status' => 200]);
|
||||
},
|
||||
new MessageFactory(),
|
||||
3
|
||||
)
|
||||
]);
|
||||
$request = $client->createRequest('GET', 'http://foo.com:123');
|
||||
$request->getEmitter()->on('before', function () {
|
||||
|
|
|
@ -107,24 +107,6 @@ class RingBridgeTest extends \PHPUnit_Framework_TestCase
|
|||
$this->assertEquals('foo', (string) $response->getBody());
|
||||
}
|
||||
|
||||
public function testEmitsCompleteEventOnSuccess()
|
||||
{
|
||||
$c = false;
|
||||
$trans = new Transaction(new Client(), new Request('GET', 'http://f.co'));
|
||||
$trans->request->getEmitter()->on('complete', function () use (&$c) {
|
||||
$c = true;
|
||||
});
|
||||
$f = new MessageFactory();
|
||||
$res = ['status' => 200, 'headers' => []];
|
||||
$fsm = new RequestFsm(function () {}, new MessageFactory());
|
||||
RingBridge::completeRingResponse($trans, $res, $f, $fsm);
|
||||
$this->assertInstanceOf(
|
||||
'GuzzleHttp\Message\ResponseInterface',
|
||||
$trans->response
|
||||
);
|
||||
$this->assertTrue($c);
|
||||
}
|
||||
|
||||
public function testEmitsErrorEventOnError()
|
||||
{
|
||||
$client = new Client(['base_url' => 'http://127.0.0.1:123']);
|
||||
|
|
|
@ -266,4 +266,23 @@ class RedirectTest extends \PHPUnit_Framework_TestCase
|
|||
$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']
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ php:
|
|||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7.0
|
||||
- hhvm
|
||||
|
||||
before_script:
|
||||
|
|
|
@ -1,5 +1,27 @@
|
|||
# CHANGELOG
|
||||
|
||||
## 1.0.7 - 2015-03-29
|
||||
|
||||
* PHP 7 fixes.
|
||||
|
||||
## 1.0.6 - 2015-02-26
|
||||
|
||||
* Bug fix: futures now extend from React's PromiseInterface to ensure that they
|
||||
are properly forwarded down the promise chain.
|
||||
* The multi handle of the CurlMultiHandler is now created lazily.
|
||||
|
||||
## 1.0.5 - 2014-12-10
|
||||
|
||||
* Adding more error information to PHP stream wrapper exceptions.
|
||||
* Added digest auth integration test support to test server.
|
||||
|
||||
## 1.0.4 - 2014-12-01
|
||||
|
||||
* Added support for older versions of cURL that do not have CURLOPT_TIMEOUT_MS.
|
||||
* Setting debug to `false` does not enable debug output.
|
||||
* Added a fix to the StreamHandler to return a `FutureArrayInterface` when an
|
||||
error occurs.
|
||||
|
||||
## 1.0.3 - 2014-11-03
|
||||
|
||||
* Setting the `header` stream option as a string to be compatible with GAE.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"name": "guzzlehttp/ringphp",
|
||||
"description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
|
|
|
@ -15,11 +15,11 @@ without tying your application to a specific implementation.
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
spec
|
||||
futures
|
||||
client_middleware
|
||||
client_handlers
|
||||
testing
|
||||
spec
|
||||
futures
|
||||
client_middleware
|
||||
client_handlers
|
||||
testing
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
|
|
|
@ -406,12 +406,20 @@ class CurlFactory
|
|||
|
||||
case 'timeout':
|
||||
|
||||
$options[CURLOPT_TIMEOUT_MS] = $value * 1000;
|
||||
if (defined('CURLOPT_TIMEOUT_MS')) {
|
||||
$options[CURLOPT_TIMEOUT_MS] = $value * 1000;
|
||||
} else {
|
||||
$options[CURLOPT_TIMEOUT] = $value;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'connect_timeout':
|
||||
|
||||
$options[CURLOPT_CONNECTTIMEOUT_MS] = $value * 1000;
|
||||
if (defined('CURLOPT_CONNECTTIMEOUT_MS')) {
|
||||
$options[CURLOPT_CONNECTTIMEOUT_MS] = $value * 1000;
|
||||
} else {
|
||||
$options[CURLOPT_CONNECTTIMEOUT] = $value;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'proxy':
|
||||
|
|
|
@ -71,6 +71,7 @@ class CurlHandler
|
|||
$response = ['transfer_stats' => curl_getinfo($h)];
|
||||
$response['curl']['error'] = curl_error($h);
|
||||
$response['curl']['errno'] = curl_errno($h);
|
||||
$response['transfer_stats'] = array_merge($response['transfer_stats'], $response['curl']);
|
||||
$this->releaseEasyHandle($h);
|
||||
|
||||
return new CompletedFutureArray(
|
||||
|
|
|
@ -13,13 +13,14 @@ use React\Promise\Deferred;
|
|||
* When using the CurlMultiHandler, custom curl options can be specified as an
|
||||
* associative array of curl option constants mapping to values in the
|
||||
* **curl** key of the "client" key of the request.
|
||||
*
|
||||
* @property resource $_mh Internal use only. Lazy loaded multi-handle.
|
||||
*/
|
||||
class CurlMultiHandler
|
||||
{
|
||||
/** @var callable */
|
||||
private $factory;
|
||||
private $selectTimeout;
|
||||
private $mh;
|
||||
private $active;
|
||||
private $handles = [];
|
||||
private $delays = [];
|
||||
|
@ -42,8 +43,9 @@ class CurlMultiHandler
|
|||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->mh = isset($options['mh'])
|
||||
? $options['mh'] : curl_multi_init();
|
||||
if (isset($options['mh'])) {
|
||||
$this->_mh = $options['mh'];
|
||||
}
|
||||
$this->factory = isset($options['handle_factory'])
|
||||
? $options['handle_factory'] : new CurlFactory();
|
||||
$this->selectTimeout = isset($options['select_timeout'])
|
||||
|
@ -52,6 +54,15 @@ class CurlMultiHandler
|
|||
? $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.
|
||||
|
@ -59,9 +70,9 @@ class CurlMultiHandler
|
|||
$this->execute();
|
||||
}
|
||||
|
||||
if ($this->mh) {
|
||||
curl_multi_close($this->mh);
|
||||
$this->mh = null;
|
||||
if (isset($this->_mh)) {
|
||||
curl_multi_close($this->_mh);
|
||||
unset($this->_mh);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,7 +117,7 @@ class CurlMultiHandler
|
|||
do {
|
||||
|
||||
if ($this->active &&
|
||||
curl_multi_select($this->mh, $this->selectTimeout) === -1
|
||||
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
|
||||
|
@ -119,7 +130,7 @@ class CurlMultiHandler
|
|||
}
|
||||
|
||||
do {
|
||||
$mrc = curl_multi_exec($this->mh, $this->active);
|
||||
$mrc = curl_multi_exec($this->_mh, $this->active);
|
||||
} while ($mrc === CURLM_CALL_MULTI_PERFORM);
|
||||
|
||||
$this->processMessages();
|
||||
|
@ -142,13 +153,13 @@ class CurlMultiHandler
|
|||
if (isset($entry['request']['client']['delay'])) {
|
||||
$this->delays[$id] = microtime(true) + ($entry['request']['client']['delay'] / 1000);
|
||||
} elseif (empty($entry['request']['future'])) {
|
||||
curl_multi_add_handle($this->mh, $entry['handle']);
|
||||
curl_multi_add_handle($this->_mh, $entry['handle']);
|
||||
} else {
|
||||
curl_multi_add_handle($this->mh, $entry['handle']);
|
||||
curl_multi_add_handle($this->_mh, $entry['handle']);
|
||||
// "lazy" futures are only sent once the pool has many requests.
|
||||
if ($entry['request']['future'] !== 'lazy') {
|
||||
do {
|
||||
$mrc = curl_multi_exec($this->mh, $this->active);
|
||||
$mrc = curl_multi_exec($this->_mh, $this->active);
|
||||
} while ($mrc === CURLM_CALL_MULTI_PERFORM);
|
||||
$this->processMessages();
|
||||
}
|
||||
|
@ -159,7 +170,7 @@ class CurlMultiHandler
|
|||
{
|
||||
if (isset($this->handles[$id])) {
|
||||
curl_multi_remove_handle(
|
||||
$this->mh,
|
||||
$this->_mh,
|
||||
$this->handles[$id]['handle']
|
||||
);
|
||||
curl_close($this->handles[$id]['handle']);
|
||||
|
@ -183,7 +194,7 @@ class CurlMultiHandler
|
|||
|
||||
$handle = $this->handles[$id]['handle'];
|
||||
unset($this->delays[$id], $this->handles[$id]);
|
||||
curl_multi_remove_handle($this->mh, $handle);
|
||||
curl_multi_remove_handle($this->_mh, $handle);
|
||||
curl_close($handle);
|
||||
|
||||
return true;
|
||||
|
@ -197,7 +208,7 @@ class CurlMultiHandler
|
|||
if ($currentTime >= $delay) {
|
||||
unset($this->delays[$id]);
|
||||
curl_multi_add_handle(
|
||||
$this->mh,
|
||||
$this->_mh,
|
||||
$this->handles[$id]['handle']
|
||||
);
|
||||
}
|
||||
|
@ -206,7 +217,7 @@ class CurlMultiHandler
|
|||
|
||||
private function processMessages()
|
||||
{
|
||||
while ($done = curl_multi_info_read($this->mh)) {
|
||||
while ($done = curl_multi_info_read($this->_mh)) {
|
||||
$id = (int) $done['handle'];
|
||||
|
||||
if (!isset($this->handles[$id])) {
|
||||
|
|
|
@ -16,6 +16,7 @@ use GuzzleHttp\Stream\Utils;
|
|||
class StreamHandler
|
||||
{
|
||||
private $options;
|
||||
private $lastHeaders;
|
||||
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
|
@ -30,15 +31,17 @@ class StreamHandler
|
|||
try {
|
||||
// Does not support the expect header.
|
||||
$request = Core::removeHeader($request, 'Expect');
|
||||
$stream = $this->createStream($url, $request, $headers);
|
||||
return $this->createResponse($request, $url, $headers, $stream);
|
||||
$stream = $this->createStream($url, $request);
|
||||
return $this->createResponse($request, $url, $stream);
|
||||
} catch (RingException $e) {
|
||||
return $this->createErrorResponse($url, $e);
|
||||
}
|
||||
}
|
||||
|
||||
private function createResponse(array $request, $url, array $hdrs, $stream)
|
||||
private function createResponse(array $request, $url, $stream)
|
||||
{
|
||||
$hdrs = $this->lastHeaders;
|
||||
$this->lastHeaders = null;
|
||||
$parts = explode(' ', array_shift($hdrs), 3);
|
||||
$response = [
|
||||
'status' => $parts[1],
|
||||
|
@ -131,13 +134,13 @@ class StreamHandler
|
|||
$e = new ConnectException($e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
return [
|
||||
return new CompletedFutureArray([
|
||||
'status' => null,
|
||||
'body' => null,
|
||||
'headers' => [],
|
||||
'effective_url' => $url,
|
||||
'error' => $e
|
||||
];
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -150,17 +153,25 @@ class StreamHandler
|
|||
*/
|
||||
private function createResource(callable $callback)
|
||||
{
|
||||
// Turn off error reporting while we try to initiate the request
|
||||
$level = error_reporting(0);
|
||||
$resource = call_user_func($callback);
|
||||
error_reporting($level);
|
||||
$errors = null;
|
||||
set_error_handler(function ($_, $msg, $file, $line) use (&$errors) {
|
||||
$errors[] = [
|
||||
'message' => $msg,
|
||||
'file' => $file,
|
||||
'line' => $line
|
||||
];
|
||||
return true;
|
||||
});
|
||||
|
||||
// If the resource could not be created, then grab the last error and
|
||||
// throw an exception.
|
||||
if (!is_resource($resource)) {
|
||||
$resource = $callback();
|
||||
restore_error_handler();
|
||||
|
||||
if (!$resource) {
|
||||
$message = 'Error creating resource: ';
|
||||
foreach ((array) error_get_last() as $key => $value) {
|
||||
$message .= "[{$key}] {$value} ";
|
||||
foreach ($errors as $err) {
|
||||
foreach ($err as $key => $value) {
|
||||
$message .= "[$key] $value" . PHP_EOL;
|
||||
}
|
||||
}
|
||||
throw new RingException(trim($message));
|
||||
}
|
||||
|
@ -168,11 +179,8 @@ class StreamHandler
|
|||
return $resource;
|
||||
}
|
||||
|
||||
private function createStream(
|
||||
$url,
|
||||
array $request,
|
||||
&$http_response_header
|
||||
) {
|
||||
private function createStream($url, array $request)
|
||||
{
|
||||
static $methods;
|
||||
if (!$methods) {
|
||||
$methods = array_flip(get_class_methods(__CLASS__));
|
||||
|
@ -207,8 +215,7 @@ class StreamHandler
|
|||
$url,
|
||||
$request,
|
||||
$options,
|
||||
$this->createContext($request, $options, $params),
|
||||
$http_response_header
|
||||
$this->createContext($request, $options, $params)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -302,7 +309,7 @@ class StreamHandler
|
|||
|
||||
private function add_progress(array $request, &$options, $value, &$params)
|
||||
{
|
||||
$fn = function ($code, $_, $_, $_, $transferred, $total) use ($value) {
|
||||
$fn = function ($code, $_1, $_2, $_3, $transferred, $total) use ($value) {
|
||||
if ($code == STREAM_NOTIFY_PROGRESS) {
|
||||
$value($total, $transferred, null, null);
|
||||
}
|
||||
|
@ -316,6 +323,10 @@ class StreamHandler
|
|||
|
||||
private function add_debug(array $request, &$options, $value, &$params)
|
||||
{
|
||||
if ($value === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
static $map = [
|
||||
STREAM_NOTIFY_CONNECT => 'CONNECT',
|
||||
STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
|
||||
|
@ -382,16 +393,17 @@ class StreamHandler
|
|||
$url,
|
||||
array $request,
|
||||
array $options,
|
||||
$context,
|
||||
&$http_response_header
|
||||
$context
|
||||
) {
|
||||
return $this->createResource(
|
||||
function () use ($url, &$http_response_header, $context) {
|
||||
function () use ($url, $context) {
|
||||
if (false === strpos($url, 'http')) {
|
||||
trigger_error("URL is invalid: {$url}", E_USER_WARNING);
|
||||
return null;
|
||||
}
|
||||
return fopen($url, 'r', null, $context);
|
||||
$resource = fopen($url, 'r', null, $context);
|
||||
$this->lastHeaders = $http_response_header;
|
||||
return $resource;
|
||||
},
|
||||
$request,
|
||||
$options
|
||||
|
|
|
@ -16,7 +16,7 @@ use React\Promise\PromisorInterface;
|
|||
* computation has not yet completed when wait() is called, the call to wait()
|
||||
* will block until the future has completed.
|
||||
*/
|
||||
interface FutureInterface extends PromisorInterface
|
||||
interface FutureInterface extends PromiseInterface, PromisorInterface
|
||||
{
|
||||
/**
|
||||
* Returns the result of the future either from cache or by blocking until
|
||||
|
@ -37,20 +37,4 @@ interface FutureInterface extends PromisorInterface
|
|||
* Cancels the future, if possible.
|
||||
*/
|
||||
public function cancel();
|
||||
|
||||
/**
|
||||
* Create and return a promise that invokes the given methods when the
|
||||
* future has a value, exception, or progress events.
|
||||
*
|
||||
* @param callable $onFulfilled Called when the promise is resolved.
|
||||
* @param callable $onRejected Called when the promise is rejected.
|
||||
* @param callable $onProgress Called on progress events.
|
||||
*
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
public function then(
|
||||
callable $onFulfilled = null,
|
||||
callable $onRejected = null,
|
||||
callable $onProgress = null
|
||||
);
|
||||
}
|
||||
|
|
|
@ -41,7 +41,10 @@ class StreamHandlerTest extends \PHPUnit_Framework_TestCase
|
|||
'headers' => ['host' => ['localhost:123']],
|
||||
'client' => ['timeout' => 0.01],
|
||||
]);
|
||||
|
||||
$this->assertInstanceOf(
|
||||
'GuzzleHttp\Ring\Future\CompletedFutureArray',
|
||||
$result
|
||||
);
|
||||
$this->assertNull($result['status']);
|
||||
$this->assertNull($result['body']);
|
||||
$this->assertEquals([], $result['headers']);
|
||||
|
|
|
@ -21,6 +21,15 @@
|
|||
* <
|
||||
* < [{'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:8125
|
||||
*
|
||||
* < 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:8125
|
||||
|
@ -44,6 +53,77 @@ var GuzzleServer = function(port, log) {
|
|||
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});
|
||||
|
@ -140,7 +220,7 @@ var GuzzleServer = function(port, log) {
|
|||
|
||||
// Called when the request completes
|
||||
req.addListener('end', function() {
|
||||
receivedRequest(request, req, res);
|
||||
firewallRequest(request, req, res, receivedRequest);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue