e_lib 코드들을 composer로 이동
func_http 삭제. guzzle로 대체 php 파일에서 include, require 앞에 있는 구문들을 전부 뒤로 이동.
This commit is contained in:
+1264
@@ -0,0 +1,1264 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 6.3.0 - 2017-06-22
|
||||
|
||||
* Feature: force IP resolution (ipv4 or ipv6) [#1608](https://github.com/guzzle/guzzle/pull/1608), [#1659](https://github.com/guzzle/guzzle/pull/1659)
|
||||
* Improvement: Don't include summary in exception message when body is empty [#1621](https://github.com/guzzle/guzzle/pull/1621)
|
||||
* Improvement: Handle `on_headers` option in MockHandler [#1580](https://github.com/guzzle/guzzle/pull/1580)
|
||||
* Improvement: Added SUSE Linux CA path [#1609](https://github.com/guzzle/guzzle/issues/1609)
|
||||
* Improvement: Use class reference for getting the name of the class instead of using hardcoded strings [#1641](https://github.com/guzzle/guzzle/pull/1641)
|
||||
* Feature: Added `read_timeout` option [#1611](https://github.com/guzzle/guzzle/pull/1611)
|
||||
* Bug fix: PHP 7.x fixes [#1685](https://github.com/guzzle/guzzle/pull/1685), [#1686](https://github.com/guzzle/guzzle/pull/1686), [#1811](https://github.com/guzzle/guzzle/pull/1811)
|
||||
* Deprecation: BadResponseException instantiation without a response [#1642](https://github.com/guzzle/guzzle/pull/1642)
|
||||
* Feature: Added NTLM auth [#1569](https://github.com/guzzle/guzzle/pull/1569)
|
||||
* Feature: Track redirect HTTP status codes [#1711](https://github.com/guzzle/guzzle/pull/1711)
|
||||
* Improvement: Check handler type during construction [#1745](https://github.com/guzzle/guzzle/pull/1745)
|
||||
* Improvement: Always include the Content-Length if there's a body [#1721](https://github.com/guzzle/guzzle/pull/1721)
|
||||
* Feature: Added convenience method to access a cookie by name [#1318](https://github.com/guzzle/guzzle/pull/1318)
|
||||
* Bug fix: Fill `CURLOPT_CAPATH` and `CURLOPT_CAINFO` properly [#1684](https://github.com/guzzle/guzzle/pull/1684)
|
||||
* Improvement: Use `\GuzzleHttp\Promise\rejection_for` function instead of object init [#1827](https://github.com/guzzle/guzzle/pull/1827)
|
||||
|
||||
|
||||
+ Minor code cleanups, documentation fixes and clarifications.
|
||||
|
||||
## 6.2.3 - 2017-02-28
|
||||
|
||||
* Fix deprecations with guzzle/psr7 version 1.4
|
||||
|
||||
## 6.2.2 - 2016-10-08
|
||||
|
||||
* Allow to pass nullable Response to delay callable
|
||||
* Only add scheme when host is present
|
||||
* Fix drain case where content-length is the literal string zero
|
||||
* Obfuscate in-URL credentials in exceptions
|
||||
|
||||
## 6.2.1 - 2016-07-18
|
||||
|
||||
* Address HTTP_PROXY security vulnerability, CVE-2016-5385:
|
||||
https://httpoxy.org/
|
||||
* Fixing timeout bug with StreamHandler:
|
||||
https://github.com/guzzle/guzzle/pull/1488
|
||||
* Only read up to `Content-Length` in PHP StreamHandler to avoid timeouts when
|
||||
a server does not honor `Connection: close`.
|
||||
* Ignore URI fragment when sending requests.
|
||||
|
||||
## 6.2.0 - 2016-03-21
|
||||
|
||||
* Feature: added `GuzzleHttp\json_encode` and `GuzzleHttp\json_decode`.
|
||||
https://github.com/guzzle/guzzle/pull/1389
|
||||
* Bug fix: Fix sleep calculation when waiting for delayed requests.
|
||||
https://github.com/guzzle/guzzle/pull/1324
|
||||
* Feature: More flexible history containers.
|
||||
https://github.com/guzzle/guzzle/pull/1373
|
||||
* Bug fix: defer sink stream opening in StreamHandler.
|
||||
https://github.com/guzzle/guzzle/pull/1377
|
||||
* Bug fix: do not attempt to escape cookie values.
|
||||
https://github.com/guzzle/guzzle/pull/1406
|
||||
* Feature: report original content encoding and length on decoded responses.
|
||||
https://github.com/guzzle/guzzle/pull/1409
|
||||
* Bug fix: rewind seekable request bodies before dispatching to cURL.
|
||||
https://github.com/guzzle/guzzle/pull/1422
|
||||
* Bug fix: provide an empty string to `http_build_query` for HHVM workaround.
|
||||
https://github.com/guzzle/guzzle/pull/1367
|
||||
|
||||
## 6.1.1 - 2015-11-22
|
||||
|
||||
* Bug fix: Proxy::wrapSync() now correctly proxies to the appropriate handler
|
||||
https://github.com/guzzle/guzzle/commit/911bcbc8b434adce64e223a6d1d14e9a8f63e4e4
|
||||
* Feature: HandlerStack is now more generic.
|
||||
https://github.com/guzzle/guzzle/commit/f2102941331cda544745eedd97fc8fd46e1ee33e
|
||||
* Bug fix: setting verify to false in the StreamHandler now disables peer
|
||||
verification. https://github.com/guzzle/guzzle/issues/1256
|
||||
* Feature: Middleware now uses an exception factory, including more error
|
||||
context. https://github.com/guzzle/guzzle/pull/1282
|
||||
* Feature: better support for disabled functions.
|
||||
https://github.com/guzzle/guzzle/pull/1287
|
||||
* Bug fix: fixed regression where MockHandler was not using `sink`.
|
||||
https://github.com/guzzle/guzzle/pull/1292
|
||||
|
||||
## 6.1.0 - 2015-09-08
|
||||
|
||||
* Feature: Added the `on_stats` request option to provide access to transfer
|
||||
statistics for requests. https://github.com/guzzle/guzzle/pull/1202
|
||||
* Feature: Added the ability to persist session cookies in CookieJars.
|
||||
https://github.com/guzzle/guzzle/pull/1195
|
||||
* Feature: Some compatibility updates for Google APP Engine
|
||||
https://github.com/guzzle/guzzle/pull/1216
|
||||
* Feature: Added support for NO_PROXY to prevent the use of a proxy based on
|
||||
a simple set of rules. https://github.com/guzzle/guzzle/pull/1197
|
||||
* Feature: Cookies can now contain square brackets.
|
||||
https://github.com/guzzle/guzzle/pull/1237
|
||||
* Bug fix: Now correctly parsing `=` inside of quotes in Cookies.
|
||||
https://github.com/guzzle/guzzle/pull/1232
|
||||
* Bug fix: Cusotm cURL options now correctly override curl options of the
|
||||
same name. https://github.com/guzzle/guzzle/pull/1221
|
||||
* Bug fix: Content-Type header is now added when using an explicitly provided
|
||||
multipart body. https://github.com/guzzle/guzzle/pull/1218
|
||||
* Bug fix: Now ignoring Set-Cookie headers that have no name.
|
||||
* Bug fix: Reason phrase is no longer cast to an int in some cases in the
|
||||
cURL handler. https://github.com/guzzle/guzzle/pull/1187
|
||||
* Bug fix: Remove the Authorization header when redirecting if the Host
|
||||
header changes. https://github.com/guzzle/guzzle/pull/1207
|
||||
* Bug fix: Cookie path matching fixes
|
||||
https://github.com/guzzle/guzzle/issues/1129
|
||||
* Bug fix: Fixing the cURL `body_as_string` setting
|
||||
https://github.com/guzzle/guzzle/pull/1201
|
||||
* Bug fix: quotes are no longer stripped when parsing cookies.
|
||||
https://github.com/guzzle/guzzle/issues/1172
|
||||
* Bug fix: `form_params` and `query` now always uses the `&` separator.
|
||||
https://github.com/guzzle/guzzle/pull/1163
|
||||
* Bug fix: Adding a Content-Length to PHP stream wrapper requests if not set.
|
||||
https://github.com/guzzle/guzzle/pull/1189
|
||||
|
||||
## 6.0.2 - 2015-07-04
|
||||
|
||||
* Fixed a memory leak in the curl handlers in which references to callbacks
|
||||
were not being removed by `curl_reset`.
|
||||
* Cookies are now extracted properly before redirects.
|
||||
* Cookies now allow more character ranges.
|
||||
* Decoded Content-Encoding responses are now modified to correctly reflect
|
||||
their state if the encoding was automatically removed by a handler. This
|
||||
means that the `Content-Encoding` header may be removed an the
|
||||
`Content-Length` modified to reflect the message size after removing the
|
||||
encoding.
|
||||
* Added a more explicit error message when trying to use `form_params` and
|
||||
`multipart` in the same request.
|
||||
* Several fixes for HHVM support.
|
||||
* Functions are now conditionally required using an additional level of
|
||||
indirection to help with global Composer installations.
|
||||
|
||||
## 6.0.1 - 2015-05-27
|
||||
|
||||
* Fixed a bug with serializing the `query` request option where the `&`
|
||||
separator was missing.
|
||||
* Added a better error message for when `body` is provided as an array. Please
|
||||
use `form_params` or `multipart` instead.
|
||||
* Various doc fixes.
|
||||
|
||||
## 6.0.0 - 2015-05-26
|
||||
|
||||
* See the UPGRADING.md document for more information.
|
||||
* Added `multipart` and `form_params` request options.
|
||||
* Added `synchronous` request option.
|
||||
* Added the `on_headers` request option.
|
||||
* Fixed `expect` handling.
|
||||
* No longer adding default middlewares in the client ctor. These need to be
|
||||
present on the provided handler in order to work.
|
||||
* Requests are no longer initiated when sending async requests with the
|
||||
CurlMultiHandler. This prevents unexpected recursion from requests completing
|
||||
while ticking the cURL loop.
|
||||
* Removed the semantics of setting `default` to `true`. This is no longer
|
||||
required now that the cURL loop is not ticked for async requests.
|
||||
* Added request and response logging middleware.
|
||||
* No longer allowing self signed certificates when using the StreamHandler.
|
||||
* Ensuring that `sink` is valid if saving to a file.
|
||||
* Request exceptions now include a "handler context" which provides handler
|
||||
specific contextual information.
|
||||
* Added `GuzzleHttp\RequestOptions` to allow request options to be applied
|
||||
using constants.
|
||||
* `$maxHandles` has been removed from CurlMultiHandler.
|
||||
* `MultipartPostBody` is now part of the `guzzlehttp/psr7` package.
|
||||
|
||||
## 5.3.0 - 2015-05-19
|
||||
|
||||
* Mock now supports `save_to`
|
||||
* Marked `AbstractRequestEvent::getTransaction()` as public.
|
||||
* Fixed a bug in which multiple headers using different casing would overwrite
|
||||
previous headers in the associative array.
|
||||
* Added `Utils::getDefaultHandler()`
|
||||
* Marked `GuzzleHttp\Client::getDefaultUserAgent` as deprecated.
|
||||
* URL scheme is now always lowercased.
|
||||
|
||||
## 6.0.0-beta.1
|
||||
|
||||
* Requires PHP >= 5.5
|
||||
* Updated to use PSR-7
|
||||
* Requires immutable messages, which basically means an event based system
|
||||
owned by a request instance is no longer possible.
|
||||
* Utilizing the [Guzzle PSR-7 package](https://github.com/guzzle/psr7).
|
||||
* Removed the dependency on `guzzlehttp/streams`. These stream abstractions
|
||||
are available in the `guzzlehttp/psr7` package under the `GuzzleHttp\Psr7`
|
||||
namespace.
|
||||
* Added middleware and handler system
|
||||
* Replaced the Guzzle event and subscriber system with a middleware system.
|
||||
* No longer depends on RingPHP, but rather places the HTTP handlers directly
|
||||
in Guzzle, operating on PSR-7 messages.
|
||||
* Retry logic is now encapsulated in `GuzzleHttp\Middleware::retry`, which
|
||||
means the `guzzlehttp/retry-subscriber` is now obsolete.
|
||||
* Mocking responses is now handled using `GuzzleHttp\Handler\MockHandler`.
|
||||
* Asynchronous responses
|
||||
* No longer supports the `future` request option to send an async request.
|
||||
Instead, use one of the `*Async` methods of a client (e.g., `requestAsync`,
|
||||
`getAsync`, etc.).
|
||||
* Utilizing `GuzzleHttp\Promise` instead of React's promise library to avoid
|
||||
recursion required by chaining and forwarding react promises. See
|
||||
https://github.com/guzzle/promises
|
||||
* Added `requestAsync` and `sendAsync` to send request asynchronously.
|
||||
* Added magic methods for `getAsync()`, `postAsync()`, etc. to send requests
|
||||
asynchronously.
|
||||
* Request options
|
||||
* POST and form updates
|
||||
* Added the `form_fields` and `form_files` request options.
|
||||
* Removed the `GuzzleHttp\Post` namespace.
|
||||
* The `body` request option no longer accepts an array for POST requests.
|
||||
* The `exceptions` request option has been deprecated in favor of the
|
||||
`http_errors` request options.
|
||||
* The `save_to` request option has been deprecated in favor of `sink` request
|
||||
option.
|
||||
* Clients no longer accept an array of URI template string and variables for
|
||||
URI variables. You will need to expand URI templates before passing them
|
||||
into a client constructor or request method.
|
||||
* Client methods `get()`, `post()`, `put()`, `patch()`, `options()`, etc. are
|
||||
now magic methods that will send synchronous requests.
|
||||
* Replaced `Utils.php` with plain functions in `functions.php`.
|
||||
* Removed `GuzzleHttp\Collection`.
|
||||
* Removed `GuzzleHttp\BatchResults`. Batched pool results are now returned as
|
||||
an array.
|
||||
* Removed `GuzzleHttp\Query`. Query string handling is now handled using an
|
||||
associative array passed into the `query` request option. The query string
|
||||
is serialized using PHP's `http_build_query`. If you need more control, you
|
||||
can pass the query string in as a string.
|
||||
* `GuzzleHttp\QueryParser` has been replaced with the
|
||||
`GuzzleHttp\Psr7\parse_query`.
|
||||
|
||||
## 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
|
||||
by default where the value represents an un-encoded value to send over the
|
||||
wire. A Query object then encodes the value before sending over the wire. This
|
||||
means that even value query string values (e.g., ":") are url encoded. This
|
||||
makes the Query class match PHP's http_build_query function. However, if you
|
||||
want to send requests over the wire using valid query string characters that do
|
||||
not need to be encoded, then you can provide a string to Url::setQuery() and
|
||||
pass true as the second argument to specify that the query string is a raw
|
||||
string that should not be parsed or encoded (unless a call to getQuery() is
|
||||
subsequently made, forcing the query-string to be converted into a Query
|
||||
object).
|
||||
|
||||
## 5.0.2 - 2014-10-30
|
||||
|
||||
* Added a trailing `\r\n` to multipart/form-data payloads. See
|
||||
https://github.com/guzzle/guzzle/pull/871
|
||||
* Added a `GuzzleHttp\Pool::send()` convenience method to match the docs.
|
||||
* Status codes are now returned as integers. See
|
||||
https://github.com/guzzle/guzzle/issues/881
|
||||
* No longer overwriting an existing `application/x-www-form-urlencoded` header
|
||||
when sending POST requests, allowing for customized headers. See
|
||||
https://github.com/guzzle/guzzle/issues/877
|
||||
* Improved path URL serialization.
|
||||
|
||||
* No longer double percent-encoding characters in the path or query string if
|
||||
they are already encoded.
|
||||
* Now properly encoding the supplied path to a URL object, instead of only
|
||||
encoding ' ' and '?'.
|
||||
* Note: This has been changed in 5.0.3 to now encode query string values by
|
||||
default unless the `rawString` argument is provided when setting the query
|
||||
string on a URL: Now allowing many more characters to be present in the
|
||||
query string without being percent encoded. See http://tools.ietf.org/html/rfc3986#appendix-A
|
||||
|
||||
## 5.0.1 - 2014-10-16
|
||||
|
||||
Bugfix release.
|
||||
|
||||
* Fixed an issue where connection errors still returned response object in
|
||||
error and end events event though the response is unusable. This has been
|
||||
corrected so that a response is not returned in the `getResponse` method of
|
||||
these events if the response did not complete. https://github.com/guzzle/guzzle/issues/867
|
||||
* Fixed an issue where transfer statistics were not being populated in the
|
||||
RingBridge. https://github.com/guzzle/guzzle/issues/866
|
||||
|
||||
## 5.0.0 - 2014-10-12
|
||||
|
||||
Adding support for non-blocking responses and some minor API cleanup.
|
||||
|
||||
### New Features
|
||||
|
||||
* Added support for non-blocking responses based on `guzzlehttp/guzzle-ring`.
|
||||
* Added a public API for creating a default HTTP adapter.
|
||||
* Updated the redirect plugin to be non-blocking so that redirects are sent
|
||||
concurrently. Other plugins like this can now be updated to be non-blocking.
|
||||
* Added a "progress" event so that you can get upload and download progress
|
||||
events.
|
||||
* Added `GuzzleHttp\Pool` which implements FutureInterface and transfers
|
||||
requests concurrently using a capped pool size as efficiently as possible.
|
||||
* Added `hasListeners()` to EmitterInterface.
|
||||
* Removed `GuzzleHttp\ClientInterface::sendAll` and marked
|
||||
`GuzzleHttp\Client::sendAll` as deprecated (it's still there, just not the
|
||||
recommended way).
|
||||
|
||||
### Breaking changes
|
||||
|
||||
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
|
||||
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.
|
||||
* Removed "functions.php", so that Guzzle is truly PSR-4 compliant. Except
|
||||
for the HTTP request functions from function.php, these functions are now
|
||||
implemented in `GuzzleHttp\Utils` using camelCase. `GuzzleHttp\json_decode`
|
||||
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 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.
|
||||
* Rewrote adapter layer.
|
||||
* Removing all classes from `GuzzleHttp\Adapter`, these are now
|
||||
implemented as callables that are stored in `GuzzleHttp\Ring\Client`.
|
||||
* Removed the concept of "parallel adapters". Sending requests serially or
|
||||
concurrently is now handled using a single adapter.
|
||||
* Moved `GuzzleHttp\Adapter\Transaction` to `GuzzleHttp\Transaction`. The
|
||||
Transaction object now exposes the request, response, and client as public
|
||||
properties. The getters and setters have been removed.
|
||||
* Removed the "headers" event. This event was only useful for changing the
|
||||
body a response once the headers of the response were known. You can implement
|
||||
a similar behavior in a number of ways. One example might be to use a
|
||||
FnStream that has access to the transaction being sent. For example, when the
|
||||
first byte is written, you could check if the response headers match your
|
||||
expectations, and if so, change the actual stream body that is being
|
||||
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
|
||||
the PSR-7 interfaces.
|
||||
* `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
|
||||
function that applies the option value to a request.
|
||||
* Removed the concept of "throwImmediately" from exceptions and error events.
|
||||
This control mechanism was used to stop a transfer of concurrent requests
|
||||
from completing. This can now be handled by throwing the exception or by
|
||||
cancelling a pool of requests or each outstanding future request individually.
|
||||
* Updated to "GuzzleHttp\Streams" 3.0.
|
||||
* `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
|
||||
argument. They now accept an associative array of options, including the
|
||||
"size" key and "metadata" key which can be used to provide custom metadata.
|
||||
|
||||
## 4.2.2 - 2014-09-08
|
||||
|
||||
* Fixed a memory leak in the CurlAdapter when reusing cURL handles.
|
||||
* No longer using `request_fulluri` in stream adapter proxies.
|
||||
* Relative redirects are now based on the last response, not the first response.
|
||||
|
||||
## 4.2.1 - 2014-08-19
|
||||
|
||||
* Ensuring that the StreamAdapter does not always add a Content-Type header
|
||||
* Adding automated github releases with a phar and zip
|
||||
|
||||
## 4.2.0 - 2014-08-17
|
||||
|
||||
* Now merging in default options using a case-insensitive comparison.
|
||||
Closes https://github.com/guzzle/guzzle/issues/767
|
||||
* Added the ability to automatically decode `Content-Encoding` response bodies
|
||||
using the `decode_content` request option. This is set to `true` by default
|
||||
to decode the response body if it comes over the wire with a
|
||||
`Content-Encoding`. Set this value to `false` to disable decoding the
|
||||
response content, and pass a string to provide a request `Accept-Encoding`
|
||||
header and turn on automatic response decoding. This feature now allows you
|
||||
to pass an `Accept-Encoding` header in the headers of a request but still
|
||||
disable automatic response decoding.
|
||||
Closes https://github.com/guzzle/guzzle/issues/764
|
||||
* Added the ability to throw an exception immediately when transferring
|
||||
requests in parallel. Closes https://github.com/guzzle/guzzle/issues/760
|
||||
* Updating guzzlehttp/streams dependency to ~2.1
|
||||
* No longer utilizing the now deprecated namespaced methods from the stream
|
||||
package.
|
||||
|
||||
## 4.1.8 - 2014-08-14
|
||||
|
||||
* Fixed an issue in the CurlFactory that caused setting the `stream=false`
|
||||
request option to throw an exception.
|
||||
See: https://github.com/guzzle/guzzle/issues/769
|
||||
* TransactionIterator now calls rewind on the inner iterator.
|
||||
See: https://github.com/guzzle/guzzle/pull/765
|
||||
* You can now set the `Content-Type` header to `multipart/form-data`
|
||||
when creating POST requests to force multipart bodies.
|
||||
See https://github.com/guzzle/guzzle/issues/768
|
||||
|
||||
## 4.1.7 - 2014-08-07
|
||||
|
||||
* Fixed an error in the HistoryPlugin that caused the same request and response
|
||||
to be logged multiple times when an HTTP protocol error occurs.
|
||||
* Ensuring that cURL does not add a default Content-Type when no Content-Type
|
||||
has been supplied by the user. This prevents the adapter layer from modifying
|
||||
the request that is sent over the wire after any listeners may have already
|
||||
put the request in a desired state (e.g., signed the request).
|
||||
* Throwing an exception when you attempt to send requests that have the
|
||||
"stream" set to true in parallel using the MultiAdapter.
|
||||
* Only calling curl_multi_select when there are active cURL handles. This was
|
||||
previously changed and caused performance problems on some systems due to PHP
|
||||
always selecting until the maximum select timeout.
|
||||
* Fixed a bug where multipart/form-data POST fields were not correctly
|
||||
aggregated (e.g., values with "&").
|
||||
|
||||
## 4.1.6 - 2014-08-03
|
||||
|
||||
* Added helper methods to make it easier to represent messages as strings,
|
||||
including getting the start line and getting headers as a string.
|
||||
|
||||
## 4.1.5 - 2014-08-02
|
||||
|
||||
* Automatically retrying cURL "Connection died, retrying a fresh connect"
|
||||
errors when possible.
|
||||
* cURL implementation cleanup
|
||||
* Allowing multiple event subscriber listeners to be registered per event by
|
||||
passing an array of arrays of listener configuration.
|
||||
|
||||
## 4.1.4 - 2014-07-22
|
||||
|
||||
* Fixed a bug that caused multi-part POST requests with more than one field to
|
||||
serialize incorrectly.
|
||||
* Paths can now be set to "0"
|
||||
* `ResponseInterface::xml` now accepts a `libxml_options` option and added a
|
||||
missing default argument that was required when parsing XML response bodies.
|
||||
* A `save_to` stream is now created lazily, which means that files are not
|
||||
created on disk unless a request succeeds.
|
||||
|
||||
## 4.1.3 - 2014-07-15
|
||||
|
||||
* Various fixes to multipart/form-data POST uploads
|
||||
* Wrapping function.php in an if-statement to ensure Guzzle can be used
|
||||
globally and in a Composer install
|
||||
* Fixed an issue with generating and merging in events to an event array
|
||||
* POST headers are only applied before sending a request to allow you to change
|
||||
the query aggregator used before uploading
|
||||
* Added much more robust query string parsing
|
||||
* Fixed various parsing and normalization issues with URLs
|
||||
* Fixing an issue where multi-valued headers were not being utilized correctly
|
||||
in the StreamAdapter
|
||||
|
||||
## 4.1.2 - 2014-06-18
|
||||
|
||||
* Added support for sending payloads with GET requests
|
||||
|
||||
## 4.1.1 - 2014-06-08
|
||||
|
||||
* Fixed an issue related to using custom message factory options in subclasses
|
||||
* Fixed an issue with nested form fields in a multi-part POST
|
||||
* Fixed an issue with using the `json` request option for POST requests
|
||||
* Added `ToArrayInterface` to `GuzzleHttp\Cookie\CookieJar`
|
||||
|
||||
## 4.1.0 - 2014-05-27
|
||||
|
||||
* Added a `json` request option to easily serialize JSON payloads.
|
||||
* Added a `GuzzleHttp\json_decode()` wrapper to safely parse JSON.
|
||||
* Added `setPort()` and `getPort()` to `GuzzleHttp\Message\RequestInterface`.
|
||||
* Added the ability to provide an emitter to a client in the client constructor.
|
||||
* Added the ability to persist a cookie session using $_SESSION.
|
||||
* Added a trait that can be used to add event listeners to an iterator.
|
||||
* Removed request method constants from RequestInterface.
|
||||
* Fixed warning when invalid request start-lines are received.
|
||||
* Updated MessageFactory to work with custom request option methods.
|
||||
* Updated cacert bundle to latest build.
|
||||
|
||||
4.0.2 (2014-04-16)
|
||||
------------------
|
||||
|
||||
* Proxy requests using the StreamAdapter now properly use request_fulluri (#632)
|
||||
* Added the ability to set scalars as POST fields (#628)
|
||||
|
||||
## 4.0.1 - 2014-04-04
|
||||
|
||||
* The HTTP status code of a response is now set as the exception code of
|
||||
RequestException objects.
|
||||
* 303 redirects will now correctly switch from POST to GET requests.
|
||||
* The default parallel adapter of a client now correctly uses the MultiAdapter.
|
||||
* HasDataTrait now initializes the internal data array as an empty array so
|
||||
that the toArray() method always returns an array.
|
||||
|
||||
## 4.0.0 - 2014-03-29
|
||||
|
||||
* For more information on the 4.0 transition, see:
|
||||
http://mtdowling.com/blog/2014/03/15/guzzle-4-rc/
|
||||
* For information on changes and upgrading, see:
|
||||
https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40
|
||||
* Added `GuzzleHttp\batch()` as a convenience function for sending requests in
|
||||
parallel without needing to write asynchronous code.
|
||||
* Restructured how events are added to `GuzzleHttp\ClientInterface::sendAll()`.
|
||||
You can now pass a callable or an array of associative arrays where each
|
||||
associative array contains the "fn", "priority", and "once" keys.
|
||||
|
||||
## 4.0.0.rc-2 - 2014-03-25
|
||||
|
||||
* Removed `getConfig()` and `setConfig()` from clients to avoid confusion
|
||||
around whether things like base_url, message_factory, etc. should be able to
|
||||
be retrieved or modified.
|
||||
* Added `getDefaultOption()` and `setDefaultOption()` to ClientInterface
|
||||
* functions.php functions were renamed using snake_case to match PHP idioms
|
||||
* Added support for `HTTP_PROXY`, `HTTPS_PROXY`, and
|
||||
`GUZZLE_CURL_SELECT_TIMEOUT` environment variables
|
||||
* Added the ability to specify custom `sendAll()` event priorities
|
||||
* Added the ability to specify custom stream context options to the stream
|
||||
adapter.
|
||||
* Added a functions.php function for `get_path()` and `set_path()`
|
||||
* CurlAdapter and MultiAdapter now use a callable to generate curl resources
|
||||
* MockAdapter now properly reads a body and emits a `headers` event
|
||||
* Updated Url class to check if a scheme and host are set before adding ":"
|
||||
and "//". This allows empty Url (e.g., "") to be serialized as "".
|
||||
* Parsing invalid XML no longer emits warnings
|
||||
* Curl classes now properly throw AdapterExceptions
|
||||
* Various performance optimizations
|
||||
* Streams are created with the faster `Stream\create()` function
|
||||
* Marked deprecation_proxy() as internal
|
||||
* Test server is now a collection of static methods on a class
|
||||
|
||||
## 4.0.0-rc.1 - 2014-03-15
|
||||
|
||||
* See https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40
|
||||
|
||||
## 3.8.1 - 2014-01-28
|
||||
|
||||
* Bug: Always using GET requests when redirecting from a 303 response
|
||||
* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in
|
||||
`Guzzle\Http\ClientInterface::setSslVerification()`
|
||||
* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL
|
||||
* Bug: The body of a request can now be set to `"0"`
|
||||
* Sending PHP stream requests no longer forces `HTTP/1.0`
|
||||
* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of
|
||||
each sub-exception
|
||||
* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than
|
||||
clobbering everything).
|
||||
* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators)
|
||||
* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`.
|
||||
For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`.
|
||||
* Now properly escaping the regular expression delimiter when matching Cookie domains.
|
||||
* Network access is now disabled when loading XML documents
|
||||
|
||||
## 3.8.0 - 2013-12-05
|
||||
|
||||
* Added the ability to define a POST name for a file
|
||||
* JSON response parsing now properly walks additionalProperties
|
||||
* cURL error code 18 is now retried automatically in the BackoffPlugin
|
||||
* Fixed a cURL error when URLs contain fragments
|
||||
* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were
|
||||
CurlExceptions
|
||||
* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e)
|
||||
* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS`
|
||||
* Fixed a bug that was encountered when parsing empty header parameters
|
||||
* UriTemplate now has a `setRegex()` method to match the docs
|
||||
* The `debug` request parameter now checks if it is truthy rather than if it exists
|
||||
* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin
|
||||
* Added the ability to combine URLs using strict RFC 3986 compliance
|
||||
* Command objects can now return the validation errors encountered by the command
|
||||
* Various fixes to cache revalidation (#437 and 29797e5)
|
||||
* Various fixes to the AsyncPlugin
|
||||
* Cleaned up build scripts
|
||||
|
||||
## 3.7.4 - 2013-10-02
|
||||
|
||||
* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430)
|
||||
* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp
|
||||
(see https://github.com/aws/aws-sdk-php/issues/147)
|
||||
* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots
|
||||
* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420)
|
||||
* Updated the bundled cacert.pem (#419)
|
||||
* OauthPlugin now supports adding authentication to headers or query string (#425)
|
||||
|
||||
## 3.7.3 - 2013-09-08
|
||||
|
||||
* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and
|
||||
`CommandTransferException`.
|
||||
* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description
|
||||
* Schemas are only injected into response models when explicitly configured.
|
||||
* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of
|
||||
an EntityBody.
|
||||
* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator.
|
||||
* 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 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
|
||||
|
||||
## 3.7.2 - 2013-08-02
|
||||
|
||||
* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander
|
||||
See https://github.com/guzzle/guzzle/issues/371
|
||||
* Bug fix: Cookie domains are now matched correctly according to RFC 6265
|
||||
See https://github.com/guzzle/guzzle/issues/377
|
||||
* Bug fix: GET parameters are now used when calculating an OAuth signature
|
||||
* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted
|
||||
* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched
|
||||
* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input.
|
||||
See https://github.com/guzzle/guzzle/issues/379
|
||||
* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See
|
||||
https://github.com/guzzle/guzzle/pull/380
|
||||
* cURL multi cleanup and optimizations
|
||||
|
||||
## 3.7.1 - 2013-07-05
|
||||
|
||||
* Bug fix: Setting default options on a client now works
|
||||
* Bug fix: Setting options on HEAD requests now works. See #352
|
||||
* Bug fix: Moving stream factory before send event to before building the stream. See #353
|
||||
* Bug fix: Cookies no longer match on IP addresses per RFC 6265
|
||||
* Bug fix: Correctly parsing header parameters that are in `<>` and quotes
|
||||
* Added `cert` and `ssl_key` as request options
|
||||
* `Host` header can now diverge from the host part of a URL if the header is set manually
|
||||
* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter
|
||||
* OAuth parameters are only added via the plugin if they aren't already set
|
||||
* Exceptions are now thrown when a URL cannot be parsed
|
||||
* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails
|
||||
* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin
|
||||
|
||||
## 3.7.0 - 2013-06-10
|
||||
|
||||
* See UPGRADING.md for more information on how to upgrade.
|
||||
* Requests now support the ability to specify an array of $options when creating a request to more easily modify a
|
||||
request. You can pass a 'request.options' configuration setting to a client to apply default request options to
|
||||
every request created by a client (e.g. default query string variables, headers, curl options, etc.).
|
||||
* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`.
|
||||
See `Guzzle\Http\StaticClient::mount`.
|
||||
* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests
|
||||
created by a command (e.g. custom headers, query string variables, timeout settings, etc.).
|
||||
* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the
|
||||
headers of a response
|
||||
* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key
|
||||
(e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`)
|
||||
* ServiceBuilders now support storing and retrieving arbitrary data
|
||||
* CachePlugin can now purge all resources for a given URI
|
||||
* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource
|
||||
* CachePlugin now uses the Vary header to determine if a resource is a cache hit
|
||||
* `Guzzle\Http\Message\Response` now implements `\Serializable`
|
||||
* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters
|
||||
* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable
|
||||
* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()`
|
||||
* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size
|
||||
* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message
|
||||
* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older
|
||||
Symfony users can still use the old version of Monolog.
|
||||
* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`.
|
||||
Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`.
|
||||
* Several performance improvements to `Guzzle\Common\Collection`
|
||||
* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
|
||||
createRequest, head, delete, put, patch, post, options, prepareRequest
|
||||
* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
|
||||
* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
|
||||
* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
|
||||
`Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
|
||||
resource, string, or EntityBody into the $options parameter to specify the download location of the response.
|
||||
* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
|
||||
default `array()`
|
||||
* Added `Guzzle\Stream\StreamInterface::isRepeatable`
|
||||
* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
|
||||
$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
|
||||
$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`.
|
||||
* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`.
|
||||
* Removed `Guzzle\Http\ClientInterface::expandTemplate()`
|
||||
* Removed `Guzzle\Http\ClientInterface::setRequestFactory()`
|
||||
* Removed `Guzzle\Http\ClientInterface::getCurlMulti()`
|
||||
* Removed `Guzzle\Http\Message\RequestInterface::canCache`
|
||||
* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`
|
||||
* Removed `Guzzle\Http\Message\RequestInterface::isRedirect`
|
||||
* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
|
||||
* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting
|
||||
`Guzzle\Common\Version::$emitWarnings` to true.
|
||||
* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use
|
||||
`$request->getResponseBody()->isRepeatable()` instead.
|
||||
* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
|
||||
`Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
|
||||
* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
|
||||
`Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
|
||||
* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
|
||||
* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
|
||||
* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
|
||||
* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand.
|
||||
These will work through Guzzle 4.0
|
||||
* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params].
|
||||
* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
|
||||
* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`.
|
||||
* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`.
|
||||
* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
|
||||
* Marked `Guzzle\Common\Collection::inject()` as deprecated.
|
||||
* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');`
|
||||
* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
|
||||
CacheStorageInterface. These two objects and interface will be removed in a future version.
|
||||
* Always setting X-cache headers on cached responses
|
||||
* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
|
||||
* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
|
||||
$request, Response $response);`
|
||||
* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
|
||||
* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
|
||||
* Added `CacheStorageInterface::purge($url)`
|
||||
* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
|
||||
$plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
|
||||
CanCacheStrategyInterface $canCache = null)`
|
||||
* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
|
||||
|
||||
## 3.6.0 - 2013-05-29
|
||||
|
||||
* ServiceDescription now implements ToArrayInterface
|
||||
* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters
|
||||
* Guzzle can now correctly parse incomplete URLs
|
||||
* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
|
||||
* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
|
||||
* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
|
||||
* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
|
||||
HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
|
||||
CacheControl header implementation.
|
||||
* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
|
||||
* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
|
||||
* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
|
||||
Guzzle\Http\Curl\RequestMediator
|
||||
* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
|
||||
* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
|
||||
* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
|
||||
* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
|
||||
* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
|
||||
* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
|
||||
* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle
|
||||
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
|
||||
`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
|
||||
* The ability to case-insensitively search for header values
|
||||
* Guzzle\Http\Message\Header::hasExactHeader
|
||||
* Guzzle\Http\Message\Header::raw. Use getAll()
|
||||
* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
|
||||
instead.
|
||||
* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
|
||||
* Added the ability to cast Model objects to a string to view debug information.
|
||||
|
||||
## 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 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
|
||||
* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a
|
||||
non-existent key
|
||||
* Bug: All __call() method arguments are now required (helps with mocking frameworks)
|
||||
* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference
|
||||
to help with refcount based garbage collection of resources created by sending a request
|
||||
* Deprecating ZF1 cache and log adapters. These will be removed in the next major version.
|
||||
* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it's deprecated). Use the
|
||||
HistoryPlugin for a history.
|
||||
* Added a `responseBody` alias for the `response_body` location
|
||||
* Refactored internals to no longer rely on Response::getRequest()
|
||||
* HistoryPlugin can now be cast to a string
|
||||
* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests
|
||||
and responses that are sent over the wire
|
||||
* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects
|
||||
|
||||
## 3.4.3 - 2013-04-30
|
||||
|
||||
* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response
|
||||
* Added a check to re-extract the temp cacert bundle from the phar before sending each request
|
||||
|
||||
## 3.4.2 - 2013-04-29
|
||||
|
||||
* Bug fix: Stream objects now work correctly with "a" and "a+" modes
|
||||
* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present
|
||||
* Bug fix: AsyncPlugin no longer forces HEAD requests
|
||||
* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter
|
||||
* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails
|
||||
* Setting a response on a request will write to the custom request body from the response body if one is specified
|
||||
* LogPlugin now writes to php://output when STDERR is undefined
|
||||
* Added the ability to set multiple POST files for the same key in a single call
|
||||
* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default
|
||||
* Added the ability to queue CurlExceptions to the MockPlugin
|
||||
* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send)
|
||||
* Configuration loading now allows remote files
|
||||
|
||||
## 3.4.1 - 2013-04-16
|
||||
|
||||
* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti
|
||||
handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost.
|
||||
* Exceptions are now properly grouped when sending requests in parallel
|
||||
* Redirects are now properly aggregated when a multi transaction fails
|
||||
* Redirects now set the response on the original object even in the event of a failure
|
||||
* Bug fix: Model names are now properly set even when using $refs
|
||||
* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax
|
||||
* Added support for oauth_callback in OAuth signatures
|
||||
* Added support for oauth_verifier in OAuth signatures
|
||||
* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection
|
||||
|
||||
## 3.4.0 - 2013-04-11
|
||||
|
||||
* Bug fix: URLs are now resolved correctly based on http://tools.ietf.org/html/rfc3986#section-5.2. #289
|
||||
* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289
|
||||
* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263
|
||||
* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264.
|
||||
* Bug fix: Added `number` type to service descriptions.
|
||||
* Bug fix: empty parameters are removed from an OAuth signature
|
||||
* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header
|
||||
* Bug fix: Fixed "array to string" error when validating a union of types in a service description
|
||||
* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream
|
||||
* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin.
|
||||
* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs.
|
||||
* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections.
|
||||
* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if
|
||||
the Content-Type can be determined based on the entity body or the path of the request.
|
||||
* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder.
|
||||
* Added support for a PSR-3 LogAdapter.
|
||||
* Added a `command.after_prepare` event
|
||||
* Added `oauth_callback` parameter to the OauthPlugin
|
||||
* Added the ability to create a custom stream class when using a stream factory
|
||||
* Added a CachingEntityBody decorator
|
||||
* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized.
|
||||
* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar.
|
||||
* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies
|
||||
* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This
|
||||
means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use
|
||||
POST fields or files (the latter is only used when emulating a form POST in the browser).
|
||||
* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest
|
||||
|
||||
## 3.3.1 - 2013-03-10
|
||||
|
||||
* Added the ability to create PHP streaming responses from HTTP requests
|
||||
* Bug fix: Running any filters when parsing response headers with service descriptions
|
||||
* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing
|
||||
* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across
|
||||
response location visitors.
|
||||
* Bug fix: Removed the possibility of creating configuration files with circular dependencies
|
||||
* RequestFactory::create() now uses the key of a POST file when setting the POST file name
|
||||
* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set
|
||||
|
||||
## 3.3.0 - 2013-03-03
|
||||
|
||||
* A large number of performance optimizations have been made
|
||||
* Bug fix: Added 'wb' as a valid write mode for streams
|
||||
* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned
|
||||
* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()`
|
||||
* BC: Removed `Guzzle\Http\Utils` class
|
||||
* BC: Setting a service description on a client will no longer modify the client's command factories.
|
||||
* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using
|
||||
the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
|
||||
* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to
|
||||
lowercase
|
||||
* Operation parameter objects are now lazy loaded internally
|
||||
* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses
|
||||
* Added support for instantiating responseType=class responseClass classes. Classes must implement
|
||||
`Guzzle\Service\Command\ResponseClassInterface`
|
||||
* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These
|
||||
additional properties also support locations and can be used to parse JSON responses where the outermost part of the
|
||||
JSON is an array
|
||||
* Added support for nested renaming of JSON models (rename sentAs to name)
|
||||
* CachePlugin
|
||||
* Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error
|
||||
* Debug headers can now added to cached response in the CachePlugin
|
||||
|
||||
## 3.2.0 - 2013-02-14
|
||||
|
||||
* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients.
|
||||
* URLs with no path no longer contain a "/" by default
|
||||
* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url.
|
||||
* BadResponseException no longer includes the full request and response message
|
||||
* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface
|
||||
* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface
|
||||
* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription
|
||||
* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list
|
||||
* xmlEncoding can now be customized for the XML declaration of a XML service description operation
|
||||
* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value
|
||||
aggregation and no longer uses callbacks
|
||||
* The URL encoding implementation of Guzzle\Http\QueryString can now be customized
|
||||
* Bug fix: Filters were not always invoked for array service description parameters
|
||||
* Bug fix: Redirects now use a target response body rather than a temporary response body
|
||||
* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded
|
||||
* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives
|
||||
|
||||
## 3.1.2 - 2013-01-27
|
||||
|
||||
* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the
|
||||
response body. For example, the XmlVisitor now parses the XML response into an array in the before() method.
|
||||
* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent
|
||||
* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444)
|
||||
* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse()
|
||||
* Setting default headers on a client after setting the user-agent will not erase the user-agent setting
|
||||
|
||||
## 3.1.1 - 2013-01-20
|
||||
|
||||
* Adding wildcard support to Guzzle\Common\Collection::getPath()
|
||||
* Adding alias support to ServiceBuilder configs
|
||||
* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface
|
||||
|
||||
## 3.1.0 - 2013-01-12
|
||||
|
||||
* BC: CurlException now extends from RequestException rather than BadResponseException
|
||||
* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse()
|
||||
* Added getData to ServiceDescriptionInterface
|
||||
* Added context array to RequestInterface::setState()
|
||||
* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http
|
||||
* Bug: Adding required content-type when JSON request visitor adds JSON to a command
|
||||
* Bug: Fixing the serialization of a service description with custom data
|
||||
* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing
|
||||
an array of successful and failed responses
|
||||
* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection
|
||||
* Added Guzzle\Http\IoEmittingEntityBody
|
||||
* Moved command filtration from validators to location visitors
|
||||
* Added `extends` attributes to service description parameters
|
||||
* Added getModels to ServiceDescriptionInterface
|
||||
|
||||
## 3.0.7 - 2012-12-19
|
||||
|
||||
* Fixing phar detection when forcing a cacert to system if null or true
|
||||
* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()`
|
||||
* Cleaning up `Guzzle\Common\Collection::inject` method
|
||||
* Adding a response_body location to service descriptions
|
||||
|
||||
## 3.0.6 - 2012-12-09
|
||||
|
||||
* CurlMulti performance improvements
|
||||
* Adding setErrorResponses() to Operation
|
||||
* composer.json tweaks
|
||||
|
||||
## 3.0.5 - 2012-11-18
|
||||
|
||||
* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin
|
||||
* Bug: Response body can now be a string containing "0"
|
||||
* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert
|
||||
* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs
|
||||
* Added support for XML attributes in service description responses
|
||||
* DefaultRequestSerializer now supports array URI parameter values for URI template expansion
|
||||
* Added better mimetype guessing to requests and post files
|
||||
|
||||
## 3.0.4 - 2012-11-11
|
||||
|
||||
* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value
|
||||
* Bug: Cookies can now be added that have a name, domain, or value set to "0"
|
||||
* Bug: Using the system cacert bundle when using the Phar
|
||||
* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures
|
||||
* Enhanced cookie jar de-duplication
|
||||
* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added
|
||||
* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies
|
||||
* Added the ability to create any sort of hash for a stream rather than just an MD5 hash
|
||||
|
||||
## 3.0.3 - 2012-11-04
|
||||
|
||||
* Implementing redirects in PHP rather than cURL
|
||||
* Added PECL URI template extension and using as default parser if available
|
||||
* Bug: Fixed Content-Length parsing of Response factory
|
||||
* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams.
|
||||
* Adding ToArrayInterface throughout library
|
||||
* Fixing OauthPlugin to create unique nonce values per request
|
||||
|
||||
## 3.0.2 - 2012-10-25
|
||||
|
||||
* Magic methods are enabled by default on clients
|
||||
* Magic methods return the result of a command
|
||||
* Service clients no longer require a base_url option in the factory
|
||||
* Bug: Fixed an issue with URI templates where null template variables were being expanded
|
||||
|
||||
## 3.0.1 - 2012-10-22
|
||||
|
||||
* Models can now be used like regular collection objects by calling filter, map, etc.
|
||||
* Models no longer require a Parameter structure or initial data in the constructor
|
||||
* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator`
|
||||
|
||||
## 3.0.0 - 2012-10-15
|
||||
|
||||
* Rewrote service description format to be based on Swagger
|
||||
* Now based on JSON schema
|
||||
* Added nested input structures and nested response models
|
||||
* Support for JSON and XML input and output models
|
||||
* Renamed `commands` to `operations`
|
||||
* Removed dot class notation
|
||||
* Removed custom types
|
||||
* Broke the project into smaller top-level namespaces to be more component friendly
|
||||
* Removed support for XML configs and descriptions. Use arrays or JSON files.
|
||||
* Removed the Validation component and Inspector
|
||||
* Moved all cookie code to Guzzle\Plugin\Cookie
|
||||
* Magic methods on a Guzzle\Service\Client now return the command un-executed.
|
||||
* Calling getResult() or getResponse() on a command will lazily execute the command if needed.
|
||||
* Now shipping with cURL's CA certs and using it by default
|
||||
* Added previousResponse() method to response objects
|
||||
* No longer sending Accept and Accept-Encoding headers on every request
|
||||
* Only sending an Expect header by default when a payload is greater than 1MB
|
||||
* Added/moved client options:
|
||||
* curl.blacklist to curl.option.blacklist
|
||||
* Added ssl.certificate_authority
|
||||
* Added a Guzzle\Iterator component
|
||||
* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin
|
||||
* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin)
|
||||
* Added a more robust caching plugin
|
||||
* Added setBody to response objects
|
||||
* Updating LogPlugin to use a more flexible MessageFormatter
|
||||
* Added a completely revamped build process
|
||||
* Cleaning up Collection class and removing default values from the get method
|
||||
* Fixed ZF2 cache adapters
|
||||
|
||||
## 2.8.8 - 2012-10-15
|
||||
|
||||
* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did
|
||||
|
||||
## 2.8.7 - 2012-09-30
|
||||
|
||||
* Bug: Fixed config file aliases for JSON includes
|
||||
* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests
|
||||
* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload
|
||||
* Bug: Hardening request and response parsing to account for missing parts
|
||||
* Bug: Fixed PEAR packaging
|
||||
* Bug: Fixed Request::getInfo
|
||||
* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail
|
||||
* Adding the ability for the namespace Iterator factory to look in multiple directories
|
||||
* Added more getters/setters/removers from service descriptions
|
||||
* Added the ability to remove POST fields from OAuth signatures
|
||||
* OAuth plugin now supports 2-legged OAuth
|
||||
|
||||
## 2.8.6 - 2012-09-05
|
||||
|
||||
* Added the ability to modify and build service descriptions
|
||||
* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command
|
||||
* Added a `json` parameter location
|
||||
* Now allowing dot notation for classes in the CacheAdapterFactory
|
||||
* Using the union of two arrays rather than an array_merge when extending service builder services and service params
|
||||
* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references
|
||||
in service builder config files.
|
||||
* Services defined in two different config files that include one another will by default replace the previously
|
||||
defined service, but you can now create services that extend themselves and merge their settings over the previous
|
||||
* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like
|
||||
'_default' with a default JSON configuration file.
|
||||
|
||||
## 2.8.5 - 2012-08-29
|
||||
|
||||
* Bug: Suppressed empty arrays from URI templates
|
||||
* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching
|
||||
* Added support for HTTP responses that do not contain a reason phrase in the start-line
|
||||
* AbstractCommand commands are now invokable
|
||||
* Added a way to get the data used when signing an Oauth request before a request is sent
|
||||
|
||||
## 2.8.4 - 2012-08-15
|
||||
|
||||
* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin
|
||||
* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable.
|
||||
* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream
|
||||
* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream
|
||||
* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5())
|
||||
* Added additional response status codes
|
||||
* Removed SSL information from the default User-Agent header
|
||||
* DELETE requests can now send an entity body
|
||||
* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries
|
||||
* Added the ability of the MockPlugin to consume mocked request bodies
|
||||
* LogPlugin now exposes request and response objects in the extras array
|
||||
|
||||
## 2.8.3 - 2012-07-30
|
||||
|
||||
* Bug: Fixed a case where empty POST requests were sent as GET requests
|
||||
* 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()`
|
||||
* 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
|
||||
* QueryString and Collection performance improvements
|
||||
* Allowing dot notation for class paths in filters attribute of a service descriptions
|
||||
|
||||
## 2.8.1 - 2012-07-16
|
||||
|
||||
* Loosening Event Dispatcher dependency
|
||||
* POST redirects can now be customized using CURLOPT_POSTREDIR
|
||||
|
||||
## 2.8.0 - 2012-07-15
|
||||
|
||||
* BC: Guzzle\Http\Query
|
||||
* Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl)
|
||||
* Changed isEncodingValues() and isEncodingFields() to isUrlEncoding()
|
||||
* 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
|
||||
* 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
|
||||
|
||||
## 2.7.2 - 2012-07-02
|
||||
|
||||
* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser.
|
||||
* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty()
|
||||
* CachePlugin now allows for a custom request parameter function to check if a request can be cached
|
||||
* Bug fix: CachePlugin now only caches GET and HEAD requests by default
|
||||
* Bug fix: Using header glue when transferring headers over the wire
|
||||
* Allowing deeply nested arrays for composite variables in URI templates
|
||||
* Batch divisors can now return iterators or arrays
|
||||
|
||||
## 2.7.1 - 2012-06-26
|
||||
|
||||
* Minor patch to update version number in UA string
|
||||
* Updating build process
|
||||
|
||||
## 2.7.0 - 2012-06-25
|
||||
|
||||
* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes.
|
||||
* BC: Removed magic setX methods from commands
|
||||
* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method
|
||||
* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable.
|
||||
* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity)
|
||||
* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace
|
||||
* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin
|
||||
* Added the ability to set POST fields and files in a service description
|
||||
* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method
|
||||
* Adding a command.before_prepare event to clients
|
||||
* Added BatchClosureTransfer and BatchClosureDivisor
|
||||
* BatchTransferException now includes references to the batch divisor and transfer strategies
|
||||
* Fixed some tests so that they pass more reliably
|
||||
* Added Guzzle\Common\Log\ArrayLogAdapter
|
||||
|
||||
## 2.6.6 - 2012-06-10
|
||||
|
||||
* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin
|
||||
* BC: Removing Guzzle\Service\Command\CommandSet
|
||||
* Adding generic batching system (replaces the batch queue plugin and command set)
|
||||
* Updating ZF cache and log adapters and now using ZF's composer repository
|
||||
* Bug: Setting the name of each ApiParam when creating through an ApiCommand
|
||||
* Adding result_type, result_doc, deprecated, and doc_url to service descriptions
|
||||
* Bug: Changed the default cookie header casing back to 'Cookie'
|
||||
|
||||
## 2.6.5 - 2012-06-03
|
||||
|
||||
* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource()
|
||||
* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from
|
||||
* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data
|
||||
* BC: Renaming methods in the CookieJarInterface
|
||||
* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations
|
||||
* Making the default glue for HTTP headers ';' instead of ','
|
||||
* Adding a removeValue to Guzzle\Http\Message\Header
|
||||
* Adding getCookies() to request interface.
|
||||
* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber()
|
||||
|
||||
## 2.6.4 - 2012-05-30
|
||||
|
||||
* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class.
|
||||
* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand
|
||||
* Bug: Fixing magic method command calls on clients
|
||||
* Bug: Email constraint only validates strings
|
||||
* Bug: Aggregate POST fields when POST files are present in curl handle
|
||||
* Bug: Fixing default User-Agent header
|
||||
* Bug: Only appending or prepending parameters in commands if they are specified
|
||||
* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes
|
||||
* Allowing the use of dot notation for class namespaces when using instance_of constraint
|
||||
* Added any_match validation constraint
|
||||
* Added an AsyncPlugin
|
||||
* Passing request object to the calculateWait method of the ExponentialBackoffPlugin
|
||||
* Allowing the result of a command object to be changed
|
||||
* Parsing location and type sub values when instantiating a service description rather than over and over at runtime
|
||||
|
||||
## 2.6.3 - 2012-05-23
|
||||
|
||||
* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options.
|
||||
* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields.
|
||||
* You can now use an array of data when creating PUT request bodies in the request factory.
|
||||
* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable.
|
||||
* [Http] Adding support for Content-Type in multipart POST uploads per upload
|
||||
* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1])
|
||||
* Adding more POST data operations for easier manipulation of POST data.
|
||||
* You can now set empty POST fields.
|
||||
* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files.
|
||||
* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate.
|
||||
* CS updates
|
||||
|
||||
## 2.6.2 - 2012-05-19
|
||||
|
||||
* [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method.
|
||||
|
||||
## 2.6.1 - 2012-05-19
|
||||
|
||||
* [BC] Removing 'path' support in service descriptions. Use 'uri'.
|
||||
* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache.
|
||||
* [BC] Removing Guzzle\Common\NullObject. Use https://github.com/mtdowling/NullObject if you need it.
|
||||
* [BC] Removing Guzzle\Common\XmlElement.
|
||||
* All commands, both dynamic and concrete, have ApiCommand objects.
|
||||
* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits.
|
||||
* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored.
|
||||
* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible.
|
||||
|
||||
## 2.6.0 - 2012-05-15
|
||||
|
||||
* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder
|
||||
* [BC] Executing a Command returns the result of the command rather than the command
|
||||
* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed.
|
||||
* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args.
|
||||
* [BC] Moving ResourceIterator* to Guzzle\Service\Resource
|
||||
* [BC] Completely refactored ResourceIterators to iterate over a cloned command object
|
||||
* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate
|
||||
* [BC] Guzzle\Guzzle is now deprecated
|
||||
* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject
|
||||
* Adding Guzzle\Version class to give version information about Guzzle
|
||||
* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate()
|
||||
* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data
|
||||
* ServiceDescription and ServiceBuilder are now cacheable using similar configs
|
||||
* Changing the format of XML and JSON service builder configs. Backwards compatible.
|
||||
* Cleaned up Cookie parsing
|
||||
* Trimming the default Guzzle User-Agent header
|
||||
* Adding a setOnComplete() method to Commands that is called when a command completes
|
||||
* Keeping track of requests that were mocked in the MockPlugin
|
||||
* Fixed a caching bug in the CacheAdapterFactory
|
||||
* Inspector objects can be injected into a Command object
|
||||
* Refactoring a lot of code and tests to be case insensitive when dealing with headers
|
||||
* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL
|
||||
* Adding the ability to set global option overrides to service builder configs
|
||||
* Adding the ability to include other service builder config files from within XML and JSON files
|
||||
* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method.
|
||||
|
||||
## 2.5.0 - 2012-05-08
|
||||
|
||||
* Major performance improvements
|
||||
* [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated.
|
||||
* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component.
|
||||
* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates. Use "{}"
|
||||
* Added the ability to passed parameters to all requests created by a client
|
||||
* Added callback functionality to the ExponentialBackoffPlugin
|
||||
* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies.
|
||||
* Rewinding request stream bodies when retrying requests
|
||||
* Exception is thrown when JSON response body cannot be decoded
|
||||
* Added configurable magic method calls to clients and commands. This is off by default.
|
||||
* Fixed a defect that added a hash to every parsed URL part
|
||||
* Fixed duplicate none generation for OauthPlugin.
|
||||
* Emitting an event each time a client is generated by a ServiceBuilder
|
||||
* Using an ApiParams object instead of a Collection for parameters of an ApiCommand
|
||||
* cache.* request parameters should be renamed to params.cache.*
|
||||
* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc.). See CurlHandle.
|
||||
* Added the ability to disable type validation of service descriptions
|
||||
* ServiceDescriptions and ServiceBuilders are now Serializable
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2011-2016 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
Vendored
+89
@@ -0,0 +1,89 @@
|
||||
Guzzle, PHP HTTP client
|
||||
=======================
|
||||
|
||||
[](https://travis-ci.org/guzzle/guzzle)
|
||||
|
||||
Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and
|
||||
trivial to integrate with web services.
|
||||
|
||||
- Simple interface for building query strings, POST requests, streaming large
|
||||
uploads, streaming large downloads, using HTTP cookies, uploading JSON data,
|
||||
etc...
|
||||
- Can send both synchronous and asynchronous requests using the same interface.
|
||||
- Uses PSR-7 interfaces for requests, responses, and streams. This allows you
|
||||
to utilize other PSR-7 compatible libraries with Guzzle.
|
||||
- Abstracts away the underlying HTTP transport, allowing you to write
|
||||
environment and transport agnostic code; i.e., no hard dependency on cURL,
|
||||
PHP streams, sockets, or non-blocking event loops.
|
||||
- Middleware system allows you to augment and compose client behavior.
|
||||
|
||||
```php
|
||||
$client = new \GuzzleHttp\Client();
|
||||
$res = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle');
|
||||
echo $res->getStatusCode();
|
||||
// 200
|
||||
echo $res->getHeaderLine('content-type');
|
||||
// 'application/json; charset=utf8'
|
||||
echo $res->getBody();
|
||||
// '{"id": 1420053, "name": "guzzle", ...}'
|
||||
|
||||
// Send an asynchronous request.
|
||||
$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org');
|
||||
$promise = $client->sendAsync($request)->then(function ($response) {
|
||||
echo 'I completed! ' . $response->getBody();
|
||||
});
|
||||
$promise->wait();
|
||||
```
|
||||
|
||||
## Help and docs
|
||||
|
||||
- [Documentation](http://guzzlephp.org/)
|
||||
- [Stack Overflow](http://stackoverflow.com/questions/tagged/guzzle)
|
||||
- [Gitter](https://gitter.im/guzzle/guzzle)
|
||||
|
||||
|
||||
## Installing Guzzle
|
||||
|
||||
The recommended way to install Guzzle is through
|
||||
[Composer](http://getcomposer.org).
|
||||
|
||||
```bash
|
||||
# Install Composer
|
||||
curl -sS https://getcomposer.org/installer | php
|
||||
```
|
||||
|
||||
Next, run the Composer command to install the latest stable version of Guzzle:
|
||||
|
||||
```bash
|
||||
php composer.phar require guzzlehttp/guzzle
|
||||
```
|
||||
|
||||
After installing, you need to require Composer's autoloader:
|
||||
|
||||
```php
|
||||
require 'vendor/autoload.php';
|
||||
```
|
||||
|
||||
You can then later update Guzzle using composer:
|
||||
|
||||
```bash
|
||||
composer.phar update
|
||||
```
|
||||
|
||||
|
||||
## Version Guidance
|
||||
|
||||
| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version |
|
||||
|---------|------------|---------------------|--------------|---------------------|---------------------|-------|-------------|
|
||||
| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >= 5.3.3 |
|
||||
| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >= 5.4 |
|
||||
| 5.x | Maintained | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >= 5.4 |
|
||||
| 6.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >= 5.5 |
|
||||
|
||||
[guzzle-3-repo]: https://github.com/guzzle/guzzle3
|
||||
[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x
|
||||
[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3
|
||||
[guzzle-6-repo]: https://github.com/guzzle/guzzle
|
||||
[guzzle-3-docs]: http://guzzle3.readthedocs.org/en/latest/
|
||||
[guzzle-5-docs]: http://guzzle.readthedocs.org/en/5.3/
|
||||
[guzzle-6-docs]: http://guzzle.readthedocs.org/en/latest/
|
||||
+1203
@@ -0,0 +1,1203 @@
|
||||
Guzzle Upgrade Guide
|
||||
====================
|
||||
|
||||
5.0 to 6.0
|
||||
----------
|
||||
|
||||
Guzzle now uses [PSR-7](http://www.php-fig.org/psr/psr-7/) for HTTP messages.
|
||||
Due to the fact that these messages are immutable, this prompted a refactoring
|
||||
of Guzzle to use a middleware based system rather than an event system. Any
|
||||
HTTP message interaction (e.g., `GuzzleHttp\Message\Request`) need to be
|
||||
updated to work with the new immutable PSR-7 request and response objects. Any
|
||||
event listeners or subscribers need to be updated to become middleware
|
||||
functions that wrap handlers (or are injected into a
|
||||
`GuzzleHttp\HandlerStack`).
|
||||
|
||||
- Removed `GuzzleHttp\BatchResults`
|
||||
- Removed `GuzzleHttp\Collection`
|
||||
- Removed `GuzzleHttp\HasDataTrait`
|
||||
- Removed `GuzzleHttp\ToArrayInterface`
|
||||
- The `guzzlehttp/streams` dependency has been removed. Stream functionality
|
||||
is now present in the `GuzzleHttp\Psr7` namespace provided by the
|
||||
`guzzlehttp/psr7` package.
|
||||
- Guzzle no longer uses ReactPHP promises and now uses the
|
||||
`guzzlehttp/promises` library. We use a custom promise library for three
|
||||
significant reasons:
|
||||
1. React promises (at the time of writing this) are recursive. Promise
|
||||
chaining and promise resolution will eventually blow the stack. Guzzle
|
||||
promises are not recursive as they use a sort of trampolining technique.
|
||||
Note: there has been movement in the React project to modify promises to
|
||||
no longer utilize recursion.
|
||||
2. Guzzle needs to have the ability to synchronously block on a promise to
|
||||
wait for a result. Guzzle promises allows this functionality (and does
|
||||
not require the use of recursion).
|
||||
3. Because we need to be able to wait on a result, doing so using React
|
||||
promises requires wrapping react promises with RingPHP futures. This
|
||||
overhead is no longer needed, reducing stack sizes, reducing complexity,
|
||||
and improving performance.
|
||||
- `GuzzleHttp\Mimetypes` has been moved to a function in
|
||||
`GuzzleHttp\Psr7\mimetype_from_extension` and
|
||||
`GuzzleHttp\Psr7\mimetype_from_filename`.
|
||||
- `GuzzleHttp\Query` and `GuzzleHttp\QueryParser` have been removed. Query
|
||||
strings must now be passed into request objects as strings, or provided to
|
||||
the `query` request option when creating requests with clients. The `query`
|
||||
option uses PHP's `http_build_query` to convert an array to a string. If you
|
||||
need a different serialization technique, you will need to pass the query
|
||||
string in as a string. There are a couple helper functions that will make
|
||||
working with query strings easier: `GuzzleHttp\Psr7\parse_query` and
|
||||
`GuzzleHttp\Psr7\build_query`.
|
||||
- Guzzle no longer has a dependency on RingPHP. Due to the use of a middleware
|
||||
system based on PSR-7, using RingPHP and it's middleware system as well adds
|
||||
more complexity than the benefits it provides. All HTTP handlers that were
|
||||
present in RingPHP have been modified to work directly with PSR-7 messages
|
||||
and placed in the `GuzzleHttp\Handler` namespace. This significantly reduces
|
||||
complexity in Guzzle, removes a dependency, and improves performance. RingPHP
|
||||
will be maintained for Guzzle 5 support, but will no longer be a part of
|
||||
Guzzle 6.
|
||||
- As Guzzle now uses a middleware based systems the event system and RingPHP
|
||||
integration has been removed. Note: while the event system has been removed,
|
||||
it is possible to add your own type of event system that is powered by the
|
||||
middleware system.
|
||||
- Removed the `Event` namespace.
|
||||
- Removed the `Subscriber` namespace.
|
||||
- Removed `Transaction` class
|
||||
- Removed `RequestFsm`
|
||||
- Removed `RingBridge`
|
||||
- `GuzzleHttp\Subscriber\Cookie` is now provided by
|
||||
`GuzzleHttp\Middleware::cookies`
|
||||
- `GuzzleHttp\Subscriber\HttpError` is now provided by
|
||||
`GuzzleHttp\Middleware::httpError`
|
||||
- `GuzzleHttp\Subscriber\History` is now provided by
|
||||
`GuzzleHttp\Middleware::history`
|
||||
- `GuzzleHttp\Subscriber\Mock` is now provided by
|
||||
`GuzzleHttp\Handler\MockHandler`
|
||||
- `GuzzleHttp\Subscriber\Prepare` is now provided by
|
||||
`GuzzleHttp\PrepareBodyMiddleware`
|
||||
- `GuzzleHttp\Subscriber\Redirect` is now provided by
|
||||
`GuzzleHttp\RedirectMiddleware`
|
||||
- Guzzle now uses `Psr\Http\Message\UriInterface` (implements in
|
||||
`GuzzleHttp\Psr7\Uri`) for URI support. `GuzzleHttp\Url` is now gone.
|
||||
- Static functions in `GuzzleHttp\Utils` have been moved to namespaced
|
||||
functions under the `GuzzleHttp` namespace. This requires either a Composer
|
||||
based autoloader or you to include functions.php.
|
||||
- `GuzzleHttp\ClientInterface::getDefaultOption` has been renamed to
|
||||
`GuzzleHttp\ClientInterface::getConfig`.
|
||||
- `GuzzleHttp\ClientInterface::setDefaultOption` has been removed.
|
||||
- The `json` and `xml` methods of response objects has been removed. With the
|
||||
migration to strictly adhering to PSR-7 as the interface for Guzzle messages,
|
||||
adding methods to message interfaces would actually require Guzzle messages
|
||||
to extend from PSR-7 messages rather then work with them directly.
|
||||
|
||||
## Migrating to middleware
|
||||
|
||||
The change to PSR-7 unfortunately required significant refactoring to Guzzle
|
||||
due to the fact that PSR-7 messages are immutable. Guzzle 5 relied on an event
|
||||
system from plugins. The event system relied on mutability of HTTP messages and
|
||||
side effects in order to work. With immutable messages, you have to change your
|
||||
workflow to become more about either returning a value (e.g., functional
|
||||
middlewares) or setting a value on an object. Guzzle v6 has chosen the
|
||||
functional middleware approach.
|
||||
|
||||
Instead of using the event system to listen for things like the `before` event,
|
||||
you now create a stack based middleware function that intercepts a request on
|
||||
the way in and the promise of the response on the way out. This is a much
|
||||
simpler and more predictable approach than the event system and works nicely
|
||||
with PSR-7 middleware. Due to the use of promises, the middleware system is
|
||||
also asynchronous.
|
||||
|
||||
v5:
|
||||
|
||||
```php
|
||||
use GuzzleHttp\Event\BeforeEvent;
|
||||
$client = new GuzzleHttp\Client();
|
||||
// Get the emitter and listen to the before event.
|
||||
$client->getEmitter()->on('before', function (BeforeEvent $e) {
|
||||
// Guzzle v5 events relied on mutation
|
||||
$e->getRequest()->setHeader('X-Foo', 'Bar');
|
||||
});
|
||||
```
|
||||
|
||||
v6:
|
||||
|
||||
In v6, you can modify the request before it is sent using the `mapRequest`
|
||||
middleware. The idiomatic way in v6 to modify the request/response lifecycle is
|
||||
to setup a handler middleware stack up front and inject the handler into a
|
||||
client.
|
||||
|
||||
```php
|
||||
use GuzzleHttp\Middleware;
|
||||
// Create a handler stack that has all of the default middlewares attached
|
||||
$handler = GuzzleHttp\HandlerStack::create();
|
||||
// Push the handler onto the handler stack
|
||||
$handler->push(Middleware::mapRequest(function (RequestInterface $request) {
|
||||
// Notice that we have to return a request object
|
||||
return $request->withHeader('X-Foo', 'Bar');
|
||||
}));
|
||||
// Inject the handler into the client
|
||||
$client = new GuzzleHttp\Client(['handler' => $handler]);
|
||||
```
|
||||
|
||||
## POST Requests
|
||||
|
||||
This version added the [`form_params`](http://guzzle.readthedocs.org/en/latest/request-options.html#form_params)
|
||||
and `multipart` request options. `form_params` is an associative array of
|
||||
strings or array of strings and is used to serialize an
|
||||
`application/x-www-form-urlencoded` POST request. The
|
||||
[`multipart`](http://guzzle.readthedocs.org/en/latest/request-options.html#multipart)
|
||||
option is now used to send a multipart/form-data POST request.
|
||||
|
||||
`GuzzleHttp\Post\PostFile` has been removed. Use the `multipart` option to add
|
||||
POST files to a multipart/form-data request.
|
||||
|
||||
The `body` option no longer accepts an array to send POST requests. Please use
|
||||
`multipart` or `form_params` instead.
|
||||
|
||||
The `base_url` option has been renamed to `base_uri`.
|
||||
|
||||
4.x to 5.0
|
||||
----------
|
||||
|
||||
## Rewritten Adapter Layer
|
||||
|
||||
Guzzle now uses [RingPHP](http://ringphp.readthedocs.org/en/latest) to send
|
||||
HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor
|
||||
is still supported, but it has now been renamed to `handler`. Instead of
|
||||
passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP
|
||||
`callable` that follows the RingPHP specification.
|
||||
|
||||
## Removed Fluent Interfaces
|
||||
|
||||
[Fluent interfaces were removed](http://ocramius.github.io/blog/fluent-interfaces-are-evil)
|
||||
from the following classes:
|
||||
|
||||
- `GuzzleHttp\Collection`
|
||||
- `GuzzleHttp\Url`
|
||||
- `GuzzleHttp\Query`
|
||||
- `GuzzleHttp\Post\PostBody`
|
||||
- `GuzzleHttp\Cookie\SetCookie`
|
||||
|
||||
## Removed functions.php
|
||||
|
||||
Removed "functions.php", so that Guzzle is truly PSR-4 compliant. The following
|
||||
functions can be used as replacements.
|
||||
|
||||
- `GuzzleHttp\json_decode` -> `GuzzleHttp\Utils::jsonDecode`
|
||||
- `GuzzleHttp\get_path` -> `GuzzleHttp\Utils::getPath`
|
||||
- `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path`
|
||||
- `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however,
|
||||
deprecated in favor of using `GuzzleHttp\Pool::batch()`.
|
||||
|
||||
The "procedural" global client has been removed with no replacement (e.g.,
|
||||
`GuzzleHttp\get()`, `GuzzleHttp\post()`, etc.). Use a `GuzzleHttp\Client`
|
||||
object as a replacement.
|
||||
|
||||
## `throwImmediately` has been removed
|
||||
|
||||
The concept of "throwImmediately" has been removed from exceptions and error
|
||||
events. This control mechanism was used to stop a transfer of concurrent
|
||||
requests from completing. This can now be handled by throwing the exception or
|
||||
by cancelling a pool of requests or each outstanding future request
|
||||
individually.
|
||||
|
||||
## headers event has been removed
|
||||
|
||||
Removed the "headers" event. This event was only useful for changing the
|
||||
body a response once the headers of the response were known. You can implement
|
||||
a similar behavior in a number of ways. One example might be to use a
|
||||
FnStream that has access to the transaction being sent. For example, when the
|
||||
first byte is written, you could check if the response headers match your
|
||||
expectations, and if so, change the actual stream body that is being
|
||||
written to.
|
||||
|
||||
## Updates to HTTP Messages
|
||||
|
||||
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
|
||||
the PSR-7 interfaces.
|
||||
|
||||
3.x to 4.0
|
||||
----------
|
||||
|
||||
## Overarching changes:
|
||||
|
||||
- Now requires PHP 5.4 or greater.
|
||||
- No longer requires cURL to send requests.
|
||||
- Guzzle no longer wraps every exception it throws. Only exceptions that are
|
||||
recoverable are now wrapped by Guzzle.
|
||||
- Various namespaces have been removed or renamed.
|
||||
- No longer requiring the Symfony EventDispatcher. A custom event dispatcher
|
||||
based on the Symfony EventDispatcher is
|
||||
now utilized in `GuzzleHttp\Event\EmitterInterface` (resulting in significant
|
||||
speed and functionality improvements).
|
||||
|
||||
Changes per Guzzle 3.x namespace are described below.
|
||||
|
||||
## Batch
|
||||
|
||||
The `Guzzle\Batch` namespace has been removed. This is best left to
|
||||
third-parties to implement on top of Guzzle's core HTTP library.
|
||||
|
||||
## Cache
|
||||
|
||||
The `Guzzle\Cache` namespace has been removed. (Todo: No suitable replacement
|
||||
has been implemented yet, but hoping to utilize a PSR cache interface).
|
||||
|
||||
## Common
|
||||
|
||||
- Removed all of the wrapped exceptions. It's better to use the standard PHP
|
||||
library for unrecoverable exceptions.
|
||||
- `FromConfigInterface` has been removed.
|
||||
- `Guzzle\Common\Version` has been removed. The VERSION constant can be found
|
||||
at `GuzzleHttp\ClientInterface::VERSION`.
|
||||
|
||||
### Collection
|
||||
|
||||
- `getAll` has been removed. Use `toArray` to convert a collection to an array.
|
||||
- `inject` has been removed.
|
||||
- `keySearch` has been removed.
|
||||
- `getPath` no longer supports wildcard expressions. Use something better like
|
||||
JMESPath for this.
|
||||
- `setPath` now supports appending to an existing array via the `[]` notation.
|
||||
|
||||
### Events
|
||||
|
||||
Guzzle no longer requires Symfony's EventDispatcher component. Guzzle now uses
|
||||
`GuzzleHttp\Event\Emitter`.
|
||||
|
||||
- `Symfony\Component\EventDispatcher\EventDispatcherInterface` is replaced by
|
||||
`GuzzleHttp\Event\EmitterInterface`.
|
||||
- `Symfony\Component\EventDispatcher\EventDispatcher` is replaced by
|
||||
`GuzzleHttp\Event\Emitter`.
|
||||
- `Symfony\Component\EventDispatcher\Event` is replaced by
|
||||
`GuzzleHttp\Event\Event`, and Guzzle now has an EventInterface in
|
||||
`GuzzleHttp\Event\EventInterface`.
|
||||
- `AbstractHasDispatcher` has moved to a trait, `HasEmitterTrait`, and
|
||||
`HasDispatcherInterface` has moved to `HasEmitterInterface`. Retrieving the
|
||||
event emitter of a request, client, etc. now uses the `getEmitter` method
|
||||
rather than the `getDispatcher` method.
|
||||
|
||||
#### Emitter
|
||||
|
||||
- Use the `once()` method to add a listener that automatically removes itself
|
||||
the first time it is invoked.
|
||||
- Use the `listeners()` method to retrieve a list of event listeners rather than
|
||||
the `getListeners()` method.
|
||||
- Use `emit()` instead of `dispatch()` to emit an event from an emitter.
|
||||
- Use `attach()` instead of `addSubscriber()` and `detach()` instead of
|
||||
`removeSubscriber()`.
|
||||
|
||||
```php
|
||||
$mock = new Mock();
|
||||
// 3.x
|
||||
$request->getEventDispatcher()->addSubscriber($mock);
|
||||
$request->getEventDispatcher()->removeSubscriber($mock);
|
||||
// 4.x
|
||||
$request->getEmitter()->attach($mock);
|
||||
$request->getEmitter()->detach($mock);
|
||||
```
|
||||
|
||||
Use the `on()` method to add a listener rather than the `addListener()` method.
|
||||
|
||||
```php
|
||||
// 3.x
|
||||
$request->getEventDispatcher()->addListener('foo', function (Event $event) { /* ... */ } );
|
||||
// 4.x
|
||||
$request->getEmitter()->on('foo', function (Event $event, $name) { /* ... */ } );
|
||||
```
|
||||
|
||||
## Http
|
||||
|
||||
### General changes
|
||||
|
||||
- The cacert.pem certificate has been moved to `src/cacert.pem`.
|
||||
- Added the concept of adapters that are used to transfer requests over the
|
||||
wire.
|
||||
- Simplified the event system.
|
||||
- Sending requests in parallel is still possible, but batching is no longer a
|
||||
concept of the HTTP layer. Instead, you must use the `complete` and `error`
|
||||
events to asynchronously manage parallel request transfers.
|
||||
- `Guzzle\Http\Url` has moved to `GuzzleHttp\Url`.
|
||||
- `Guzzle\Http\QueryString` has moved to `GuzzleHttp\Query`.
|
||||
- QueryAggregators have been rewritten so that they are simply callable
|
||||
functions.
|
||||
- `GuzzleHttp\StaticClient` has been removed. Use the functions provided in
|
||||
`functions.php` for an easy to use static client instance.
|
||||
- Exceptions in `GuzzleHttp\Exception` have been updated to all extend from
|
||||
`GuzzleHttp\Exception\TransferException`.
|
||||
|
||||
### Client
|
||||
|
||||
Calling methods like `get()`, `post()`, `head()`, etc. no longer create and
|
||||
return a request, but rather creates a request, sends the request, and returns
|
||||
the response.
|
||||
|
||||
```php
|
||||
// 3.0
|
||||
$request = $client->get('/');
|
||||
$response = $request->send();
|
||||
|
||||
// 4.0
|
||||
$response = $client->get('/');
|
||||
|
||||
// or, to mirror the previous behavior
|
||||
$request = $client->createRequest('GET', '/');
|
||||
$response = $client->send($request);
|
||||
```
|
||||
|
||||
`GuzzleHttp\ClientInterface` has changed.
|
||||
|
||||
- The `send` method no longer accepts more than one request. Use `sendAll` to
|
||||
send multiple requests in parallel.
|
||||
- `setUserAgent()` has been removed. Use a default request option instead. You
|
||||
could, for example, do something like:
|
||||
`$client->setConfig('defaults/headers/User-Agent', 'Foo/Bar ' . $client::getDefaultUserAgent())`.
|
||||
- `setSslVerification()` has been removed. Use default request options instead,
|
||||
like `$client->setConfig('defaults/verify', true)`.
|
||||
|
||||
`GuzzleHttp\Client` has changed.
|
||||
|
||||
- The constructor now accepts only an associative array. You can include a
|
||||
`base_url` string or array to use a URI template as the base URL of a client.
|
||||
You can also specify a `defaults` key that is an associative array of default
|
||||
request options. You can pass an `adapter` to use a custom adapter,
|
||||
`batch_adapter` to use a custom adapter for sending requests in parallel, or
|
||||
a `message_factory` to change the factory used to create HTTP requests and
|
||||
responses.
|
||||
- The client no longer emits a `client.create_request` event.
|
||||
- Creating requests with a client no longer automatically utilize a URI
|
||||
template. You must pass an array into a creational method (e.g.,
|
||||
`createRequest`, `get`, `put`, etc.) in order to expand a URI template.
|
||||
|
||||
### Messages
|
||||
|
||||
Messages no longer have references to their counterparts (i.e., a request no
|
||||
longer has a reference to it's response, and a response no loger has a
|
||||
reference to its request). This association is now managed through a
|
||||
`GuzzleHttp\Adapter\TransactionInterface` object. You can get references to
|
||||
these transaction objects using request events that are emitted over the
|
||||
lifecycle of a request.
|
||||
|
||||
#### Requests with a body
|
||||
|
||||
- `GuzzleHttp\Message\EntityEnclosingRequest` and
|
||||
`GuzzleHttp\Message\EntityEnclosingRequestInterface` have been removed. The
|
||||
separation between requests that contain a body and requests that do not
|
||||
contain a body has been removed, and now `GuzzleHttp\Message\RequestInterface`
|
||||
handles both use cases.
|
||||
- Any method that previously accepts a `GuzzleHttp\Response` object now accept a
|
||||
`GuzzleHttp\Message\ResponseInterface`.
|
||||
- `GuzzleHttp\Message\RequestFactoryInterface` has been renamed to
|
||||
`GuzzleHttp\Message\MessageFactoryInterface`. This interface is used to create
|
||||
both requests and responses and is implemented in
|
||||
`GuzzleHttp\Message\MessageFactory`.
|
||||
- POST field and file methods have been removed from the request object. You
|
||||
must now use the methods made available to `GuzzleHttp\Post\PostBodyInterface`
|
||||
to control the format of a POST body. Requests that are created using a
|
||||
standard `GuzzleHttp\Message\MessageFactoryInterface` will automatically use
|
||||
a `GuzzleHttp\Post\PostBody` body if the body was passed as an array or if
|
||||
the method is POST and no body is provided.
|
||||
|
||||
```php
|
||||
$request = $client->createRequest('POST', '/');
|
||||
$request->getBody()->setField('foo', 'bar');
|
||||
$request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r')));
|
||||
```
|
||||
|
||||
#### Headers
|
||||
|
||||
- `GuzzleHttp\Message\Header` has been removed. Header values are now simply
|
||||
represented by an array of values or as a string. Header values are returned
|
||||
as a string by default when retrieving a header value from a message. You can
|
||||
pass an optional argument of `true` to retrieve a header value as an array
|
||||
of strings instead of a single concatenated string.
|
||||
- `GuzzleHttp\PostFile` and `GuzzleHttp\PostFileInterface` have been moved to
|
||||
`GuzzleHttp\Post`. This interface has been simplified and now allows the
|
||||
addition of arbitrary headers.
|
||||
- Custom headers like `GuzzleHttp\Message\Header\Link` have been removed. Most
|
||||
of the custom headers are now handled separately in specific
|
||||
subscribers/plugins, and `GuzzleHttp\Message\HeaderValues::parseParams()` has
|
||||
been updated to properly handle headers that contain parameters (like the
|
||||
`Link` header).
|
||||
|
||||
#### Responses
|
||||
|
||||
- `GuzzleHttp\Message\Response::getInfo()` and
|
||||
`GuzzleHttp\Message\Response::setInfo()` have been removed. Use the event
|
||||
system to retrieve this type of information.
|
||||
- `GuzzleHttp\Message\Response::getRawHeaders()` has been removed.
|
||||
- `GuzzleHttp\Message\Response::getMessage()` has been removed.
|
||||
- `GuzzleHttp\Message\Response::calculateAge()` and other cache specific
|
||||
methods have moved to the CacheSubscriber.
|
||||
- Header specific helper functions like `getContentMd5()` have been removed.
|
||||
Just use `getHeader('Content-MD5')` instead.
|
||||
- `GuzzleHttp\Message\Response::setRequest()` and
|
||||
`GuzzleHttp\Message\Response::getRequest()` have been removed. Use the event
|
||||
system to work with request and response objects as a transaction.
|
||||
- `GuzzleHttp\Message\Response::getRedirectCount()` has been removed. Use the
|
||||
Redirect subscriber instead.
|
||||
- `GuzzleHttp\Message\Response::isSuccessful()` and other related methods have
|
||||
been removed. Use `getStatusCode()` instead.
|
||||
|
||||
#### Streaming responses
|
||||
|
||||
Streaming requests can now be created by a client directly, returning a
|
||||
`GuzzleHttp\Message\ResponseInterface` object that contains a body stream
|
||||
referencing an open PHP HTTP stream.
|
||||
|
||||
```php
|
||||
// 3.0
|
||||
use Guzzle\Stream\PhpStreamRequestFactory;
|
||||
$request = $client->get('/');
|
||||
$factory = new PhpStreamRequestFactory();
|
||||
$stream = $factory->fromRequest($request);
|
||||
$data = $stream->read(1024);
|
||||
|
||||
// 4.0
|
||||
$response = $client->get('/', ['stream' => true]);
|
||||
// Read some data off of the stream in the response body
|
||||
$data = $response->getBody()->read(1024);
|
||||
```
|
||||
|
||||
#### Redirects
|
||||
|
||||
The `configureRedirects()` method has been removed in favor of a
|
||||
`allow_redirects` request option.
|
||||
|
||||
```php
|
||||
// Standard redirects with a default of a max of 5 redirects
|
||||
$request = $client->createRequest('GET', '/', ['allow_redirects' => true]);
|
||||
|
||||
// Strict redirects with a custom number of redirects
|
||||
$request = $client->createRequest('GET', '/', [
|
||||
'allow_redirects' => ['max' => 5, 'strict' => true]
|
||||
]);
|
||||
```
|
||||
|
||||
#### EntityBody
|
||||
|
||||
EntityBody interfaces and classes have been removed or moved to
|
||||
`GuzzleHttp\Stream`. All classes and interfaces that once required
|
||||
`GuzzleHttp\EntityBodyInterface` now require
|
||||
`GuzzleHttp\Stream\StreamInterface`. Creating a new body for a request no
|
||||
longer uses `GuzzleHttp\EntityBody::factory` but now uses
|
||||
`GuzzleHttp\Stream\Stream::factory` or even better:
|
||||
`GuzzleHttp\Stream\create()`.
|
||||
|
||||
- `Guzzle\Http\EntityBodyInterface` is now `GuzzleHttp\Stream\StreamInterface`
|
||||
- `Guzzle\Http\EntityBody` is now `GuzzleHttp\Stream\Stream`
|
||||
- `Guzzle\Http\CachingEntityBody` is now `GuzzleHttp\Stream\CachingStream`
|
||||
- `Guzzle\Http\ReadLimitEntityBody` is now `GuzzleHttp\Stream\LimitStream`
|
||||
- `Guzzle\Http\IoEmittyinEntityBody` has been removed.
|
||||
|
||||
#### Request lifecycle events
|
||||
|
||||
Requests previously submitted a large number of requests. The number of events
|
||||
emitted over the lifecycle of a request has been significantly reduced to make
|
||||
it easier to understand how to extend the behavior of a request. All events
|
||||
emitted during the lifecycle of a request now emit a custom
|
||||
`GuzzleHttp\Event\EventInterface` object that contains context providing
|
||||
methods and a way in which to modify the transaction at that specific point in
|
||||
time (e.g., intercept the request and set a response on the transaction).
|
||||
|
||||
- `request.before_send` has been renamed to `before` and now emits a
|
||||
`GuzzleHttp\Event\BeforeEvent`
|
||||
- `request.complete` has been renamed to `complete` and now emits a
|
||||
`GuzzleHttp\Event\CompleteEvent`.
|
||||
- `request.sent` has been removed. Use `complete`.
|
||||
- `request.success` has been removed. Use `complete`.
|
||||
- `error` is now an event that emits a `GuzzleHttp\Event\ErrorEvent`.
|
||||
- `request.exception` has been removed. Use `error`.
|
||||
- `request.receive.status_line` has been removed.
|
||||
- `curl.callback.progress` has been removed. Use a custom `StreamInterface` to
|
||||
maintain a status update.
|
||||
- `curl.callback.write` has been removed. Use a custom `StreamInterface` to
|
||||
intercept writes.
|
||||
- `curl.callback.read` has been removed. Use a custom `StreamInterface` to
|
||||
intercept reads.
|
||||
|
||||
`headers` is a new event that is emitted after the response headers of a
|
||||
request have been received before the body of the response is downloaded. This
|
||||
event emits a `GuzzleHttp\Event\HeadersEvent`.
|
||||
|
||||
You can intercept a request and inject a response using the `intercept()` event
|
||||
of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and
|
||||
`GuzzleHttp\Event\ErrorEvent` event.
|
||||
|
||||
See: http://docs.guzzlephp.org/en/latest/events.html
|
||||
|
||||
## Inflection
|
||||
|
||||
The `Guzzle\Inflection` namespace has been removed. This is not a core concern
|
||||
of Guzzle.
|
||||
|
||||
## Iterator
|
||||
|
||||
The `Guzzle\Iterator` namespace has been removed.
|
||||
|
||||
- `Guzzle\Iterator\AppendIterator`, `Guzzle\Iterator\ChunkedIterator`, and
|
||||
`Guzzle\Iterator\MethodProxyIterator` are nice, but not a core requirement of
|
||||
Guzzle itself.
|
||||
- `Guzzle\Iterator\FilterIterator` is no longer needed because an equivalent
|
||||
class is shipped with PHP 5.4.
|
||||
- `Guzzle\Iterator\MapIterator` is not really needed when using PHP 5.5 because
|
||||
it's easier to just wrap an iterator in a generator that maps values.
|
||||
|
||||
For a replacement of these iterators, see https://github.com/nikic/iter
|
||||
|
||||
## Log
|
||||
|
||||
The LogPlugin has moved to https://github.com/guzzle/log-subscriber. The
|
||||
`Guzzle\Log` namespace has been removed. Guzzle now relies on
|
||||
`Psr\Log\LoggerInterface` for all logging. The MessageFormatter class has been
|
||||
moved to `GuzzleHttp\Subscriber\Log\Formatter`.
|
||||
|
||||
## Parser
|
||||
|
||||
The `Guzzle\Parser` namespace has been removed. This was previously used to
|
||||
make it possible to plug in custom parsers for cookies, messages, URI
|
||||
templates, and URLs; however, this level of complexity is not needed in Guzzle
|
||||
so it has been removed.
|
||||
|
||||
- Cookie: Cookie parsing logic has been moved to
|
||||
`GuzzleHttp\Cookie\SetCookie::fromString`.
|
||||
- Message: Message parsing logic for both requests and responses has been moved
|
||||
to `GuzzleHttp\Message\MessageFactory::fromMessage`. Message parsing is only
|
||||
used in debugging or deserializing messages, so it doesn't make sense for
|
||||
Guzzle as a library to add this level of complexity to parsing messages.
|
||||
- UriTemplate: URI template parsing has been moved to
|
||||
`GuzzleHttp\UriTemplate`. The Guzzle library will automatically use the PECL
|
||||
URI template library if it is installed.
|
||||
- Url: URL parsing is now performed in `GuzzleHttp\Url::fromString` (previously
|
||||
it was `Guzzle\Http\Url::factory()`). If custom URL parsing is necessary,
|
||||
then developers are free to subclass `GuzzleHttp\Url`.
|
||||
|
||||
## Plugin
|
||||
|
||||
The `Guzzle\Plugin` namespace has been renamed to `GuzzleHttp\Subscriber`.
|
||||
Several plugins are shipping with the core Guzzle library under this namespace.
|
||||
|
||||
- `GuzzleHttp\Subscriber\Cookie`: Replaces the old CookiePlugin. Cookie jar
|
||||
code has moved to `GuzzleHttp\Cookie`.
|
||||
- `GuzzleHttp\Subscriber\History`: Replaces the old HistoryPlugin.
|
||||
- `GuzzleHttp\Subscriber\HttpError`: Throws errors when a bad HTTP response is
|
||||
received.
|
||||
- `GuzzleHttp\Subscriber\Mock`: Replaces the old MockPlugin.
|
||||
- `GuzzleHttp\Subscriber\Prepare`: Prepares the body of a request just before
|
||||
sending. This subscriber is attached to all requests by default.
|
||||
- `GuzzleHttp\Subscriber\Redirect`: Replaces the RedirectPlugin.
|
||||
|
||||
The following plugins have been removed (third-parties are free to re-implement
|
||||
these if needed):
|
||||
|
||||
- `GuzzleHttp\Plugin\Async` has been removed.
|
||||
- `GuzzleHttp\Plugin\CurlAuth` has been removed.
|
||||
- `GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePlugin` has been removed. This
|
||||
functionality should instead be implemented with event listeners that occur
|
||||
after normal response parsing occurs in the guzzle/command package.
|
||||
|
||||
The following plugins are not part of the core Guzzle package, but are provided
|
||||
in separate repositories:
|
||||
|
||||
- `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be much simpler
|
||||
to build custom retry policies using simple functions rather than various
|
||||
chained classes. See: https://github.com/guzzle/retry-subscriber
|
||||
- `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to
|
||||
https://github.com/guzzle/cache-subscriber
|
||||
- `Guzzle\Http\Plugin\Log\LogPlugin` has moved to
|
||||
https://github.com/guzzle/log-subscriber
|
||||
- `Guzzle\Http\Plugin\Md5\Md5Plugin` has moved to
|
||||
https://github.com/guzzle/message-integrity-subscriber
|
||||
- `Guzzle\Http\Plugin\Mock\MockPlugin` has moved to
|
||||
`GuzzleHttp\Subscriber\MockSubscriber`.
|
||||
- `Guzzle\Http\Plugin\Oauth\OauthPlugin` has moved to
|
||||
https://github.com/guzzle/oauth-subscriber
|
||||
|
||||
## Service
|
||||
|
||||
The service description layer of Guzzle has moved into two separate packages:
|
||||
|
||||
- http://github.com/guzzle/command Provides a high level abstraction over web
|
||||
services by representing web service operations using commands.
|
||||
- http://github.com/guzzle/guzzle-services Provides an implementation of
|
||||
guzzle/command that provides request serialization and response parsing using
|
||||
Guzzle service descriptions.
|
||||
|
||||
## Stream
|
||||
|
||||
Stream have moved to a separate package available at
|
||||
https://github.com/guzzle/streams.
|
||||
|
||||
`Guzzle\Stream\StreamInterface` has been given a large update to cleanly take
|
||||
on the responsibilities of `Guzzle\Http\EntityBody` and
|
||||
`Guzzle\Http\EntityBodyInterface` now that they have been removed. The number
|
||||
of methods implemented by the `StreamInterface` has been drastically reduced to
|
||||
allow developers to more easily extend and decorate stream behavior.
|
||||
|
||||
## Removed methods from StreamInterface
|
||||
|
||||
- `getStream` and `setStream` have been removed to better encapsulate streams.
|
||||
- `getMetadata` and `setMetadata` have been removed in favor of
|
||||
`GuzzleHttp\Stream\MetadataStreamInterface`.
|
||||
- `getWrapper`, `getWrapperData`, `getStreamType`, and `getUri` have all been
|
||||
removed. This data is accessible when
|
||||
using streams that implement `GuzzleHttp\Stream\MetadataStreamInterface`.
|
||||
- `rewind` has been removed. Use `seek(0)` for a similar behavior.
|
||||
|
||||
## Renamed methods
|
||||
|
||||
- `detachStream` has been renamed to `detach`.
|
||||
- `feof` has been renamed to `eof`.
|
||||
- `ftell` has been renamed to `tell`.
|
||||
- `readLine` has moved from an instance method to a static class method of
|
||||
`GuzzleHttp\Stream\Stream`.
|
||||
|
||||
## Metadata streams
|
||||
|
||||
`GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams
|
||||
that contain additional metadata accessible via `getMetadata()`.
|
||||
`GuzzleHttp\Stream\StreamInterface::getMetadata` and
|
||||
`GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed.
|
||||
|
||||
## StreamRequestFactory
|
||||
|
||||
The entire concept of the StreamRequestFactory has been removed. The way this
|
||||
was used in Guzzle 3 broke the actual interface of sending streaming requests
|
||||
(instead of getting back a Response, you got a StreamInterface). Streaming
|
||||
PHP requests are now implemented through the `GuzzleHttp\Adapter\StreamAdapter`.
|
||||
|
||||
3.6 to 3.7
|
||||
----------
|
||||
|
||||
### Deprecations
|
||||
|
||||
- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.:
|
||||
|
||||
```php
|
||||
\Guzzle\Common\Version::$emitWarnings = true;
|
||||
```
|
||||
|
||||
The following APIs and options have been marked as deprecated:
|
||||
|
||||
- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead.
|
||||
- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
|
||||
- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
|
||||
- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
|
||||
- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
|
||||
- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
|
||||
- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
|
||||
- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
|
||||
- Marked `Guzzle\Common\Collection::inject()` as deprecated.
|
||||
- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use
|
||||
`$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or
|
||||
`$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));`
|
||||
|
||||
3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational
|
||||
request methods. When paired with a client's configuration settings, these options allow you to specify default settings
|
||||
for various aspects of a request. Because these options make other previous configuration options redundant, several
|
||||
configuration options and methods of a client and AbstractCommand have been deprecated.
|
||||
|
||||
- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`.
|
||||
- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`.
|
||||
- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')`
|
||||
- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0
|
||||
|
||||
$command = $client->getCommand('foo', array(
|
||||
'command.headers' => array('Test' => '123'),
|
||||
'command.response_body' => '/path/to/file'
|
||||
));
|
||||
|
||||
// Should be changed to:
|
||||
|
||||
$command = $client->getCommand('foo', array(
|
||||
'command.request_options' => array(
|
||||
'headers' => array('Test' => '123'),
|
||||
'save_as' => '/path/to/file'
|
||||
)
|
||||
));
|
||||
|
||||
### Interface changes
|
||||
|
||||
Additions and changes (you will need to update any implementations or subclasses you may have created):
|
||||
|
||||
- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
|
||||
createRequest, head, delete, put, patch, post, options, prepareRequest
|
||||
- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
|
||||
- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
|
||||
- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
|
||||
`Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
|
||||
resource, string, or EntityBody into the $options parameter to specify the download location of the response.
|
||||
- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
|
||||
default `array()`
|
||||
- Added `Guzzle\Stream\StreamInterface::isRepeatable`
|
||||
- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
|
||||
|
||||
The following methods were removed from interfaces. All of these methods are still available in the concrete classes
|
||||
that implement them, but you should update your code to use alternative methods:
|
||||
|
||||
- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
|
||||
`$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
|
||||
`$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or
|
||||
`$client->setDefaultOption('headers/{header_name}', 'value')`. or
|
||||
`$client->setDefaultOption('headers', array('header_name' => 'value'))`.
|
||||
- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`.
|
||||
- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail.
|
||||
- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail.
|
||||
- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail.
|
||||
- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin.
|
||||
- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin.
|
||||
- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin.
|
||||
|
||||
### Cache plugin breaking changes
|
||||
|
||||
- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
|
||||
CacheStorageInterface. These two objects and interface will be removed in a future version.
|
||||
- Always setting X-cache headers on cached responses
|
||||
- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
|
||||
- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
|
||||
$request, Response $response);`
|
||||
- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
|
||||
- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
|
||||
- Added `CacheStorageInterface::purge($url)`
|
||||
- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
|
||||
$plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
|
||||
CanCacheStrategyInterface $canCache = null)`
|
||||
- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
|
||||
|
||||
3.5 to 3.6
|
||||
----------
|
||||
|
||||
* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
|
||||
* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
|
||||
* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
|
||||
For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader().
|
||||
Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request.
|
||||
* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
|
||||
HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
|
||||
CacheControl header implementation.
|
||||
* Moved getLinks() from Response to just be used on a Link header object.
|
||||
|
||||
If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the
|
||||
HeaderInterface (e.g. toArray(), getAll(), etc.).
|
||||
|
||||
### Interface changes
|
||||
|
||||
* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
|
||||
* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
|
||||
* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
|
||||
Guzzle\Http\Curl\RequestMediator
|
||||
* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
|
||||
* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
|
||||
* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
|
||||
|
||||
### Removed deprecated functions
|
||||
|
||||
* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
|
||||
* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
|
||||
|
||||
### Deprecations
|
||||
|
||||
* The ability to case-insensitively search for header values
|
||||
* Guzzle\Http\Message\Header::hasExactHeader
|
||||
* Guzzle\Http\Message\Header::raw. Use getAll()
|
||||
* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
|
||||
instead.
|
||||
|
||||
### Other changes
|
||||
|
||||
* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
|
||||
* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle
|
||||
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
|
||||
`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
|
||||
* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
|
||||
|
||||
3.3 to 3.4
|
||||
----------
|
||||
|
||||
Base URLs of a client now follow the rules of http://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs.
|
||||
|
||||
3.2 to 3.3
|
||||
----------
|
||||
|
||||
### Response::getEtag() quote stripping removed
|
||||
|
||||
`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header
|
||||
|
||||
### Removed `Guzzle\Http\Utils`
|
||||
|
||||
The `Guzzle\Http\Utils` class was removed. This class was only used for testing.
|
||||
|
||||
### Stream wrapper and type
|
||||
|
||||
`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getStreamType()` are no longer converted to lowercase.
|
||||
|
||||
### curl.emit_io became emit_io
|
||||
|
||||
Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the
|
||||
'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
|
||||
|
||||
3.1 to 3.2
|
||||
----------
|
||||
|
||||
### CurlMulti is no longer reused globally
|
||||
|
||||
Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added
|
||||
to a single client can pollute requests dispatched from other clients.
|
||||
|
||||
If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the
|
||||
ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is
|
||||
created.
|
||||
|
||||
```php
|
||||
$multi = new Guzzle\Http\Curl\CurlMulti();
|
||||
$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json');
|
||||
$builder->addListener('service_builder.create_client', function ($event) use ($multi) {
|
||||
$event['client']->setCurlMulti($multi);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### No default path
|
||||
|
||||
URLs no longer have a default path value of '/' if no path was specified.
|
||||
|
||||
Before:
|
||||
|
||||
```php
|
||||
$request = $client->get('http://www.foo.com');
|
||||
echo $request->getUrl();
|
||||
// >> http://www.foo.com/
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```php
|
||||
$request = $client->get('http://www.foo.com');
|
||||
echo $request->getUrl();
|
||||
// >> http://www.foo.com
|
||||
```
|
||||
|
||||
### Less verbose BadResponseException
|
||||
|
||||
The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and
|
||||
response information. You can, however, get access to the request and response object by calling `getRequest()` or
|
||||
`getResponse()` on the exception object.
|
||||
|
||||
### Query parameter aggregation
|
||||
|
||||
Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a
|
||||
setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is
|
||||
responsible for handling the aggregation of multi-valued query string variables into a flattened hash.
|
||||
|
||||
2.8 to 3.x
|
||||
----------
|
||||
|
||||
### Guzzle\Service\Inspector
|
||||
|
||||
Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig`
|
||||
|
||||
**Before**
|
||||
|
||||
```php
|
||||
use Guzzle\Service\Inspector;
|
||||
|
||||
class YourClient extends \Guzzle\Service\Client
|
||||
{
|
||||
public static function factory($config = array())
|
||||
{
|
||||
$default = array();
|
||||
$required = array('base_url', 'username', 'api_key');
|
||||
$config = Inspector::fromConfig($config, $default, $required);
|
||||
|
||||
$client = new self(
|
||||
$config->get('base_url'),
|
||||
$config->get('username'),
|
||||
$config->get('api_key')
|
||||
);
|
||||
$client->setConfig($config);
|
||||
|
||||
$client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
|
||||
|
||||
return $client;
|
||||
}
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```php
|
||||
use Guzzle\Common\Collection;
|
||||
|
||||
class YourClient extends \Guzzle\Service\Client
|
||||
{
|
||||
public static function factory($config = array())
|
||||
{
|
||||
$default = array();
|
||||
$required = array('base_url', 'username', 'api_key');
|
||||
$config = Collection::fromConfig($config, $default, $required);
|
||||
|
||||
$client = new self(
|
||||
$config->get('base_url'),
|
||||
$config->get('username'),
|
||||
$config->get('api_key')
|
||||
);
|
||||
$client->setConfig($config);
|
||||
|
||||
$client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
|
||||
|
||||
return $client;
|
||||
}
|
||||
```
|
||||
|
||||
### Convert XML Service Descriptions to JSON
|
||||
|
||||
**Before**
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<client>
|
||||
<commands>
|
||||
<!-- Groups -->
|
||||
<command name="list_groups" method="GET" uri="groups.json">
|
||||
<doc>Get a list of groups</doc>
|
||||
</command>
|
||||
<command name="search_groups" method="GET" uri='search.json?query="{{query}} type:group"'>
|
||||
<doc>Uses a search query to get a list of groups</doc>
|
||||
<param name="query" type="string" required="true" />
|
||||
</command>
|
||||
<command name="create_group" method="POST" uri="groups.json">
|
||||
<doc>Create a group</doc>
|
||||
<param name="data" type="array" location="body" filters="json_encode" doc="Group JSON"/>
|
||||
<param name="Content-Type" location="header" static="application/json"/>
|
||||
</command>
|
||||
<command name="delete_group" method="DELETE" uri="groups/{{id}}.json">
|
||||
<doc>Delete a group by ID</doc>
|
||||
<param name="id" type="integer" required="true"/>
|
||||
</command>
|
||||
<command name="get_group" method="GET" uri="groups/{{id}}.json">
|
||||
<param name="id" type="integer" required="true"/>
|
||||
</command>
|
||||
<command name="update_group" method="PUT" uri="groups/{{id}}.json">
|
||||
<doc>Update a group</doc>
|
||||
<param name="id" type="integer" required="true"/>
|
||||
<param name="data" type="array" location="body" filters="json_encode" doc="Group JSON"/>
|
||||
<param name="Content-Type" location="header" static="application/json"/>
|
||||
</command>
|
||||
</commands>
|
||||
</client>
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Zendesk REST API v2",
|
||||
"apiVersion": "2012-12-31",
|
||||
"description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users",
|
||||
"operations": {
|
||||
"list_groups": {
|
||||
"httpMethod":"GET",
|
||||
"uri": "groups.json",
|
||||
"summary": "Get a list of groups"
|
||||
},
|
||||
"search_groups":{
|
||||
"httpMethod":"GET",
|
||||
"uri": "search.json?query=\"{query} type:group\"",
|
||||
"summary": "Uses a search query to get a list of groups",
|
||||
"parameters":{
|
||||
"query":{
|
||||
"location": "uri",
|
||||
"description":"Zendesk Search Query",
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"create_group": {
|
||||
"httpMethod":"POST",
|
||||
"uri": "groups.json",
|
||||
"summary": "Create a group",
|
||||
"parameters":{
|
||||
"data": {
|
||||
"type": "array",
|
||||
"location": "body",
|
||||
"description":"Group JSON",
|
||||
"filters": "json_encode",
|
||||
"required": true
|
||||
},
|
||||
"Content-Type":{
|
||||
"type": "string",
|
||||
"location":"header",
|
||||
"static": "application/json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete_group": {
|
||||
"httpMethod":"DELETE",
|
||||
"uri": "groups/{id}.json",
|
||||
"summary": "Delete a group",
|
||||
"parameters":{
|
||||
"id":{
|
||||
"location": "uri",
|
||||
"description":"Group to delete by ID",
|
||||
"type": "integer",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"get_group": {
|
||||
"httpMethod":"GET",
|
||||
"uri": "groups/{id}.json",
|
||||
"summary": "Get a ticket",
|
||||
"parameters":{
|
||||
"id":{
|
||||
"location": "uri",
|
||||
"description":"Group to get by ID",
|
||||
"type": "integer",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"update_group": {
|
||||
"httpMethod":"PUT",
|
||||
"uri": "groups/{id}.json",
|
||||
"summary": "Update a group",
|
||||
"parameters":{
|
||||
"id": {
|
||||
"location": "uri",
|
||||
"description":"Group to update by ID",
|
||||
"type": "integer",
|
||||
"required": true
|
||||
},
|
||||
"data": {
|
||||
"type": "array",
|
||||
"location": "body",
|
||||
"description":"Group JSON",
|
||||
"filters": "json_encode",
|
||||
"required": true
|
||||
},
|
||||
"Content-Type":{
|
||||
"type": "string",
|
||||
"location":"header",
|
||||
"static": "application/json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Guzzle\Service\Description\ServiceDescription
|
||||
|
||||
Commands are now called Operations
|
||||
|
||||
**Before**
|
||||
|
||||
```php
|
||||
use Guzzle\Service\Description\ServiceDescription;
|
||||
|
||||
$sd = new ServiceDescription();
|
||||
$sd->getCommands(); // @returns ApiCommandInterface[]
|
||||
$sd->hasCommand($name);
|
||||
$sd->getCommand($name); // @returns ApiCommandInterface|null
|
||||
$sd->addCommand($command); // @param ApiCommandInterface $command
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```php
|
||||
use Guzzle\Service\Description\ServiceDescription;
|
||||
|
||||
$sd = new ServiceDescription();
|
||||
$sd->getOperations(); // @returns OperationInterface[]
|
||||
$sd->hasOperation($name);
|
||||
$sd->getOperation($name); // @returns OperationInterface|null
|
||||
$sd->addOperation($operation); // @param OperationInterface $operation
|
||||
```
|
||||
|
||||
### Guzzle\Common\Inflection\Inflector
|
||||
|
||||
Namespace is now `Guzzle\Inflection\Inflector`
|
||||
|
||||
### Guzzle\Http\Plugin
|
||||
|
||||
Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below.
|
||||
|
||||
### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log
|
||||
|
||||
Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively.
|
||||
|
||||
**Before**
|
||||
|
||||
```php
|
||||
use Guzzle\Common\Log\ClosureLogAdapter;
|
||||
use Guzzle\Http\Plugin\LogPlugin;
|
||||
|
||||
/** @var \Guzzle\Http\Client */
|
||||
$client;
|
||||
|
||||
// $verbosity is an integer indicating desired message verbosity level
|
||||
$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE);
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```php
|
||||
use Guzzle\Log\ClosureLogAdapter;
|
||||
use Guzzle\Log\MessageFormatter;
|
||||
use Guzzle\Plugin\Log\LogPlugin;
|
||||
|
||||
/** @var \Guzzle\Http\Client */
|
||||
$client;
|
||||
|
||||
// $format is a string indicating desired message format -- @see MessageFormatter
|
||||
$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT);
|
||||
```
|
||||
|
||||
### Guzzle\Http\Plugin\CurlAuthPlugin
|
||||
|
||||
Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`.
|
||||
|
||||
### Guzzle\Http\Plugin\ExponentialBackoffPlugin
|
||||
|
||||
Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes.
|
||||
|
||||
**Before**
|
||||
|
||||
```php
|
||||
use Guzzle\Http\Plugin\ExponentialBackoffPlugin;
|
||||
|
||||
$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge(
|
||||
ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429)
|
||||
));
|
||||
|
||||
$client->addSubscriber($backoffPlugin);
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```php
|
||||
use Guzzle\Plugin\Backoff\BackoffPlugin;
|
||||
use Guzzle\Plugin\Backoff\HttpBackoffStrategy;
|
||||
|
||||
// Use convenient factory method instead -- see implementation for ideas of what
|
||||
// you can do with chaining backoff strategies
|
||||
$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge(
|
||||
HttpBackoffStrategy::getDefaultFailureCodes(), array(429)
|
||||
));
|
||||
$client->addSubscriber($backoffPlugin);
|
||||
```
|
||||
|
||||
### Known Issues
|
||||
|
||||
#### [BUG] Accept-Encoding header behavior changed unintentionally.
|
||||
|
||||
(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e)
|
||||
|
||||
In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to
|
||||
properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen.
|
||||
See issue #217 for a workaround, or use a version containing the fix.
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"type": "library",
|
||||
"description": "Guzzle is a PHP HTTP client library",
|
||||
"keywords": ["framework", "http", "rest", "web service", "curl", "client", "HTTP client"],
|
||||
"homepage": "http://guzzlephp.org/",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.5",
|
||||
"guzzlehttp/psr7": "^1.4",
|
||||
"guzzlehttp/promises": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-curl": "*",
|
||||
"phpunit/phpunit": "^4.0 || ^5.0",
|
||||
"psr/log": "^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"files": ["src/functions_include.php"],
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"suggest": {
|
||||
"psr/log": "Required for using the Log middleware"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "6.2-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
+414
@@ -0,0 +1,414 @@
|
||||
<?php
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Cookie\CookieJar;
|
||||
use GuzzleHttp\Promise;
|
||||
use GuzzleHttp\Psr7;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @method ResponseInterface get(string|UriInterface $uri, array $options = [])
|
||||
* @method ResponseInterface head(string|UriInterface $uri, array $options = [])
|
||||
* @method ResponseInterface put(string|UriInterface $uri, array $options = [])
|
||||
* @method ResponseInterface post(string|UriInterface $uri, array $options = [])
|
||||
* @method ResponseInterface patch(string|UriInterface $uri, array $options = [])
|
||||
* @method ResponseInterface delete(string|UriInterface $uri, array $options = [])
|
||||
* @method Promise\PromiseInterface getAsync(string|UriInterface $uri, array $options = [])
|
||||
* @method Promise\PromiseInterface headAsync(string|UriInterface $uri, array $options = [])
|
||||
* @method Promise\PromiseInterface putAsync(string|UriInterface $uri, array $options = [])
|
||||
* @method Promise\PromiseInterface postAsync(string|UriInterface $uri, array $options = [])
|
||||
* @method Promise\PromiseInterface patchAsync(string|UriInterface $uri, array $options = [])
|
||||
* @method Promise\PromiseInterface deleteAsync(string|UriInterface $uri, array $options = [])
|
||||
*/
|
||||
class Client implements ClientInterface
|
||||
{
|
||||
/** @var array Default request options */
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* Clients accept an array of constructor parameters.
|
||||
*
|
||||
* Here's an example of creating a client using a base_uri and an array of
|
||||
* default request options to apply to each request:
|
||||
*
|
||||
* $client = new Client([
|
||||
* 'base_uri' => 'http://www.foo.com/1.0/',
|
||||
* 'timeout' => 0,
|
||||
* 'allow_redirects' => false,
|
||||
* 'proxy' => '192.168.16.1:10'
|
||||
* ]);
|
||||
*
|
||||
* Client configuration settings include the following options:
|
||||
*
|
||||
* - handler: (callable) Function that transfers HTTP requests over the
|
||||
* wire. The function is called with a Psr7\Http\Message\RequestInterface
|
||||
* and array of transfer options, and must return a
|
||||
* GuzzleHttp\Promise\PromiseInterface that is fulfilled with a
|
||||
* Psr7\Http\Message\ResponseInterface on success. "handler" is a
|
||||
* constructor only option that cannot be overridden in per/request
|
||||
* options. If no handler is provided, a default handler will be created
|
||||
* that enables all of the request options below by attaching all of the
|
||||
* default middleware to the handler.
|
||||
* - base_uri: (string|UriInterface) Base URI of the client that is merged
|
||||
* into relative URIs. Can be a string or instance of UriInterface.
|
||||
* - **: any request option
|
||||
*
|
||||
* @param array $config Client configuration settings.
|
||||
*
|
||||
* @see \GuzzleHttp\RequestOptions for a list of available request options.
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
if (!isset($config['handler'])) {
|
||||
$config['handler'] = HandlerStack::create();
|
||||
} elseif (!is_callable($config['handler'])) {
|
||||
throw new \InvalidArgumentException('handler must be a callable');
|
||||
}
|
||||
|
||||
// Convert the base_uri to a UriInterface
|
||||
if (isset($config['base_uri'])) {
|
||||
$config['base_uri'] = Psr7\uri_for($config['base_uri']);
|
||||
}
|
||||
|
||||
$this->configureDefaults($config);
|
||||
}
|
||||
|
||||
public function __call($method, $args)
|
||||
{
|
||||
if (count($args) < 1) {
|
||||
throw new \InvalidArgumentException('Magic request methods require a URI and optional options array');
|
||||
}
|
||||
|
||||
$uri = $args[0];
|
||||
$opts = isset($args[1]) ? $args[1] : [];
|
||||
|
||||
return substr($method, -5) === 'Async'
|
||||
? $this->requestAsync(substr($method, 0, -5), $uri, $opts)
|
||||
: $this->request($method, $uri, $opts);
|
||||
}
|
||||
|
||||
public function sendAsync(RequestInterface $request, array $options = [])
|
||||
{
|
||||
// Merge the base URI into the request URI if needed.
|
||||
$options = $this->prepareDefaults($options);
|
||||
|
||||
return $this->transfer(
|
||||
$request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')),
|
||||
$options
|
||||
);
|
||||
}
|
||||
|
||||
public function send(RequestInterface $request, array $options = [])
|
||||
{
|
||||
$options[RequestOptions::SYNCHRONOUS] = true;
|
||||
return $this->sendAsync($request, $options)->wait();
|
||||
}
|
||||
|
||||
public function requestAsync($method, $uri = '', array $options = [])
|
||||
{
|
||||
$options = $this->prepareDefaults($options);
|
||||
// Remove request modifying parameter because it can be done up-front.
|
||||
$headers = isset($options['headers']) ? $options['headers'] : [];
|
||||
$body = isset($options['body']) ? $options['body'] : null;
|
||||
$version = isset($options['version']) ? $options['version'] : '1.1';
|
||||
// Merge the URI into the base URI.
|
||||
$uri = $this->buildUri($uri, $options);
|
||||
if (is_array($body)) {
|
||||
$this->invalidBody();
|
||||
}
|
||||
$request = new Psr7\Request($method, $uri, $headers, $body, $version);
|
||||
// Remove the option so that they are not doubly-applied.
|
||||
unset($options['headers'], $options['body'], $options['version']);
|
||||
|
||||
return $this->transfer($request, $options);
|
||||
}
|
||||
|
||||
public function request($method, $uri = '', array $options = [])
|
||||
{
|
||||
$options[RequestOptions::SYNCHRONOUS] = true;
|
||||
return $this->requestAsync($method, $uri, $options)->wait();
|
||||
}
|
||||
|
||||
public function getConfig($option = null)
|
||||
{
|
||||
return $option === null
|
||||
? $this->config
|
||||
: (isset($this->config[$option]) ? $this->config[$option] : null);
|
||||
}
|
||||
|
||||
private function buildUri($uri, array $config)
|
||||
{
|
||||
// for BC we accept null which would otherwise fail in uri_for
|
||||
$uri = Psr7\uri_for($uri === null ? '' : $uri);
|
||||
|
||||
if (isset($config['base_uri'])) {
|
||||
$uri = Psr7\UriResolver::resolve(Psr7\uri_for($config['base_uri']), $uri);
|
||||
}
|
||||
|
||||
return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the default options for a client.
|
||||
*
|
||||
* @param array $config
|
||||
*/
|
||||
private function configureDefaults(array $config)
|
||||
{
|
||||
$defaults = [
|
||||
'allow_redirects' => RedirectMiddleware::$defaultSettings,
|
||||
'http_errors' => true,
|
||||
'decode_content' => true,
|
||||
'verify' => true,
|
||||
'cookies' => false
|
||||
];
|
||||
|
||||
// Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set.
|
||||
|
||||
// We can only trust the HTTP_PROXY environment variable in a CLI
|
||||
// process due to the fact that PHP has no reliable mechanism to
|
||||
// get environment variables that start with "HTTP_".
|
||||
if (php_sapi_name() == 'cli' && getenv('HTTP_PROXY')) {
|
||||
$defaults['proxy']['http'] = getenv('HTTP_PROXY');
|
||||
}
|
||||
|
||||
if ($proxy = getenv('HTTPS_PROXY')) {
|
||||
$defaults['proxy']['https'] = $proxy;
|
||||
}
|
||||
|
||||
if ($noProxy = getenv('NO_PROXY')) {
|
||||
$cleanedNoProxy = str_replace(' ', '', $noProxy);
|
||||
$defaults['proxy']['no'] = explode(',', $cleanedNoProxy);
|
||||
}
|
||||
|
||||
$this->config = $config + $defaults;
|
||||
|
||||
if (!empty($config['cookies']) && $config['cookies'] === true) {
|
||||
$this->config['cookies'] = new CookieJar();
|
||||
}
|
||||
|
||||
// Add the default user-agent header.
|
||||
if (!isset($this->config['headers'])) {
|
||||
$this->config['headers'] = ['User-Agent' => default_user_agent()];
|
||||
} else {
|
||||
// Add the User-Agent header if one was not already set.
|
||||
foreach (array_keys($this->config['headers']) as $name) {
|
||||
if (strtolower($name) === 'user-agent') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->config['headers']['User-Agent'] = default_user_agent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges default options into the array.
|
||||
*
|
||||
* @param array $options Options to modify by reference
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function prepareDefaults($options)
|
||||
{
|
||||
$defaults = $this->config;
|
||||
|
||||
if (!empty($defaults['headers'])) {
|
||||
// Default headers are only added if they are not present.
|
||||
$defaults['_conditional'] = $defaults['headers'];
|
||||
unset($defaults['headers']);
|
||||
}
|
||||
|
||||
// Special handling for headers is required as they are added as
|
||||
// conditional headers and as headers passed to a request ctor.
|
||||
if (array_key_exists('headers', $options)) {
|
||||
// Allows default headers to be unset.
|
||||
if ($options['headers'] === null) {
|
||||
$defaults['_conditional'] = null;
|
||||
unset($options['headers']);
|
||||
} elseif (!is_array($options['headers'])) {
|
||||
throw new \InvalidArgumentException('headers must be an array');
|
||||
}
|
||||
}
|
||||
|
||||
// Shallow merge defaults underneath options.
|
||||
$result = $options + $defaults;
|
||||
|
||||
// Remove null values.
|
||||
foreach ($result as $k => $v) {
|
||||
if ($v === null) {
|
||||
unset($result[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfers the given request and applies request options.
|
||||
*
|
||||
* The URI of the request is not modified and the request options are used
|
||||
* as-is without merging in default options.
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param array $options
|
||||
*
|
||||
* @return Promise\PromiseInterface
|
||||
*/
|
||||
private function transfer(RequestInterface $request, array $options)
|
||||
{
|
||||
// save_to -> sink
|
||||
if (isset($options['save_to'])) {
|
||||
$options['sink'] = $options['save_to'];
|
||||
unset($options['save_to']);
|
||||
}
|
||||
|
||||
// exceptions -> http_errors
|
||||
if (isset($options['exceptions'])) {
|
||||
$options['http_errors'] = $options['exceptions'];
|
||||
unset($options['exceptions']);
|
||||
}
|
||||
|
||||
$request = $this->applyOptions($request, $options);
|
||||
$handler = $options['handler'];
|
||||
|
||||
try {
|
||||
return Promise\promise_for($handler($request, $options));
|
||||
} catch (\Exception $e) {
|
||||
return Promise\rejection_for($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the array of request options to a request.
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param array $options
|
||||
*
|
||||
* @return RequestInterface
|
||||
*/
|
||||
private function applyOptions(RequestInterface $request, array &$options)
|
||||
{
|
||||
$modify = [];
|
||||
|
||||
if (isset($options['form_params'])) {
|
||||
if (isset($options['multipart'])) {
|
||||
throw new \InvalidArgumentException('You cannot use '
|
||||
. 'form_params and multipart at the same time. Use the '
|
||||
. 'form_params option if you want to send application/'
|
||||
. 'x-www-form-urlencoded requests, and the multipart '
|
||||
. 'option to send multipart/form-data requests.');
|
||||
}
|
||||
$options['body'] = http_build_query($options['form_params'], '', '&');
|
||||
unset($options['form_params']);
|
||||
$options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
|
||||
if (isset($options['multipart'])) {
|
||||
$options['body'] = new Psr7\MultipartStream($options['multipart']);
|
||||
unset($options['multipart']);
|
||||
}
|
||||
|
||||
if (isset($options['json'])) {
|
||||
$options['body'] = \GuzzleHttp\json_encode($options['json']);
|
||||
unset($options['json']);
|
||||
$options['_conditional']['Content-Type'] = 'application/json';
|
||||
}
|
||||
|
||||
if (!empty($options['decode_content'])
|
||||
&& $options['decode_content'] !== true
|
||||
) {
|
||||
$modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
|
||||
}
|
||||
|
||||
if (isset($options['headers'])) {
|
||||
if (isset($modify['set_headers'])) {
|
||||
$modify['set_headers'] = $options['headers'] + $modify['set_headers'];
|
||||
} else {
|
||||
$modify['set_headers'] = $options['headers'];
|
||||
}
|
||||
unset($options['headers']);
|
||||
}
|
||||
|
||||
if (isset($options['body'])) {
|
||||
if (is_array($options['body'])) {
|
||||
$this->invalidBody();
|
||||
}
|
||||
$modify['body'] = Psr7\stream_for($options['body']);
|
||||
unset($options['body']);
|
||||
}
|
||||
|
||||
if (!empty($options['auth']) && is_array($options['auth'])) {
|
||||
$value = $options['auth'];
|
||||
$type = isset($value[2]) ? strtolower($value[2]) : 'basic';
|
||||
switch ($type) {
|
||||
case 'basic':
|
||||
$modify['set_headers']['Authorization'] = 'Basic '
|
||||
. base64_encode("$value[0]:$value[1]");
|
||||
break;
|
||||
case 'digest':
|
||||
// @todo: Do not rely on curl
|
||||
$options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST;
|
||||
$options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
|
||||
break;
|
||||
case 'ntlm':
|
||||
$options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_NTLM;
|
||||
$options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['query'])) {
|
||||
$value = $options['query'];
|
||||
if (is_array($value)) {
|
||||
$value = http_build_query($value, null, '&', PHP_QUERY_RFC3986);
|
||||
}
|
||||
if (!is_string($value)) {
|
||||
throw new \InvalidArgumentException('query must be a string or array');
|
||||
}
|
||||
$modify['query'] = $value;
|
||||
unset($options['query']);
|
||||
}
|
||||
|
||||
// Ensure that sink is not an invalid value.
|
||||
if (isset($options['sink'])) {
|
||||
// TODO: Add more sink validation?
|
||||
if (is_bool($options['sink'])) {
|
||||
throw new \InvalidArgumentException('sink must not be a boolean');
|
||||
}
|
||||
}
|
||||
|
||||
$request = Psr7\modify_request($request, $modify);
|
||||
if ($request->getBody() instanceof Psr7\MultipartStream) {
|
||||
// Use a multipart/form-data POST if a Content-Type is not set.
|
||||
$options['_conditional']['Content-Type'] = 'multipart/form-data; boundary='
|
||||
. $request->getBody()->getBoundary();
|
||||
}
|
||||
|
||||
// Merge in conditional headers if they are not present.
|
||||
if (isset($options['_conditional'])) {
|
||||
// Build up the changes so it's in a single clone of the message.
|
||||
$modify = [];
|
||||
foreach ($options['_conditional'] as $k => $v) {
|
||||
if (!$request->hasHeader($k)) {
|
||||
$modify['set_headers'][$k] = $v;
|
||||
}
|
||||
}
|
||||
$request = Psr7\modify_request($request, $modify);
|
||||
// Don't pass this internal value along to middleware/handlers.
|
||||
unset($options['_conditional']);
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
private function invalidBody()
|
||||
{
|
||||
throw new \InvalidArgumentException('Passing in the "body" request '
|
||||
. 'option as an array to send a POST request has been deprecated. '
|
||||
. 'Please use the "form_params" request option to send a '
|
||||
. 'application/x-www-form-urlencoded request, or the "multipart" '
|
||||
. 'request option to send a multipart/form-data request.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* Client interface for sending HTTP requests.
|
||||
*/
|
||||
interface ClientInterface
|
||||
{
|
||||
const VERSION = '6.2.1';
|
||||
|
||||
/**
|
||||
* Send an HTTP request.
|
||||
*
|
||||
* @param RequestInterface $request Request to send
|
||||
* @param array $options Request options to apply to the given
|
||||
* request and to the transfer.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function send(RequestInterface $request, array $options = []);
|
||||
|
||||
/**
|
||||
* Asynchronously send an HTTP request.
|
||||
*
|
||||
* @param RequestInterface $request Request to send
|
||||
* @param array $options Request options to apply to the given
|
||||
* request and to the transfer.
|
||||
*
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
public function sendAsync(RequestInterface $request, array $options = []);
|
||||
|
||||
/**
|
||||
* Create and send an HTTP request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well.
|
||||
*
|
||||
* @param string $method HTTP method.
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function request($method, $uri, array $options = []);
|
||||
|
||||
/**
|
||||
* Create and send an asynchronous HTTP request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well. Use an array to provide a URL
|
||||
* template and additional variables to use in the URL template expansion.
|
||||
*
|
||||
* @param string $method HTTP method
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
public function requestAsync($method, $uri, array $options = []);
|
||||
|
||||
/**
|
||||
* Get a client configuration option.
|
||||
*
|
||||
* These options include default request options of the client, a "handler"
|
||||
* (if utilized by the concrete client), and a "base_uri" if utilized by
|
||||
* the concrete client.
|
||||
*
|
||||
* @param string|null $option The config option to retrieve.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getConfig($option = null);
|
||||
}
|
||||
+314
@@ -0,0 +1,314 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Cookie;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Cookie jar that stores cookies as an array
|
||||
*/
|
||||
class CookieJar implements CookieJarInterface
|
||||
{
|
||||
/** @var SetCookie[] Loaded cookie data */
|
||||
private $cookies = [];
|
||||
|
||||
/** @var bool */
|
||||
private $strictMode;
|
||||
|
||||
/**
|
||||
* @param bool $strictMode Set to true to throw exceptions when invalid
|
||||
* cookies are added to the cookie jar.
|
||||
* @param array $cookieArray Array of SetCookie objects or a hash of
|
||||
* arrays that can be used with the SetCookie
|
||||
* constructor
|
||||
*/
|
||||
public function __construct($strictMode = false, $cookieArray = [])
|
||||
{
|
||||
$this->strictMode = $strictMode;
|
||||
|
||||
foreach ($cookieArray as $cookie) {
|
||||
if (!($cookie instanceof SetCookie)) {
|
||||
$cookie = new SetCookie($cookie);
|
||||
}
|
||||
$this->setCookie($cookie);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Cookie jar from an associative array and domain.
|
||||
*
|
||||
* @param array $cookies Cookies to create the jar from
|
||||
* @param string $domain Domain to set the cookies to
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function fromArray(array $cookies, $domain)
|
||||
{
|
||||
$cookieJar = new self();
|
||||
foreach ($cookies as $name => $value) {
|
||||
$cookieJar->setCookie(new SetCookie([
|
||||
'Domain' => $domain,
|
||||
'Name' => $name,
|
||||
'Value' => $value,
|
||||
'Discard' => true
|
||||
]));
|
||||
}
|
||||
|
||||
return $cookieJar;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public static function getCookieValue($value)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate if this cookie should be persisted to storage
|
||||
* that survives between requests.
|
||||
*
|
||||
* @param SetCookie $cookie Being evaluated.
|
||||
* @param bool $allowSessionCookies If we should persist session cookies
|
||||
* @return bool
|
||||
*/
|
||||
public static function shouldPersist(
|
||||
SetCookie $cookie,
|
||||
$allowSessionCookies = false
|
||||
) {
|
||||
if ($cookie->getExpires() || $allowSessionCookies) {
|
||||
if (!$cookie->getDiscard()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns the cookie based on the name
|
||||
*
|
||||
* @param string $name cookie name to search for
|
||||
* @return SetCookie|null cookie that was found or null if not found
|
||||
*/
|
||||
public function getCookieByName($name)
|
||||
{
|
||||
// don't allow a null name
|
||||
if($name === null) {
|
||||
return null;
|
||||
}
|
||||
foreach($this->cookies as $cookie) {
|
||||
if($cookie->getName() !== null && strcasecmp($cookie->getName(), $name) === 0) {
|
||||
return $cookie;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
return array_map(function (SetCookie $cookie) {
|
||||
return $cookie->toArray();
|
||||
}, $this->getIterator()->getArrayCopy());
|
||||
}
|
||||
|
||||
public function clear($domain = null, $path = null, $name = null)
|
||||
{
|
||||
if (!$domain) {
|
||||
$this->cookies = [];
|
||||
return;
|
||||
} elseif (!$path) {
|
||||
$this->cookies = array_filter(
|
||||
$this->cookies,
|
||||
function (SetCookie $cookie) use ($path, $domain) {
|
||||
return !$cookie->matchesDomain($domain);
|
||||
}
|
||||
);
|
||||
} elseif (!$name) {
|
||||
$this->cookies = array_filter(
|
||||
$this->cookies,
|
||||
function (SetCookie $cookie) use ($path, $domain) {
|
||||
return !($cookie->matchesPath($path) &&
|
||||
$cookie->matchesDomain($domain));
|
||||
}
|
||||
);
|
||||
} else {
|
||||
$this->cookies = array_filter(
|
||||
$this->cookies,
|
||||
function (SetCookie $cookie) use ($path, $domain, $name) {
|
||||
return !($cookie->getName() == $name &&
|
||||
$cookie->matchesPath($path) &&
|
||||
$cookie->matchesDomain($domain));
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function clearSessionCookies()
|
||||
{
|
||||
$this->cookies = array_filter(
|
||||
$this->cookies,
|
||||
function (SetCookie $cookie) {
|
||||
return !$cookie->getDiscard() && $cookie->getExpires();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function setCookie(SetCookie $cookie)
|
||||
{
|
||||
// If the name string is empty (but not 0), ignore the set-cookie
|
||||
// string entirely.
|
||||
$name = $cookie->getName();
|
||||
if (!$name && $name !== '0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only allow cookies with set and valid domain, name, value
|
||||
$result = $cookie->validate();
|
||||
if ($result !== true) {
|
||||
if ($this->strictMode) {
|
||||
throw new \RuntimeException('Invalid cookie: ' . $result);
|
||||
} else {
|
||||
$this->removeCookieIfEmpty($cookie);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve conflicts with previously set cookies
|
||||
foreach ($this->cookies as $i => $c) {
|
||||
|
||||
// Two cookies are identical, when their path, and domain are
|
||||
// identical.
|
||||
if ($c->getPath() != $cookie->getPath() ||
|
||||
$c->getDomain() != $cookie->getDomain() ||
|
||||
$c->getName() != $cookie->getName()
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The previously set cookie is a discard cookie and this one is
|
||||
// not so allow the new cookie to be set
|
||||
if (!$cookie->getDiscard() && $c->getDiscard()) {
|
||||
unset($this->cookies[$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the new cookie's expiration is further into the future, then
|
||||
// replace the old cookie
|
||||
if ($cookie->getExpires() > $c->getExpires()) {
|
||||
unset($this->cookies[$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the value has changed, we better change it
|
||||
if ($cookie->getValue() !== $c->getValue()) {
|
||||
unset($this->cookies[$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// The cookie exists, so no need to continue
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->cookies[] = $cookie;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function count()
|
||||
{
|
||||
return count($this->cookies);
|
||||
}
|
||||
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator(array_values($this->cookies));
|
||||
}
|
||||
|
||||
public function extractCookies(
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response
|
||||
) {
|
||||
if ($cookieHeader = $response->getHeader('Set-Cookie')) {
|
||||
foreach ($cookieHeader as $cookie) {
|
||||
$sc = SetCookie::fromString($cookie);
|
||||
if (!$sc->getDomain()) {
|
||||
$sc->setDomain($request->getUri()->getHost());
|
||||
}
|
||||
if (0 !== strpos($sc->getPath(), '/')) {
|
||||
$sc->setPath($this->getCookiePathFromRequest($request));
|
||||
}
|
||||
$this->setCookie($sc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes cookie path following RFC 6265 section 5.1.4
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc6265#section-5.1.4
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @return string
|
||||
*/
|
||||
private function getCookiePathFromRequest(RequestInterface $request)
|
||||
{
|
||||
$uriPath = $request->getUri()->getPath();
|
||||
if ('' === $uriPath) {
|
||||
return '/';
|
||||
}
|
||||
if (0 !== strpos($uriPath, '/')) {
|
||||
return '/';
|
||||
}
|
||||
if ('/' === $uriPath) {
|
||||
return '/';
|
||||
}
|
||||
if (0 === $lastSlashPos = strrpos($uriPath, '/')) {
|
||||
return '/';
|
||||
}
|
||||
|
||||
return substr($uriPath, 0, $lastSlashPos);
|
||||
}
|
||||
|
||||
public function withCookieHeader(RequestInterface $request)
|
||||
{
|
||||
$values = [];
|
||||
$uri = $request->getUri();
|
||||
$scheme = $uri->getScheme();
|
||||
$host = $uri->getHost();
|
||||
$path = $uri->getPath() ?: '/';
|
||||
|
||||
foreach ($this->cookies as $cookie) {
|
||||
if ($cookie->matchesPath($path) &&
|
||||
$cookie->matchesDomain($host) &&
|
||||
!$cookie->isExpired() &&
|
||||
(!$cookie->getSecure() || $scheme === 'https')
|
||||
) {
|
||||
$values[] = $cookie->getName() . '='
|
||||
. $cookie->getValue();
|
||||
}
|
||||
}
|
||||
|
||||
return $values
|
||||
? $request->withHeader('Cookie', implode('; ', $values))
|
||||
: $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a cookie already exists and the server asks to set it again with a
|
||||
* null value, the cookie must be deleted.
|
||||
*
|
||||
* @param SetCookie $cookie
|
||||
*/
|
||||
private function removeCookieIfEmpty(SetCookie $cookie)
|
||||
{
|
||||
$cookieValue = $cookie->getValue();
|
||||
if ($cookieValue === null || $cookieValue === '') {
|
||||
$this->clear(
|
||||
$cookie->getDomain(),
|
||||
$cookie->getPath(),
|
||||
$cookie->getName()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Cookie;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Stores HTTP cookies.
|
||||
*
|
||||
* It extracts cookies from HTTP requests, and returns them in HTTP responses.
|
||||
* CookieJarInterface instances automatically expire contained cookies when
|
||||
* necessary. Subclasses are also responsible for storing and retrieving
|
||||
* cookies from a file, database, etc.
|
||||
*
|
||||
* @link http://docs.python.org/2/library/cookielib.html Inspiration
|
||||
*/
|
||||
interface CookieJarInterface extends \Countable, \IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* Create a request with added cookie headers.
|
||||
*
|
||||
* If no matching cookies are found in the cookie jar, then no Cookie
|
||||
* header is added to the request and the same request is returned.
|
||||
*
|
||||
* @param RequestInterface $request Request object to modify.
|
||||
*
|
||||
* @return RequestInterface returns the modified request.
|
||||
*/
|
||||
public function withCookieHeader(RequestInterface $request);
|
||||
|
||||
/**
|
||||
* Extract cookies from an HTTP response and store them in the CookieJar.
|
||||
*
|
||||
* @param RequestInterface $request Request that was sent
|
||||
* @param ResponseInterface $response Response that was received
|
||||
*/
|
||||
public function extractCookies(
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets a cookie in the cookie jar.
|
||||
*
|
||||
* @param SetCookie $cookie Cookie to set.
|
||||
*
|
||||
* @return bool Returns true on success or false on failure
|
||||
*/
|
||||
public function setCookie(SetCookie $cookie);
|
||||
|
||||
/**
|
||||
* Remove cookies currently held in the cookie jar.
|
||||
*
|
||||
* Invoking this method without arguments will empty the whole cookie jar.
|
||||
* If given a $domain argument only cookies belonging to that domain will
|
||||
* be removed. If given a $domain and $path argument, cookies belonging to
|
||||
* the specified path within that domain are removed. If given all three
|
||||
* arguments, then the cookie with the specified name, path and domain is
|
||||
* removed.
|
||||
*
|
||||
* @param string $domain Clears cookies matching a domain
|
||||
* @param string $path Clears cookies matching a domain and path
|
||||
* @param string $name Clears cookies matching a domain, path, and name
|
||||
*
|
||||
* @return CookieJarInterface
|
||||
*/
|
||||
public function clear($domain = null, $path = null, $name = null);
|
||||
|
||||
/**
|
||||
* Discard all sessions cookies.
|
||||
*
|
||||
* Removes cookies that don't have an expire field or a have a discard
|
||||
* field set to true. To be called when the user agent shuts down according
|
||||
* to RFC 2965.
|
||||
*/
|
||||
public function clearSessionCookies();
|
||||
|
||||
/**
|
||||
* Converts the cookie jar to an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray();
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Cookie;
|
||||
|
||||
/**
|
||||
* Persists non-session cookies using a JSON formatted file
|
||||
*/
|
||||
class FileCookieJar extends CookieJar
|
||||
{
|
||||
/** @var string filename */
|
||||
private $filename;
|
||||
|
||||
/** @var bool Control whether to persist session cookies or not. */
|
||||
private $storeSessionCookies;
|
||||
|
||||
/**
|
||||
* Create a new FileCookieJar object
|
||||
*
|
||||
* @param string $cookieFile File to store the cookie data
|
||||
* @param bool $storeSessionCookies Set to true to store session cookies
|
||||
* in the cookie jar.
|
||||
*
|
||||
* @throws \RuntimeException if the file cannot be found or created
|
||||
*/
|
||||
public function __construct($cookieFile, $storeSessionCookies = false)
|
||||
{
|
||||
$this->filename = $cookieFile;
|
||||
$this->storeSessionCookies = $storeSessionCookies;
|
||||
|
||||
if (file_exists($cookieFile)) {
|
||||
$this->load($cookieFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the file when shutting down
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->save($this->filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the cookies to a file.
|
||||
*
|
||||
* @param string $filename File to save
|
||||
* @throws \RuntimeException if the file cannot be found or created
|
||||
*/
|
||||
public function save($filename)
|
||||
{
|
||||
$json = [];
|
||||
foreach ($this as $cookie) {
|
||||
/** @var SetCookie $cookie */
|
||||
if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
|
||||
$json[] = $cookie->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
$jsonStr = \GuzzleHttp\json_encode($json);
|
||||
if (false === file_put_contents($filename, $jsonStr)) {
|
||||
throw new \RuntimeException("Unable to save file {$filename}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load cookies from a JSON formatted file.
|
||||
*
|
||||
* Old cookies are kept unless overwritten by newly loaded ones.
|
||||
*
|
||||
* @param string $filename Cookie file to load.
|
||||
* @throws \RuntimeException if the file cannot be loaded.
|
||||
*/
|
||||
public function load($filename)
|
||||
{
|
||||
$json = file_get_contents($filename);
|
||||
if (false === $json) {
|
||||
throw new \RuntimeException("Unable to load file {$filename}");
|
||||
} elseif ($json === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = \GuzzleHttp\json_decode($json, true);
|
||||
if (is_array($data)) {
|
||||
foreach (json_decode($json, true) as $cookie) {
|
||||
$this->setCookie(new SetCookie($cookie));
|
||||
}
|
||||
} elseif (strlen($data)) {
|
||||
throw new \RuntimeException("Invalid cookie file: {$filename}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Cookie;
|
||||
|
||||
/**
|
||||
* Persists cookies in the client session
|
||||
*/
|
||||
class SessionCookieJar extends CookieJar
|
||||
{
|
||||
/** @var string session key */
|
||||
private $sessionKey;
|
||||
|
||||
/** @var bool Control whether to persist session cookies or not. */
|
||||
private $storeSessionCookies;
|
||||
|
||||
/**
|
||||
* Create a new SessionCookieJar object
|
||||
*
|
||||
* @param string $sessionKey Session key name to store the cookie
|
||||
* data in session
|
||||
* @param bool $storeSessionCookies Set to true to store session cookies
|
||||
* in the cookie jar.
|
||||
*/
|
||||
public function __construct($sessionKey, $storeSessionCookies = false)
|
||||
{
|
||||
$this->sessionKey = $sessionKey;
|
||||
$this->storeSessionCookies = $storeSessionCookies;
|
||||
$this->load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves cookies to session when shutting down
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save cookies to the client session
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$json = [];
|
||||
foreach ($this as $cookie) {
|
||||
/** @var SetCookie $cookie */
|
||||
if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
|
||||
$json[] = $cookie->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
$_SESSION[$this->sessionKey] = json_encode($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the contents of the client session into the data array
|
||||
*/
|
||||
protected function load()
|
||||
{
|
||||
if (!isset($_SESSION[$this->sessionKey])) {
|
||||
return;
|
||||
}
|
||||
$data = json_decode($_SESSION[$this->sessionKey], true);
|
||||
if (is_array($data)) {
|
||||
foreach ($data as $cookie) {
|
||||
$this->setCookie(new SetCookie($cookie));
|
||||
}
|
||||
} elseif (strlen($data)) {
|
||||
throw new \RuntimeException("Invalid cookie data");
|
||||
}
|
||||
}
|
||||
}
|
||||
+404
@@ -0,0 +1,404 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Cookie;
|
||||
|
||||
/**
|
||||
* Set-Cookie object
|
||||
*/
|
||||
class SetCookie
|
||||
{
|
||||
/** @var array */
|
||||
private static $defaults = [
|
||||
'Name' => null,
|
||||
'Value' => null,
|
||||
'Domain' => null,
|
||||
'Path' => '/',
|
||||
'Max-Age' => null,
|
||||
'Expires' => null,
|
||||
'Secure' => false,
|
||||
'Discard' => false,
|
||||
'HttpOnly' => false
|
||||
];
|
||||
|
||||
/** @var array Cookie data */
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* Create a new SetCookie object from a string
|
||||
*
|
||||
* @param string $cookie Set-Cookie header string
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function fromString($cookie)
|
||||
{
|
||||
// Create the default return array
|
||||
$data = self::$defaults;
|
||||
// Explode the cookie string using a series of semicolons
|
||||
$pieces = array_filter(array_map('trim', explode(';', $cookie)));
|
||||
// The name of the cookie (first kvp) must include an equal sign.
|
||||
if (empty($pieces) || !strpos($pieces[0], '=')) {
|
||||
return new self($data);
|
||||
}
|
||||
|
||||
// Add the cookie pieces into the parsed data array
|
||||
foreach ($pieces as $part) {
|
||||
|
||||
$cookieParts = explode('=', $part, 2);
|
||||
$key = trim($cookieParts[0]);
|
||||
$value = isset($cookieParts[1])
|
||||
? trim($cookieParts[1], " \n\r\t\0\x0B")
|
||||
: true;
|
||||
|
||||
// Only check for non-cookies when cookies have been found
|
||||
if (empty($data['Name'])) {
|
||||
$data['Name'] = $key;
|
||||
$data['Value'] = $value;
|
||||
} else {
|
||||
foreach (array_keys(self::$defaults) as $search) {
|
||||
if (!strcasecmp($search, $key)) {
|
||||
$data[$search] = $value;
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
$data[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return new self($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data Array of cookie data provided by a Cookie parser
|
||||
*/
|
||||
public function __construct(array $data = [])
|
||||
{
|
||||
$this->data = array_replace(self::$defaults, $data);
|
||||
// Extract the Expires value and turn it into a UNIX timestamp if needed
|
||||
if (!$this->getExpires() && $this->getMaxAge()) {
|
||||
// Calculate the Expires date
|
||||
$this->setExpires(time() + $this->getMaxAge());
|
||||
} elseif ($this->getExpires() && !is_numeric($this->getExpires())) {
|
||||
$this->setExpires($this->getExpires());
|
||||
}
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
$str = $this->data['Name'] . '=' . $this->data['Value'] . '; ';
|
||||
foreach ($this->data as $k => $v) {
|
||||
if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) {
|
||||
if ($k === 'Expires') {
|
||||
$str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; ';
|
||||
} else {
|
||||
$str .= ($v === true ? $k : "{$k}={$v}") . '; ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rtrim($str, '; ');
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cookie name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->data['Name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cookie name
|
||||
*
|
||||
* @param string $name Cookie name
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->data['Name'] = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cookie value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->data['Value'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cookie value
|
||||
*
|
||||
* @param string $value Cookie value
|
||||
*/
|
||||
public function setValue($value)
|
||||
{
|
||||
$this->data['Value'] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the domain
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDomain()
|
||||
{
|
||||
return $this->data['Domain'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the domain of the cookie
|
||||
*
|
||||
* @param string $domain
|
||||
*/
|
||||
public function setDomain($domain)
|
||||
{
|
||||
$this->data['Domain'] = $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->data['Path'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the path of the cookie
|
||||
*
|
||||
* @param string $path Path of the cookie
|
||||
*/
|
||||
public function setPath($path)
|
||||
{
|
||||
$this->data['Path'] = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum lifetime of the cookie in seconds
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getMaxAge()
|
||||
{
|
||||
return $this->data['Max-Age'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the max-age of the cookie
|
||||
*
|
||||
* @param int $maxAge Max age of the cookie in seconds
|
||||
*/
|
||||
public function setMaxAge($maxAge)
|
||||
{
|
||||
$this->data['Max-Age'] = $maxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* The UNIX timestamp when the cookie Expires
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getExpires()
|
||||
{
|
||||
return $this->data['Expires'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the unix timestamp for which the cookie will expire
|
||||
*
|
||||
* @param int $timestamp Unix timestamp
|
||||
*/
|
||||
public function setExpires($timestamp)
|
||||
{
|
||||
$this->data['Expires'] = is_numeric($timestamp)
|
||||
? (int) $timestamp
|
||||
: strtotime($timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether or not this is a secure cookie
|
||||
*
|
||||
* @return null|bool
|
||||
*/
|
||||
public function getSecure()
|
||||
{
|
||||
return $this->data['Secure'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not the cookie is secure
|
||||
*
|
||||
* @param bool $secure Set to true or false if secure
|
||||
*/
|
||||
public function setSecure($secure)
|
||||
{
|
||||
$this->data['Secure'] = $secure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether or not this is a session cookie
|
||||
*
|
||||
* @return null|bool
|
||||
*/
|
||||
public function getDiscard()
|
||||
{
|
||||
return $this->data['Discard'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not this is a session cookie
|
||||
*
|
||||
* @param bool $discard Set to true or false if this is a session cookie
|
||||
*/
|
||||
public function setDiscard($discard)
|
||||
{
|
||||
$this->data['Discard'] = $discard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether or not this is an HTTP only cookie
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getHttpOnly()
|
||||
{
|
||||
return $this->data['HttpOnly'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not this is an HTTP only cookie
|
||||
*
|
||||
* @param bool $httpOnly Set to true or false if this is HTTP only
|
||||
*/
|
||||
public function setHttpOnly($httpOnly)
|
||||
{
|
||||
$this->data['HttpOnly'] = $httpOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cookie matches a path value.
|
||||
*
|
||||
* A request-path path-matches a given cookie-path if at least one of
|
||||
* the following conditions holds:
|
||||
*
|
||||
* - The cookie-path and the request-path are identical.
|
||||
* - The cookie-path is a prefix of the request-path, and the last
|
||||
* character of the cookie-path is %x2F ("/").
|
||||
* - The cookie-path is a prefix of the request-path, and the first
|
||||
* character of the request-path that is not included in the cookie-
|
||||
* path is a %x2F ("/") character.
|
||||
*
|
||||
* @param string $requestPath Path to check against
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function matchesPath($requestPath)
|
||||
{
|
||||
$cookiePath = $this->getPath();
|
||||
|
||||
// Match on exact matches or when path is the default empty "/"
|
||||
if ($cookiePath === '/' || $cookiePath == $requestPath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ensure that the cookie-path is a prefix of the request path.
|
||||
if (0 !== strpos($requestPath, $cookiePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Match if the last character of the cookie-path is "/"
|
||||
if (substr($cookiePath, -1, 1) === '/') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Match if the first character not included in cookie path is "/"
|
||||
return substr($requestPath, strlen($cookiePath), 1) === '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cookie matches a domain value
|
||||
*
|
||||
* @param string $domain Domain to check against
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function matchesDomain($domain)
|
||||
{
|
||||
// Remove the leading '.' as per spec in RFC 6265.
|
||||
// http://tools.ietf.org/html/rfc6265#section-5.2.3
|
||||
$cookieDomain = ltrim($this->getDomain(), '.');
|
||||
|
||||
// Domain not set or exact match.
|
||||
if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Matching the subdomain according to RFC 6265.
|
||||
// http://tools.ietf.org/html/rfc6265#section-5.1.3
|
||||
if (filter_var($domain, FILTER_VALIDATE_IP)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) preg_match('/\.' . preg_quote($cookieDomain) . '$/', $domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cookie is expired
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isExpired()
|
||||
{
|
||||
return $this->getExpires() && time() > $this->getExpires();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cookie is valid according to RFC 6265
|
||||
*
|
||||
* @return bool|string Returns true if valid or an error message if invalid
|
||||
*/
|
||||
public function validate()
|
||||
{
|
||||
// Names must not be empty, but can be 0
|
||||
$name = $this->getName();
|
||||
if (empty($name) && !is_numeric($name)) {
|
||||
return 'The cookie name must not be empty';
|
||||
}
|
||||
|
||||
// Check if any of the invalid characters are present in the cookie name
|
||||
if (preg_match(
|
||||
'/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/',
|
||||
$name)
|
||||
) {
|
||||
return 'Cookie name must not contain invalid characters: ASCII '
|
||||
. 'Control characters (0-31;127), space, tab and the '
|
||||
. 'following characters: ()<>@,;:\"/?={}';
|
||||
}
|
||||
|
||||
// Value must not be empty, but can be 0
|
||||
$value = $this->getValue();
|
||||
if (empty($value) && !is_numeric($value)) {
|
||||
return 'The cookie value must not be empty';
|
||||
}
|
||||
|
||||
// Domains must not be empty, but can be 0
|
||||
// A "0" is not a valid internet domain, but may be used as server name
|
||||
// in a private network.
|
||||
$domain = $this->getDomain();
|
||||
if (empty($domain) && !is_numeric($domain)) {
|
||||
return 'The cookie domain must not be empty';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Exception when an HTTP error occurs (4xx or 5xx error)
|
||||
*/
|
||||
class BadResponseException extends RequestException
|
||||
{
|
||||
public function __construct(
|
||||
$message,
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response = null,
|
||||
\Exception $previous = null,
|
||||
array $handlerContext = []
|
||||
) {
|
||||
if (null === $response) {
|
||||
@trigger_error(
|
||||
'Instantiating the ' . __CLASS__ . ' class without a Response is deprecated since version 6.3 and will be removed in 7.0.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
}
|
||||
parent::__construct($message, $request, $response, $previous, $handlerContext);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
/**
|
||||
* Exception when a client error is encountered (4xx codes)
|
||||
*/
|
||||
class ClientException extends BadResponseException {}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Exception thrown when a connection cannot be established.
|
||||
*
|
||||
* Note that no response is present for a ConnectException
|
||||
*/
|
||||
class ConnectException extends RequestException
|
||||
{
|
||||
public function __construct(
|
||||
$message,
|
||||
RequestInterface $request,
|
||||
\Exception $previous = null,
|
||||
array $handlerContext = []
|
||||
) {
|
||||
parent::__construct($message, $request, null, $previous, $handlerContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null
|
||||
*/
|
||||
public function getResponse()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasResponse()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
interface GuzzleException {}
|
||||
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* HTTP Request exception
|
||||
*/
|
||||
class RequestException extends TransferException
|
||||
{
|
||||
/** @var RequestInterface */
|
||||
private $request;
|
||||
|
||||
/** @var ResponseInterface */
|
||||
private $response;
|
||||
|
||||
/** @var array */
|
||||
private $handlerContext;
|
||||
|
||||
public function __construct(
|
||||
$message,
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response = null,
|
||||
\Exception $previous = null,
|
||||
array $handlerContext = []
|
||||
) {
|
||||
// Set the code of the exception if the response is set and not future.
|
||||
$code = $response && !($response instanceof PromiseInterface)
|
||||
? $response->getStatusCode()
|
||||
: 0;
|
||||
parent::__construct($message, $code, $previous);
|
||||
$this->request = $request;
|
||||
$this->response = $response;
|
||||
$this->handlerContext = $handlerContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap non-RequestExceptions with a RequestException
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param \Exception $e
|
||||
*
|
||||
* @return RequestException
|
||||
*/
|
||||
public static function wrapException(RequestInterface $request, \Exception $e)
|
||||
{
|
||||
return $e instanceof RequestException
|
||||
? $e
|
||||
: new RequestException($e->getMessage(), $request, null, $e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new exception with a normalized error message
|
||||
*
|
||||
* @param RequestInterface $request Request
|
||||
* @param ResponseInterface $response Response received
|
||||
* @param \Exception $previous Previous exception
|
||||
* @param array $ctx Optional handler context.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function create(
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response = null,
|
||||
\Exception $previous = null,
|
||||
array $ctx = []
|
||||
) {
|
||||
if (!$response) {
|
||||
return new self(
|
||||
'Error completing request',
|
||||
$request,
|
||||
null,
|
||||
$previous,
|
||||
$ctx
|
||||
);
|
||||
}
|
||||
|
||||
$level = (int) floor($response->getStatusCode() / 100);
|
||||
if ($level === 4) {
|
||||
$label = 'Client error';
|
||||
$className = ClientException::class;
|
||||
} elseif ($level === 5) {
|
||||
$label = 'Server error';
|
||||
$className = ServerException::class;
|
||||
} else {
|
||||
$label = 'Unsuccessful request';
|
||||
$className = __CLASS__;
|
||||
}
|
||||
|
||||
$uri = $request->getUri();
|
||||
$uri = static::obfuscateUri($uri);
|
||||
|
||||
// Client Error: `GET /` resulted in a `404 Not Found` response:
|
||||
// <html> ... (truncated)
|
||||
$message = sprintf(
|
||||
'%s: `%s %s` resulted in a `%s %s` response',
|
||||
$label,
|
||||
$request->getMethod(),
|
||||
$uri,
|
||||
$response->getStatusCode(),
|
||||
$response->getReasonPhrase()
|
||||
);
|
||||
|
||||
$summary = static::getResponseBodySummary($response);
|
||||
|
||||
if ($summary !== null) {
|
||||
$message .= ":\n{$summary}\n";
|
||||
}
|
||||
|
||||
return new $className($message, $request, $response, $previous, $ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a short summary of the response
|
||||
*
|
||||
* Will return `null` if the response is not printable.
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getResponseBodySummary(ResponseInterface $response)
|
||||
{
|
||||
$body = $response->getBody();
|
||||
|
||||
if (!$body->isSeekable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$size = $body->getSize();
|
||||
|
||||
if ($size === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$summary = $body->read(120);
|
||||
$body->rewind();
|
||||
|
||||
if ($size > 120) {
|
||||
$summary .= ' (truncated...)';
|
||||
}
|
||||
|
||||
// Matches any printable character, including unicode characters:
|
||||
// letters, marks, numbers, punctuation, spacing, and separators.
|
||||
if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/', $summary)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obfuscates URI if there is an username and a password present
|
||||
*
|
||||
* @param UriInterface $uri
|
||||
*
|
||||
* @return UriInterface
|
||||
*/
|
||||
private static function obfuscateUri($uri)
|
||||
{
|
||||
$userInfo = $uri->getUserInfo();
|
||||
|
||||
if (false !== ($pos = strpos($userInfo, ':'))) {
|
||||
return $uri->withUserInfo(substr($userInfo, 0, $pos), '***');
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request that caused the exception
|
||||
*
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public function getRequest()
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the associated response
|
||||
*
|
||||
* @return ResponseInterface|null
|
||||
*/
|
||||
public function getResponse()
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a response was received
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasResponse()
|
||||
{
|
||||
return $this->response !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contextual information about the error from the underlying handler.
|
||||
*
|
||||
* The contents of this array will vary depending on which handler you are
|
||||
* using. It may also be just an empty array. Relying on this data will
|
||||
* couple you to a specific handler, but can give more debug information
|
||||
* when needed.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getHandlerContext()
|
||||
{
|
||||
return $this->handlerContext;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* Exception thrown when a seek fails on a stream.
|
||||
*/
|
||||
class SeekException extends \RuntimeException implements GuzzleException
|
||||
{
|
||||
private $stream;
|
||||
|
||||
public function __construct(StreamInterface $stream, $pos = 0, $msg = '')
|
||||
{
|
||||
$this->stream = $stream;
|
||||
$msg = $msg ?: 'Could not seek the stream to position ' . $pos;
|
||||
parent::__construct($msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return StreamInterface
|
||||
*/
|
||||
public function getStream()
|
||||
{
|
||||
return $this->stream;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
/**
|
||||
* Exception when a server error is encountered (5xx codes)
|
||||
*/
|
||||
class ServerException extends BadResponseException {}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
class TooManyRedirectsException extends RequestException {}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
class TransferException extends \RuntimeException implements GuzzleException {}
|
||||
@@ -0,0 +1,559 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Exception\ConnectException;
|
||||
use GuzzleHttp\Promise\FulfilledPromise;
|
||||
use GuzzleHttp\Promise\RejectedPromise;
|
||||
use GuzzleHttp\Psr7;
|
||||
use GuzzleHttp\Psr7\LazyOpenStream;
|
||||
use GuzzleHttp\TransferStats;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Creates curl resources from a request
|
||||
*/
|
||||
class CurlFactory implements CurlFactoryInterface
|
||||
{
|
||||
/** @var array */
|
||||
private $handles = [];
|
||||
|
||||
/** @var int Total number of idle handles to keep in cache */
|
||||
private $maxHandles;
|
||||
|
||||
/**
|
||||
* @param int $maxHandles Maximum number of idle handles.
|
||||
*/
|
||||
public function __construct($maxHandles)
|
||||
{
|
||||
$this->maxHandles = $maxHandles;
|
||||
}
|
||||
|
||||
public function create(RequestInterface $request, array $options)
|
||||
{
|
||||
if (isset($options['curl']['body_as_string'])) {
|
||||
$options['_body_as_string'] = $options['curl']['body_as_string'];
|
||||
unset($options['curl']['body_as_string']);
|
||||
}
|
||||
|
||||
$easy = new EasyHandle;
|
||||
$easy->request = $request;
|
||||
$easy->options = $options;
|
||||
$conf = $this->getDefaultConf($easy);
|
||||
$this->applyMethod($easy, $conf);
|
||||
$this->applyHandlerOptions($easy, $conf);
|
||||
$this->applyHeaders($easy, $conf);
|
||||
unset($conf['_headers']);
|
||||
|
||||
// Add handler options from the request configuration options
|
||||
if (isset($options['curl'])) {
|
||||
$conf = array_replace($conf, $options['curl']);
|
||||
}
|
||||
|
||||
$conf[CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy);
|
||||
$easy->handle = $this->handles
|
||||
? array_pop($this->handles)
|
||||
: curl_init();
|
||||
curl_setopt_array($easy->handle, $conf);
|
||||
|
||||
return $easy;
|
||||
}
|
||||
|
||||
public function release(EasyHandle $easy)
|
||||
{
|
||||
$resource = $easy->handle;
|
||||
unset($easy->handle);
|
||||
|
||||
if (count($this->handles) >= $this->maxHandles) {
|
||||
curl_close($resource);
|
||||
} else {
|
||||
// Remove all callback functions as they can hold onto references
|
||||
// and are not cleaned up by curl_reset. Using curl_setopt_array
|
||||
// does not work for some reason, so removing each one
|
||||
// individually.
|
||||
curl_setopt($resource, CURLOPT_HEADERFUNCTION, null);
|
||||
curl_setopt($resource, CURLOPT_READFUNCTION, null);
|
||||
curl_setopt($resource, CURLOPT_WRITEFUNCTION, null);
|
||||
curl_setopt($resource, CURLOPT_PROGRESSFUNCTION, null);
|
||||
curl_reset($resource);
|
||||
$this->handles[] = $resource;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes a cURL transaction, either returning a response promise or a
|
||||
* rejected promise.
|
||||
*
|
||||
* @param callable $handler
|
||||
* @param EasyHandle $easy
|
||||
* @param CurlFactoryInterface $factory Dictates how the handle is released
|
||||
*
|
||||
* @return \GuzzleHttp\Promise\PromiseInterface
|
||||
*/
|
||||
public static function finish(
|
||||
callable $handler,
|
||||
EasyHandle $easy,
|
||||
CurlFactoryInterface $factory
|
||||
) {
|
||||
if (isset($easy->options['on_stats'])) {
|
||||
self::invokeStats($easy);
|
||||
}
|
||||
|
||||
if (!$easy->response || $easy->errno) {
|
||||
return self::finishError($handler, $easy, $factory);
|
||||
}
|
||||
|
||||
// Return the response if it is present and there is no error.
|
||||
$factory->release($easy);
|
||||
|
||||
// Rewind the body of the response if possible.
|
||||
$body = $easy->response->getBody();
|
||||
if ($body->isSeekable()) {
|
||||
$body->rewind();
|
||||
}
|
||||
|
||||
return new FulfilledPromise($easy->response);
|
||||
}
|
||||
|
||||
private static function invokeStats(EasyHandle $easy)
|
||||
{
|
||||
$curlStats = curl_getinfo($easy->handle);
|
||||
$stats = new TransferStats(
|
||||
$easy->request,
|
||||
$easy->response,
|
||||
$curlStats['total_time'],
|
||||
$easy->errno,
|
||||
$curlStats
|
||||
);
|
||||
call_user_func($easy->options['on_stats'], $stats);
|
||||
}
|
||||
|
||||
private static function finishError(
|
||||
callable $handler,
|
||||
EasyHandle $easy,
|
||||
CurlFactoryInterface $factory
|
||||
) {
|
||||
// Get error information and release the handle to the factory.
|
||||
$ctx = [
|
||||
'errno' => $easy->errno,
|
||||
'error' => curl_error($easy->handle),
|
||||
] + curl_getinfo($easy->handle);
|
||||
$factory->release($easy);
|
||||
|
||||
// Retry when nothing is present or when curl failed to rewind.
|
||||
if (empty($easy->options['_err_message'])
|
||||
&& (!$easy->errno || $easy->errno == 65)
|
||||
) {
|
||||
return self::retryFailedRewind($handler, $easy, $ctx);
|
||||
}
|
||||
|
||||
return self::createRejection($easy, $ctx);
|
||||
}
|
||||
|
||||
private static function createRejection(EasyHandle $easy, array $ctx)
|
||||
{
|
||||
static $connectionErrors = [
|
||||
CURLE_OPERATION_TIMEOUTED => true,
|
||||
CURLE_COULDNT_RESOLVE_HOST => true,
|
||||
CURLE_COULDNT_CONNECT => true,
|
||||
CURLE_SSL_CONNECT_ERROR => true,
|
||||
CURLE_GOT_NOTHING => true,
|
||||
];
|
||||
|
||||
// If an exception was encountered during the onHeaders event, then
|
||||
// return a rejected promise that wraps that exception.
|
||||
if ($easy->onHeadersException) {
|
||||
return \GuzzleHttp\Promise\rejection_for(
|
||||
new RequestException(
|
||||
'An error was encountered during the on_headers event',
|
||||
$easy->request,
|
||||
$easy->response,
|
||||
$easy->onHeadersException,
|
||||
$ctx
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$message = sprintf(
|
||||
'cURL error %s: %s (%s)',
|
||||
$ctx['errno'],
|
||||
$ctx['error'],
|
||||
'see http://curl.haxx.se/libcurl/c/libcurl-errors.html'
|
||||
);
|
||||
|
||||
// Create a connection exception if it was a specific error code.
|
||||
$error = isset($connectionErrors[$easy->errno])
|
||||
? new ConnectException($message, $easy->request, null, $ctx)
|
||||
: new RequestException($message, $easy->request, $easy->response, null, $ctx);
|
||||
|
||||
return \GuzzleHttp\Promise\rejection_for($error);
|
||||
}
|
||||
|
||||
private function getDefaultConf(EasyHandle $easy)
|
||||
{
|
||||
$conf = [
|
||||
'_headers' => $easy->request->getHeaders(),
|
||||
CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(),
|
||||
CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''),
|
||||
CURLOPT_RETURNTRANSFER => false,
|
||||
CURLOPT_HEADER => false,
|
||||
CURLOPT_CONNECTTIMEOUT => 150,
|
||||
];
|
||||
|
||||
if (defined('CURLOPT_PROTOCOLS')) {
|
||||
$conf[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
|
||||
}
|
||||
|
||||
$version = $easy->request->getProtocolVersion();
|
||||
if ($version == 1.1) {
|
||||
$conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
|
||||
} elseif ($version == 2.0) {
|
||||
$conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
|
||||
} else {
|
||||
$conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
|
||||
}
|
||||
|
||||
return $conf;
|
||||
}
|
||||
|
||||
private function applyMethod(EasyHandle $easy, array &$conf)
|
||||
{
|
||||
$body = $easy->request->getBody();
|
||||
$size = $body->getSize();
|
||||
|
||||
if ($size === null || $size > 0) {
|
||||
$this->applyBody($easy->request, $easy->options, $conf);
|
||||
return;
|
||||
}
|
||||
|
||||
$method = $easy->request->getMethod();
|
||||
if ($method === 'PUT' || $method === 'POST') {
|
||||
// See http://tools.ietf.org/html/rfc7230#section-3.3.2
|
||||
if (!$easy->request->hasHeader('Content-Length')) {
|
||||
$conf[CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
|
||||
}
|
||||
} elseif ($method === 'HEAD') {
|
||||
$conf[CURLOPT_NOBODY] = true;
|
||||
unset(
|
||||
$conf[CURLOPT_WRITEFUNCTION],
|
||||
$conf[CURLOPT_READFUNCTION],
|
||||
$conf[CURLOPT_FILE],
|
||||
$conf[CURLOPT_INFILE]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function applyBody(RequestInterface $request, array $options, array &$conf)
|
||||
{
|
||||
$size = $request->hasHeader('Content-Length')
|
||||
? (int) $request->getHeaderLine('Content-Length')
|
||||
: null;
|
||||
|
||||
// Send the body as a string if the size is less than 1MB OR if the
|
||||
// [curl][body_as_string] request value is set.
|
||||
if (($size !== null && $size < 1000000) ||
|
||||
!empty($options['_body_as_string'])
|
||||
) {
|
||||
$conf[CURLOPT_POSTFIELDS] = (string) $request->getBody();
|
||||
// Don't duplicate the Content-Length header
|
||||
$this->removeHeader('Content-Length', $conf);
|
||||
$this->removeHeader('Transfer-Encoding', $conf);
|
||||
} else {
|
||||
$conf[CURLOPT_UPLOAD] = true;
|
||||
if ($size !== null) {
|
||||
$conf[CURLOPT_INFILESIZE] = $size;
|
||||
$this->removeHeader('Content-Length', $conf);
|
||||
}
|
||||
$body = $request->getBody();
|
||||
if ($body->isSeekable()) {
|
||||
$body->rewind();
|
||||
}
|
||||
$conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
|
||||
return $body->read($length);
|
||||
};
|
||||
}
|
||||
|
||||
// If the Expect header is not present, prevent curl from adding it
|
||||
if (!$request->hasHeader('Expect')) {
|
||||
$conf[CURLOPT_HTTPHEADER][] = 'Expect:';
|
||||
}
|
||||
|
||||
// cURL sometimes adds a content-type by default. Prevent this.
|
||||
if (!$request->hasHeader('Content-Type')) {
|
||||
$conf[CURLOPT_HTTPHEADER][] = 'Content-Type:';
|
||||
}
|
||||
}
|
||||
|
||||
private function applyHeaders(EasyHandle $easy, array &$conf)
|
||||
{
|
||||
foreach ($conf['_headers'] as $name => $values) {
|
||||
foreach ($values as $value) {
|
||||
$conf[CURLOPT_HTTPHEADER][] = "$name: $value";
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the Accept header if one was not set
|
||||
if (!$easy->request->hasHeader('Accept')) {
|
||||
$conf[CURLOPT_HTTPHEADER][] = 'Accept:';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a header from the options array.
|
||||
*
|
||||
* @param string $name Case-insensitive header to remove
|
||||
* @param array $options Array of options to modify
|
||||
*/
|
||||
private function removeHeader($name, array &$options)
|
||||
{
|
||||
foreach (array_keys($options['_headers']) as $key) {
|
||||
if (!strcasecmp($key, $name)) {
|
||||
unset($options['_headers'][$key]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function applyHandlerOptions(EasyHandle $easy, array &$conf)
|
||||
{
|
||||
$options = $easy->options;
|
||||
if (isset($options['verify'])) {
|
||||
if ($options['verify'] === false) {
|
||||
unset($conf[CURLOPT_CAINFO]);
|
||||
$conf[CURLOPT_SSL_VERIFYHOST] = 0;
|
||||
$conf[CURLOPT_SSL_VERIFYPEER] = false;
|
||||
} else {
|
||||
$conf[CURLOPT_SSL_VERIFYHOST] = 2;
|
||||
$conf[CURLOPT_SSL_VERIFYPEER] = true;
|
||||
if (is_string($options['verify'])) {
|
||||
// Throw an error if the file/folder/link path is not valid or doesn't exist.
|
||||
if (!file_exists($options['verify'])) {
|
||||
throw new \InvalidArgumentException(
|
||||
"SSL CA bundle not found: {$options['verify']}"
|
||||
);
|
||||
}
|
||||
// If it's a directory or a link to a directory use CURLOPT_CAPATH.
|
||||
// If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO.
|
||||
if (is_dir($options['verify']) ||
|
||||
(is_link($options['verify']) && is_dir(readlink($options['verify'])))) {
|
||||
$conf[CURLOPT_CAPATH] = $options['verify'];
|
||||
} else {
|
||||
$conf[CURLOPT_CAINFO] = $options['verify'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($options['decode_content'])) {
|
||||
$accept = $easy->request->getHeaderLine('Accept-Encoding');
|
||||
if ($accept) {
|
||||
$conf[CURLOPT_ENCODING] = $accept;
|
||||
} else {
|
||||
$conf[CURLOPT_ENCODING] = '';
|
||||
// Don't let curl send the header over the wire
|
||||
$conf[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['sink'])) {
|
||||
$sink = $options['sink'];
|
||||
if (!is_string($sink)) {
|
||||
$sink = \GuzzleHttp\Psr7\stream_for($sink);
|
||||
} elseif (!is_dir(dirname($sink))) {
|
||||
// Ensure that the directory exists before failing in curl.
|
||||
throw new \RuntimeException(sprintf(
|
||||
'Directory %s does not exist for sink value of %s',
|
||||
dirname($sink),
|
||||
$sink
|
||||
));
|
||||
} else {
|
||||
$sink = new LazyOpenStream($sink, 'w+');
|
||||
}
|
||||
$easy->sink = $sink;
|
||||
$conf[CURLOPT_WRITEFUNCTION] = function ($ch, $write) use ($sink) {
|
||||
return $sink->write($write);
|
||||
};
|
||||
} else {
|
||||
// Use a default temp stream if no sink was set.
|
||||
$conf[CURLOPT_FILE] = fopen('php://temp', 'w+');
|
||||
$easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]);
|
||||
}
|
||||
$timeoutRequiresNoSignal = false;
|
||||
if (isset($options['timeout'])) {
|
||||
$timeoutRequiresNoSignal |= $options['timeout'] < 1;
|
||||
$conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000;
|
||||
}
|
||||
|
||||
// CURL default value is CURL_IPRESOLVE_WHATEVER
|
||||
if (isset($options['force_ip_resolve'])) {
|
||||
if ('v4' === $options['force_ip_resolve']) {
|
||||
$conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
|
||||
} else if ('v6' === $options['force_ip_resolve']) {
|
||||
$conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V6;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['connect_timeout'])) {
|
||||
$timeoutRequiresNoSignal |= $options['connect_timeout'] < 1;
|
||||
$conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000;
|
||||
}
|
||||
|
||||
if ($timeoutRequiresNoSignal && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
|
||||
$conf[CURLOPT_NOSIGNAL] = true;
|
||||
}
|
||||
|
||||
if (isset($options['proxy'])) {
|
||||
if (!is_array($options['proxy'])) {
|
||||
$conf[CURLOPT_PROXY] = $options['proxy'];
|
||||
} else {
|
||||
$scheme = $easy->request->getUri()->getScheme();
|
||||
if (isset($options['proxy'][$scheme])) {
|
||||
$host = $easy->request->getUri()->getHost();
|
||||
if (!isset($options['proxy']['no']) ||
|
||||
!\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no'])
|
||||
) {
|
||||
$conf[CURLOPT_PROXY] = $options['proxy'][$scheme];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['cert'])) {
|
||||
$cert = $options['cert'];
|
||||
if (is_array($cert)) {
|
||||
$conf[CURLOPT_SSLCERTPASSWD] = $cert[1];
|
||||
$cert = $cert[0];
|
||||
}
|
||||
if (!file_exists($cert)) {
|
||||
throw new \InvalidArgumentException(
|
||||
"SSL certificate not found: {$cert}"
|
||||
);
|
||||
}
|
||||
$conf[CURLOPT_SSLCERT] = $cert;
|
||||
}
|
||||
|
||||
if (isset($options['ssl_key'])) {
|
||||
$sslKey = $options['ssl_key'];
|
||||
if (is_array($sslKey)) {
|
||||
$conf[CURLOPT_SSLKEYPASSWD] = $sslKey[1];
|
||||
$sslKey = $sslKey[0];
|
||||
}
|
||||
if (!file_exists($sslKey)) {
|
||||
throw new \InvalidArgumentException(
|
||||
"SSL private key not found: {$sslKey}"
|
||||
);
|
||||
}
|
||||
$conf[CURLOPT_SSLKEY] = $sslKey;
|
||||
}
|
||||
|
||||
if (isset($options['progress'])) {
|
||||
$progress = $options['progress'];
|
||||
if (!is_callable($progress)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'progress client option must be callable'
|
||||
);
|
||||
}
|
||||
$conf[CURLOPT_NOPROGRESS] = false;
|
||||
$conf[CURLOPT_PROGRESSFUNCTION] = function () use ($progress) {
|
||||
$args = func_get_args();
|
||||
// PHP 5.5 pushed the handle onto the start of the args
|
||||
if (is_resource($args[0])) {
|
||||
array_shift($args);
|
||||
}
|
||||
call_user_func_array($progress, $args);
|
||||
};
|
||||
}
|
||||
|
||||
if (!empty($options['debug'])) {
|
||||
$conf[CURLOPT_STDERR] = \GuzzleHttp\debug_resource($options['debug']);
|
||||
$conf[CURLOPT_VERBOSE] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function ensures that a response was set on a transaction. If one
|
||||
* was not set, then the request is retried if possible. This error
|
||||
* typically means you are sending a payload, curl encountered a
|
||||
* "Connection died, retrying a fresh connect" error, tried to rewind the
|
||||
* stream, and then encountered a "necessary data rewind wasn't possible"
|
||||
* error, causing the request to be sent through curl_multi_info_read()
|
||||
* without an error status.
|
||||
*/
|
||||
private static function retryFailedRewind(
|
||||
callable $handler,
|
||||
EasyHandle $easy,
|
||||
array $ctx
|
||||
) {
|
||||
try {
|
||||
// Only rewind if the body has been read from.
|
||||
$body = $easy->request->getBody();
|
||||
if ($body->tell() > 0) {
|
||||
$body->rewind();
|
||||
}
|
||||
} catch (\RuntimeException $e) {
|
||||
$ctx['error'] = 'The connection unexpectedly failed without '
|
||||
. 'providing an error. The request would have been retried, '
|
||||
. 'but attempting to rewind the request body failed. '
|
||||
. 'Exception: ' . $e;
|
||||
return self::createRejection($easy, $ctx);
|
||||
}
|
||||
|
||||
// Retry no more than 3 times before giving up.
|
||||
if (!isset($easy->options['_curl_retries'])) {
|
||||
$easy->options['_curl_retries'] = 1;
|
||||
} elseif ($easy->options['_curl_retries'] == 2) {
|
||||
$ctx['error'] = 'The cURL request was retried 3 times '
|
||||
. 'and did not succeed. The most likely reason for the failure '
|
||||
. 'is that cURL was unable to rewind the body of the request '
|
||||
. 'and subsequent retries resulted in the same error. Turn on '
|
||||
. 'the debug option to see what went wrong. See '
|
||||
. 'https://bugs.php.net/bug.php?id=47204 for more information.';
|
||||
return self::createRejection($easy, $ctx);
|
||||
} else {
|
||||
$easy->options['_curl_retries']++;
|
||||
}
|
||||
|
||||
return $handler($easy->request, $easy->options);
|
||||
}
|
||||
|
||||
private function createHeaderFn(EasyHandle $easy)
|
||||
{
|
||||
if (isset($easy->options['on_headers'])) {
|
||||
$onHeaders = $easy->options['on_headers'];
|
||||
|
||||
if (!is_callable($onHeaders)) {
|
||||
throw new \InvalidArgumentException('on_headers must be callable');
|
||||
}
|
||||
} else {
|
||||
$onHeaders = null;
|
||||
}
|
||||
|
||||
return function ($ch, $h) use (
|
||||
$onHeaders,
|
||||
$easy,
|
||||
&$startingResponse
|
||||
) {
|
||||
$value = trim($h);
|
||||
if ($value === '') {
|
||||
$startingResponse = true;
|
||||
$easy->createResponse();
|
||||
if ($onHeaders !== null) {
|
||||
try {
|
||||
$onHeaders($easy->response);
|
||||
} catch (\Exception $e) {
|
||||
// Associate the exception with the handle and trigger
|
||||
// a curl header write error by returning 0.
|
||||
$easy->onHeadersException = $e;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
} elseif ($startingResponse) {
|
||||
$startingResponse = false;
|
||||
$easy->headers = [$value];
|
||||
} else {
|
||||
$easy->headers[] = $value;
|
||||
}
|
||||
return strlen($h);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
interface CurlFactoryInterface
|
||||
{
|
||||
/**
|
||||
* Creates a cURL handle resource.
|
||||
*
|
||||
* @param RequestInterface $request Request
|
||||
* @param array $options Transfer options
|
||||
*
|
||||
* @return EasyHandle
|
||||
* @throws \RuntimeException when an option cannot be applied
|
||||
*/
|
||||
public function create(RequestInterface $request, array $options);
|
||||
|
||||
/**
|
||||
* Release an easy handle, allowing it to be reused or closed.
|
||||
*
|
||||
* This function must call unset on the easy handle's "handle" property.
|
||||
*
|
||||
* @param EasyHandle $easy
|
||||
*/
|
||||
public function release(EasyHandle $easy);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Psr7;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* HTTP handler that uses cURL easy handles as a transport layer.
|
||||
*
|
||||
* When using the CurlHandler, 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.
|
||||
*/
|
||||
class CurlHandler
|
||||
{
|
||||
/** @var CurlFactoryInterface */
|
||||
private $factory;
|
||||
|
||||
/**
|
||||
* Accepts an associative array of options:
|
||||
*
|
||||
* - factory: Optional curl factory used to create cURL handles.
|
||||
*
|
||||
* @param array $options Array of options to use with the handler
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->factory = isset($options['handle_factory'])
|
||||
? $options['handle_factory']
|
||||
: new CurlFactory(3);
|
||||
}
|
||||
|
||||
public function __invoke(RequestInterface $request, array $options)
|
||||
{
|
||||
if (isset($options['delay'])) {
|
||||
usleep($options['delay'] * 1000);
|
||||
}
|
||||
|
||||
$easy = $this->factory->create($request, $options);
|
||||
curl_exec($easy->handle);
|
||||
$easy->errno = curl_errno($easy->handle);
|
||||
|
||||
return CurlFactory::finish($this, $easy, $this->factory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Promise as P;
|
||||
use GuzzleHttp\Promise\Promise;
|
||||
use GuzzleHttp\Psr7;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Returns an asynchronous response using curl_multi_* functions.
|
||||
*
|
||||
* 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 provided request options.
|
||||
*
|
||||
* @property resource $_mh Internal use only. Lazy loaded multi-handle.
|
||||
*/
|
||||
class CurlMultiHandler
|
||||
{
|
||||
/** @var CurlFactoryInterface */
|
||||
private $factory;
|
||||
private $selectTimeout;
|
||||
private $active;
|
||||
private $handles = [];
|
||||
private $delays = [];
|
||||
|
||||
/**
|
||||
* This handler accepts the following options:
|
||||
*
|
||||
* - handle_factory: An optional factory used to create curl handles
|
||||
* - select_timeout: Optional timeout (in seconds) to block before timing
|
||||
* out while selecting curl handles. Defaults to 1 second.
|
||||
*
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->factory = isset($options['handle_factory'])
|
||||
? $options['handle_factory'] : new CurlFactory(50);
|
||||
$this->selectTimeout = isset($options['select_timeout'])
|
||||
? $options['select_timeout'] : 1;
|
||||
}
|
||||
|
||||
public function __get($name)
|
||||
{
|
||||
if ($name === '_mh') {
|
||||
return $this->_mh = curl_multi_init();
|
||||
}
|
||||
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (isset($this->_mh)) {
|
||||
curl_multi_close($this->_mh);
|
||||
unset($this->_mh);
|
||||
}
|
||||
}
|
||||
|
||||
public function __invoke(RequestInterface $request, array $options)
|
||||
{
|
||||
$easy = $this->factory->create($request, $options);
|
||||
$id = (int) $easy->handle;
|
||||
|
||||
$promise = new Promise(
|
||||
[$this, 'execute'],
|
||||
function () use ($id) { return $this->cancel($id); }
|
||||
);
|
||||
|
||||
$this->addRequest(['easy' => $easy, 'deferred' => $promise]);
|
||||
|
||||
return $promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ticks the curl event loop.
|
||||
*/
|
||||
public function tick()
|
||||
{
|
||||
// Add any delayed handles if needed.
|
||||
if ($this->delays) {
|
||||
$currentTime = microtime(true);
|
||||
foreach ($this->delays as $id => $delay) {
|
||||
if ($currentTime >= $delay) {
|
||||
unset($this->delays[$id]);
|
||||
curl_multi_add_handle(
|
||||
$this->_mh,
|
||||
$this->handles[$id]['easy']->handle
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step through the task queue which may add additional requests.
|
||||
P\queue()->run();
|
||||
|
||||
if ($this->active &&
|
||||
curl_multi_select($this->_mh, $this->selectTimeout) === -1
|
||||
) {
|
||||
// Perform a usleep if a select returns -1.
|
||||
// See: https://bugs.php.net/bug.php?id=61141
|
||||
usleep(250);
|
||||
}
|
||||
|
||||
while (curl_multi_exec($this->_mh, $this->active) === CURLM_CALL_MULTI_PERFORM);
|
||||
|
||||
$this->processMessages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs until all outstanding connections have completed.
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$queue = P\queue();
|
||||
|
||||
while ($this->handles || !$queue->isEmpty()) {
|
||||
// If there are no transfers, then sleep for the next delay
|
||||
if (!$this->active && $this->delays) {
|
||||
usleep($this->timeToNext());
|
||||
}
|
||||
$this->tick();
|
||||
}
|
||||
}
|
||||
|
||||
private function addRequest(array $entry)
|
||||
{
|
||||
$easy = $entry['easy'];
|
||||
$id = (int) $easy->handle;
|
||||
$this->handles[$id] = $entry;
|
||||
if (empty($easy->options['delay'])) {
|
||||
curl_multi_add_handle($this->_mh, $easy->handle);
|
||||
} else {
|
||||
$this->delays[$id] = microtime(true) + ($easy->options['delay'] / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a handle from sending and removes references to it.
|
||||
*
|
||||
* @param int $id Handle ID to cancel and remove.
|
||||
*
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
private function cancel($id)
|
||||
{
|
||||
// Cannot cancel if it has been processed.
|
||||
if (!isset($this->handles[$id])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$handle = $this->handles[$id]['easy']->handle;
|
||||
unset($this->delays[$id], $this->handles[$id]);
|
||||
curl_multi_remove_handle($this->_mh, $handle);
|
||||
curl_close($handle);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function processMessages()
|
||||
{
|
||||
while ($done = curl_multi_info_read($this->_mh)) {
|
||||
$id = (int) $done['handle'];
|
||||
curl_multi_remove_handle($this->_mh, $done['handle']);
|
||||
|
||||
if (!isset($this->handles[$id])) {
|
||||
// Probably was cancelled.
|
||||
continue;
|
||||
}
|
||||
|
||||
$entry = $this->handles[$id];
|
||||
unset($this->handles[$id], $this->delays[$id]);
|
||||
$entry['easy']->errno = $done['result'];
|
||||
$entry['deferred']->resolve(
|
||||
CurlFactory::finish(
|
||||
$this,
|
||||
$entry['easy'],
|
||||
$this->factory
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function timeToNext()
|
||||
{
|
||||
$currentTime = microtime(true);
|
||||
$nextTime = PHP_INT_MAX;
|
||||
foreach ($this->delays as $time) {
|
||||
if ($time < $nextTime) {
|
||||
$nextTime = $time;
|
||||
}
|
||||
}
|
||||
|
||||
return max(0, $nextTime - $currentTime) * 1000000;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* Represents a cURL easy handle and the data it populates.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class EasyHandle
|
||||
{
|
||||
/** @var resource cURL resource */
|
||||
public $handle;
|
||||
|
||||
/** @var StreamInterface Where data is being written */
|
||||
public $sink;
|
||||
|
||||
/** @var array Received HTTP headers so far */
|
||||
public $headers = [];
|
||||
|
||||
/** @var ResponseInterface Received response (if any) */
|
||||
public $response;
|
||||
|
||||
/** @var RequestInterface Request being sent */
|
||||
public $request;
|
||||
|
||||
/** @var array Request options */
|
||||
public $options = [];
|
||||
|
||||
/** @var int cURL error number (if any) */
|
||||
public $errno = 0;
|
||||
|
||||
/** @var \Exception Exception during on_headers (if any) */
|
||||
public $onHeadersException;
|
||||
|
||||
/**
|
||||
* Attach a response to the easy handle based on the received headers.
|
||||
*
|
||||
* @throws \RuntimeException if no headers have been received.
|
||||
*/
|
||||
public function createResponse()
|
||||
{
|
||||
if (empty($this->headers)) {
|
||||
throw new \RuntimeException('No headers have been received');
|
||||
}
|
||||
|
||||
// HTTP-version SP status-code SP reason-phrase
|
||||
$startLine = explode(' ', array_shift($this->headers), 3);
|
||||
$headers = \GuzzleHttp\headers_from_lines($this->headers);
|
||||
$normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);
|
||||
|
||||
if (!empty($this->options['decode_content'])
|
||||
&& isset($normalizedKeys['content-encoding'])
|
||||
) {
|
||||
$headers['x-encoded-content-encoding']
|
||||
= $headers[$normalizedKeys['content-encoding']];
|
||||
unset($headers[$normalizedKeys['content-encoding']]);
|
||||
if (isset($normalizedKeys['content-length'])) {
|
||||
$headers['x-encoded-content-length']
|
||||
= $headers[$normalizedKeys['content-length']];
|
||||
|
||||
$bodyLength = (int) $this->sink->getSize();
|
||||
if ($bodyLength) {
|
||||
$headers[$normalizedKeys['content-length']] = $bodyLength;
|
||||
} else {
|
||||
unset($headers[$normalizedKeys['content-length']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attach a response to the easy handle with the parsed headers.
|
||||
$this->response = new Response(
|
||||
$startLine[1],
|
||||
$headers,
|
||||
$this->sink,
|
||||
substr($startLine[0], 5),
|
||||
isset($startLine[2]) ? (string) $startLine[2] : null
|
||||
);
|
||||
}
|
||||
|
||||
public function __get($name)
|
||||
{
|
||||
$msg = $name === 'handle'
|
||||
? 'The EasyHandle has been released'
|
||||
: 'Invalid property: ' . $name;
|
||||
throw new \BadMethodCallException($msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\Promise\RejectedPromise;
|
||||
use GuzzleHttp\TransferStats;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Handler that returns responses or throw exceptions from a queue.
|
||||
*/
|
||||
class MockHandler implements \Countable
|
||||
{
|
||||
private $queue = [];
|
||||
private $lastRequest;
|
||||
private $lastOptions;
|
||||
private $onFulfilled;
|
||||
private $onRejected;
|
||||
|
||||
/**
|
||||
* Creates a new MockHandler that uses the default handler stack list of
|
||||
* middlewares.
|
||||
*
|
||||
* @param array $queue Array of responses, callables, or exceptions.
|
||||
* @param callable $onFulfilled Callback to invoke when the return value is fulfilled.
|
||||
* @param callable $onRejected Callback to invoke when the return value is rejected.
|
||||
*
|
||||
* @return HandlerStack
|
||||
*/
|
||||
public static function createWithMiddleware(
|
||||
array $queue = null,
|
||||
callable $onFulfilled = null,
|
||||
callable $onRejected = null
|
||||
) {
|
||||
return HandlerStack::create(new self($queue, $onFulfilled, $onRejected));
|
||||
}
|
||||
|
||||
/**
|
||||
* The passed in value must be an array of
|
||||
* {@see Psr7\Http\Message\ResponseInterface} objects, Exceptions,
|
||||
* callables, or Promises.
|
||||
*
|
||||
* @param array $queue
|
||||
* @param callable $onFulfilled Callback to invoke when the return value is fulfilled.
|
||||
* @param callable $onRejected Callback to invoke when the return value is rejected.
|
||||
*/
|
||||
public function __construct(
|
||||
array $queue = null,
|
||||
callable $onFulfilled = null,
|
||||
callable $onRejected = null
|
||||
) {
|
||||
$this->onFulfilled = $onFulfilled;
|
||||
$this->onRejected = $onRejected;
|
||||
|
||||
if ($queue) {
|
||||
call_user_func_array([$this, 'append'], $queue);
|
||||
}
|
||||
}
|
||||
|
||||
public function __invoke(RequestInterface $request, array $options)
|
||||
{
|
||||
if (!$this->queue) {
|
||||
throw new \OutOfBoundsException('Mock queue is empty');
|
||||
}
|
||||
|
||||
if (isset($options['delay'])) {
|
||||
usleep($options['delay'] * 1000);
|
||||
}
|
||||
|
||||
$this->lastRequest = $request;
|
||||
$this->lastOptions = $options;
|
||||
$response = array_shift($this->queue);
|
||||
|
||||
if (isset($options['on_headers'])) {
|
||||
if (!is_callable($options['on_headers'])) {
|
||||
throw new \InvalidArgumentException('on_headers must be callable');
|
||||
}
|
||||
try {
|
||||
$options['on_headers']($response);
|
||||
} catch (\Exception $e) {
|
||||
$msg = 'An error was encountered during the on_headers event';
|
||||
$response = new RequestException($msg, $request, $response, $e);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_callable($response)) {
|
||||
$response = call_user_func($response, $request, $options);
|
||||
}
|
||||
|
||||
$response = $response instanceof \Exception
|
||||
? \GuzzleHttp\Promise\rejection_for($response)
|
||||
: \GuzzleHttp\Promise\promise_for($response);
|
||||
|
||||
return $response->then(
|
||||
function ($value) use ($request, $options) {
|
||||
$this->invokeStats($request, $options, $value);
|
||||
if ($this->onFulfilled) {
|
||||
call_user_func($this->onFulfilled, $value);
|
||||
}
|
||||
if (isset($options['sink'])) {
|
||||
$contents = (string) $value->getBody();
|
||||
$sink = $options['sink'];
|
||||
|
||||
if (is_resource($sink)) {
|
||||
fwrite($sink, $contents);
|
||||
} elseif (is_string($sink)) {
|
||||
file_put_contents($sink, $contents);
|
||||
} elseif ($sink instanceof \Psr\Http\Message\StreamInterface) {
|
||||
$sink->write($contents);
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
function ($reason) use ($request, $options) {
|
||||
$this->invokeStats($request, $options, null, $reason);
|
||||
if ($this->onRejected) {
|
||||
call_user_func($this->onRejected, $reason);
|
||||
}
|
||||
return \GuzzleHttp\Promise\rejection_for($reason);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one or more variadic requests, exceptions, callables, or promises
|
||||
* to the queue.
|
||||
*/
|
||||
public function append()
|
||||
{
|
||||
foreach (func_get_args() as $value) {
|
||||
if ($value instanceof ResponseInterface
|
||||
|| $value instanceof \Exception
|
||||
|| $value instanceof PromiseInterface
|
||||
|| is_callable($value)
|
||||
) {
|
||||
$this->queue[] = $value;
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Expected a response or '
|
||||
. 'exception. Found ' . \GuzzleHttp\describe_type($value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last received request.
|
||||
*
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public function getLastRequest()
|
||||
{
|
||||
return $this->lastRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last received request options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getLastOptions()
|
||||
{
|
||||
return $this->lastOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of remaining items in the queue.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return count($this->queue);
|
||||
}
|
||||
|
||||
private function invokeStats(
|
||||
RequestInterface $request,
|
||||
array $options,
|
||||
ResponseInterface $response = null,
|
||||
$reason = null
|
||||
) {
|
||||
if (isset($options['on_stats'])) {
|
||||
$stats = new TransferStats($request, $response, 0, $reason);
|
||||
call_user_func($options['on_stats'], $stats);
|
||||
}
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\RequestOptions;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Provides basic proxies for handlers.
|
||||
*/
|
||||
class Proxy
|
||||
{
|
||||
/**
|
||||
* Sends synchronous requests to a specific handler while sending all other
|
||||
* requests to another handler.
|
||||
*
|
||||
* @param callable $default Handler used for normal responses
|
||||
* @param callable $sync Handler used for synchronous responses.
|
||||
*
|
||||
* @return callable Returns the composed handler.
|
||||
*/
|
||||
public static function wrapSync(
|
||||
callable $default,
|
||||
callable $sync
|
||||
) {
|
||||
return function (RequestInterface $request, array $options) use ($default, $sync) {
|
||||
return empty($options[RequestOptions::SYNCHRONOUS])
|
||||
? $default($request, $options)
|
||||
: $sync($request, $options);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends streaming requests to a streaming compatible handler while sending
|
||||
* all other requests to a default handler.
|
||||
*
|
||||
* This, for example, could be useful for taking advantage of the
|
||||
* performance benefits of curl while still supporting true streaming
|
||||
* through the StreamHandler.
|
||||
*
|
||||
* @param callable $default Handler used for non-streaming responses
|
||||
* @param callable $streaming Handler used for streaming responses
|
||||
*
|
||||
* @return callable Returns the composed handler.
|
||||
*/
|
||||
public static function wrapStreaming(
|
||||
callable $default,
|
||||
callable $streaming
|
||||
) {
|
||||
return function (RequestInterface $request, array $options) use ($default, $streaming) {
|
||||
return empty($options['stream'])
|
||||
? $default($request, $options)
|
||||
: $streaming($request, $options);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,533 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Exception\ConnectException;
|
||||
use GuzzleHttp\Promise\FulfilledPromise;
|
||||
use GuzzleHttp\Promise\RejectedPromise;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\Psr7;
|
||||
use GuzzleHttp\TransferStats;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* HTTP handler that uses PHP's HTTP stream wrapper.
|
||||
*/
|
||||
class StreamHandler
|
||||
{
|
||||
private $lastHeaders = [];
|
||||
|
||||
/**
|
||||
* Sends an HTTP request.
|
||||
*
|
||||
* @param RequestInterface $request Request to send.
|
||||
* @param array $options Request transfer options.
|
||||
*
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
public function __invoke(RequestInterface $request, array $options)
|
||||
{
|
||||
// Sleep if there is a delay specified.
|
||||
if (isset($options['delay'])) {
|
||||
usleep($options['delay'] * 1000);
|
||||
}
|
||||
|
||||
$startTime = isset($options['on_stats']) ? microtime(true) : null;
|
||||
|
||||
try {
|
||||
// Does not support the expect header.
|
||||
$request = $request->withoutHeader('Expect');
|
||||
|
||||
// Append a content-length header if body size is zero to match
|
||||
// cURL's behavior.
|
||||
if (0 === $request->getBody()->getSize()) {
|
||||
$request = $request->withHeader('Content-Length', 0);
|
||||
}
|
||||
|
||||
return $this->createResponse(
|
||||
$request,
|
||||
$options,
|
||||
$this->createStream($request, $options),
|
||||
$startTime
|
||||
);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw $e;
|
||||
} catch (\Exception $e) {
|
||||
// Determine if the error was a networking error.
|
||||
$message = $e->getMessage();
|
||||
// This list can probably get more comprehensive.
|
||||
if (strpos($message, 'getaddrinfo') // DNS lookup failed
|
||||
|| strpos($message, 'Connection refused')
|
||||
|| strpos($message, "couldn't connect to host") // error on HHVM
|
||||
) {
|
||||
$e = new ConnectException($e->getMessage(), $request, $e);
|
||||
}
|
||||
$e = RequestException::wrapException($request, $e);
|
||||
$this->invokeStats($options, $request, $startTime, null, $e);
|
||||
|
||||
return \GuzzleHttp\Promise\rejection_for($e);
|
||||
}
|
||||
}
|
||||
|
||||
private function invokeStats(
|
||||
array $options,
|
||||
RequestInterface $request,
|
||||
$startTime,
|
||||
ResponseInterface $response = null,
|
||||
$error = null
|
||||
) {
|
||||
if (isset($options['on_stats'])) {
|
||||
$stats = new TransferStats(
|
||||
$request,
|
||||
$response,
|
||||
microtime(true) - $startTime,
|
||||
$error,
|
||||
[]
|
||||
);
|
||||
call_user_func($options['on_stats'], $stats);
|
||||
}
|
||||
}
|
||||
|
||||
private function createResponse(
|
||||
RequestInterface $request,
|
||||
array $options,
|
||||
$stream,
|
||||
$startTime
|
||||
) {
|
||||
$hdrs = $this->lastHeaders;
|
||||
$this->lastHeaders = [];
|
||||
$parts = explode(' ', array_shift($hdrs), 3);
|
||||
$ver = explode('/', $parts[0])[1];
|
||||
$status = $parts[1];
|
||||
$reason = isset($parts[2]) ? $parts[2] : null;
|
||||
$headers = \GuzzleHttp\headers_from_lines($hdrs);
|
||||
list ($stream, $headers) = $this->checkDecode($options, $headers, $stream);
|
||||
$stream = Psr7\stream_for($stream);
|
||||
$sink = $stream;
|
||||
|
||||
if (strcasecmp('HEAD', $request->getMethod())) {
|
||||
$sink = $this->createSink($stream, $options);
|
||||
}
|
||||
|
||||
$response = new Psr7\Response($status, $headers, $sink, $ver, $reason);
|
||||
|
||||
if (isset($options['on_headers'])) {
|
||||
try {
|
||||
$options['on_headers']($response);
|
||||
} catch (\Exception $e) {
|
||||
$msg = 'An error was encountered during the on_headers event';
|
||||
$ex = new RequestException($msg, $request, $response, $e);
|
||||
return \GuzzleHttp\Promise\rejection_for($ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Do not drain when the request is a HEAD request because they have
|
||||
// no body.
|
||||
if ($sink !== $stream) {
|
||||
$this->drain(
|
||||
$stream,
|
||||
$sink,
|
||||
$response->getHeaderLine('Content-Length')
|
||||
);
|
||||
}
|
||||
|
||||
$this->invokeStats($options, $request, $startTime, $response, null);
|
||||
|
||||
return new FulfilledPromise($response);
|
||||
}
|
||||
|
||||
private function createSink(StreamInterface $stream, array $options)
|
||||
{
|
||||
if (!empty($options['stream'])) {
|
||||
return $stream;
|
||||
}
|
||||
|
||||
$sink = isset($options['sink'])
|
||||
? $options['sink']
|
||||
: fopen('php://temp', 'r+');
|
||||
|
||||
return is_string($sink)
|
||||
? new Psr7\LazyOpenStream($sink, 'w+')
|
||||
: Psr7\stream_for($sink);
|
||||
}
|
||||
|
||||
private function checkDecode(array $options, array $headers, $stream)
|
||||
{
|
||||
// Automatically decode responses when instructed.
|
||||
if (!empty($options['decode_content'])) {
|
||||
$normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);
|
||||
if (isset($normalizedKeys['content-encoding'])) {
|
||||
$encoding = $headers[$normalizedKeys['content-encoding']];
|
||||
if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') {
|
||||
$stream = new Psr7\InflateStream(
|
||||
Psr7\stream_for($stream)
|
||||
);
|
||||
$headers['x-encoded-content-encoding']
|
||||
= $headers[$normalizedKeys['content-encoding']];
|
||||
// Remove content-encoding header
|
||||
unset($headers[$normalizedKeys['content-encoding']]);
|
||||
// Fix content-length header
|
||||
if (isset($normalizedKeys['content-length'])) {
|
||||
$headers['x-encoded-content-length']
|
||||
= $headers[$normalizedKeys['content-length']];
|
||||
|
||||
$length = (int) $stream->getSize();
|
||||
if ($length === 0) {
|
||||
unset($headers[$normalizedKeys['content-length']]);
|
||||
} else {
|
||||
$headers[$normalizedKeys['content-length']] = [$length];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [$stream, $headers];
|
||||
}
|
||||
|
||||
/**
|
||||
* Drains the source stream into the "sink" client option.
|
||||
*
|
||||
* @param StreamInterface $source
|
||||
* @param StreamInterface $sink
|
||||
* @param string $contentLength Header specifying the amount of
|
||||
* data to read.
|
||||
*
|
||||
* @return StreamInterface
|
||||
* @throws \RuntimeException when the sink option is invalid.
|
||||
*/
|
||||
private function drain(
|
||||
StreamInterface $source,
|
||||
StreamInterface $sink,
|
||||
$contentLength
|
||||
) {
|
||||
// If a content-length header is provided, then stop reading once
|
||||
// that number of bytes has been read. This can prevent infinitely
|
||||
// reading from a stream when dealing with servers that do not honor
|
||||
// Connection: Close headers.
|
||||
Psr7\copy_to_stream(
|
||||
$source,
|
||||
$sink,
|
||||
(strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1
|
||||
);
|
||||
|
||||
$sink->seek(0);
|
||||
$source->close();
|
||||
|
||||
return $sink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a resource and check to ensure it was created successfully
|
||||
*
|
||||
* @param callable $callback Callable that returns stream resource
|
||||
*
|
||||
* @return resource
|
||||
* @throws \RuntimeException on error
|
||||
*/
|
||||
private function createResource(callable $callback)
|
||||
{
|
||||
$errors = null;
|
||||
set_error_handler(function ($_, $msg, $file, $line) use (&$errors) {
|
||||
$errors[] = [
|
||||
'message' => $msg,
|
||||
'file' => $file,
|
||||
'line' => $line
|
||||
];
|
||||
return true;
|
||||
});
|
||||
|
||||
$resource = $callback();
|
||||
restore_error_handler();
|
||||
|
||||
if (!$resource) {
|
||||
$message = 'Error creating resource: ';
|
||||
foreach ($errors as $err) {
|
||||
foreach ($err as $key => $value) {
|
||||
$message .= "[$key] $value" . PHP_EOL;
|
||||
}
|
||||
}
|
||||
throw new \RuntimeException(trim($message));
|
||||
}
|
||||
|
||||
return $resource;
|
||||
}
|
||||
|
||||
private function createStream(RequestInterface $request, array $options)
|
||||
{
|
||||
static $methods;
|
||||
if (!$methods) {
|
||||
$methods = array_flip(get_class_methods(__CLASS__));
|
||||
}
|
||||
|
||||
// HTTP/1.1 streams using the PHP stream wrapper require a
|
||||
// Connection: close header
|
||||
if ($request->getProtocolVersion() == '1.1'
|
||||
&& !$request->hasHeader('Connection')
|
||||
) {
|
||||
$request = $request->withHeader('Connection', 'close');
|
||||
}
|
||||
|
||||
// Ensure SSL is verified by default
|
||||
if (!isset($options['verify'])) {
|
||||
$options['verify'] = true;
|
||||
}
|
||||
|
||||
$params = [];
|
||||
$context = $this->getDefaultContext($request, $options);
|
||||
|
||||
if (isset($options['on_headers']) && !is_callable($options['on_headers'])) {
|
||||
throw new \InvalidArgumentException('on_headers must be callable');
|
||||
}
|
||||
|
||||
if (!empty($options)) {
|
||||
foreach ($options as $key => $value) {
|
||||
$method = "add_{$key}";
|
||||
if (isset($methods[$method])) {
|
||||
$this->{$method}($request, $context, $value, $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['stream_context'])) {
|
||||
if (!is_array($options['stream_context'])) {
|
||||
throw new \InvalidArgumentException('stream_context must be an array');
|
||||
}
|
||||
$context = array_replace_recursive(
|
||||
$context,
|
||||
$options['stream_context']
|
||||
);
|
||||
}
|
||||
|
||||
// Microsoft NTLM authentication only supported with curl handler
|
||||
if (isset($options['auth'])
|
||||
&& is_array($options['auth'])
|
||||
&& isset($options['auth'][2])
|
||||
&& 'ntlm' == $options['auth'][2]
|
||||
) {
|
||||
|
||||
throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler');
|
||||
}
|
||||
|
||||
$uri = $this->resolveHost($request, $options);
|
||||
|
||||
$context = $this->createResource(
|
||||
function () use ($context, $params) {
|
||||
return stream_context_create($context, $params);
|
||||
}
|
||||
);
|
||||
|
||||
return $this->createResource(
|
||||
function () use ($uri, &$http_response_header, $context, $options) {
|
||||
$resource = fopen((string) $uri, 'r', null, $context);
|
||||
$this->lastHeaders = $http_response_header;
|
||||
|
||||
if (isset($options['read_timeout'])) {
|
||||
$readTimeout = $options['read_timeout'];
|
||||
$sec = (int) $readTimeout;
|
||||
$usec = ($readTimeout - $sec) * 100000;
|
||||
stream_set_timeout($resource, $sec, $usec);
|
||||
}
|
||||
|
||||
return $resource;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private function resolveHost(RequestInterface $request, array $options)
|
||||
{
|
||||
$uri = $request->getUri();
|
||||
|
||||
if (isset($options['force_ip_resolve']) && !filter_var($uri->getHost(), FILTER_VALIDATE_IP)) {
|
||||
if ('v4' === $options['force_ip_resolve']) {
|
||||
$records = dns_get_record($uri->getHost(), DNS_A);
|
||||
if (!isset($records[0]['ip'])) {
|
||||
throw new ConnectException(sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request);
|
||||
}
|
||||
$uri = $uri->withHost($records[0]['ip']);
|
||||
} elseif ('v6' === $options['force_ip_resolve']) {
|
||||
$records = dns_get_record($uri->getHost(), DNS_AAAA);
|
||||
if (!isset($records[0]['ipv6'])) {
|
||||
throw new ConnectException(sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request);
|
||||
}
|
||||
$uri = $uri->withHost('[' . $records[0]['ipv6'] . ']');
|
||||
}
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
private function getDefaultContext(RequestInterface $request)
|
||||
{
|
||||
$headers = '';
|
||||
foreach ($request->getHeaders() as $name => $value) {
|
||||
foreach ($value as $val) {
|
||||
$headers .= "$name: $val\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
$context = [
|
||||
'http' => [
|
||||
'method' => $request->getMethod(),
|
||||
'header' => $headers,
|
||||
'protocol_version' => $request->getProtocolVersion(),
|
||||
'ignore_errors' => true,
|
||||
'follow_location' => 0,
|
||||
],
|
||||
];
|
||||
|
||||
$body = (string) $request->getBody();
|
||||
|
||||
if (!empty($body)) {
|
||||
$context['http']['content'] = $body;
|
||||
// Prevent the HTTP handler from adding a Content-Type header.
|
||||
if (!$request->hasHeader('Content-Type')) {
|
||||
$context['http']['header'] .= "Content-Type:\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
$context['http']['header'] = rtrim($context['http']['header']);
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
||||
private function add_proxy(RequestInterface $request, &$options, $value, &$params)
|
||||
{
|
||||
if (!is_array($value)) {
|
||||
$options['http']['proxy'] = $value;
|
||||
} else {
|
||||
$scheme = $request->getUri()->getScheme();
|
||||
if (isset($value[$scheme])) {
|
||||
if (!isset($value['no'])
|
||||
|| !\GuzzleHttp\is_host_in_noproxy(
|
||||
$request->getUri()->getHost(),
|
||||
$value['no']
|
||||
)
|
||||
) {
|
||||
$options['http']['proxy'] = $value[$scheme];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function add_timeout(RequestInterface $request, &$options, $value, &$params)
|
||||
{
|
||||
if ($value > 0) {
|
||||
$options['http']['timeout'] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
private function add_verify(RequestInterface $request, &$options, $value, &$params)
|
||||
{
|
||||
if ($value === true) {
|
||||
// PHP 5.6 or greater will find the system cert by default. When
|
||||
// < 5.6, use the Guzzle bundled cacert.
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
$options['ssl']['cafile'] = \GuzzleHttp\default_ca_bundle();
|
||||
}
|
||||
} elseif (is_string($value)) {
|
||||
$options['ssl']['cafile'] = $value;
|
||||
if (!file_exists($value)) {
|
||||
throw new \RuntimeException("SSL CA bundle not found: $value");
|
||||
}
|
||||
} elseif ($value === false) {
|
||||
$options['ssl']['verify_peer'] = false;
|
||||
$options['ssl']['verify_peer_name'] = false;
|
||||
return;
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Invalid verify request option');
|
||||
}
|
||||
|
||||
$options['ssl']['verify_peer'] = true;
|
||||
$options['ssl']['verify_peer_name'] = true;
|
||||
$options['ssl']['allow_self_signed'] = false;
|
||||
}
|
||||
|
||||
private function add_cert(RequestInterface $request, &$options, $value, &$params)
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$options['ssl']['passphrase'] = $value[1];
|
||||
$value = $value[0];
|
||||
}
|
||||
|
||||
if (!file_exists($value)) {
|
||||
throw new \RuntimeException("SSL certificate not found: {$value}");
|
||||
}
|
||||
|
||||
$options['ssl']['local_cert'] = $value;
|
||||
}
|
||||
|
||||
private function add_progress(RequestInterface $request, &$options, $value, &$params)
|
||||
{
|
||||
$this->addNotification(
|
||||
$params,
|
||||
function ($code, $a, $b, $c, $transferred, $total) use ($value) {
|
||||
if ($code == STREAM_NOTIFY_PROGRESS) {
|
||||
$value($total, $transferred, null, null);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private function add_debug(RequestInterface $request, &$options, $value, &$params)
|
||||
{
|
||||
if ($value === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
static $map = [
|
||||
STREAM_NOTIFY_CONNECT => 'CONNECT',
|
||||
STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
|
||||
STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT',
|
||||
STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS',
|
||||
STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS',
|
||||
STREAM_NOTIFY_REDIRECTED => 'REDIRECTED',
|
||||
STREAM_NOTIFY_PROGRESS => 'PROGRESS',
|
||||
STREAM_NOTIFY_FAILURE => 'FAILURE',
|
||||
STREAM_NOTIFY_COMPLETED => 'COMPLETED',
|
||||
STREAM_NOTIFY_RESOLVE => 'RESOLVE',
|
||||
];
|
||||
static $args = ['severity', 'message', 'message_code',
|
||||
'bytes_transferred', 'bytes_max'];
|
||||
|
||||
$value = \GuzzleHttp\debug_resource($value);
|
||||
$ident = $request->getMethod() . ' ' . $request->getUri()->withFragment('');
|
||||
$this->addNotification(
|
||||
$params,
|
||||
function () use ($ident, $value, $map, $args) {
|
||||
$passed = func_get_args();
|
||||
$code = array_shift($passed);
|
||||
fprintf($value, '<%s> [%s] ', $ident, $map[$code]);
|
||||
foreach (array_filter($passed) as $i => $v) {
|
||||
fwrite($value, $args[$i] . ': "' . $v . '" ');
|
||||
}
|
||||
fwrite($value, "\n");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private function addNotification(array &$params, callable $notify)
|
||||
{
|
||||
// Wrap the existing function if needed.
|
||||
if (!isset($params['notification'])) {
|
||||
$params['notification'] = $notify;
|
||||
} else {
|
||||
$params['notification'] = $this->callArray([
|
||||
$params['notification'],
|
||||
$notify
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function callArray(array $functions)
|
||||
{
|
||||
return function () use ($functions) {
|
||||
$args = func_get_args();
|
||||
foreach ($functions as $fn) {
|
||||
call_user_func_array($fn, $args);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Creates a composed Guzzle handler function by stacking middlewares on top of
|
||||
* an HTTP handler function.
|
||||
*/
|
||||
class HandlerStack
|
||||
{
|
||||
/** @var callable */
|
||||
private $handler;
|
||||
|
||||
/** @var array */
|
||||
private $stack = [];
|
||||
|
||||
/** @var callable|null */
|
||||
private $cached;
|
||||
|
||||
/**
|
||||
* Creates a default handler stack that can be used by clients.
|
||||
*
|
||||
* The returned handler will wrap the provided handler or use the most
|
||||
* appropriate default handler for you system. The returned HandlerStack has
|
||||
* support for cookies, redirects, HTTP error exceptions, and preparing a body
|
||||
* before sending.
|
||||
*
|
||||
* The returned handler stack can be passed to a client in the "handler"
|
||||
* option.
|
||||
*
|
||||
* @param callable $handler HTTP handler function to use with the stack. If no
|
||||
* handler is provided, the best handler for your
|
||||
* system will be utilized.
|
||||
*
|
||||
* @return HandlerStack
|
||||
*/
|
||||
public static function create(callable $handler = null)
|
||||
{
|
||||
$stack = new self($handler ?: choose_handler());
|
||||
$stack->push(Middleware::httpErrors(), 'http_errors');
|
||||
$stack->push(Middleware::redirect(), 'allow_redirects');
|
||||
$stack->push(Middleware::cookies(), 'cookies');
|
||||
$stack->push(Middleware::prepareBody(), 'prepare_body');
|
||||
|
||||
return $stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $handler Underlying HTTP handler.
|
||||
*/
|
||||
public function __construct(callable $handler = null)
|
||||
{
|
||||
$this->handler = $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the handler stack as a composed handler
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param array $options
|
||||
*/
|
||||
public function __invoke(RequestInterface $request, array $options)
|
||||
{
|
||||
$handler = $this->resolve();
|
||||
|
||||
return $handler($request, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps a string representation of the stack.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$depth = 0;
|
||||
$stack = [];
|
||||
if ($this->handler) {
|
||||
$stack[] = "0) Handler: " . $this->debugCallable($this->handler);
|
||||
}
|
||||
|
||||
$result = '';
|
||||
foreach (array_reverse($this->stack) as $tuple) {
|
||||
$depth++;
|
||||
$str = "{$depth}) Name: '{$tuple[1]}', ";
|
||||
$str .= "Function: " . $this->debugCallable($tuple[0]);
|
||||
$result = "> {$str}\n{$result}";
|
||||
$stack[] = $str;
|
||||
}
|
||||
|
||||
foreach (array_keys($stack) as $k) {
|
||||
$result .= "< {$stack[$k]}\n";
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the HTTP handler that actually returns a promise.
|
||||
*
|
||||
* @param callable $handler Accepts a request and array of options and
|
||||
* returns a Promise.
|
||||
*/
|
||||
public function setHandler(callable $handler)
|
||||
{
|
||||
$this->handler = $handler;
|
||||
$this->cached = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the builder has a handler.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasHandler()
|
||||
{
|
||||
return (bool) $this->handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unshift a middleware to the bottom of the stack.
|
||||
*
|
||||
* @param callable $middleware Middleware function
|
||||
* @param string $name Name to register for this middleware.
|
||||
*/
|
||||
public function unshift(callable $middleware, $name = null)
|
||||
{
|
||||
array_unshift($this->stack, [$middleware, $name]);
|
||||
$this->cached = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a middleware to the top of the stack.
|
||||
*
|
||||
* @param callable $middleware Middleware function
|
||||
* @param string $name Name to register for this middleware.
|
||||
*/
|
||||
public function push(callable $middleware, $name = '')
|
||||
{
|
||||
$this->stack[] = [$middleware, $name];
|
||||
$this->cached = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a middleware before another middleware by name.
|
||||
*
|
||||
* @param string $findName Middleware to find
|
||||
* @param callable $middleware Middleware function
|
||||
* @param string $withName Name to register for this middleware.
|
||||
*/
|
||||
public function before($findName, callable $middleware, $withName = '')
|
||||
{
|
||||
$this->splice($findName, $withName, $middleware, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a middleware after another middleware by name.
|
||||
*
|
||||
* @param string $findName Middleware to find
|
||||
* @param callable $middleware Middleware function
|
||||
* @param string $withName Name to register for this middleware.
|
||||
*/
|
||||
public function after($findName, callable $middleware, $withName = '')
|
||||
{
|
||||
$this->splice($findName, $withName, $middleware, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a middleware by instance or name from the stack.
|
||||
*
|
||||
* @param callable|string $remove Middleware to remove by instance or name.
|
||||
*/
|
||||
public function remove($remove)
|
||||
{
|
||||
$this->cached = null;
|
||||
$idx = is_callable($remove) ? 0 : 1;
|
||||
$this->stack = array_values(array_filter(
|
||||
$this->stack,
|
||||
function ($tuple) use ($idx, $remove) {
|
||||
return $tuple[$idx] !== $remove;
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose the middleware and handler into a single callable function.
|
||||
*
|
||||
* @return callable
|
||||
*/
|
||||
public function resolve()
|
||||
{
|
||||
if (!$this->cached) {
|
||||
if (!($prev = $this->handler)) {
|
||||
throw new \LogicException('No handler has been specified');
|
||||
}
|
||||
|
||||
foreach (array_reverse($this->stack) as $fn) {
|
||||
$prev = $fn[0]($prev);
|
||||
}
|
||||
|
||||
$this->cached = $prev;
|
||||
}
|
||||
|
||||
return $this->cached;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @return int
|
||||
*/
|
||||
private function findByName($name)
|
||||
{
|
||||
foreach ($this->stack as $k => $v) {
|
||||
if ($v[1] === $name) {
|
||||
return $k;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException("Middleware not found: $name");
|
||||
}
|
||||
|
||||
/**
|
||||
* Splices a function into the middleware list at a specific position.
|
||||
*
|
||||
* @param $findName
|
||||
* @param $withName
|
||||
* @param callable $middleware
|
||||
* @param $before
|
||||
*/
|
||||
private function splice($findName, $withName, callable $middleware, $before)
|
||||
{
|
||||
$this->cached = null;
|
||||
$idx = $this->findByName($findName);
|
||||
$tuple = [$middleware, $withName];
|
||||
|
||||
if ($before) {
|
||||
if ($idx === 0) {
|
||||
array_unshift($this->stack, $tuple);
|
||||
} else {
|
||||
$replacement = [$tuple, $this->stack[$idx]];
|
||||
array_splice($this->stack, $idx, 1, $replacement);
|
||||
}
|
||||
} elseif ($idx === count($this->stack) - 1) {
|
||||
$this->stack[] = $tuple;
|
||||
} else {
|
||||
$replacement = [$this->stack[$idx], $tuple];
|
||||
array_splice($this->stack, $idx, 1, $replacement);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a debug string for a given callable.
|
||||
*
|
||||
* @param array|callable $fn Function to write as a string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function debugCallable($fn)
|
||||
{
|
||||
if (is_string($fn)) {
|
||||
return "callable({$fn})";
|
||||
}
|
||||
|
||||
if (is_array($fn)) {
|
||||
return is_string($fn[0])
|
||||
? "callable({$fn[0]}::{$fn[1]})"
|
||||
: "callable(['" . get_class($fn[0]) . "', '{$fn[1]}'])";
|
||||
}
|
||||
|
||||
return 'callable(' . spl_object_hash($fn) . ')';
|
||||
}
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Formats log messages using variable substitutions for requests, responses,
|
||||
* and other transactional data.
|
||||
*
|
||||
* The following variable substitutions are supported:
|
||||
*
|
||||
* - {request}: Full HTTP request message
|
||||
* - {response}: Full HTTP response message
|
||||
* - {ts}: ISO 8601 date in GMT
|
||||
* - {date_iso_8601} ISO 8601 date in GMT
|
||||
* - {date_common_log} Apache common log date using the configured timezone.
|
||||
* - {host}: Host of the request
|
||||
* - {method}: Method of the request
|
||||
* - {uri}: URI of the request
|
||||
* - {host}: Host of the request
|
||||
* - {version}: Protocol version
|
||||
* - {target}: Request target of the request (path + query + fragment)
|
||||
* - {hostname}: Hostname of the machine that sent the request
|
||||
* - {code}: Status code of the response (if available)
|
||||
* - {phrase}: Reason phrase of the response (if available)
|
||||
* - {error}: Any error messages (if available)
|
||||
* - {req_header_*}: Replace `*` with the lowercased name of a request header to add to the message
|
||||
* - {res_header_*}: Replace `*` with the lowercased name of a response header to add to the message
|
||||
* - {req_headers}: Request headers
|
||||
* - {res_headers}: Response headers
|
||||
* - {req_body}: Request body
|
||||
* - {res_body}: Response body
|
||||
*/
|
||||
class MessageFormatter
|
||||
{
|
||||
/**
|
||||
* Apache Common Log Format.
|
||||
* @link http://httpd.apache.org/docs/2.4/logs.html#common
|
||||
* @var string
|
||||
*/
|
||||
const CLF = "{hostname} {req_header_User-Agent} - [{date_common_log}] \"{method} {target} HTTP/{version}\" {code} {res_header_Content-Length}";
|
||||
const DEBUG = ">>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}";
|
||||
const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}';
|
||||
|
||||
/** @var string Template used to format log messages */
|
||||
private $template;
|
||||
|
||||
/**
|
||||
* @param string $template Log message template
|
||||
*/
|
||||
public function __construct($template = self::CLF)
|
||||
{
|
||||
$this->template = $template ?: self::CLF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a formatted message string.
|
||||
*
|
||||
* @param RequestInterface $request Request that was sent
|
||||
* @param ResponseInterface $response Response that was received
|
||||
* @param \Exception $error Exception that was received
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function format(
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response = null,
|
||||
\Exception $error = null
|
||||
) {
|
||||
$cache = [];
|
||||
|
||||
return preg_replace_callback(
|
||||
'/{\s*([A-Za-z_\-\.0-9]+)\s*}/',
|
||||
function (array $matches) use ($request, $response, $error, &$cache) {
|
||||
|
||||
if (isset($cache[$matches[1]])) {
|
||||
return $cache[$matches[1]];
|
||||
}
|
||||
|
||||
$result = '';
|
||||
switch ($matches[1]) {
|
||||
case 'request':
|
||||
$result = Psr7\str($request);
|
||||
break;
|
||||
case 'response':
|
||||
$result = $response ? Psr7\str($response) : '';
|
||||
break;
|
||||
case 'req_headers':
|
||||
$result = trim($request->getMethod()
|
||||
. ' ' . $request->getRequestTarget())
|
||||
. ' HTTP/' . $request->getProtocolVersion() . "\r\n"
|
||||
. $this->headers($request);
|
||||
break;
|
||||
case 'res_headers':
|
||||
$result = $response ?
|
||||
sprintf(
|
||||
'HTTP/%s %d %s',
|
||||
$response->getProtocolVersion(),
|
||||
$response->getStatusCode(),
|
||||
$response->getReasonPhrase()
|
||||
) . "\r\n" . $this->headers($response)
|
||||
: 'NULL';
|
||||
break;
|
||||
case 'req_body':
|
||||
$result = $request->getBody();
|
||||
break;
|
||||
case 'res_body':
|
||||
$result = $response ? $response->getBody() : 'NULL';
|
||||
break;
|
||||
case 'ts':
|
||||
case 'date_iso_8601':
|
||||
$result = gmdate('c');
|
||||
break;
|
||||
case 'date_common_log':
|
||||
$result = date('d/M/Y:H:i:s O');
|
||||
break;
|
||||
case 'method':
|
||||
$result = $request->getMethod();
|
||||
break;
|
||||
case 'version':
|
||||
$result = $request->getProtocolVersion();
|
||||
break;
|
||||
case 'uri':
|
||||
case 'url':
|
||||
$result = $request->getUri();
|
||||
break;
|
||||
case 'target':
|
||||
$result = $request->getRequestTarget();
|
||||
break;
|
||||
case 'req_version':
|
||||
$result = $request->getProtocolVersion();
|
||||
break;
|
||||
case 'res_version':
|
||||
$result = $response
|
||||
? $response->getProtocolVersion()
|
||||
: 'NULL';
|
||||
break;
|
||||
case 'host':
|
||||
$result = $request->getHeaderLine('Host');
|
||||
break;
|
||||
case 'hostname':
|
||||
$result = gethostname();
|
||||
break;
|
||||
case 'code':
|
||||
$result = $response ? $response->getStatusCode() : 'NULL';
|
||||
break;
|
||||
case 'phrase':
|
||||
$result = $response ? $response->getReasonPhrase() : 'NULL';
|
||||
break;
|
||||
case 'error':
|
||||
$result = $error ? $error->getMessage() : 'NULL';
|
||||
break;
|
||||
default:
|
||||
// handle prefixed dynamic headers
|
||||
if (strpos($matches[1], 'req_header_') === 0) {
|
||||
$result = $request->getHeaderLine(substr($matches[1], 11));
|
||||
} elseif (strpos($matches[1], 'res_header_') === 0) {
|
||||
$result = $response
|
||||
? $response->getHeaderLine(substr($matches[1], 11))
|
||||
: 'NULL';
|
||||
}
|
||||
}
|
||||
|
||||
$cache[$matches[1]] = $result;
|
||||
return $result;
|
||||
},
|
||||
$this->template
|
||||
);
|
||||
}
|
||||
|
||||
private function headers(MessageInterface $message)
|
||||
{
|
||||
$result = '';
|
||||
foreach ($message->getHeaders() as $name => $values) {
|
||||
$result .= $name . ': ' . implode(', ', $values) . "\r\n";
|
||||
}
|
||||
|
||||
return trim($result);
|
||||
}
|
||||
}
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Cookie\CookieJarInterface;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Promise\RejectedPromise;
|
||||
use GuzzleHttp\Psr7;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
|
||||
/**
|
||||
* Functions used to create and wrap handlers with handler middleware.
|
||||
*/
|
||||
final class Middleware
|
||||
{
|
||||
/**
|
||||
* Middleware that adds cookies to requests.
|
||||
*
|
||||
* The options array must be set to a CookieJarInterface in order to use
|
||||
* cookies. This is typically handled for you by a client.
|
||||
*
|
||||
* @return callable Returns a function that accepts the next handler.
|
||||
*/
|
||||
public static function cookies()
|
||||
{
|
||||
return function (callable $handler) {
|
||||
return function ($request, array $options) use ($handler) {
|
||||
if (empty($options['cookies'])) {
|
||||
return $handler($request, $options);
|
||||
} elseif (!($options['cookies'] instanceof CookieJarInterface)) {
|
||||
throw new \InvalidArgumentException('cookies must be an instance of GuzzleHttp\Cookie\CookieJarInterface');
|
||||
}
|
||||
$cookieJar = $options['cookies'];
|
||||
$request = $cookieJar->withCookieHeader($request);
|
||||
return $handler($request, $options)
|
||||
->then(function ($response) use ($cookieJar, $request) {
|
||||
$cookieJar->extractCookies($request, $response);
|
||||
return $response;
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that throws exceptions for 4xx or 5xx responses when the
|
||||
* "http_error" request option is set to true.
|
||||
*
|
||||
* @return callable Returns a function that accepts the next handler.
|
||||
*/
|
||||
public static function httpErrors()
|
||||
{
|
||||
return function (callable $handler) {
|
||||
return function ($request, array $options) use ($handler) {
|
||||
if (empty($options['http_errors'])) {
|
||||
return $handler($request, $options);
|
||||
}
|
||||
return $handler($request, $options)->then(
|
||||
function (ResponseInterface $response) use ($request, $handler) {
|
||||
$code = $response->getStatusCode();
|
||||
if ($code < 400) {
|
||||
return $response;
|
||||
}
|
||||
throw RequestException::create($request, $response);
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that pushes history data to an ArrayAccess container.
|
||||
*
|
||||
* @param array $container Container to hold the history (by reference).
|
||||
*
|
||||
* @return callable Returns a function that accepts the next handler.
|
||||
* @throws \InvalidArgumentException if container is not an array or ArrayAccess.
|
||||
*/
|
||||
public static function history(&$container)
|
||||
{
|
||||
if (!is_array($container) && !$container instanceof \ArrayAccess) {
|
||||
throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess');
|
||||
}
|
||||
|
||||
return function (callable $handler) use (&$container) {
|
||||
return function ($request, array $options) use ($handler, &$container) {
|
||||
return $handler($request, $options)->then(
|
||||
function ($value) use ($request, &$container, $options) {
|
||||
$container[] = [
|
||||
'request' => $request,
|
||||
'response' => $value,
|
||||
'error' => null,
|
||||
'options' => $options
|
||||
];
|
||||
return $value;
|
||||
},
|
||||
function ($reason) use ($request, &$container, $options) {
|
||||
$container[] = [
|
||||
'request' => $request,
|
||||
'response' => null,
|
||||
'error' => $reason,
|
||||
'options' => $options
|
||||
];
|
||||
return \GuzzleHttp\Promise\rejection_for($reason);
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that invokes a callback before and after sending a request.
|
||||
*
|
||||
* The provided listener cannot modify or alter the response. It simply
|
||||
* "taps" into the chain to be notified before returning the promise. The
|
||||
* before listener accepts a request and options array, and the after
|
||||
* listener accepts a request, options array, and response promise.
|
||||
*
|
||||
* @param callable $before Function to invoke before forwarding the request.
|
||||
* @param callable $after Function invoked after forwarding.
|
||||
*
|
||||
* @return callable Returns a function that accepts the next handler.
|
||||
*/
|
||||
public static function tap(callable $before = null, callable $after = null)
|
||||
{
|
||||
return function (callable $handler) use ($before, $after) {
|
||||
return function ($request, array $options) use ($handler, $before, $after) {
|
||||
if ($before) {
|
||||
$before($request, $options);
|
||||
}
|
||||
$response = $handler($request, $options);
|
||||
if ($after) {
|
||||
$after($request, $options, $response);
|
||||
}
|
||||
return $response;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that handles request redirects.
|
||||
*
|
||||
* @return callable Returns a function that accepts the next handler.
|
||||
*/
|
||||
public static function redirect()
|
||||
{
|
||||
return function (callable $handler) {
|
||||
return new RedirectMiddleware($handler);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that retries requests based on the boolean result of
|
||||
* invoking the provided "decider" function.
|
||||
*
|
||||
* If no delay function is provided, a simple implementation of exponential
|
||||
* backoff will be utilized.
|
||||
*
|
||||
* @param callable $decider Function that accepts the number of retries,
|
||||
* a request, [response], and [exception] and
|
||||
* returns true if the request is to be retried.
|
||||
* @param callable $delay Function that accepts the number of retries and
|
||||
* returns the number of milliseconds to delay.
|
||||
*
|
||||
* @return callable Returns a function that accepts the next handler.
|
||||
*/
|
||||
public static function retry(callable $decider, callable $delay = null)
|
||||
{
|
||||
return function (callable $handler) use ($decider, $delay) {
|
||||
return new RetryMiddleware($decider, $handler, $delay);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that logs requests, responses, and errors using a message
|
||||
* formatter.
|
||||
*
|
||||
* @param LoggerInterface $logger Logs messages.
|
||||
* @param MessageFormatter $formatter Formatter used to create message strings.
|
||||
* @param string $logLevel Level at which to log requests.
|
||||
*
|
||||
* @return callable Returns a function that accepts the next handler.
|
||||
*/
|
||||
public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = LogLevel::INFO)
|
||||
{
|
||||
return function (callable $handler) use ($logger, $formatter, $logLevel) {
|
||||
return function ($request, array $options) use ($handler, $logger, $formatter, $logLevel) {
|
||||
return $handler($request, $options)->then(
|
||||
function ($response) use ($logger, $request, $formatter, $logLevel) {
|
||||
$message = $formatter->format($request, $response);
|
||||
$logger->log($logLevel, $message);
|
||||
return $response;
|
||||
},
|
||||
function ($reason) use ($logger, $request, $formatter) {
|
||||
$response = $reason instanceof RequestException
|
||||
? $reason->getResponse()
|
||||
: null;
|
||||
$message = $formatter->format($request, $response, $reason);
|
||||
$logger->notice($message);
|
||||
return \GuzzleHttp\Promise\rejection_for($reason);
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This middleware adds a default content-type if possible, a default
|
||||
* content-length or transfer-encoding header, and the expect header.
|
||||
*
|
||||
* @return callable
|
||||
*/
|
||||
public static function prepareBody()
|
||||
{
|
||||
return function (callable $handler) {
|
||||
return new PrepareBodyMiddleware($handler);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that applies a map function to the request before passing to
|
||||
* the next handler.
|
||||
*
|
||||
* @param callable $fn Function that accepts a RequestInterface and returns
|
||||
* a RequestInterface.
|
||||
* @return callable
|
||||
*/
|
||||
public static function mapRequest(callable $fn)
|
||||
{
|
||||
return function (callable $handler) use ($fn) {
|
||||
return function ($request, array $options) use ($handler, $fn) {
|
||||
return $handler($fn($request), $options);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that applies a map function to the resolved promise's
|
||||
* response.
|
||||
*
|
||||
* @param callable $fn Function that accepts a ResponseInterface and
|
||||
* returns a ResponseInterface.
|
||||
* @return callable
|
||||
*/
|
||||
public static function mapResponse(callable $fn)
|
||||
{
|
||||
return function (callable $handler) use ($fn) {
|
||||
return function ($request, array $options) use ($handler, $fn) {
|
||||
return $handler($request, $options)->then($fn);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Promise\PromisorInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use GuzzleHttp\Promise\EachPromise;
|
||||
|
||||
/**
|
||||
* Sends and iterator of requests concurrently using a capped pool size.
|
||||
*
|
||||
* The pool will read from an iterator until it is cancelled or until the
|
||||
* iterator is consumed. When a request is yielded, the request is sent after
|
||||
* applying the "request_options" request options (if provided in the ctor).
|
||||
*
|
||||
* When a function is yielded by the iterator, the function is provided the
|
||||
* "request_options" array that should be merged on top of any existing
|
||||
* options, and the function MUST then return a wait-able promise.
|
||||
*/
|
||||
class Pool implements PromisorInterface
|
||||
{
|
||||
/** @var EachPromise */
|
||||
private $each;
|
||||
|
||||
/**
|
||||
* @param ClientInterface $client Client used to send the requests.
|
||||
* @param array|\Iterator $requests Requests or functions that return
|
||||
* requests to send concurrently.
|
||||
* @param array $config Associative array of options
|
||||
* - concurrency: (int) Maximum number of requests to send concurrently
|
||||
* - options: Array of request options to apply to each request.
|
||||
* - fulfilled: (callable) Function to invoke when a request completes.
|
||||
* - rejected: (callable) Function to invoke when a request is rejected.
|
||||
*/
|
||||
public function __construct(
|
||||
ClientInterface $client,
|
||||
$requests,
|
||||
array $config = []
|
||||
) {
|
||||
// Backwards compatibility.
|
||||
if (isset($config['pool_size'])) {
|
||||
$config['concurrency'] = $config['pool_size'];
|
||||
} elseif (!isset($config['concurrency'])) {
|
||||
$config['concurrency'] = 25;
|
||||
}
|
||||
|
||||
if (isset($config['options'])) {
|
||||
$opts = $config['options'];
|
||||
unset($config['options']);
|
||||
} else {
|
||||
$opts = [];
|
||||
}
|
||||
|
||||
$iterable = \GuzzleHttp\Promise\iter_for($requests);
|
||||
$requests = function () use ($iterable, $client, $opts) {
|
||||
foreach ($iterable as $key => $rfn) {
|
||||
if ($rfn instanceof RequestInterface) {
|
||||
yield $key => $client->sendAsync($rfn, $opts);
|
||||
} elseif (is_callable($rfn)) {
|
||||
yield $key => $rfn($opts);
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Each value yielded by '
|
||||
. 'the iterator must be a Psr7\Http\Message\RequestInterface '
|
||||
. 'or a callable that returns a promise that fulfills '
|
||||
. 'with a Psr7\Message\Http\ResponseInterface object.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$this->each = new EachPromise($requests(), $config);
|
||||
}
|
||||
|
||||
public function promise()
|
||||
{
|
||||
return $this->each->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends multiple requests concurrently and returns an array of responses
|
||||
* and exceptions that uses the same ordering as the provided requests.
|
||||
*
|
||||
* IMPORTANT: This method keeps every request and response in memory, and
|
||||
* as such, is NOT recommended when sending a large number or an
|
||||
* indeterminate number of requests concurrently.
|
||||
*
|
||||
* @param ClientInterface $client Client used to send the requests
|
||||
* @param array|\Iterator $requests Requests to send concurrently.
|
||||
* @param array $options Passes through the options available in
|
||||
* {@see GuzzleHttp\Pool::__construct}
|
||||
*
|
||||
* @return array Returns an array containing the response or an exception
|
||||
* in the same order that the requests were sent.
|
||||
* @throws \InvalidArgumentException if the event format is incorrect.
|
||||
*/
|
||||
public static function batch(
|
||||
ClientInterface $client,
|
||||
$requests,
|
||||
array $options = []
|
||||
) {
|
||||
$res = [];
|
||||
self::cmpCallback($options, 'fulfilled', $res);
|
||||
self::cmpCallback($options, 'rejected', $res);
|
||||
$pool = new static($client, $requests, $options);
|
||||
$pool->promise()->wait();
|
||||
ksort($res);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
private static function cmpCallback(array &$options, $name, array &$results)
|
||||
{
|
||||
if (!isset($options[$name])) {
|
||||
$options[$name] = function ($v, $k) use (&$results) {
|
||||
$results[$k] = $v;
|
||||
};
|
||||
} else {
|
||||
$currentFn = $options[$name];
|
||||
$options[$name] = function ($v, $k) use (&$results, $currentFn) {
|
||||
$currentFn($v, $k);
|
||||
$results[$k] = $v;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\Psr7;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Prepares requests that contain a body, adding the Content-Length,
|
||||
* Content-Type, and Expect headers.
|
||||
*/
|
||||
class PrepareBodyMiddleware
|
||||
{
|
||||
/** @var callable */
|
||||
private $nextHandler;
|
||||
|
||||
/**
|
||||
* @param callable $nextHandler Next handler to invoke.
|
||||
*/
|
||||
public function __construct(callable $nextHandler)
|
||||
{
|
||||
$this->nextHandler = $nextHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestInterface $request
|
||||
* @param array $options
|
||||
*
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
public function __invoke(RequestInterface $request, array $options)
|
||||
{
|
||||
$fn = $this->nextHandler;
|
||||
|
||||
// Don't do anything if the request has no body.
|
||||
if ($request->getBody()->getSize() === 0) {
|
||||
return $fn($request, $options);
|
||||
}
|
||||
|
||||
$modify = [];
|
||||
|
||||
// Add a default content-type if possible.
|
||||
if (!$request->hasHeader('Content-Type')) {
|
||||
if ($uri = $request->getBody()->getMetadata('uri')) {
|
||||
if ($type = Psr7\mimetype_from_filename($uri)) {
|
||||
$modify['set_headers']['Content-Type'] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a default content-length or transfer-encoding header.
|
||||
if (!$request->hasHeader('Content-Length')
|
||||
&& !$request->hasHeader('Transfer-Encoding')
|
||||
) {
|
||||
$size = $request->getBody()->getSize();
|
||||
if ($size !== null) {
|
||||
$modify['set_headers']['Content-Length'] = $size;
|
||||
} else {
|
||||
$modify['set_headers']['Transfer-Encoding'] = 'chunked';
|
||||
}
|
||||
}
|
||||
|
||||
// Add the expect header if needed.
|
||||
$this->addExpectHeader($request, $options, $modify);
|
||||
|
||||
return $fn(Psr7\modify_request($request, $modify), $options);
|
||||
}
|
||||
|
||||
private function addExpectHeader(
|
||||
RequestInterface $request,
|
||||
array $options,
|
||||
array &$modify
|
||||
) {
|
||||
// Determine if the Expect header should be used
|
||||
if ($request->hasHeader('Expect')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$expect = isset($options['expect']) ? $options['expect'] : null;
|
||||
|
||||
// Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0
|
||||
if ($expect === false || $request->getProtocolVersion() < 1.1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The expect header is unconditionally enabled
|
||||
if ($expect === true) {
|
||||
$modify['set_headers']['Expect'] = '100-Continue';
|
||||
return;
|
||||
}
|
||||
|
||||
// By default, send the expect header when the payload is > 1mb
|
||||
if ($expect === null) {
|
||||
$expect = 1048576;
|
||||
}
|
||||
|
||||
// Always add if the body cannot be rewound, the size cannot be
|
||||
// determined, or the size is greater than the cutoff threshold
|
||||
$body = $request->getBody();
|
||||
$size = $body->getSize();
|
||||
|
||||
if ($size === null || $size >= (int) $expect || !$body->isSeekable()) {
|
||||
$modify['set_headers']['Expect'] = '100-Continue';
|
||||
}
|
||||
}
|
||||
}
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
<?php
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Exception\BadResponseException;
|
||||
use GuzzleHttp\Exception\TooManyRedirectsException;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\Psr7;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* Request redirect middleware.
|
||||
*
|
||||
* Apply this middleware like other middleware using
|
||||
* {@see GuzzleHttp\Middleware::redirect()}.
|
||||
*/
|
||||
class RedirectMiddleware
|
||||
{
|
||||
const HISTORY_HEADER = 'X-Guzzle-Redirect-History';
|
||||
|
||||
const STATUS_HISTORY_HEADER = 'X-Guzzle-Redirect-Status-History';
|
||||
|
||||
public static $defaultSettings = [
|
||||
'max' => 5,
|
||||
'protocols' => ['http', 'https'],
|
||||
'strict' => false,
|
||||
'referer' => false,
|
||||
'track_redirects' => false,
|
||||
];
|
||||
|
||||
/** @var callable */
|
||||
private $nextHandler;
|
||||
|
||||
/**
|
||||
* @param callable $nextHandler Next handler to invoke.
|
||||
*/
|
||||
public function __construct(callable $nextHandler)
|
||||
{
|
||||
$this->nextHandler = $nextHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestInterface $request
|
||||
* @param array $options
|
||||
*
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
public function __invoke(RequestInterface $request, array $options)
|
||||
{
|
||||
$fn = $this->nextHandler;
|
||||
|
||||
if (empty($options['allow_redirects'])) {
|
||||
return $fn($request, $options);
|
||||
}
|
||||
|
||||
if ($options['allow_redirects'] === true) {
|
||||
$options['allow_redirects'] = self::$defaultSettings;
|
||||
} elseif (!is_array($options['allow_redirects'])) {
|
||||
throw new \InvalidArgumentException('allow_redirects must be true, false, or array');
|
||||
} else {
|
||||
// Merge the default settings with the provided settings
|
||||
$options['allow_redirects'] += self::$defaultSettings;
|
||||
}
|
||||
|
||||
if (empty($options['allow_redirects']['max'])) {
|
||||
return $fn($request, $options);
|
||||
}
|
||||
|
||||
return $fn($request, $options)
|
||||
->then(function (ResponseInterface $response) use ($request, $options) {
|
||||
return $this->checkRedirect($request, $options, $response);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestInterface $request
|
||||
* @param array $options
|
||||
* @param ResponseInterface|PromiseInterface $response
|
||||
*
|
||||
* @return ResponseInterface|PromiseInterface
|
||||
*/
|
||||
public function checkRedirect(
|
||||
RequestInterface $request,
|
||||
array $options,
|
||||
ResponseInterface $response
|
||||
) {
|
||||
if (substr($response->getStatusCode(), 0, 1) != '3'
|
||||
|| !$response->hasHeader('Location')
|
||||
) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$this->guardMax($request, $options);
|
||||
$nextRequest = $this->modifyRequest($request, $options, $response);
|
||||
|
||||
if (isset($options['allow_redirects']['on_redirect'])) {
|
||||
call_user_func(
|
||||
$options['allow_redirects']['on_redirect'],
|
||||
$request,
|
||||
$response,
|
||||
$nextRequest->getUri()
|
||||
);
|
||||
}
|
||||
|
||||
/** @var PromiseInterface|ResponseInterface $promise */
|
||||
$promise = $this($nextRequest, $options);
|
||||
|
||||
// Add headers to be able to track history of redirects.
|
||||
if (!empty($options['allow_redirects']['track_redirects'])) {
|
||||
return $this->withTracking(
|
||||
$promise,
|
||||
(string) $nextRequest->getUri(),
|
||||
$response->getStatusCode()
|
||||
);
|
||||
}
|
||||
|
||||
return $promise;
|
||||
}
|
||||
|
||||
private function withTracking(PromiseInterface $promise, $uri, $statusCode)
|
||||
{
|
||||
return $promise->then(
|
||||
function (ResponseInterface $response) use ($uri, $statusCode) {
|
||||
// Note that we are pushing to the front of the list as this
|
||||
// would be an earlier response than what is currently present
|
||||
// in the history header.
|
||||
$historyHeader = $response->getHeader(self::HISTORY_HEADER);
|
||||
$statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER);
|
||||
array_unshift($historyHeader, $uri);
|
||||
array_unshift($statusHeader, $statusCode);
|
||||
return $response->withHeader(self::HISTORY_HEADER, $historyHeader)
|
||||
->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private function guardMax(RequestInterface $request, array &$options)
|
||||
{
|
||||
$current = isset($options['__redirect_count'])
|
||||
? $options['__redirect_count']
|
||||
: 0;
|
||||
$options['__redirect_count'] = $current + 1;
|
||||
$max = $options['allow_redirects']['max'];
|
||||
|
||||
if ($options['__redirect_count'] > $max) {
|
||||
throw new TooManyRedirectsException(
|
||||
"Will not follow more than {$max} redirects",
|
||||
$request
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestInterface $request
|
||||
* @param array $options
|
||||
* @param ResponseInterface $response
|
||||
*
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public function modifyRequest(
|
||||
RequestInterface $request,
|
||||
array $options,
|
||||
ResponseInterface $response
|
||||
) {
|
||||
// Request modifications to apply.
|
||||
$modify = [];
|
||||
$protocols = $options['allow_redirects']['protocols'];
|
||||
|
||||
// Use a GET request if this is an entity enclosing request and we are
|
||||
// not forcing RFC compliance, but rather emulating what all browsers
|
||||
// would do.
|
||||
$statusCode = $response->getStatusCode();
|
||||
if ($statusCode == 303 ||
|
||||
($statusCode <= 302 && $request->getBody() && !$options['allow_redirects']['strict'])
|
||||
) {
|
||||
$modify['method'] = 'GET';
|
||||
$modify['body'] = '';
|
||||
}
|
||||
|
||||
$modify['uri'] = $this->redirectUri($request, $response, $protocols);
|
||||
Psr7\rewind_body($request);
|
||||
|
||||
// Add the Referer header if it is told to do so and only
|
||||
// add the header if we are not redirecting from https to http.
|
||||
if ($options['allow_redirects']['referer']
|
||||
&& $modify['uri']->getScheme() === $request->getUri()->getScheme()
|
||||
) {
|
||||
$uri = $request->getUri()->withUserInfo('', '');
|
||||
$modify['set_headers']['Referer'] = (string) $uri;
|
||||
} else {
|
||||
$modify['remove_headers'][] = 'Referer';
|
||||
}
|
||||
|
||||
// Remove Authorization header if host is different.
|
||||
if ($request->getUri()->getHost() !== $modify['uri']->getHost()) {
|
||||
$modify['remove_headers'][] = 'Authorization';
|
||||
}
|
||||
|
||||
return Psr7\modify_request($request, $modify);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the appropriate URL on the request based on the location header
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @param array $protocols
|
||||
*
|
||||
* @return UriInterface
|
||||
*/
|
||||
private function redirectUri(
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response,
|
||||
array $protocols
|
||||
) {
|
||||
$location = Psr7\UriResolver::resolve(
|
||||
$request->getUri(),
|
||||
new Psr7\Uri($response->getHeaderLine('Location'))
|
||||
);
|
||||
|
||||
// Ensure that the redirect URI is allowed based on the protocols.
|
||||
if (!in_array($location->getScheme(), $protocols)) {
|
||||
throw new BadResponseException(
|
||||
sprintf(
|
||||
'Redirect URI, %s, does not use one of the allowed redirect protocols: %s',
|
||||
$location,
|
||||
implode(', ', $protocols)
|
||||
),
|
||||
$request,
|
||||
$response
|
||||
);
|
||||
}
|
||||
|
||||
return $location;
|
||||
}
|
||||
}
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
namespace GuzzleHttp;
|
||||
|
||||
/**
|
||||
* This class contains a list of built-in Guzzle request options.
|
||||
*
|
||||
* More documentation for each option can be found at http://guzzlephp.org/.
|
||||
*
|
||||
* @link http://docs.guzzlephp.org/en/v6/request-options.html
|
||||
*/
|
||||
final class RequestOptions
|
||||
{
|
||||
/**
|
||||
* allow_redirects: (bool|array) Controls redirect behavior. Pass false
|
||||
* to disable redirects, pass true to enable redirects, pass an
|
||||
* associative to provide custom redirect settings. Defaults to "false".
|
||||
* This option only works if your handler has the RedirectMiddleware. When
|
||||
* passing an associative array, you can provide the following key value
|
||||
* pairs:
|
||||
*
|
||||
* - max: (int, default=5) maximum number of allowed redirects.
|
||||
* - strict: (bool, default=false) Set to true to use strict redirects
|
||||
* meaning redirect POST requests with POST requests vs. doing what most
|
||||
* browsers do which is redirect POST requests with GET requests
|
||||
* - referer: (bool, default=true) Set to false to disable the Referer
|
||||
* header.
|
||||
* - protocols: (array, default=['http', 'https']) Allowed redirect
|
||||
* protocols.
|
||||
* - on_redirect: (callable) PHP callable that is invoked when a redirect
|
||||
* is encountered. The callable is invoked with the request, the redirect
|
||||
* response that was received, and the effective URI. Any return value
|
||||
* from the on_redirect function is ignored.
|
||||
*/
|
||||
const ALLOW_REDIRECTS = 'allow_redirects';
|
||||
|
||||
/**
|
||||
* auth: (array) Pass an array of HTTP authentication parameters to use
|
||||
* with the request. The array must contain the username in index [0],
|
||||
* the password in index [1], and you can optionally provide a built-in
|
||||
* authentication type in index [2]. Pass null to disable authentication
|
||||
* for a request.
|
||||
*/
|
||||
const AUTH = 'auth';
|
||||
|
||||
/**
|
||||
* body: (resource|string|null|int|float|StreamInterface|callable|\Iterator)
|
||||
* Body to send in the request.
|
||||
*/
|
||||
const BODY = 'body';
|
||||
|
||||
/**
|
||||
* cert: (string|array) Set to a string to specify the path to a file
|
||||
* containing a PEM formatted SSL client side certificate. If a password
|
||||
* is required, then set cert to an array containing the path to the PEM
|
||||
* file in the first array element followed by the certificate password
|
||||
* in the second array element.
|
||||
*/
|
||||
const CERT = 'cert';
|
||||
|
||||
/**
|
||||
* cookies: (bool|GuzzleHttp\Cookie\CookieJarInterface, default=false)
|
||||
* Specifies whether or not cookies are used in a request or what cookie
|
||||
* jar to use or what cookies to send. This option only works if your
|
||||
* handler has the `cookie` middleware. Valid values are `false` and
|
||||
* an instance of {@see GuzzleHttp\Cookie\CookieJarInterface}.
|
||||
*/
|
||||
const COOKIES = 'cookies';
|
||||
|
||||
/**
|
||||
* connect_timeout: (float, default=0) Float describing the number of
|
||||
* seconds to wait while trying to connect to a server. Use 0 to wait
|
||||
* indefinitely (the default behavior).
|
||||
*/
|
||||
const CONNECT_TIMEOUT = 'connect_timeout';
|
||||
|
||||
/**
|
||||
* debug: (bool|resource) Set to true or set to a PHP stream returned by
|
||||
* fopen() enable debug output with the HTTP handler used to send a
|
||||
* request.
|
||||
*/
|
||||
const DEBUG = 'debug';
|
||||
|
||||
/**
|
||||
* decode_content: (bool, default=true) Specify whether or not
|
||||
* Content-Encoding responses (gzip, deflate, etc.) are automatically
|
||||
* decoded.
|
||||
*/
|
||||
const DECODE_CONTENT = 'decode_content';
|
||||
|
||||
/**
|
||||
* delay: (int) The amount of time to delay before sending in milliseconds.
|
||||
*/
|
||||
const DELAY = 'delay';
|
||||
|
||||
/**
|
||||
* expect: (bool|integer) Controls the behavior of the
|
||||
* "Expect: 100-Continue" header.
|
||||
*
|
||||
* Set to `true` to enable the "Expect: 100-Continue" header for all
|
||||
* requests that sends a body. Set to `false` to disable the
|
||||
* "Expect: 100-Continue" header for all requests. Set to a number so that
|
||||
* the size of the payload must be greater than the number in order to send
|
||||
* the Expect header. Setting to a number will send the Expect header for
|
||||
* all requests in which the size of the payload cannot be determined or
|
||||
* where the body is not rewindable.
|
||||
*
|
||||
* By default, Guzzle will add the "Expect: 100-Continue" header when the
|
||||
* size of the body of a request is greater than 1 MB and a request is
|
||||
* using HTTP/1.1.
|
||||
*/
|
||||
const EXPECT = 'expect';
|
||||
|
||||
/**
|
||||
* form_params: (array) Associative array of form field names to values
|
||||
* where each value is a string or array of strings. Sets the Content-Type
|
||||
* header to application/x-www-form-urlencoded when no Content-Type header
|
||||
* is already present.
|
||||
*/
|
||||
const FORM_PARAMS = 'form_params';
|
||||
|
||||
/**
|
||||
* headers: (array) Associative array of HTTP headers. Each value MUST be
|
||||
* a string or array of strings.
|
||||
*/
|
||||
const HEADERS = 'headers';
|
||||
|
||||
/**
|
||||
* http_errors: (bool, default=true) Set to false to disable exceptions
|
||||
* when a non- successful HTTP response is received. By default,
|
||||
* exceptions will be thrown for 4xx and 5xx responses. This option only
|
||||
* works if your handler has the `httpErrors` middleware.
|
||||
*/
|
||||
const HTTP_ERRORS = 'http_errors';
|
||||
|
||||
/**
|
||||
* json: (mixed) Adds JSON data to a request. The provided value is JSON
|
||||
* encoded and a Content-Type header of application/json will be added to
|
||||
* the request if no Content-Type header is already present.
|
||||
*/
|
||||
const JSON = 'json';
|
||||
|
||||
/**
|
||||
* multipart: (array) Array of associative arrays, each containing a
|
||||
* required "name" key mapping to the form field, name, a required
|
||||
* "contents" key mapping to a StreamInterface|resource|string, an
|
||||
* optional "headers" associative array of custom headers, and an
|
||||
* optional "filename" key mapping to a string to send as the filename in
|
||||
* the part. If no "filename" key is present, then no "filename" attribute
|
||||
* will be added to the part.
|
||||
*/
|
||||
const MULTIPART = 'multipart';
|
||||
|
||||
/**
|
||||
* on_headers: (callable) A callable that is invoked when the HTTP headers
|
||||
* of the response have been received but the body has not yet begun to
|
||||
* download.
|
||||
*/
|
||||
const ON_HEADERS = 'on_headers';
|
||||
|
||||
/**
|
||||
* on_stats: (callable) allows you to get access to transfer statistics of
|
||||
* a request and access the lower level transfer details of the handler
|
||||
* associated with your client. ``on_stats`` is a callable that is invoked
|
||||
* when a handler has finished sending a request. The callback is invoked
|
||||
* with transfer statistics about the request, the response received, or
|
||||
* the error encountered. Included in the data is the total amount of time
|
||||
* taken to send the request.
|
||||
*/
|
||||
const ON_STATS = 'on_stats';
|
||||
|
||||
/**
|
||||
* progress: (callable) Defines a function to invoke when transfer
|
||||
* progress is made. The function accepts the following positional
|
||||
* arguments: the total number of bytes expected to be downloaded, the
|
||||
* number of bytes downloaded so far, the number of bytes expected to be
|
||||
* uploaded, the number of bytes uploaded so far.
|
||||
*/
|
||||
const PROGRESS = 'progress';
|
||||
|
||||
/**
|
||||
* proxy: (string|array) Pass a string to specify an HTTP proxy, or an
|
||||
* array to specify different proxies for different protocols (where the
|
||||
* key is the protocol and the value is a proxy string).
|
||||
*/
|
||||
const PROXY = 'proxy';
|
||||
|
||||
/**
|
||||
* query: (array|string) Associative array of query string values to add
|
||||
* to the request. This option uses PHP's http_build_query() to create
|
||||
* the string representation. Pass a string value if you need more
|
||||
* control than what this method provides
|
||||
*/
|
||||
const QUERY = 'query';
|
||||
|
||||
/**
|
||||
* sink: (resource|string|StreamInterface) Where the data of the
|
||||
* response is written to. Defaults to a PHP temp stream. Providing a
|
||||
* string will write data to a file by the given name.
|
||||
*/
|
||||
const SINK = 'sink';
|
||||
|
||||
/**
|
||||
* synchronous: (bool) Set to true to inform HTTP handlers that you intend
|
||||
* on waiting on the response. This can be useful for optimizations. Note
|
||||
* that a promise is still returned if you are using one of the async
|
||||
* client methods.
|
||||
*/
|
||||
const SYNCHRONOUS = 'synchronous';
|
||||
|
||||
/**
|
||||
* ssl_key: (array|string) Specify the path to a file containing a private
|
||||
* SSL key in PEM format. If a password is required, then set to an array
|
||||
* containing the path to the SSL key in the first array element followed
|
||||
* by the password required for the certificate in the second element.
|
||||
*/
|
||||
const SSL_KEY = 'ssl_key';
|
||||
|
||||
/**
|
||||
* stream: Set to true to attempt to stream a response rather than
|
||||
* download it all up-front.
|
||||
*/
|
||||
const STREAM = 'stream';
|
||||
|
||||
/**
|
||||
* verify: (bool|string, default=true) Describes the SSL certificate
|
||||
* verification behavior of a request. Set to true to enable SSL
|
||||
* certificate verification using the system CA bundle when available
|
||||
* (the default). Set to false to disable certificate verification (this
|
||||
* is insecure!). Set to a string to provide the path to a CA bundle on
|
||||
* disk to enable verification using a custom certificate.
|
||||
*/
|
||||
const VERIFY = 'verify';
|
||||
|
||||
/**
|
||||
* timeout: (float, default=0) Float describing the timeout of the
|
||||
* request in seconds. Use 0 to wait indefinitely (the default behavior).
|
||||
*/
|
||||
const TIMEOUT = 'timeout';
|
||||
|
||||
/**
|
||||
* read_timeout: (float, default=default_socket_timeout ini setting) Float describing
|
||||
* the body read timeout, for stream requests.
|
||||
*/
|
||||
const READ_TIMEOUT = 'read_timeout';
|
||||
|
||||
/**
|
||||
* version: (float) Specifies the HTTP protocol version to attempt to use.
|
||||
*/
|
||||
const VERSION = 'version';
|
||||
|
||||
/**
|
||||
* force_ip_resolve: (bool) Force client to use only ipv4 or ipv6 protocol
|
||||
*/
|
||||
const FORCE_IP_RESOLVE = 'force_ip_resolve';
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\Promise\RejectedPromise;
|
||||
use GuzzleHttp\Psr7;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Middleware that retries requests based on the boolean result of
|
||||
* invoking the provided "decider" function.
|
||||
*/
|
||||
class RetryMiddleware
|
||||
{
|
||||
/** @var callable */
|
||||
private $nextHandler;
|
||||
|
||||
/** @var callable */
|
||||
private $decider;
|
||||
|
||||
/**
|
||||
* @param callable $decider Function that accepts the number of retries,
|
||||
* a request, [response], and [exception] and
|
||||
* returns true if the request is to be
|
||||
* retried.
|
||||
* @param callable $nextHandler Next handler to invoke.
|
||||
* @param callable $delay Function that accepts the number of retries
|
||||
* and [response] and returns the number of
|
||||
* milliseconds to delay.
|
||||
*/
|
||||
public function __construct(
|
||||
callable $decider,
|
||||
callable $nextHandler,
|
||||
callable $delay = null
|
||||
) {
|
||||
$this->decider = $decider;
|
||||
$this->nextHandler = $nextHandler;
|
||||
$this->delay = $delay ?: __CLASS__ . '::exponentialDelay';
|
||||
}
|
||||
|
||||
/**
|
||||
* Default exponential backoff delay function.
|
||||
*
|
||||
* @param $retries
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function exponentialDelay($retries)
|
||||
{
|
||||
return (int) pow(2, $retries - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestInterface $request
|
||||
* @param array $options
|
||||
*
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
public function __invoke(RequestInterface $request, array $options)
|
||||
{
|
||||
if (!isset($options['retries'])) {
|
||||
$options['retries'] = 0;
|
||||
}
|
||||
|
||||
$fn = $this->nextHandler;
|
||||
return $fn($request, $options)
|
||||
->then(
|
||||
$this->onFulfilled($request, $options),
|
||||
$this->onRejected($request, $options)
|
||||
);
|
||||
}
|
||||
|
||||
private function onFulfilled(RequestInterface $req, array $options)
|
||||
{
|
||||
return function ($value) use ($req, $options) {
|
||||
if (!call_user_func(
|
||||
$this->decider,
|
||||
$options['retries'],
|
||||
$req,
|
||||
$value,
|
||||
null
|
||||
)) {
|
||||
return $value;
|
||||
}
|
||||
return $this->doRetry($req, $options, $value);
|
||||
};
|
||||
}
|
||||
|
||||
private function onRejected(RequestInterface $req, array $options)
|
||||
{
|
||||
return function ($reason) use ($req, $options) {
|
||||
if (!call_user_func(
|
||||
$this->decider,
|
||||
$options['retries'],
|
||||
$req,
|
||||
null,
|
||||
$reason
|
||||
)) {
|
||||
return \GuzzleHttp\Promise\rejection_for($reason);
|
||||
}
|
||||
return $this->doRetry($req, $options);
|
||||
};
|
||||
}
|
||||
|
||||
private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null)
|
||||
{
|
||||
$options['delay'] = call_user_func($this->delay, ++$options['retries'], $response);
|
||||
|
||||
return $this($request, $options);
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* Represents data at the point after it was transferred either successfully
|
||||
* or after a network error.
|
||||
*/
|
||||
final class TransferStats
|
||||
{
|
||||
private $request;
|
||||
private $response;
|
||||
private $transferTime;
|
||||
private $handlerStats;
|
||||
private $handlerErrorData;
|
||||
|
||||
/**
|
||||
* @param RequestInterface $request Request that was sent.
|
||||
* @param ResponseInterface $response Response received (if any)
|
||||
* @param null $transferTime Total handler transfer time.
|
||||
* @param mixed $handlerErrorData Handler error data.
|
||||
* @param array $handlerStats Handler specific stats.
|
||||
*/
|
||||
public function __construct(
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response = null,
|
||||
$transferTime = null,
|
||||
$handlerErrorData = null,
|
||||
$handlerStats = []
|
||||
) {
|
||||
$this->request = $request;
|
||||
$this->response = $response;
|
||||
$this->transferTime = $transferTime;
|
||||
$this->handlerErrorData = $handlerErrorData;
|
||||
$this->handlerStats = $handlerStats;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public function getRequest()
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response that was received (if any).
|
||||
*
|
||||
* @return ResponseInterface|null
|
||||
*/
|
||||
public function getResponse()
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a response was received.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasResponse()
|
||||
{
|
||||
return $this->response !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets handler specific error data.
|
||||
*
|
||||
* This might be an exception, a integer representing an error code, or
|
||||
* anything else. Relying on this value assumes that you know what handler
|
||||
* you are using.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getHandlerErrorData()
|
||||
{
|
||||
return $this->handlerErrorData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the effective URI the request was sent to.
|
||||
*
|
||||
* @return UriInterface
|
||||
*/
|
||||
public function getEffectiveUri()
|
||||
{
|
||||
return $this->request->getUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the estimated time the request was being transferred by the handler.
|
||||
*
|
||||
* @return float Time in seconds.
|
||||
*/
|
||||
public function getTransferTime()
|
||||
{
|
||||
return $this->transferTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of all of the handler specific transfer data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getHandlerStats()
|
||||
{
|
||||
return $this->handlerStats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific handler statistic from the handler by name.
|
||||
*
|
||||
* @param string $stat Handler specific transfer stat to retrieve.
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getHandlerStat($stat)
|
||||
{
|
||||
return isset($this->handlerStats[$stat])
|
||||
? $this->handlerStats[$stat]
|
||||
: null;
|
||||
}
|
||||
}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
<?php
|
||||
namespace GuzzleHttp;
|
||||
|
||||
/**
|
||||
* Expands URI templates. Userland implementation of PECL uri_template.
|
||||
*
|
||||
* @link http://tools.ietf.org/html/rfc6570
|
||||
*/
|
||||
class UriTemplate
|
||||
{
|
||||
/** @var string URI template */
|
||||
private $template;
|
||||
|
||||
/** @var array Variables to use in the template expansion */
|
||||
private $variables;
|
||||
|
||||
/** @var array Hash for quick operator lookups */
|
||||
private static $operatorHash = [
|
||||
'' => ['prefix' => '', 'joiner' => ',', 'query' => false],
|
||||
'+' => ['prefix' => '', 'joiner' => ',', 'query' => false],
|
||||
'#' => ['prefix' => '#', 'joiner' => ',', 'query' => false],
|
||||
'.' => ['prefix' => '.', 'joiner' => '.', 'query' => false],
|
||||
'/' => ['prefix' => '/', 'joiner' => '/', 'query' => false],
|
||||
';' => ['prefix' => ';', 'joiner' => ';', 'query' => true],
|
||||
'?' => ['prefix' => '?', 'joiner' => '&', 'query' => true],
|
||||
'&' => ['prefix' => '&', 'joiner' => '&', 'query' => true]
|
||||
];
|
||||
|
||||
/** @var array Delimiters */
|
||||
private static $delims = [':', '/', '?', '#', '[', ']', '@', '!', '$',
|
||||
'&', '\'', '(', ')', '*', '+', ',', ';', '='];
|
||||
|
||||
/** @var array Percent encoded delimiters */
|
||||
private static $delimsPct = ['%3A', '%2F', '%3F', '%23', '%5B', '%5D',
|
||||
'%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C',
|
||||
'%3B', '%3D'];
|
||||
|
||||
public function expand($template, array $variables)
|
||||
{
|
||||
if (false === strpos($template, '{')) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
$this->template = $template;
|
||||
$this->variables = $variables;
|
||||
|
||||
return preg_replace_callback(
|
||||
'/\{([^\}]+)\}/',
|
||||
[$this, 'expandMatch'],
|
||||
$this->template
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an expression into parts
|
||||
*
|
||||
* @param string $expression Expression to parse
|
||||
*
|
||||
* @return array Returns an associative array of parts
|
||||
*/
|
||||
private function parseExpression($expression)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
if (isset(self::$operatorHash[$expression[0]])) {
|
||||
$result['operator'] = $expression[0];
|
||||
$expression = substr($expression, 1);
|
||||
} else {
|
||||
$result['operator'] = '';
|
||||
}
|
||||
|
||||
foreach (explode(',', $expression) as $value) {
|
||||
$value = trim($value);
|
||||
$varspec = [];
|
||||
if ($colonPos = strpos($value, ':')) {
|
||||
$varspec['value'] = substr($value, 0, $colonPos);
|
||||
$varspec['modifier'] = ':';
|
||||
$varspec['position'] = (int) substr($value, $colonPos + 1);
|
||||
} elseif (substr($value, -1) === '*') {
|
||||
$varspec['modifier'] = '*';
|
||||
$varspec['value'] = substr($value, 0, -1);
|
||||
} else {
|
||||
$varspec['value'] = (string) $value;
|
||||
$varspec['modifier'] = '';
|
||||
}
|
||||
$result['values'][] = $varspec;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an expansion
|
||||
*
|
||||
* @param array $matches Matches met in the preg_replace_callback
|
||||
*
|
||||
* @return string Returns the replacement string
|
||||
*/
|
||||
private function expandMatch(array $matches)
|
||||
{
|
||||
static $rfc1738to3986 = ['+' => '%20', '%7e' => '~'];
|
||||
|
||||
$replacements = [];
|
||||
$parsed = self::parseExpression($matches[1]);
|
||||
$prefix = self::$operatorHash[$parsed['operator']]['prefix'];
|
||||
$joiner = self::$operatorHash[$parsed['operator']]['joiner'];
|
||||
$useQuery = self::$operatorHash[$parsed['operator']]['query'];
|
||||
|
||||
foreach ($parsed['values'] as $value) {
|
||||
|
||||
if (!isset($this->variables[$value['value']])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$variable = $this->variables[$value['value']];
|
||||
$actuallyUseQuery = $useQuery;
|
||||
$expanded = '';
|
||||
|
||||
if (is_array($variable)) {
|
||||
|
||||
$isAssoc = $this->isAssoc($variable);
|
||||
$kvp = [];
|
||||
foreach ($variable as $key => $var) {
|
||||
|
||||
if ($isAssoc) {
|
||||
$key = rawurlencode($key);
|
||||
$isNestedArray = is_array($var);
|
||||
} else {
|
||||
$isNestedArray = false;
|
||||
}
|
||||
|
||||
if (!$isNestedArray) {
|
||||
$var = rawurlencode($var);
|
||||
if ($parsed['operator'] === '+' ||
|
||||
$parsed['operator'] === '#'
|
||||
) {
|
||||
$var = $this->decodeReserved($var);
|
||||
}
|
||||
}
|
||||
|
||||
if ($value['modifier'] === '*') {
|
||||
if ($isAssoc) {
|
||||
if ($isNestedArray) {
|
||||
// Nested arrays must allow for deeply nested
|
||||
// structures.
|
||||
$var = strtr(
|
||||
http_build_query([$key => $var]),
|
||||
$rfc1738to3986
|
||||
);
|
||||
} else {
|
||||
$var = $key . '=' . $var;
|
||||
}
|
||||
} elseif ($key > 0 && $actuallyUseQuery) {
|
||||
$var = $value['value'] . '=' . $var;
|
||||
}
|
||||
}
|
||||
|
||||
$kvp[$key] = $var;
|
||||
}
|
||||
|
||||
if (empty($variable)) {
|
||||
$actuallyUseQuery = false;
|
||||
} elseif ($value['modifier'] === '*') {
|
||||
$expanded = implode($joiner, $kvp);
|
||||
if ($isAssoc) {
|
||||
// Don't prepend the value name when using the explode
|
||||
// modifier with an associative array.
|
||||
$actuallyUseQuery = false;
|
||||
}
|
||||
} else {
|
||||
if ($isAssoc) {
|
||||
// When an associative array is encountered and the
|
||||
// explode modifier is not set, then the result must be
|
||||
// a comma separated list of keys followed by their
|
||||
// respective values.
|
||||
foreach ($kvp as $k => &$v) {
|
||||
$v = $k . ',' . $v;
|
||||
}
|
||||
}
|
||||
$expanded = implode(',', $kvp);
|
||||
}
|
||||
|
||||
} else {
|
||||
if ($value['modifier'] === ':') {
|
||||
$variable = substr($variable, 0, $value['position']);
|
||||
}
|
||||
$expanded = rawurlencode($variable);
|
||||
if ($parsed['operator'] === '+' || $parsed['operator'] === '#') {
|
||||
$expanded = $this->decodeReserved($expanded);
|
||||
}
|
||||
}
|
||||
|
||||
if ($actuallyUseQuery) {
|
||||
if (!$expanded && $joiner !== '&') {
|
||||
$expanded = $value['value'];
|
||||
} else {
|
||||
$expanded = $value['value'] . '=' . $expanded;
|
||||
}
|
||||
}
|
||||
|
||||
$replacements[] = $expanded;
|
||||
}
|
||||
|
||||
$ret = implode($joiner, $replacements);
|
||||
if ($ret && $prefix) {
|
||||
return $prefix . $ret;
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an array is associative.
|
||||
*
|
||||
* This makes the assumption that input arrays are sequences or hashes.
|
||||
* This assumption is a tradeoff for accuracy in favor of speed, but it
|
||||
* should work in almost every case where input is supplied for a URI
|
||||
* template.
|
||||
*
|
||||
* @param array $array Array to check
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isAssoc(array $array)
|
||||
{
|
||||
return $array && array_keys($array)[0] !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes percent encoding on reserved characters (used with + and #
|
||||
* modifiers).
|
||||
*
|
||||
* @param string $string String to fix
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function decodeReserved($string)
|
||||
{
|
||||
return str_replace(self::$delimsPct, self::$delims, $string);
|
||||
}
|
||||
}
|
||||
+331
@@ -0,0 +1,331 @@
|
||||
<?php
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Handler\CurlHandler;
|
||||
use GuzzleHttp\Handler\CurlMultiHandler;
|
||||
use GuzzleHttp\Handler\Proxy;
|
||||
use GuzzleHttp\Handler\StreamHandler;
|
||||
|
||||
/**
|
||||
* Expands a URI template
|
||||
*
|
||||
* @param string $template URI template
|
||||
* @param array $variables Template variables
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function uri_template($template, array $variables)
|
||||
{
|
||||
if (extension_loaded('uri_template')) {
|
||||
// @codeCoverageIgnoreStart
|
||||
return \uri_template($template, $variables);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
static $uriTemplate;
|
||||
if (!$uriTemplate) {
|
||||
$uriTemplate = new UriTemplate();
|
||||
}
|
||||
|
||||
return $uriTemplate->expand($template, $variables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug function used to describe the provided value type and class.
|
||||
*
|
||||
* @param mixed $input
|
||||
*
|
||||
* @return string Returns a string containing the type of the variable and
|
||||
* if a class is provided, the class name.
|
||||
*/
|
||||
function describe_type($input)
|
||||
{
|
||||
switch (gettype($input)) {
|
||||
case 'object':
|
||||
return 'object(' . get_class($input) . ')';
|
||||
case 'array':
|
||||
return 'array(' . count($input) . ')';
|
||||
default:
|
||||
ob_start();
|
||||
var_dump($input);
|
||||
// normalize float vs double
|
||||
return str_replace('double(', 'float(', rtrim(ob_get_clean()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an array of header lines into an associative array of headers.
|
||||
*
|
||||
* @param array $lines Header lines array of strings in the following
|
||||
* format: "Name: Value"
|
||||
* @return array
|
||||
*/
|
||||
function headers_from_lines($lines)
|
||||
{
|
||||
$headers = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$parts = explode(':', $line, 2);
|
||||
$headers[trim($parts[0])][] = isset($parts[1])
|
||||
? trim($parts[1])
|
||||
: null;
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a debug stream based on the provided variable.
|
||||
*
|
||||
* @param mixed $value Optional value
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
function debug_resource($value = null)
|
||||
{
|
||||
if (is_resource($value)) {
|
||||
return $value;
|
||||
} elseif (defined('STDOUT')) {
|
||||
return STDOUT;
|
||||
}
|
||||
|
||||
return fopen('php://output', 'w');
|
||||
}
|
||||
|
||||
/**
|
||||
* Chooses and creates a default handler to use based on the environment.
|
||||
*
|
||||
* The returned handler is not wrapped by any default middlewares.
|
||||
*
|
||||
* @throws \RuntimeException if no viable Handler is available.
|
||||
* @return callable Returns the best handler for the given system.
|
||||
*/
|
||||
function choose_handler()
|
||||
{
|
||||
$handler = null;
|
||||
if (function_exists('curl_multi_exec') && function_exists('curl_exec')) {
|
||||
$handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler());
|
||||
} elseif (function_exists('curl_exec')) {
|
||||
$handler = new CurlHandler();
|
||||
} elseif (function_exists('curl_multi_exec')) {
|
||||
$handler = new CurlMultiHandler();
|
||||
}
|
||||
|
||||
if (ini_get('allow_url_fopen')) {
|
||||
$handler = $handler
|
||||
? Proxy::wrapStreaming($handler, new StreamHandler())
|
||||
: new StreamHandler();
|
||||
} elseif (!$handler) {
|
||||
throw new \RuntimeException('GuzzleHttp requires cURL, the '
|
||||
. 'allow_url_fopen ini setting, or a custom HTTP handler.');
|
||||
}
|
||||
|
||||
return $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default User-Agent string to use with Guzzle
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function default_user_agent()
|
||||
{
|
||||
static $defaultAgent = '';
|
||||
|
||||
if (!$defaultAgent) {
|
||||
$defaultAgent = 'GuzzleHttp/' . Client::VERSION;
|
||||
if (extension_loaded('curl') && function_exists('curl_version')) {
|
||||
$defaultAgent .= ' curl/' . \curl_version()['version'];
|
||||
}
|
||||
$defaultAgent .= ' PHP/' . PHP_VERSION;
|
||||
}
|
||||
|
||||
return $defaultAgent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default cacert bundle for the current system.
|
||||
*
|
||||
* First, the openssl.cafile and curl.cainfo php.ini settings are checked.
|
||||
* If those settings are not configured, then the common locations for
|
||||
* bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X
|
||||
* and Windows are checked. If any of these file locations are found on
|
||||
* disk, they will be utilized.
|
||||
*
|
||||
* Note: the result of this function is cached for subsequent calls.
|
||||
*
|
||||
* @return string
|
||||
* @throws \RuntimeException if no bundle can be found.
|
||||
*/
|
||||
function default_ca_bundle()
|
||||
{
|
||||
static $cached = null;
|
||||
static $cafiles = [
|
||||
// Red Hat, CentOS, Fedora (provided by the ca-certificates package)
|
||||
'/etc/pki/tls/certs/ca-bundle.crt',
|
||||
// Ubuntu, Debian (provided by the ca-certificates package)
|
||||
'/etc/ssl/certs/ca-certificates.crt',
|
||||
// FreeBSD (provided by the ca_root_nss package)
|
||||
'/usr/local/share/certs/ca-root-nss.crt',
|
||||
// SLES 12 (provided by the ca-certificates package)
|
||||
'/var/lib/ca-certificates/ca-bundle.pem',
|
||||
// OS X provided by homebrew (using the default path)
|
||||
'/usr/local/etc/openssl/cert.pem',
|
||||
// Google app engine
|
||||
'/etc/ca-certificates.crt',
|
||||
// Windows?
|
||||
'C:\\windows\\system32\\curl-ca-bundle.crt',
|
||||
'C:\\windows\\curl-ca-bundle.crt',
|
||||
];
|
||||
|
||||
if ($cached) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
if ($ca = ini_get('openssl.cafile')) {
|
||||
return $cached = $ca;
|
||||
}
|
||||
|
||||
if ($ca = ini_get('curl.cainfo')) {
|
||||
return $cached = $ca;
|
||||
}
|
||||
|
||||
foreach ($cafiles as $filename) {
|
||||
if (file_exists($filename)) {
|
||||
return $cached = $filename;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \RuntimeException(<<< EOT
|
||||
No system CA bundle could be found in any of the the common system locations.
|
||||
PHP versions earlier than 5.6 are not properly configured to use the system's
|
||||
CA bundle by default. In order to verify peer certificates, you will need to
|
||||
supply the path on disk to a certificate bundle to the 'verify' request
|
||||
option: http://docs.guzzlephp.org/en/latest/clients.html#verify. If you do not
|
||||
need a specific certificate bundle, then Mozilla provides a commonly used CA
|
||||
bundle which can be downloaded here (provided by the maintainer of cURL):
|
||||
https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt. Once
|
||||
you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP
|
||||
ini setting to point to the path to the file, allowing you to omit the 'verify'
|
||||
request option. See http://curl.haxx.se/docs/sslcerts.html for more
|
||||
information.
|
||||
EOT
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an associative array of lowercase header names to the actual
|
||||
* header casing.
|
||||
*
|
||||
* @param array $headers
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function normalize_header_keys(array $headers)
|
||||
{
|
||||
$result = [];
|
||||
foreach (array_keys($headers) as $key) {
|
||||
$result[strtolower($key)] = $key;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the provided host matches any of the no proxy areas.
|
||||
*
|
||||
* This method will strip a port from the host if it is present. Each pattern
|
||||
* can be matched with an exact match (e.g., "foo.com" == "foo.com") or a
|
||||
* partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" ==
|
||||
* "baz.foo.com", but ".foo.com" != "foo.com").
|
||||
*
|
||||
* Areas are matched in the following cases:
|
||||
* 1. "*" (without quotes) always matches any hosts.
|
||||
* 2. An exact match.
|
||||
* 3. The area starts with "." and the area is the last part of the host. e.g.
|
||||
* '.mit.edu' will match any host that ends with '.mit.edu'.
|
||||
*
|
||||
* @param string $host Host to check against the patterns.
|
||||
* @param array $noProxyArray An array of host patterns.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function is_host_in_noproxy($host, array $noProxyArray)
|
||||
{
|
||||
if (strlen($host) === 0) {
|
||||
throw new \InvalidArgumentException('Empty host provided');
|
||||
}
|
||||
|
||||
// Strip port if present.
|
||||
if (strpos($host, ':')) {
|
||||
$host = explode($host, ':', 2)[0];
|
||||
}
|
||||
|
||||
foreach ($noProxyArray as $area) {
|
||||
// Always match on wildcards.
|
||||
if ($area === '*') {
|
||||
return true;
|
||||
} elseif (empty($area)) {
|
||||
// Don't match on empty values.
|
||||
continue;
|
||||
} elseif ($area === $host) {
|
||||
// Exact matches.
|
||||
return true;
|
||||
} else {
|
||||
// Special match if the area when prefixed with ".". Remove any
|
||||
// existing leading "." and add a new leading ".".
|
||||
$area = '.' . ltrim($area, '.');
|
||||
if (substr($host, -(strlen($area))) === $area) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for json_decode that throws when an error occurs.
|
||||
*
|
||||
* @param string $json JSON data to parse
|
||||
* @param bool $assoc When true, returned objects will be converted
|
||||
* into associative arrays.
|
||||
* @param int $depth User specified recursion depth.
|
||||
* @param int $options Bitmask of JSON decode options.
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \InvalidArgumentException if the JSON cannot be decoded.
|
||||
* @link http://www.php.net/manual/en/function.json-decode.php
|
||||
*/
|
||||
function json_decode($json, $assoc = false, $depth = 512, $options = 0)
|
||||
{
|
||||
$data = \json_decode($json, $assoc, $depth, $options);
|
||||
if (JSON_ERROR_NONE !== json_last_error()) {
|
||||
throw new \InvalidArgumentException(
|
||||
'json_decode error: ' . json_last_error_msg());
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for JSON encoding that throws when an error occurs.
|
||||
*
|
||||
* @param mixed $value The value being encoded
|
||||
* @param int $options JSON encode option bitmask
|
||||
* @param int $depth Set the maximum depth. Must be greater than zero.
|
||||
*
|
||||
* @return string
|
||||
* @throws \InvalidArgumentException if the JSON cannot be encoded.
|
||||
* @link http://www.php.net/manual/en/function.json-encode.php
|
||||
*/
|
||||
function json_encode($value, $options = 0, $depth = 512)
|
||||
{
|
||||
$json = \json_encode($value, $options, $depth);
|
||||
if (JSON_ERROR_NONE !== json_last_error()) {
|
||||
throw new \InvalidArgumentException(
|
||||
'json_encode error: ' . json_last_error_msg());
|
||||
}
|
||||
|
||||
return $json;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
// Don't redefine the functions if included multiple times.
|
||||
if (!function_exists('GuzzleHttp\uri_template')) {
|
||||
require __DIR__ . '/functions.php';
|
||||
}
|
||||
Reference in New Issue
Block a user