/vendor/guzzlehttp/guzzle/src/Adapter/Curl/BatchContext.php |
@@ -0,0 +1,179 @@ |
<?php |
namespace GuzzleHttp\Adapter\Curl; |
|
use GuzzleHttp\Adapter\TransactionInterface; |
use GuzzleHttp\Exception\AdapterException; |
|
/** |
* Provides context for a Curl transaction, including active handles, |
* pending transactions, and whether or not this is a batch or single |
* transaction. |
*/ |
class BatchContext |
{ |
/** @var resource Curl multi resource */ |
private $multi; |
|
/** @var \SplObjectStorage Map of transactions to curl resources */ |
private $handles; |
|
/** @var \Iterator Yields pending transactions */ |
private $pending; |
|
/** @var bool Whether or not to throw transactions */ |
private $throwsExceptions; |
|
/** |
* @param resource $multiHandle Initialized curl_multi resource |
* @param bool $throwsExceptions Whether or not exceptions are thrown |
* @param \Iterator $pending Iterator yielding pending transactions |
*/ |
public function __construct( |
$multiHandle, |
$throwsExceptions, |
\Iterator $pending = null |
) { |
$this->multi = $multiHandle; |
$this->handles = new \SplObjectStorage(); |
$this->throwsExceptions = $throwsExceptions; |
$this->pending = $pending; |
} |
|
/** |
* Closes all of the requests associated with the underlying multi handle. |
*/ |
public function removeAll() |
{ |
foreach ($this->handles as $transaction) { |
$ch = $this->handles[$transaction]; |
curl_multi_remove_handle($this->multi, $ch); |
curl_close($ch); |
unset($this->handles[$transaction]); |
} |
} |
|
/** |
* Find a transaction for a given curl handle |
* |
* @param resource $handle Curl handle |
* |
* @return TransactionInterface |
* @throws AdapterException if a transaction is not found |
*/ |
public function findTransaction($handle) |
{ |
foreach ($this->handles as $transaction) { |
if ($this->handles[$transaction] === $handle) { |
return $transaction; |
} |
} |
|
throw new AdapterException('No curl handle was found'); |
} |
|
/** |
* Returns true if there are any active requests. |
* |
* @return bool |
*/ |
public function isActive() |
{ |
return count($this->handles) > 0; |
} |
|
/** |
* Returns true if there are any remaining pending transactions |
* |
* @return bool |
*/ |
public function hasPending() |
{ |
return $this->pending && $this->pending->valid(); |
} |
|
/** |
* Pop the next transaction from the transaction queue |
* |
* @return TransactionInterface|null |
*/ |
public function nextPending() |
{ |
if (!$this->hasPending()) { |
return null; |
} |
|
$current = $this->pending->current(); |
$this->pending->next(); |
|
return $current; |
} |
|
/** |
* Checks if the batch is to throw exceptions on error |
* |
* @return bool |
*/ |
public function throwsExceptions() |
{ |
return $this->throwsExceptions; |
} |
|
/** |
* Get the curl_multi handle |
* |
* @return resource |
*/ |
public function getMultiHandle() |
{ |
return $this->multi; |
} |
|
/** |
* Add a transaction to the multi handle |
* |
* @param TransactionInterface $transaction Transaction to add |
* @param resource $handle Resource to use with the handle |
* |
* @throws AdapterException If the handle is already registered |
*/ |
public function addTransaction(TransactionInterface $transaction, $handle) |
{ |
if (isset($this->handles[$transaction])) { |
throw new AdapterException('Transaction already registered'); |
} |
|
$code = curl_multi_add_handle($this->multi, $handle); |
if ($code != CURLM_OK) { |
MultiAdapter::throwMultiError($code); |
} |
|
$this->handles[$transaction] = $handle; |
} |
|
/** |
* Remove a transaction and associated handle from the context |
* |
* @param TransactionInterface $transaction Transaction to remove |
* |
* @return array Returns the curl_getinfo array |
* @throws AdapterException if the transaction is not found |
*/ |
public function removeTransaction(TransactionInterface $transaction) |
{ |
if (!isset($this->handles[$transaction])) { |
throw new AdapterException('Transaction not registered'); |
} |
|
$handle = $this->handles[$transaction]; |
$this->handles->detach($transaction); |
$info = curl_getinfo($handle); |
$code = curl_multi_remove_handle($this->multi, $handle); |
curl_close($handle); |
|
if ($code !== CURLM_OK) { |
MultiAdapter::throwMultiError($code); |
} |
|
return $info; |
} |
} |
/vendor/guzzlehttp/guzzle/src/Adapter/Curl/CurlAdapter.php |
@@ -0,0 +1,152 @@ |
<?php |
|
namespace GuzzleHttp\Adapter\Curl; |
|
use GuzzleHttp\Adapter\AdapterInterface; |
use GuzzleHttp\Adapter\TransactionInterface; |
use GuzzleHttp\Event\RequestEvents; |
use GuzzleHttp\Exception\AdapterException; |
use GuzzleHttp\Message\MessageFactoryInterface; |
|
/** |
* HTTP adapter that uses cURL easy handles as a transport layer. |
* |
* Requires PHP 5.5+ |
* |
* When using the CurlAdapter, custom curl options can be specified as an |
* associative array of curl option constants mapping to values in the |
* **curl** key of a request's configuration options. |
*/ |
class CurlAdapter implements AdapterInterface |
{ |
/** @var CurlFactory */ |
private $curlFactory; |
|
/** @var MessageFactoryInterface */ |
private $messageFactory; |
|
/** @var array Array of curl easy handles */ |
private $handles = []; |
|
/** @var array Array of owned curl easy handles */ |
private $ownedHandles = []; |
|
/** @var int Total number of idle handles to keep in cache */ |
private $maxHandles; |
|
/** |
* Accepts an associative array of options: |
* |
* - handle_factory: Optional callable factory used to create cURL handles. |
* The callable is invoked with the following arguments: |
* TransactionInterface, MessageFactoryInterface, and an optional cURL |
* handle to modify. The factory method must then return a cURL resource. |
* - max_handles: Maximum number of idle handles (defaults to 5). |
* |
* @param MessageFactoryInterface $messageFactory |
* @param array $options Array of options to use with the adapter |
*/ |
public function __construct( |
MessageFactoryInterface $messageFactory, |
array $options = [] |
) { |
$this->handles = $this->ownedHandles = []; |
$this->messageFactory = $messageFactory; |
$this->curlFactory = isset($options['handle_factory']) |
? $options['handle_factory'] |
: new CurlFactory(); |
$this->maxHandles = isset($options['max_handles']) |
? $options['max_handles'] |
: 5; |
} |
|
public function __destruct() |
{ |
foreach ($this->handles as $handle) { |
if (is_resource($handle)) { |
curl_close($handle); |
} |
} |
} |
|
public function send(TransactionInterface $transaction) |
{ |
RequestEvents::emitBefore($transaction); |
if ($response = $transaction->getResponse()) { |
return $response; |
} |
|
$factory = $this->curlFactory; |
$handle = $factory( |
$transaction, |
$this->messageFactory, |
$this->checkoutEasyHandle() |
); |
|
curl_exec($handle); |
$info = curl_getinfo($handle); |
$info['curl_result'] = curl_errno($handle); |
|
if ($info['curl_result']) { |
$this->handleError($transaction, $info, $handle); |
} else { |
$this->releaseEasyHandle($handle); |
if ($body = $transaction->getResponse()->getBody()) { |
$body->seek(0); |
} |
RequestEvents::emitComplete($transaction, $info); |
} |
|
return $transaction->getResponse(); |
} |
|
private function handleError( |
TransactionInterface $transaction, |
$info, |
$handle |
) { |
$error = curl_error($handle); |
$this->releaseEasyHandle($handle); |
RequestEvents::emitError( |
$transaction, |
new AdapterException("cURL error {$info['curl_result']}: {$error}"), |
$info |
); |
} |
|
private function checkoutEasyHandle() |
{ |
// Find an unused handle in the cache |
if (false !== ($key = array_search(false, $this->ownedHandles, true))) { |
$this->ownedHandles[$key] = true; |
return $this->handles[$key]; |
} |
|
// Add a new handle |
$handle = curl_init(); |
$id = (int) $handle; |
$this->handles[$id] = $handle; |
$this->ownedHandles[$id] = true; |
|
return $handle; |
} |
|
private function releaseEasyHandle($handle) |
{ |
$id = (int) $handle; |
if (count($this->ownedHandles) > $this->maxHandles) { |
curl_close($this->handles[$id]); |
unset($this->handles[$id], $this->ownedHandles[$id]); |
} else { |
// curl_reset doesn't clear these out for some reason |
curl_setopt_array($handle, [ |
CURLOPT_HEADERFUNCTION => null, |
CURLOPT_WRITEFUNCTION => null, |
CURLOPT_READFUNCTION => null, |
CURLOPT_PROGRESSFUNCTION => null |
]); |
curl_reset($handle); |
$this->ownedHandles[$id] = false; |
} |
} |
} |
/vendor/guzzlehttp/guzzle/src/Adapter/Curl/CurlFactory.php |
@@ -0,0 +1,369 @@ |
<?php |
namespace GuzzleHttp\Adapter\Curl; |
|
use GuzzleHttp\Adapter\TransactionInterface; |
use GuzzleHttp\Exception\AdapterException; |
use GuzzleHttp\Message\MessageFactoryInterface; |
use GuzzleHttp\Message\RequestInterface; |
use GuzzleHttp\Stream\LazyOpenStream; |
use GuzzleHttp\Stream\Stream; |
|
/** |
* Creates curl resources from a request and response object |
*/ |
class CurlFactory |
{ |
/** |
* Creates a cURL handle based on a transaction. |
* |
* @param TransactionInterface $transaction Holds a request and response |
* @param MessageFactoryInterface $messageFactory Used to create responses |
* @param null|resource $handle Optionally provide a curl handle to modify |
* |
* @return resource Returns a prepared cURL handle |
* @throws AdapterException when an option cannot be applied |
*/ |
public function __invoke( |
TransactionInterface $transaction, |
MessageFactoryInterface $messageFactory, |
$handle = null |
) { |
$request = $transaction->getRequest(); |
$mediator = new RequestMediator($transaction, $messageFactory); |
$options = $this->getDefaultOptions($request, $mediator); |
$this->applyMethod($request, $options); |
$this->applyTransferOptions($request, $mediator, $options); |
$this->applyHeaders($request, $options); |
unset($options['_headers']); |
|
// Add adapter options from the request's configuration options |
if ($config = $request->getConfig()['curl']) { |
$options = $this->applyCustomCurlOptions($config, $options); |
} |
|
if (!$handle) { |
$handle = curl_init(); |
} |
|
curl_setopt_array($handle, $options); |
|
return $handle; |
} |
|
protected function getDefaultOptions( |
RequestInterface $request, |
RequestMediator $mediator |
) { |
$url = $request->getUrl(); |
|
// Strip fragment from URL. See: |
// https://github.com/guzzle/guzzle/issues/453 |
if (($pos = strpos($url, '#')) !== false) { |
$url = substr($url, 0, $pos); |
} |
|
$config = $request->getConfig(); |
$options = [ |
CURLOPT_URL => $url, |
CURLOPT_CONNECTTIMEOUT => $config['connect_timeout'] ?: 150, |
CURLOPT_RETURNTRANSFER => false, |
CURLOPT_HEADER => false, |
CURLOPT_WRITEFUNCTION => [$mediator, 'writeResponseBody'], |
CURLOPT_HEADERFUNCTION => [$mediator, 'receiveResponseHeader'], |
CURLOPT_READFUNCTION => [$mediator, 'readRequestBody'], |
CURLOPT_HTTP_VERSION => $request->getProtocolVersion() === '1.0' |
? CURL_HTTP_VERSION_1_0 : CURL_HTTP_VERSION_1_1, |
CURLOPT_SSL_VERIFYPEER => 1, |
CURLOPT_SSL_VERIFYHOST => 2, |
'_headers' => $request->getHeaders() |
]; |
|
if (defined('CURLOPT_PROTOCOLS')) { |
// Allow only HTTP and HTTPS protocols |
$options[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; |
} |
|
// cURL sometimes adds a content-type by default. Prevent this. |
if (!$request->hasHeader('Content-Type')) { |
$options[CURLOPT_HTTPHEADER][] = 'Content-Type:'; |
} |
|
return $options; |
} |
|
private function applyMethod(RequestInterface $request, array &$options) |
{ |
$method = $request->getMethod(); |
if ($method == 'HEAD') { |
$options[CURLOPT_NOBODY] = true; |
unset($options[CURLOPT_WRITEFUNCTION], $options[CURLOPT_READFUNCTION]); |
} else { |
$options[CURLOPT_CUSTOMREQUEST] = $method; |
if (!$request->getBody()) { |
unset($options[CURLOPT_READFUNCTION]); |
} else { |
$this->applyBody($request, $options); |
} |
} |
} |
|
private function applyBody(RequestInterface $request, array &$options) |
{ |
if ($request->hasHeader('Content-Length')) { |
$size = (int) $request->getHeader('Content-Length'); |
} else { |
$size = null; |
} |
|
$request->getBody()->seek(0); |
|
// You can send the body as a string using curl's CURLOPT_POSTFIELDS |
if (($size !== null && $size < 32768) || |
isset($request->getConfig()['curl']['body_as_string']) |
) { |
$options[CURLOPT_POSTFIELDS] = $request->getBody()->getContents(); |
// Don't duplicate the Content-Length header |
$this->removeHeader('Content-Length', $options); |
$this->removeHeader('Transfer-Encoding', $options); |
} else { |
$options[CURLOPT_UPLOAD] = true; |
// Let cURL handle setting the Content-Length header |
if ($size !== null) { |
$options[CURLOPT_INFILESIZE] = $size; |
$this->removeHeader('Content-Length', $options); |
} |
} |
|
// If the Expect header is not present, prevent curl from adding it |
if (!$request->hasHeader('Expect')) { |
$options[CURLOPT_HTTPHEADER][] = 'Expect:'; |
} |
} |
|
private function applyHeaders(RequestInterface $request, array &$options) |
{ |
foreach ($options['_headers'] as $name => $values) { |
$options[CURLOPT_HTTPHEADER][] = $name . ': ' . implode(', ', $values); |
} |
|
// Remove the Expect header if one was not set |
if (!$request->hasHeader('Accept')) { |
$options[CURLOPT_HTTPHEADER][] = 'Accept:'; |
} |
} |
|
private function applyTransferOptions( |
RequestInterface $request, |
RequestMediator $mediator, |
array &$options |
) { |
static $methods; |
if (!$methods) { |
$methods = array_flip(get_class_methods(__CLASS__)); |
} |
|
foreach ($request->getConfig()->toArray() as $key => $value) { |
$method = "add_{$key}"; |
if (isset($methods[$method])) { |
$this->{$method}($request, $mediator, $options, $value); |
} |
} |
} |
|
private function add_debug( |
RequestInterface $request, |
RequestMediator $mediator, |
&$options, |
$value |
) { |
if (!$value) { |
return; |
} |
|
if (!is_resource($value)) { |
$value = defined('STDOUT') ? STDOUT : fopen('php://output', 'w'); |
} |
|
$options[CURLOPT_STDERR] = $value; |
$options[CURLOPT_VERBOSE] = true; |
} |
|
private function add_proxy( |
RequestInterface $request, |
RequestMediator $mediator, |
&$options, |
$value |
) { |
if (!is_array($value)) { |
$options[CURLOPT_PROXY] = $value; |
} else { |
$scheme = $request->getScheme(); |
if (isset($value[$scheme])) { |
$options[CURLOPT_PROXY] = $value[$scheme]; |
} |
} |
} |
|
private function add_timeout( |
RequestInterface $request, |
RequestMediator $mediator, |
&$options, |
$value |
) { |
$options[CURLOPT_TIMEOUT_MS] = $value * 1000; |
} |
|
private function add_connect_timeout( |
RequestInterface $request, |
RequestMediator $mediator, |
&$options, |
$value |
) { |
$options[CURLOPT_CONNECTTIMEOUT_MS] = $value * 1000; |
} |
|
private function add_verify( |
RequestInterface $request, |
RequestMediator $mediator, |
&$options, |
$value |
) { |
if ($value === false) { |
unset($options[CURLOPT_CAINFO]); |
$options[CURLOPT_SSL_VERIFYHOST] = 0; |
$options[CURLOPT_SSL_VERIFYPEER] = false; |
} elseif ($value === true || is_string($value)) { |
$options[CURLOPT_SSL_VERIFYHOST] = 2; |
$options[CURLOPT_SSL_VERIFYPEER] = true; |
if ($value !== true) { |
if (!file_exists($value)) { |
throw new AdapterException('SSL certificate authority file' |
. " not found: {$value}"); |
} |
$options[CURLOPT_CAINFO] = $value; |
} |
} |
} |
|
private function add_cert( |
RequestInterface $request, |
RequestMediator $mediator, |
&$options, |
$value |
) { |
if (!file_exists($value)) { |
throw new AdapterException("SSL certificate not found: {$value}"); |
} |
|
$options[CURLOPT_SSLCERT] = $value; |
} |
|
private function add_ssl_key( |
RequestInterface $request, |
RequestMediator $mediator, |
&$options, |
$value |
) { |
if (is_array($value)) { |
$options[CURLOPT_SSLKEYPASSWD] = $value[1]; |
$value = $value[0]; |
} |
|
if (!file_exists($value)) { |
throw new AdapterException("SSL private key not found: {$value}"); |
} |
|
$options[CURLOPT_SSLKEY] = $value; |
} |
|
private function add_stream( |
RequestInterface $request, |
RequestMediator $mediator, |
&$options, |
$value |
) { |
if ($value === false) { |
return; |
} |
|
throw new AdapterException('cURL adapters do not support the "stream"' |
. ' request option. This error is typically encountered when trying' |
. ' to send requests with the "stream" option set to true in ' |
. ' parallel. You will either need to send these one at a time or' |
. ' implement a custom ParallelAdapterInterface that supports' |
. ' sending these types of requests in parallel. This error can' |
. ' also occur if the StreamAdapter is not available on your' |
. ' system (e.g., allow_url_fopen is disabled in your php.ini).'); |
} |
|
private function add_save_to( |
RequestInterface $request, |
RequestMediator $mediator, |
&$options, |
$value |
) { |
$mediator->setResponseBody(is_string($value) |
? new LazyOpenStream($value, 'w') |
: Stream::factory($value)); |
} |
|
private function add_decode_content( |
RequestInterface $request, |
RequestMediator $mediator, |
&$options, |
$value |
) { |
if (!$request->hasHeader('Accept-Encoding')) { |
$options[CURLOPT_ENCODING] = ''; |
// Don't let curl send the header over the wire |
$options[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; |
} else { |
$options[CURLOPT_ENCODING] = $request->getHeader('Accept-Encoding'); |
} |
} |
|
/** |
* Takes an array of curl options specified in the 'curl' option of a |
* request's configuration array and maps them to CURLOPT_* options. |
* |
* This method is only called when a request has a 'curl' config setting. |
* Array key strings that start with CURL that have a matching constant |
* value will be automatically converted to the matching constant. |
* |
* @param array $config Configuration array of custom curl option |
* @param array $options Array of existing curl options |
* |
* @return array Returns a new array of curl options |
*/ |
private function applyCustomCurlOptions(array $config, array $options) |
{ |
unset($config['body_as_string']); |
$curlOptions = []; |
|
// Map curl constant strings to defined values |
foreach ($config as $key => $value) { |
if (defined($key) && substr($key, 0, 4) === 'CURL') { |
$key = constant($key); |
} |
$curlOptions[$key] = $value; |
} |
|
return $curlOptions + $options; |
} |
|
/** |
* 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; |
} |
} |
} |
} |
/vendor/guzzlehttp/guzzle/src/Adapter/Curl/MultiAdapter.php |
@@ -0,0 +1,377 @@ |
<?php |
|
namespace GuzzleHttp\Adapter\Curl; |
|
use GuzzleHttp\Adapter\AdapterInterface; |
use GuzzleHttp\Adapter\ParallelAdapterInterface; |
use GuzzleHttp\Adapter\TransactionInterface; |
use GuzzleHttp\Event\RequestEvents; |
use GuzzleHttp\Exception\AdapterException; |
use GuzzleHttp\Exception\RequestException; |
use GuzzleHttp\Message\MessageFactoryInterface; |
|
/** |
* HTTP adapter that uses cURL multi as a transport layer |
* |
* When using the CurlAdapter, custom curl options can be specified as an |
* associative array of curl option constants mapping to values in the |
* **curl** key of a request's configuration options. |
* |
* In addition to being able to supply configuration options via the curl |
* request config, you can also specify the select_timeout variable using the |
* `GUZZLE_CURL_SELECT_TIMEOUT` environment variable. |
*/ |
class MultiAdapter implements AdapterInterface, ParallelAdapterInterface |
{ |
const ERROR_STR = 'See http://curl.haxx.se/libcurl/c/libcurl-errors.html for an explanation of cURL errors'; |
const ENV_SELECT_TIMEOUT = 'GUZZLE_CURL_SELECT_TIMEOUT'; |
|
/** @var CurlFactory */ |
private $curlFactory; |
|
/** @var MessageFactoryInterface */ |
private $messageFactory; |
|
/** @var array Array of curl multi handles */ |
private $multiHandles = []; |
|
/** @var array Array of curl multi handles */ |
private $multiOwned = []; |
|
/** @var int Total number of idle handles to keep in cache */ |
private $maxHandles; |
|
/** @var double */ |
private $selectTimeout; |
|
/** |
* Accepts an associative array of options: |
* |
* - handle_factory: Optional callable factory used to create cURL handles. |
* The callable is invoked with the following arguments: |
* TransactionInterface, MessageFactoryInterface, and an optional cURL |
* handle to modify. The factory method must then return a cURL resource. |
* - select_timeout: Specify a float in seconds to use for a |
* curl_multi_select timeout. |
* - max_handles: Maximum number of idle handles (defaults to 3). |
* |
* @param MessageFactoryInterface $messageFactory |
* @param array $options Array of options to use with the adapter: |
*/ |
public function __construct( |
MessageFactoryInterface $messageFactory, |
array $options = [] |
) { |
$this->messageFactory = $messageFactory; |
$this->curlFactory = isset($options['handle_factory']) |
? $options['handle_factory'] |
: new CurlFactory(); |
|
if (isset($options['select_timeout'])) { |
$this->selectTimeout = $options['select_timeout']; |
} elseif (isset($_SERVER[self::ENV_SELECT_TIMEOUT])) { |
$this->selectTimeout = $_SERVER[self::ENV_SELECT_TIMEOUT]; |
} else { |
$this->selectTimeout = 1; |
} |
|
$this->maxHandles = isset($options['max_handles']) |
? $options['max_handles'] |
: 3; |
} |
|
public function __destruct() |
{ |
foreach ($this->multiHandles as $handle) { |
if (is_resource($handle)) { |
curl_multi_close($handle); |
} |
} |
} |
|
/** |
* Throw an exception for a cURL multi response |
* |
* @param int $code Curl response code |
* @throws AdapterException |
*/ |
public static function throwMultiError($code) |
{ |
$buffer = function_exists('curl_multi_strerror') |
? curl_multi_strerror($code) |
: self::ERROR_STR; |
|
throw new AdapterException(sprintf('cURL error %s: %s', $code, $buffer)); |
} |
|
public function send(TransactionInterface $transaction) |
{ |
$context = new BatchContext($this->checkoutMultiHandle(), true); |
$this->addHandle($transaction, $context); |
$this->perform($context); |
|
return $transaction->getResponse(); |
} |
|
public function sendAll(\Iterator $transactions, $parallel) |
{ |
$context = new BatchContext( |
$this->checkoutMultiHandle(), |
false, |
$transactions |
); |
|
foreach (new \LimitIterator($transactions, 0, $parallel) as $trans) { |
$this->addHandle($trans, $context); |
} |
|
$this->perform($context); |
} |
|
private function perform(BatchContext $context) |
{ |
// The first curl_multi_select often times out no matter what, but is |
// usually required for fast transfers. |
$active = false; |
$multi = $context->getMultiHandle(); |
|
do { |
do { |
$mrc = curl_multi_exec($multi, $active); |
} while ($mrc === CURLM_CALL_MULTI_PERFORM); |
|
if ($mrc != CURLM_OK) { |
self::throwMultiError($mrc); |
} |
|
$this->processMessages($context); |
|
if ($active && |
curl_multi_select($multi, $this->selectTimeout) === -1 |
) { |
// Perform a usleep if a select returns -1. |
// See: https://bugs.php.net/bug.php?id=61141 |
usleep(250); |
} |
|
} while ($context->isActive() || $active); |
|
$this->releaseMultiHandle($multi, $this->maxHandles); |
} |
|
private function processMessages(BatchContext $context) |
{ |
$multi = $context->getMultiHandle(); |
|
while ($done = curl_multi_info_read($multi)) { |
$transaction = $context->findTransaction($done['handle']); |
$this->processResponse($transaction, $done, $context); |
// Add the next transaction if there are more in the queue |
if ($next = $context->nextPending()) { |
$this->addHandle($next, $context); |
} |
} |
} |
|
private function processResponse( |
TransactionInterface $transaction, |
array $curl, |
BatchContext $context |
) { |
$info = $context->removeTransaction($transaction); |
|
try { |
if (!$this->isCurlException($transaction, $curl, $context, $info) && |
$this->validateResponseWasSet($transaction, $context) |
) { |
if ($body = $transaction->getResponse()->getBody()) { |
$body->seek(0); |
} |
RequestEvents::emitComplete($transaction, $info); |
} |
} catch (\Exception $e) { |
$this->throwException($e, $context); |
} |
} |
|
private function addHandle( |
TransactionInterface $transaction, |
BatchContext $context |
) { |
try { |
RequestEvents::emitBefore($transaction); |
// Only transfer if the request was not intercepted |
if (!$transaction->getResponse()) { |
$factory = $this->curlFactory; |
$context->addTransaction( |
$transaction, |
$factory($transaction, $this->messageFactory) |
); |
} |
} catch (RequestException $e) { |
$this->throwException($e, $context); |
} |
} |
|
private function isCurlException( |
TransactionInterface $transaction, |
array $curl, |
BatchContext $context, |
array $info |
) { |
if (CURLM_OK == $curl['result'] || |
CURLM_CALL_MULTI_PERFORM == $curl['result'] |
) { |
return false; |
} |
|
$request = $transaction->getRequest(); |
try { |
// Send curl stats along if they are available |
$stats = ['curl_result' => $curl['result']] + $info; |
RequestEvents::emitError( |
$transaction, |
new RequestException( |
sprintf( |
'[curl] (#%s) %s [url] %s', |
$curl['result'], |
function_exists('curl_strerror') |
? curl_strerror($curl['result']) |
: self::ERROR_STR, |
$request->getUrl() |
), |
$request |
), |
$stats |
); |
} catch (\Exception $e) { |
$this->throwException($e, $context); |
} |
|
return true; |
} |
|
private function throwException(\Exception $e, BatchContext $context) |
{ |
if ($context->throwsExceptions() |
|| ($e instanceof RequestException && $e->getThrowImmediately()) |
) { |
$context->removeAll(); |
$this->releaseMultiHandle($context->getMultiHandle(), -1); |
throw $e; |
} |
} |
|
/** |
* Returns a curl_multi handle from the cache or creates a new one |
* |
* @return resource |
*/ |
private function checkoutMultiHandle() |
{ |
// Find an unused handle in the cache |
$key = array_search(false, $this->multiOwned, true); |
if (false !== $key) { |
$this->multiOwned[$key] = true; |
return $this->multiHandles[$key]; |
} |
|
// Add a new handle |
$handle = curl_multi_init(); |
$id = (int) $handle; |
$this->multiHandles[$id] = $handle; |
$this->multiOwned[$id] = true; |
|
return $handle; |
} |
|
/** |
* Releases a curl_multi handle back into the cache and removes excess cache |
* |
* @param resource $handle Curl multi handle to remove |
* @param int $maxHandles (Optional) Maximum number of existing multiHandles to allow before pruning. |
*/ |
private function releaseMultiHandle($handle, $maxHandles) |
{ |
$id = (int) $handle; |
|
if (count($this->multiHandles) <= $maxHandles) { |
$this->multiOwned[$id] = false; |
} elseif (isset($this->multiHandles[$id], $this->multiOwned[$id])) { |
// Prune excessive handles |
curl_multi_close($this->multiHandles[$id]); |
unset($this->multiHandles[$id], $this->multiOwned[$id]); |
} |
} |
|
/** |
* This function ensures that a response was set on a transaction. If one |
* was not set, then the request is retried if possible. This error |
* typically means you are sending a payload, curl encountered a |
* "Connection died, retrying a fresh connect" error, tried to rewind the |
* stream, and then encountered a "necessary data rewind wasn't possible" |
* error, causing the request to be sent through curl_multi_info_read() |
* without an error status. |
* |
* @param TransactionInterface $transaction |
* @param BatchContext $context |
* |
* @return bool Returns true if it's OK, and false if it failed. |
* @throws \GuzzleHttp\Exception\RequestException If it failed and cannot |
* recover. |
*/ |
private function validateResponseWasSet( |
TransactionInterface $transaction, |
BatchContext $context |
) { |
if ($transaction->getResponse()) { |
return true; |
} |
|
$body = $transaction->getRequest()->getBody(); |
|
if (!$body) { |
// This is weird and should probably never happen. |
RequestEvents::emitError( |
$transaction, |
new RequestException( |
'No response was received for a request with no body. This' |
. ' could mean that you are saturating your network.', |
$transaction->getRequest() |
) |
); |
} elseif (!$body->isSeekable() || !$body->seek(0)) { |
// Nothing we can do with this. Sorry! |
RequestEvents::emitError( |
$transaction, |
new RequestException( |
'The connection was unexpectedly closed. The request would' |
. ' have been retried, but attempting to rewind the' |
. ' request body failed. Consider wrapping your request' |
. ' body in a CachingStream decorator to work around this' |
. ' issue if necessary.', |
$transaction->getRequest() |
) |
); |
} else { |
$this->retryFailedConnection($transaction, $context); |
} |
|
return false; |
} |
|
private function retryFailedConnection( |
TransactionInterface $transaction, |
BatchContext $context |
) { |
// Add the request back to the batch to retry automatically. |
$context->addTransaction( |
$transaction, |
call_user_func( |
$this->curlFactory, |
$transaction, |
$this->messageFactory |
) |
); |
} |
} |
/vendor/guzzlehttp/guzzle/src/Adapter/Curl/RequestMediator.php |
@@ -0,0 +1,130 @@ |
<?php |
|
namespace GuzzleHttp\Adapter\Curl; |
|
use GuzzleHttp\Adapter\TransactionInterface; |
use GuzzleHttp\Event\RequestEvents; |
use GuzzleHttp\Message\MessageFactoryInterface; |
use GuzzleHttp\Stream\Stream; |
use GuzzleHttp\Stream\StreamInterface; |
|
/** |
* Mediator between curl handles and request objects |
*/ |
class RequestMediator |
{ |
/** @var TransactionInterface */ |
private $transaction; |
/** @var MessageFactoryInterface */ |
private $messageFactory; |
private $statusCode; |
private $reasonPhrase; |
private $body; |
private $protocolVersion; |
private $headers; |
|
/** |
* @param TransactionInterface $transaction Transaction to populate |
* @param MessageFactoryInterface $messageFactory Creates responses |
*/ |
public function __construct( |
TransactionInterface $transaction, |
MessageFactoryInterface $messageFactory |
) { |
$this->transaction = $transaction; |
$this->messageFactory = $messageFactory; |
} |
|
/** |
* Set the body that will hold the response body |
* |
* @param StreamInterface $body Response body |
*/ |
public function setResponseBody(StreamInterface $body = null) |
{ |
$this->body = $body; |
} |
|
/** |
* Receive a response header from curl |
* |
* @param resource $curl Curl handle |
* @param string $header Received header |
* |
* @return int |
*/ |
public function receiveResponseHeader($curl, $header) |
{ |
static $normalize = ["\r", "\n"]; |
$length = strlen($header); |
$header = str_replace($normalize, '', $header); |
|
if (strpos($header, 'HTTP/') === 0) { |
$startLine = explode(' ', $header, 3); |
// Only download the body to a target body when a successful |
// response is received. |
if ($startLine[1][0] != '2') { |
$this->body = null; |
} |
$this->statusCode = $startLine[1]; |
$this->reasonPhrase = isset($startLine[2]) ? $startLine[2] : null; |
$this->protocolVersion = substr($startLine[0], -3); |
$this->headers = []; |
} elseif ($pos = strpos($header, ':')) { |
$this->headers[substr($header, 0, $pos)][] = substr($header, $pos + 1); |
} elseif ($header == '' && $this->statusCode >= 200) { |
$response = $this->messageFactory->createResponse( |
$this->statusCode, |
$this->headers, |
$this->body, |
[ |
'protocol_version' => $this->protocolVersion, |
'reason_phrase' => $this->reasonPhrase |
] |
); |
$this->headers = $this->body = null; |
$this->transaction->setResponse($response); |
// Allows events to react before downloading any of the body |
RequestEvents::emitHeaders($this->transaction); |
} |
|
return $length; |
} |
|
/** |
* Write data to the response body of a request |
* |
* @param resource $curl |
* @param string $write |
* |
* @return int |
*/ |
public function writeResponseBody($curl, $write) |
{ |
if (!($response = $this->transaction->getResponse())) { |
return 0; |
} |
|
// Add a default body on the response if one was not found |
if (!($body = $response->getBody())) { |
$body = new Stream(fopen('php://temp', 'r+')); |
$response->setBody($body); |
} |
|
return $body->write($write); |
} |
|
/** |
* Read data from the request body and send it to curl |
* |
* @param resource $ch Curl handle |
* @param resource $fd File descriptor |
* @param int $length Amount of data to read |
* |
* @return string |
*/ |
public function readRequestBody($ch, $fd, $length) |
{ |
return (string) $this->transaction->getRequest()->getBody()->read($length); |
} |
} |
/vendor/guzzlehttp/guzzle/src/Adapter/StreamAdapter.php |
@@ -0,0 +1,373 @@ |
<?php |
|
namespace GuzzleHttp\Adapter; |
|
use GuzzleHttp\Event\RequestEvents; |
use GuzzleHttp\Exception\AdapterException; |
use GuzzleHttp\Exception\RequestException; |
use GuzzleHttp\Message\AbstractMessage; |
use GuzzleHttp\Message\MessageFactoryInterface; |
use GuzzleHttp\Message\RequestInterface; |
use GuzzleHttp\Stream\InflateStream; |
use GuzzleHttp\Stream\LazyOpenStream; |
use GuzzleHttp\Stream\Stream; |
use GuzzleHttp\Stream\StreamInterface; |
use GuzzleHttp\Stream\Utils; |
|
/** |
* HTTP adapter that uses PHP's HTTP stream wrapper. |
* |
* When using the StreamAdapter, custom stream context options can be specified |
* using the **stream_context** option in a request's **config** option. The |
* structure of the "stream_context" option is an associative array where each |
* key is a transport name and each option is an associative array of options. |
*/ |
class StreamAdapter implements AdapterInterface |
{ |
/** @var MessageFactoryInterface */ |
private $messageFactory; |
|
/** |
* @param MessageFactoryInterface $messageFactory |
*/ |
public function __construct(MessageFactoryInterface $messageFactory) |
{ |
$this->messageFactory = $messageFactory; |
} |
|
public function send(TransactionInterface $transaction) |
{ |
// HTTP/1.1 streams using the PHP stream wrapper require a |
// Connection: close header. Setting here so that it is added before |
// emitting the request.before_send event. |
$request = $transaction->getRequest(); |
if ($request->getProtocolVersion() == '1.1' && |
!$request->hasHeader('Connection') |
) { |
$transaction->getRequest()->setHeader('Connection', 'close'); |
} |
|
RequestEvents::emitBefore($transaction); |
if (!$transaction->getResponse()) { |
$this->createResponse($transaction); |
RequestEvents::emitComplete($transaction); |
} |
|
return $transaction->getResponse(); |
} |
|
private function createResponse(TransactionInterface $transaction) |
{ |
$request = $transaction->getRequest(); |
$stream = $this->createStream($request, $http_response_header); |
$this->createResponseObject( |
$request, |
$http_response_header, |
$transaction, |
new Stream($stream) |
); |
} |
|
private function createResponseObject( |
RequestInterface $request, |
array $headers, |
TransactionInterface $transaction, |
StreamInterface $stream |
) { |
$parts = explode(' ', array_shift($headers), 3); |
$options = ['protocol_version' => substr($parts[0], -3)]; |
|
if (isset($parts[2])) { |
$options['reason_phrase'] = $parts[2]; |
} |
|
$response = $this->messageFactory->createResponse( |
$parts[1], |
$this->headersFromLines($headers), |
null, |
$options |
); |
|
// Automatically decode responses when instructed. |
if ($request->getConfig()->get('decode_content')) { |
switch ($response->getHeader('Content-Encoding')) { |
case 'gzip': |
case 'deflate': |
$stream = new InflateStream($stream); |
break; |
} |
} |
|
// Drain the stream immediately if 'stream' was not enabled. |
if (!$request->getConfig()['stream']) { |
$stream = $this->getSaveToBody($request, $stream); |
} |
|
$response->setBody($stream); |
$transaction->setResponse($response); |
RequestEvents::emitHeaders($transaction); |
|
return $response; |
} |
|
/** |
* Drain the stream into the destination stream |
*/ |
private function getSaveToBody( |
RequestInterface $request, |
StreamInterface $stream |
) { |
if ($saveTo = $request->getConfig()['save_to']) { |
// Stream the response into the destination stream |
$saveTo = is_string($saveTo) |
? new Stream(Utils::open($saveTo, 'r+')) |
: Stream::factory($saveTo); |
} else { |
// Stream into the default temp stream |
$saveTo = Stream::factory(); |
} |
|
Utils::copyToStream($stream, $saveTo); |
$saveTo->seek(0); |
$stream->close(); |
|
return $saveTo; |
} |
|
private function headersFromLines(array $lines) |
{ |
$responseHeaders = []; |
|
foreach ($lines as $line) { |
$headerParts = explode(':', $line, 2); |
$responseHeaders[$headerParts[0]][] = isset($headerParts[1]) |
? trim($headerParts[1]) |
: ''; |
} |
|
return $responseHeaders; |
} |
|
/** |
* Create a resource and check to ensure it was created successfully |
* |
* @param callable $callback Callable that returns stream resource |
* @param RequestInterface $request Request used when throwing exceptions |
* @param array $options Options used when throwing exceptions |
* |
* @return resource |
* @throws RequestException on error |
*/ |
private function createResource(callable $callback, RequestInterface $request, $options) |
{ |
// Turn off error reporting while we try to initiate the request |
$level = error_reporting(0); |
$resource = call_user_func($callback); |
error_reporting($level); |
|
// If the resource could not be created, then grab the last error and |
// throw an exception. |
if (!is_resource($resource)) { |
$message = 'Error creating resource. [url] ' . $request->getUrl() . ' '; |
if (isset($options['http']['proxy'])) { |
$message .= "[proxy] {$options['http']['proxy']} "; |
} |
foreach ((array) error_get_last() as $key => $value) { |
$message .= "[{$key}] {$value} "; |
} |
throw new RequestException(trim($message), $request); |
} |
|
return $resource; |
} |
|
/** |
* Create the stream for the request with the context options. |
* |
* @param RequestInterface $request Request being sent |
* @param mixed $http_response_header Populated by stream wrapper |
* |
* @return resource |
*/ |
private function createStream( |
RequestInterface $request, |
&$http_response_header |
) { |
static $methods; |
if (!$methods) { |
$methods = array_flip(get_class_methods(__CLASS__)); |
} |
|
$params = []; |
$options = $this->getDefaultOptions($request); |
foreach ($request->getConfig()->toArray() as $key => $value) { |
$method = "add_{$key}"; |
if (isset($methods[$method])) { |
$this->{$method}($request, $options, $value, $params); |
} |
} |
|
$this->applyCustomOptions($request, $options); |
$context = $this->createStreamContext($request, $options, $params); |
|
return $this->createStreamResource( |
$request, |
$options, |
$context, |
$http_response_header |
); |
} |
|
private function getDefaultOptions(RequestInterface $request) |
{ |
$headers = AbstractMessage::getHeadersAsString($request); |
|
$context = [ |
'http' => [ |
'method' => $request->getMethod(), |
'header' => trim($headers), |
'protocol_version' => $request->getProtocolVersion(), |
'ignore_errors' => true, |
'follow_location' => 0 |
] |
]; |
|
if ($body = $request->getBody()) { |
$context['http']['content'] = (string) $body; |
// Prevent the HTTP adapter from adding a Content-Type header. |
if (!$request->hasHeader('Content-Type')) { |
$context['http']['header'] .= "\r\nContent-Type:"; |
} |
} |
|
return $context; |
} |
|
private function add_proxy(RequestInterface $request, &$options, $value, &$params) |
{ |
if (!is_array($value)) { |
$options['http']['proxy'] = $value; |
} else { |
$scheme = $request->getScheme(); |
if (isset($value[$scheme])) { |
$options['http']['proxy'] = $value[$scheme]; |
} |
} |
} |
|
private function add_timeout(RequestInterface $request, &$options, $value, &$params) |
{ |
$options['http']['timeout'] = $value; |
} |
|
private function add_verify(RequestInterface $request, &$options, $value, &$params) |
{ |
if ($value === true || is_string($value)) { |
$options['http']['verify_peer'] = true; |
if ($value !== true) { |
if (!file_exists($value)) { |
throw new \RuntimeException("SSL certificate authority file not found: {$value}"); |
} |
$options['http']['allow_self_signed'] = true; |
$options['http']['cafile'] = $value; |
} |
} elseif ($value === false) { |
$options['http']['verify_peer'] = false; |
} |
} |
|
private function add_cert(RequestInterface $request, &$options, $value, &$params) |
{ |
if (is_array($value)) { |
$options['http']['passphrase'] = $value[1]; |
$value = $value[0]; |
} |
|
if (!file_exists($value)) { |
throw new \RuntimeException("SSL certificate not found: {$value}"); |
} |
|
$options['http']['local_cert'] = $value; |
} |
|
private function add_debug(RequestInterface $request, &$options, $value, &$params) |
{ |
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']; |
|
if (!is_resource($value)) { |
$value = defined('STDOUT') ? STDOUT : fopen('php://output', 'w'); |
} |
|
$params['notification'] = function () use ($request, $value, $map, $args) { |
$passed = func_get_args(); |
$code = array_shift($passed); |
fprintf($value, '<%s> [%s] ', $request->getUrl(), $map[$code]); |
foreach (array_filter($passed) as $i => $v) { |
fwrite($value, $args[$i] . ': "' . $v . '" '); |
} |
fwrite($value, "\n"); |
}; |
} |
|
private function applyCustomOptions( |
RequestInterface $request, |
array &$options |
) { |
// Overwrite any generated options with custom options |
if ($custom = $request->getConfig()['stream_context']) { |
if (!is_array($custom)) { |
throw new AdapterException('stream_context must be an array'); |
} |
$options = array_replace_recursive($options, $custom); |
} |
} |
|
private function createStreamContext( |
RequestInterface $request, |
array $options, |
array $params |
) { |
return $this->createResource( |
function () use ($request, $options, $params) { |
return stream_context_create($options, $params); |
}, |
$request, |
$options |
); |
} |
|
private function createStreamResource( |
RequestInterface $request, |
array $options, |
$context, |
&$http_response_header |
) { |
$url = $request->getUrl(); |
|
return $this->createResource( |
function () use ($url, &$http_response_header, $context) { |
if (false === strpos($url, 'http')) { |
trigger_error("URL is invalid: {$url}", E_USER_WARNING); |
return null; |
} |
return fopen($url, 'r', null, $context); |
}, |
$request, |
$options |
); |
} |
} |