mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-09-05 07:27:04 +03:00
Migrate RainLoop Webmail to use Facebook Graph API instead of FQL
This commit is contained in:
parent
9fcaf00875
commit
d0dbb26548
120 changed files with 18817 additions and 1746 deletions
|
|
@ -0,0 +1,158 @@
|
|||
<?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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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];
|
||||
|
||||
$code = curl_multi_remove_handle($this->multi, $handle);
|
||||
if ($code != CURLM_OK) {
|
||||
MultiAdapter::throwMultiError($code);
|
||||
}
|
||||
|
||||
$info = curl_getinfo($handle);
|
||||
curl_close($handle);
|
||||
unset($this->handles[$transaction]);
|
||||
|
||||
return $info;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
<?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);
|
||||
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($handle);
|
||||
$this->ownedHandles[$id] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,331 @@
|
|||
<?php
|
||||
|
||||
namespace GuzzleHttp\Adapter\Curl;
|
||||
|
||||
use GuzzleHttp\Adapter\TransactionInterface;
|
||||
use GuzzleHttp\Message\MessageFactoryInterface;
|
||||
use GuzzleHttp\Message\RequestInterface;
|
||||
use GuzzleHttp\Stream;
|
||||
use GuzzleHttp\Exception\AdapterException;
|
||||
|
||||
/**
|
||||
* 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 = array(
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_CONNECTTIMEOUT => $config['connect_timeout'] ?: 150,
|
||||
CURLOPT_RETURNTRANSFER => false,
|
||||
CURLOPT_HEADER => false,
|
||||
CURLOPT_WRITEFUNCTION => array($mediator, 'writeResponseBody'),
|
||||
CURLOPT_HEADERFUNCTION => array($mediator, 'receiveResponseHeader'),
|
||||
CURLOPT_READFUNCTION => array($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;
|
||||
}
|
||||
|
||||
// Add CURLOPT_ENCODING if Accept-Encoding header is provided
|
||||
if ($request->hasHeader('Accept-Encoding')) {
|
||||
$options[CURLOPT_ENCODING] = $request->getHeader('Accept-Encoding');
|
||||
// Let cURL set the Accept-Encoding header. Without this change
|
||||
// curl could add a duplicate value.
|
||||
$this->removeHeader('Accept-Encoding', $options);
|
||||
}
|
||||
|
||||
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) {
|
||||
$options[CURLOPT_STDERR] = is_resource($value) ? $value : STDOUT;
|
||||
$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_save_to(
|
||||
RequestInterface $request,
|
||||
RequestMediator $mediator,
|
||||
&$options,
|
||||
$value
|
||||
) {
|
||||
$mediator->setResponseBody(is_string($value)
|
||||
? Stream\create(fopen($value, 'w'))
|
||||
: Stream\create($value));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
<?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 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.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
while (($mrc = curl_multi_exec($multi, $active)) == CURLM_CALL_MULTI_PERFORM);
|
||||
if ($mrc != CURLM_OK && $mrc != CURLM_CALL_MULTI_PERFORM) {
|
||||
self::throwMultiError($mrc);
|
||||
}
|
||||
// Need to check if there are pending transactions before processing
|
||||
// them so that we don't bail from the loop too early.
|
||||
$pending = $context->hasPending();
|
||||
$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 ($active || $pending);
|
||||
|
||||
$this->releaseMultiHandle($multi);
|
||||
}
|
||||
|
||||
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)) {
|
||||
RequestEvents::emitComplete($transaction, $info);
|
||||
}
|
||||
} catch (RequestException $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 (RequestException $e) {
|
||||
$this->throwException($e, $context);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function throwException(RequestException $e, BatchContext $context)
|
||||
{
|
||||
if ($context->throwsExceptions()) {
|
||||
$this->releaseMultiHandle($context->getMultiHandle());
|
||||
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
|
||||
*/
|
||||
private function releaseMultiHandle($handle)
|
||||
{
|
||||
$id = (int) $handle;
|
||||
|
||||
if (count($this->multiHandles) <= 3) {
|
||||
$this->multiOwned[$id] = false;
|
||||
} else {
|
||||
// Prune excessive handles
|
||||
curl_multi_close($this->multiHandles[$id]);
|
||||
unset($this->multiHandles[$id], $this->multiOwned[$id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue