uawdijnntqw1x1x1
IP : 216.73.216.136
Hostname : dhandapanilive
Kernel : Linux dhandapanilive 5.15.0-139-generic #149~20.04.1-Ubuntu SMP Wed Apr 16 08:29:56 UTC 2025 x86_64
Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
OS : Linux
PATH:
/
var
/
www
/
html
/
dhandapani
/
e5964
/
..
/
dev
/
..
/
9da53
/
ringphp.tar
/
/
README.rst000077700000002753151323632020006246 0ustar00======= RingPHP ======= **Note:** this is a fork of the original project since it was abandoned. Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function. RingPHP be used to power HTTP clients and servers through a PHP function that accepts a request hash and returns a response hash that is fulfilled using a `promise <https://github.com/reactphp/promise>`_, allowing RingPHP to support both synchronous and asynchronous workflows. By abstracting the implementation details of different HTTP clients and servers, RingPHP allows you to utilize pluggable HTTP clients and servers without tying your application to a specific implementation. .. code-block:: php <?php require 'vendor/autoload.php'; use GuzzleHttp\Ring\Client\CurlHandler; $handler = new CurlHandler(); $response = $handler([ 'http_method' => 'GET', 'uri' => '/', 'headers' => [ 'host' => ['www.google.com'], 'x-foo' => ['baz'] ] ]); $response->then(function (array $response) { echo $response['status']; }); $response->wait(); RingPHP is inspired by Clojure's `Ring <https://github.com/ring-clojure/ring>`_, which, in turn, was inspired by Python's WSGI and Ruby's Rack. RingPHP is utilized as the handler layer in `Guzzle <http://guzzlephp.org>`_ 5.0+ to send HTTP requests. Documentation ------------- See http://ringphp.readthedocs.org/ for the full online documentation. src/Client/ClientUtils.php000077700000005320151323632020011525 0ustar00<?php namespace GuzzleHttp\Ring\Client; /** * Client specific utility functions. */ class ClientUtils { /** * 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. */ public static function getDefaultCaBundle() { static $cached = null; static $cafiles = [ // Red Hat, CentOS, Fedora (provided by the ca-certificates package) '/etc/pki/tls/certs/ca-bundle.crt', // Ubuntu, Debian (provided by the ca-certificates package) '/etc/ssl/certs/ca-certificates.crt', // FreeBSD (provided by the ca_root_nss package) '/usr/local/share/certs/ca-root-nss.crt', // OS X provided by homebrew (using the default path) '/usr/local/etc/openssl/cert.pem', // Windows? 'C:\\windows\\system32\\curl-ca-bundle.crt', 'C:\\windows\\curl-ca-bundle.crt', ]; if ($cached) { return $cached; } if ($ca = ini_get('openssl.cafile')) { return $cached = $ca; } if ($ca = ini_get('curl.cainfo')) { return $cached = $ca; } foreach ($cafiles as $filename) { if (file_exists($filename)) { return $cached = $filename; } } throw new \RuntimeException(self::CA_ERR); } const CA_ERR = " 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/5.3/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."; } src/Client/MockHandler.php000077700000003023151323632020011453 0ustar00<?php namespace GuzzleHttp\Ring\Client; use GuzzleHttp\Ring\Core; use GuzzleHttp\Ring\Future\CompletedFutureArray; use GuzzleHttp\Ring\Future\FutureArrayInterface; /** * Ring handler that returns a canned response or evaluated function result. */ class MockHandler { /** @var callable|array|FutureArrayInterface */ private $result; /** * Provide an array or future to always return the same value. Provide a * callable that accepts a request object and returns an array or future * to dynamically create a response. * * @param array|FutureArrayInterface|callable $result Mock return value. */ public function __construct($result) { $this->result = $result; } public function __invoke(array $request) { Core::doSleep($request); $response = is_callable($this->result) ? call_user_func($this->result, $request) : $this->result; if (is_array($response)) { $response = new CompletedFutureArray($response + [ 'status' => null, 'body' => null, 'headers' => [], 'reason' => null, 'effective_url' => null, ]); } elseif (!$response instanceof FutureArrayInterface) { throw new \InvalidArgumentException( 'Response must be an array or FutureArrayInterface. Found ' . Core::describeType($request) ); } return $response; } } src/Client/StreamHandler.php000077700000031470151323632020012024 0ustar00<?php namespace GuzzleHttp\Ring\Client; use GuzzleHttp\Ring\Core; use GuzzleHttp\Ring\Exception\ConnectException; use GuzzleHttp\Ring\Exception\RingException; use GuzzleHttp\Ring\Future\CompletedFutureArray; use GuzzleHttp\Stream\InflateStream; use GuzzleHttp\Stream\StreamInterface; use GuzzleHttp\Stream\Stream; use GuzzleHttp\Stream\Utils; /** * RingPHP client handler that uses PHP's HTTP stream wrapper. */ class StreamHandler { private $options; private $lastHeaders; public function __construct(array $options = []) { $this->options = $options; } public function __invoke(array $request) { $url = Core::url($request); Core::doSleep($request); try { // Does not support the expect header. $request = Core::removeHeader($request, 'Expect'); $stream = $this->createStream($url, $request); return $this->createResponse($request, $url, $stream); } catch (RingException $e) { return $this->createErrorResponse($url, $e); } } private function createResponse(array $request, $url, $stream) { $hdrs = $this->lastHeaders; $this->lastHeaders = null; $parts = explode(' ', array_shift($hdrs), 3); $response = [ 'version' => substr($parts[0], 5), 'status' => $parts[1], 'reason' => isset($parts[2]) ? $parts[2] : null, 'headers' => Core::headersFromLines($hdrs), 'effective_url' => $url, ]; $stream = $this->checkDecode($request, $response, $stream); // If not streaming, then drain the response into a stream. if (empty($request['client']['stream'])) { $dest = isset($request['client']['save_to']) ? $request['client']['save_to'] : fopen('php://temp', 'r+'); $stream = $this->drain($stream, $dest); } $response['body'] = $stream; return new CompletedFutureArray($response); } private function checkDecode(array $request, array $response, $stream) { // Automatically decode responses when instructed. if (!empty($request['client']['decode_content'])) { switch (Core::firstHeader($response, 'Content-Encoding', true)) { case 'gzip': case 'deflate': $stream = new InflateStream(Stream::factory($stream)); break; } } return $stream; } /** * Drains the stream into the "save_to" client option. * * @param resource $stream * @param string|resource|StreamInterface $dest * * @return Stream * @throws \RuntimeException when the save_to option is invalid. */ private function drain($stream, $dest) { if (is_resource($stream)) { if (!is_resource($dest)) { $stream = Stream::factory($stream); } else { stream_copy_to_stream($stream, $dest); fclose($stream); rewind($dest); return $dest; } } // Stream the response into the destination stream $dest = is_string($dest) ? new Stream(Utils::open($dest, 'r+')) : Stream::factory($dest); Utils::copyToStream($stream, $dest); $dest->seek(0); $stream->close(); return $dest; } /** * Creates an error response for the given stream. * * @param string $url * @param RingException $e * * @return array */ private function createErrorResponse($url, RingException $e) { // Determine if the error was a networking error. $message = $e->getMessage(); // This list can probably get more comprehensive. if (strpos($message, 'getaddrinfo') // DNS lookup failed || strpos($message, 'Connection refused') ) { $e = new ConnectException($e->getMessage(), 0, $e); } return new CompletedFutureArray([ 'status' => null, 'body' => null, 'headers' => [], 'effective_url' => $url, 'error' => $e ]); } /** * 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 RingException(trim($message)); } return $resource; } private function createStream($url, array $request) { 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 ((!isset($request['version']) || $request['version'] == '1.1') && !Core::hasHeader($request, 'Connection') ) { $request['headers']['Connection'] = ['close']; } // Ensure SSL is verified by default if (!isset($request['client']['verify'])) { $request['client']['verify'] = true; } $params = []; $options = $this->getDefaultOptions($request); if (isset($request['client'])) { foreach ($request['client'] as $key => $value) { $method = "add_{$key}"; if (isset($methods[$method])) { $this->{$method}($request, $options, $value, $params); } } } return $this->createStreamResource( $url, $request, $options, $this->createContext($request, $options, $params) ); } private function getDefaultOptions(array $request) { $headers = ""; foreach ($request['headers'] as $name => $value) { foreach ((array) $value as $val) { $headers .= "$name: $val\r\n"; } } $context = [ 'http' => [ 'method' => $request['http_method'], 'header' => $headers, 'protocol_version' => isset($request['version']) ? $request['version'] : 1.1, 'ignore_errors' => true, 'follow_location' => 0, ], ]; $body = Core::body($request); if (isset($body)) { $context['http']['content'] = $body; // Prevent the HTTP handler from adding a Content-Type header. if (!Core::hasHeader($request, 'Content-Type')) { $context['http']['header'] .= "Content-Type:\r\n"; } } $context['http']['header'] = rtrim($context['http']['header']); return $context; } private function add_proxy(array $request, &$options, $value, &$params) { if (!is_array($value)) { $options['http']['proxy'] = $value; } else { $scheme = isset($request['scheme']) ? $request['scheme'] : 'http'; if (isset($value[$scheme])) { $options['http']['proxy'] = $value[$scheme]; } } } private function add_timeout(array $request, &$options, $value, &$params) { $options['http']['timeout'] = $value; } private function add_verify(array $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'] = ClientUtils::getDefaultCaBundle(); } } elseif (is_string($value)) { $options['ssl']['cafile'] = $value; if (!file_exists($value)) { throw new RingException("SSL CA bundle not found: $value"); } } elseif ($value === false) { $options['ssl']['verify_peer'] = false; $options['ssl']['allow_self_signed'] = true; return; } else { throw new RingException('Invalid verify request option'); } $options['ssl']['verify_peer'] = true; $options['ssl']['allow_self_signed'] = false; } private function add_cert(array $request, &$options, $value, &$params) { if (is_array($value)) { $options['ssl']['passphrase'] = $value[1]; $value = $value[0]; } if (!file_exists($value)) { throw new RingException("SSL certificate not found: {$value}"); } $options['ssl']['local_cert'] = $value; } private function add_progress(array $request, &$options, $value, &$params) { $fn = function ($code, $_1, $_2, $_3, $transferred, $total) use ($value) { if ($code == STREAM_NOTIFY_PROGRESS) { $value($total, $transferred, null, null); } }; // Wrap the existing function if needed. $params['notification'] = isset($params['notification']) ? Core::callArray([$params['notification'], $fn]) : $fn; } private function add_debug(array $request, &$options, $value, &$params) { if ($value === false) { return; } static $map = [ STREAM_NOTIFY_CONNECT => 'CONNECT', STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED', 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 = Core::getDebugResource($value); $ident = $request['http_method'] . ' ' . Core::url($request); $fn = 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"); }; // Wrap the existing function if needed. $params['notification'] = isset($params['notification']) ? Core::callArray([$params['notification'], $fn]) : $fn; } private function applyCustomOptions(array $request, array &$options) { if (!isset($request['client']['stream_context'])) { return; } if (!is_array($request['client']['stream_context'])) { throw new RingException('stream_context must be an array'); } $options = array_replace_recursive( $options, $request['client']['stream_context'] ); } private function createContext(array $request, array $options, array $params) { $this->applyCustomOptions($request, $options); return $this->createResource( function () use ($request, $options, $params) { return stream_context_create($options, $params); }, $request, $options ); } private function createStreamResource( $url, array $request, array $options, $context ) { return $this->createResource( function () use ($url, $context) { if (false === strpos($url, 'http')) { trigger_error("URL is invalid: {$url}", E_USER_WARNING); return null; } $resource = fopen($url, 'r', null, $context); $this->lastHeaders = $http_response_header; return $resource; }, $request, $options ); } } src/Client/CurlFactory.php000077700000046746151323632020011544 0ustar00<?php namespace GuzzleHttp\Ring\Client; use GuzzleHttp\Ring\Core; use GuzzleHttp\Ring\Exception\ConnectException; use GuzzleHttp\Ring\Exception\RingException; use GuzzleHttp\Stream\LazyOpenStream; use GuzzleHttp\Stream\StreamInterface; /** * Creates curl resources from a request */ class CurlFactory { /** * Creates a cURL handle, header resource, and body resource based on a * transaction. * * @param array $request Request hash * @param null|resource $handle Optionally provide a curl handle to modify * * @return array Returns an array of the curl handle, headers array, and * response body handle. * @throws \RuntimeException when an option cannot be applied */ public function __invoke(array $request, $handle = null) { $headers = []; $options = $this->getDefaultOptions($request, $headers); $this->applyMethod($request, $options); if (isset($request['client'])) { $this->applyHandlerOptions($request, $options); } $this->applyHeaders($request, $options); unset($options['_headers']); // Add handler options from the request's configuration options if (isset($request['client']['curl'])) { $options = $this->applyCustomCurlOptions( $request['client']['curl'], $options ); } if (!$handle) { $handle = curl_init(); } $body = $this->getOutputBody($request, $options); curl_setopt_array($handle, $options); return [$handle, &$headers, $body]; } /** * Creates a response hash from a cURL result. * * @param callable $handler Handler that was used. * @param array $request Request that sent. * @param array $response Response hash to update. * @param array $headers Headers received during transfer. * @param resource $body Body fopen response. * * @return array */ public static function createResponse( callable $handler, array $request, array $response, array $headers, $body ) { if (isset($response['transfer_stats']['url'])) { $response['effective_url'] = $response['transfer_stats']['url']; } if (!empty($headers)) { $startLine = explode(' ', array_shift($headers), 3); $headerList = Core::headersFromLines($headers); $response['headers'] = $headerList; $response['version'] = isset($startLine[0]) ? substr($startLine[0], 5) : null; $response['status'] = isset($startLine[1]) ? (int) $startLine[1] : null; $response['reason'] = isset($startLine[2]) ? $startLine[2] : null; $response['body'] = $body; Core::rewindBody($response); } return !empty($response['curl']['errno']) || !isset($response['status']) ? self::createErrorResponse($handler, $request, $response) : $response; } private static function createErrorResponse( callable $handler, array $request, array $response ) { static $connectionErrors = [ CURLE_OPERATION_TIMEOUTED => true, CURLE_COULDNT_RESOLVE_HOST => true, CURLE_COULDNT_CONNECT => true, CURLE_SSL_CONNECT_ERROR => true, CURLE_GOT_NOTHING => true, ]; // Retry when nothing is present or when curl failed to rewind. if (!isset($response['err_message']) && (empty($response['curl']['errno']) || $response['curl']['errno'] == 65) ) { return self::retryFailedRewind($handler, $request, $response); } $message = isset($response['err_message']) ? $response['err_message'] : sprintf('cURL error %s: %s', $response['curl']['errno'], isset($response['curl']['error']) ? $response['curl']['error'] : 'See http://curl.haxx.se/libcurl/c/libcurl-errors.html'); $error = isset($response['curl']['errno']) && isset($connectionErrors[$response['curl']['errno']]) ? new ConnectException($message) : new RingException($message); return $response + [ 'status' => null, 'reason' => null, 'body' => null, 'headers' => [], 'error' => $error, ]; } private function getOutputBody(array $request, array &$options) { // Determine where the body of the response (if any) will be streamed. if (isset($options[CURLOPT_WRITEFUNCTION])) { return $request['client']['save_to']; } if (isset($options[CURLOPT_FILE])) { return $options[CURLOPT_FILE]; } if ($request['http_method'] != 'HEAD') { // Create a default body if one was not provided return $options[CURLOPT_FILE] = fopen('php://temp', 'w+'); } return null; } private function getDefaultOptions(array $request, array &$headers) { $url = Core::url($request); $startingResponse = false; $options = [ '_headers' => $request['headers'], CURLOPT_CUSTOMREQUEST => $request['http_method'], CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => false, CURLOPT_HEADER => false, CURLOPT_CONNECTTIMEOUT => 150, CURLOPT_HEADERFUNCTION => function ($ch, $h) use (&$headers, &$startingResponse) { $value = trim($h); if ($value === '') { $startingResponse = true; } elseif ($startingResponse) { $startingResponse = false; $headers = [$value]; } else { $headers[] = $value; } return strlen($h); }, ]; if (isset($request['version'])) { if ($request['version'] == 2.0) { $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0; } else if ($request['version'] == 1.1) { $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1; } else { $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; } } if (defined('CURLOPT_PROTOCOLS')) { $options[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; } return $options; } private function applyMethod(array $request, array &$options) { if (isset($request['body'])) { $this->applyBody($request, $options); return; } switch ($request['http_method']) { case 'PUT': case 'POST': // See http://tools.ietf.org/html/rfc7230#section-3.3.2 if (!Core::hasHeader($request, 'Content-Length')) { $options[CURLOPT_HTTPHEADER][] = 'Content-Length: 0'; } break; case 'HEAD': $options[CURLOPT_NOBODY] = true; unset( $options[CURLOPT_WRITEFUNCTION], $options[CURLOPT_READFUNCTION], $options[CURLOPT_FILE], $options[CURLOPT_INFILE] ); } } private function applyBody(array $request, array &$options) { $contentLength = Core::firstHeader($request, 'Content-Length'); $size = $contentLength !== null ? (int) $contentLength : null; // Send the body as a string if the size is less than 1MB OR if the // [client][curl][body_as_string] request value is set. if (($size !== null && $size < 1000000) || isset($request['client']['curl']['body_as_string']) || is_string($request['body']) ) { $options[CURLOPT_POSTFIELDS] = Core::body($request); // Don't duplicate the Content-Length header $this->removeHeader('Content-Length', $options); $this->removeHeader('Transfer-Encoding', $options); } else { $options[CURLOPT_UPLOAD] = true; if ($size !== null) { // Let cURL handle setting the Content-Length header $options[CURLOPT_INFILESIZE] = $size; $this->removeHeader('Content-Length', $options); } $this->addStreamingBody($request, $options); } // If the Expect header is not present, prevent curl from adding it if (!Core::hasHeader($request, 'Expect')) { $options[CURLOPT_HTTPHEADER][] = 'Expect:'; } // cURL sometimes adds a content-type by default. Prevent this. if (!Core::hasHeader($request, 'Content-Type')) { $options[CURLOPT_HTTPHEADER][] = 'Content-Type:'; } } private function addStreamingBody(array $request, array &$options) { $body = $request['body']; if ($body instanceof StreamInterface) { $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) { return (string) $body->read($length); }; if (!isset($options[CURLOPT_INFILESIZE])) { if ($size = $body->getSize()) { $options[CURLOPT_INFILESIZE] = $size; } } } elseif (is_resource($body)) { $options[CURLOPT_INFILE] = $body; } elseif ($body instanceof \Iterator) { $buf = ''; $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body, &$buf) { if ($body->valid()) { $buf .= $body->current(); $body->next(); } $result = (string) substr($buf, 0, $length); $buf = substr($buf, $length); return $result; }; } else { throw new \InvalidArgumentException('Invalid request body provided'); } } private function applyHeaders(array $request, array &$options) { foreach ($options['_headers'] as $name => $values) { foreach ($values as $value) { $options[CURLOPT_HTTPHEADER][] = "$name: $value"; } } // Remove the Accept header if one was not set if (!Core::hasHeader($request, 'Accept')) { $options[CURLOPT_HTTPHEADER][] = 'Accept:'; } } /** * Takes an array of curl options specified in the 'curl' option of a * request's configuration array and maps them to CURLOPT_* options. * * This method is only called when a request has a 'curl' config setting. * * @param array $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) { $curlOptions = []; foreach ($config as $key => $value) { if (is_int($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; } } } /** * Applies an array of request client options to a the options array. * * This method uses a large switch rather than double-dispatch to save on * high overhead of calling functions in PHP. */ private function applyHandlerOptions(array $request, array &$options) { foreach ($request['client'] as $key => $value) { switch ($key) { // Violating PSR-4 to provide more room. case 'verify': if ($value === false) { unset($options[CURLOPT_CAINFO]); $options[CURLOPT_SSL_VERIFYHOST] = 0; $options[CURLOPT_SSL_VERIFYPEER] = false; continue 2; } $options[CURLOPT_SSL_VERIFYHOST] = 2; $options[CURLOPT_SSL_VERIFYPEER] = true; if (is_string($value)) { $options[CURLOPT_CAINFO] = $value; if (!file_exists($value)) { throw new \InvalidArgumentException( "SSL CA bundle not found: $value" ); } } break; case 'decode_content': if ($value === false) { continue 2; } $accept = Core::firstHeader($request, 'Accept-Encoding'); if ($accept) { $options[CURLOPT_ENCODING] = $accept; } else { $options[CURLOPT_ENCODING] = ''; // Don't let curl send the header over the wire $options[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; } break; case 'save_to': if (is_string($value)) { if (!is_dir(dirname($value))) { throw new \RuntimeException(sprintf( 'Directory %s does not exist for save_to value of %s', dirname($value), $value )); } $value = new LazyOpenStream($value, 'w+'); } if ($value instanceof StreamInterface) { $options[CURLOPT_WRITEFUNCTION] = function ($ch, $write) use ($value) { return $value->write($write); }; } elseif (is_resource($value)) { $options[CURLOPT_FILE] = $value; } else { throw new \InvalidArgumentException('save_to must be a ' . 'GuzzleHttp\Stream\StreamInterface or resource'); } break; case 'timeout': if (defined('CURLOPT_TIMEOUT_MS')) { $options[CURLOPT_TIMEOUT_MS] = $value * 1000; } else { $options[CURLOPT_TIMEOUT] = $value; } break; case 'connect_timeout': if (defined('CURLOPT_CONNECTTIMEOUT_MS')) { $options[CURLOPT_CONNECTTIMEOUT_MS] = $value * 1000; } else { $options[CURLOPT_CONNECTTIMEOUT] = $value; } break; case 'proxy': if (!is_array($value)) { $options[CURLOPT_PROXY] = $value; } elseif (isset($request['scheme'])) { $scheme = $request['scheme']; if (isset($value[$scheme])) { $options[CURLOPT_PROXY] = $value[$scheme]; } } break; case 'cert': if (is_array($value)) { $options[CURLOPT_SSLCERTPASSWD] = $value[1]; $value = $value[0]; } if (!file_exists($value)) { throw new \InvalidArgumentException( "SSL certificate not found: {$value}" ); } $options[CURLOPT_SSLCERT] = $value; break; case 'ssl_key': if (is_array($value)) { $options[CURLOPT_SSLKEYPASSWD] = $value[1]; $value = $value[0]; } if (!file_exists($value)) { throw new \InvalidArgumentException( "SSL private key not found: {$value}" ); } $options[CURLOPT_SSLKEY] = $value; break; case 'progress': if (!is_callable($value)) { throw new \InvalidArgumentException( 'progress client option must be callable' ); } $options[CURLOPT_NOPROGRESS] = false; $options[CURLOPT_PROGRESSFUNCTION] = function () use ($value) { $args = func_get_args(); // PHP 5.5 pushed the handle onto the start of the args if (is_resource($args[0])) { array_shift($args); } call_user_func_array($value, $args); }; break; case 'debug': if ($value) { $options[CURLOPT_STDERR] = Core::getDebugResource($value); $options[CURLOPT_VERBOSE] = true; } break; } } } /** * This function ensures that a response was set on a transaction. If one * was not set, then the request is retried if possible. This error * typically means you are sending a payload, curl encountered a * "Connection died, retrying a fresh connect" error, tried to rewind the * stream, and then encountered a "necessary data rewind wasn't possible" * error, causing the request to be sent through curl_multi_info_read() * without an error status. */ private static function retryFailedRewind( callable $handler, array $request, array $response ) { // If there is no body, then there is some other kind of issue. This // is weird and should probably never happen. if (!isset($request['body'])) { $response['err_message'] = 'No response was received for a request ' . 'with no body. This could mean that you are saturating your ' . 'network.'; return self::createErrorResponse($handler, $request, $response); } if (!Core::rewindBody($request)) { $response['err_message'] = 'The connection unexpectedly failed ' . 'without providing an error. The request would have been ' . 'retried, but attempting to rewind the request body failed.'; return self::createErrorResponse($handler, $request, $response); } // Retry no more than 3 times before giving up. if (!isset($request['curl']['retries'])) { $request['curl']['retries'] = 1; } elseif ($request['curl']['retries'] == 2) { $response['err_message'] = 'The cURL request was retried 3 times ' . 'and did no succeed. cURL was unable to rewind the body of ' . 'the request and subsequent retries resulted in the same ' . 'error. Turn on the debug option to see what went wrong. ' . 'See https://bugs.php.net/bug.php?id=47204 for more information.'; return self::createErrorResponse($handler, $request, $response); } else { $request['curl']['retries']++; } return $handler($request); } } src/Client/CurlMultiHandler.php000077700000016605151323632020012514 0ustar00<?php namespace GuzzleHttp\Ring\Client; use GuzzleHttp\Ring\Future\FutureArray; use React\Promise\Deferred; /** * Returns an asynchronous response using curl_multi_* functions. * * This handler supports future responses and the "delay" request client * option that can be used to delay before sending a request. * * When using the CurlMultiHandler, custom curl options can be specified as an * associative array of curl option constants mapping to values in the * **curl** key of the "client" key of the request. * * @property resource $_mh Internal use only. Lazy loaded multi-handle. */ class CurlMultiHandler { /** @var callable */ private $factory; private $selectTimeout; private $active; private $handles = []; private $delays = []; private $maxHandles; /** * This handler accepts the following options: * * - mh: An optional curl_multi resource * - handle_factory: An optional callable used to generate curl handle * resources. the callable accepts a request hash and returns an array * of the handle, headers file resource, and the body resource. * - select_timeout: Optional timeout (in seconds) to block before timing * out while selecting curl handles. Defaults to 1 second. * - max_handles: Optional integer representing the maximum number of * open requests. When this number is reached, the queued futures are * flushed. * * @param array $options */ public function __construct(array $options = []) { if (isset($options['mh'])) { $this->_mh = $options['mh']; } $this->factory = isset($options['handle_factory']) ? $options['handle_factory'] : new CurlFactory(); $this->selectTimeout = isset($options['select_timeout']) ? $options['select_timeout'] : 1; $this->maxHandles = isset($options['max_handles']) ? $options['max_handles'] : 100; } public function __get($name) { if ($name === '_mh') { return $this->_mh = curl_multi_init(); } throw new \BadMethodCallException(); } public function __destruct() { // Finish any open connections before terminating the script. if ($this->handles) { $this->execute(); } if (isset($this->_mh)) { curl_multi_close($this->_mh); unset($this->_mh); } } public function __invoke(array $request) { $factory = $this->factory; $result = $factory($request); $entry = [ 'request' => $request, 'response' => [], 'handle' => $result[0], 'headers' => &$result[1], 'body' => $result[2], 'deferred' => new Deferred(), ]; $id = (int) $result[0]; $future = new FutureArray( $entry['deferred']->promise(), [$this, 'execute'], function () use ($id) { return $this->cancel($id); } ); $this->addRequest($entry); // Transfer outstanding requests if there are too many open handles. if (count($this->handles) >= $this->maxHandles) { $this->execute(); } return $future; } /** * Runs until all outstanding connections have completed. */ public function execute() { do { if ($this->active && curl_multi_select($this->_mh, $this->selectTimeout) === -1 ) { // Perform a usleep if a select returns -1. // See: https://bugs.php.net/bug.php?id=61141 usleep(250); } // Add any delayed futures if needed. if ($this->delays) { $this->addDelays(); } do { $mrc = curl_multi_exec($this->_mh, $this->active); } while ($mrc === CURLM_CALL_MULTI_PERFORM); $this->processMessages(); // If there are delays but no transfers, then sleep for a bit. if (!$this->active && $this->delays) { usleep(500); } } while ($this->active || $this->handles); } private function addRequest(array &$entry) { $id = (int) $entry['handle']; $this->handles[$id] = $entry; // If the request is a delay, then add the reques to the curl multi // pool only after the specified delay. if (isset($entry['request']['client']['delay'])) { $this->delays[$id] = microtime(true) + ($entry['request']['client']['delay'] / 1000); } elseif (empty($entry['request']['future'])) { curl_multi_add_handle($this->_mh, $entry['handle']); } else { curl_multi_add_handle($this->_mh, $entry['handle']); // "lazy" futures are only sent once the pool has many requests. if ($entry['request']['future'] !== 'lazy') { do { $mrc = curl_multi_exec($this->_mh, $this->active); } while ($mrc === CURLM_CALL_MULTI_PERFORM); $this->processMessages(); } } } private function removeProcessed($id) { if (isset($this->handles[$id])) { curl_multi_remove_handle( $this->_mh, $this->handles[$id]['handle'] ); curl_close($this->handles[$id]['handle']); unset($this->handles[$id], $this->delays[$id]); } } /** * Cancels a handle from sending and removes references to it. * * @param int $id Handle ID to cancel and remove. * * @return bool True on success, false on failure. */ private function cancel($id) { // Cannot cancel if it has been processed. if (!isset($this->handles[$id])) { return false; } $handle = $this->handles[$id]['handle']; unset($this->delays[$id], $this->handles[$id]); curl_multi_remove_handle($this->_mh, $handle); curl_close($handle); return true; } private function addDelays() { $currentTime = microtime(true); foreach ($this->delays as $id => $delay) { if ($currentTime >= $delay) { unset($this->delays[$id]); curl_multi_add_handle( $this->_mh, $this->handles[$id]['handle'] ); } } } private function processMessages() { while ($done = curl_multi_info_read($this->_mh)) { $id = (int) $done['handle']; if (!isset($this->handles[$id])) { // Probably was cancelled. continue; } $entry = $this->handles[$id]; $entry['response']['transfer_stats'] = curl_getinfo($done['handle']); if ($done['result'] !== CURLM_OK) { $entry['response']['curl']['errno'] = $done['result']; $entry['response']['curl']['error'] = curl_error($done['handle']); } $result = CurlFactory::createResponse( $this, $entry['request'], $entry['response'], $entry['headers'], $entry['body'] ); $this->removeProcessed($id); $entry['deferred']->resolve($result); } } } src/Client/CurlHandler.php000077700000007743151323632020011504 0ustar00<?php namespace GuzzleHttp\Ring\Client; use GuzzleHttp\Ring\Future\CompletedFutureArray; use GuzzleHttp\Ring\Core; /** * HTTP handler that uses cURL easy handles as a transport layer. * * Requires PHP 5.5+ * * 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 callable */ private $factory; /** @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: * * - factory: Optional callable factory used to create cURL handles. * The callable is passed a request hash when invoked, and returns an * array of the curl handle, headers resource, and body resource. * - max_handles: Maximum number of idle handles (defaults to 5). * * @param array $options Array of options to use with the handler */ public function __construct(array $options = []) { $this->handles = $this->ownedHandles = []; $this->factory = isset($options['handle_factory']) ? $options['handle_factory'] : new CurlFactory(); $this->maxHandles = isset($options['max_handles']) ? $options['max_handles'] : 5; } public function __destruct() { foreach ($this->handles as $handle) { if (is_resource($handle)) { curl_close($handle); } } } /** * @param array $request * * @return CompletedFutureArray */ public function __invoke(array $request) { return new CompletedFutureArray( $this->_invokeAsArray($request) ); } /** * @internal * * @param array $request * * @return array */ public function _invokeAsArray(array $request) { $factory = $this->factory; // Ensure headers are by reference. They're updated elsewhere. $result = $factory($request, $this->checkoutEasyHandle()); $h = $result[0]; $hd =& $result[1]; $bd = $result[2]; Core::doSleep($request); curl_exec($h); $response = ['transfer_stats' => curl_getinfo($h)]; $response['curl']['error'] = curl_error($h); $response['curl']['errno'] = curl_errno($h); $response['transfer_stats'] = array_merge($response['transfer_stats'], $response['curl']); $this->releaseEasyHandle($h); return CurlFactory::createResponse([$this, '_invokeAsArray'], $request, $response, $hd, $bd); } private function checkoutEasyHandle() { // Find an unused handle in the cache if (false !== ($key = array_search(false, $this->ownedHandles, true))) { $this->ownedHandles[$key] = true; return $this->handles[$key]; } // Add a new handle $handle = curl_init(); $id = (int) $handle; $this->handles[$id] = $handle; $this->ownedHandles[$id] = true; return $handle; } private function releaseEasyHandle($handle) { $id = (int) $handle; if (count($this->ownedHandles) > $this->maxHandles) { curl_close($this->handles[$id]); unset($this->handles[$id], $this->ownedHandles[$id]); } else { // curl_reset doesn't clear these out for some reason static $unsetValues = [ CURLOPT_HEADERFUNCTION => null, CURLOPT_WRITEFUNCTION => null, CURLOPT_READFUNCTION => null, CURLOPT_PROGRESSFUNCTION => null, ]; curl_setopt_array($handle, $unsetValues); curl_reset($handle); $this->ownedHandles[$id] = false; } } } src/Client/Middleware.php000077700000003613151323632020011346 0ustar00<?php namespace GuzzleHttp\Ring\Client; /** * Provides basic middleware wrappers. * * If a middleware is more complex than a few lines of code, then it should * be implemented in a class rather than a static method. */ class Middleware { /** * Sends future requests to a future compatible handler while sending all * other requests to a default handler. * * When the "future" option is not provided on a request, any future responses * are automatically converted to synchronous responses and block. * * @param callable $default Handler used for non-streaming responses * @param callable $future Handler used for future responses * * @return callable Returns the composed handler. */ public static function wrapFuture( callable $default, callable $future ) { return function (array $request) use ($default, $future) { return empty($request['client']['future']) ? $default($request) : $future($request); }; } /** * Sends streaming requests to a streaming compatible handler while sendin * 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 (array $request) use ($default, $streaming) { return empty($request['client']['stream']) ? $default($request) : $streaming($request); }; } } src/Client/.htaccess000077700000000177151323632020010360 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>src/Core.php000077700000025407151323632020006750 0ustar00<?php namespace GuzzleHttp\Ring; use GuzzleHttp\Stream\StreamInterface; use GuzzleHttp\Ring\Future\FutureArrayInterface; use GuzzleHttp\Ring\Future\FutureArray; /** * Provides core functionality of Ring handlers and middleware. */ class Core { /** * Returns a function that calls all of the provided functions, in order, * passing the arguments provided to the composed function to each function. * * @param callable[] $functions Array of functions to proxy to. * * @return callable */ public static function callArray(array $functions) { return function () use ($functions) { $args = func_get_args(); foreach ($functions as $fn) { call_user_func_array($fn, $args); } }; } /** * Gets an array of header line values from a message for a specific header * * This method searches through the "headers" key of a message for a header * using a case-insensitive search. * * @param array $message Request or response hash. * @param string $header Header to retrieve * * @return array */ public static function headerLines($message, $header) { $result = []; if (!empty($message['headers'])) { foreach ($message['headers'] as $name => $value) { if (!strcasecmp($name, $header)) { $result = array_merge($result, $value); } } } return $result; } /** * Gets a header value from a message as a string or null * * This method searches through the "headers" key of a message for a header * using a case-insensitive search. The lines of the header are imploded * using commas into a single string return value. * * @param array $message Request or response hash. * @param string $header Header to retrieve * * @return string|null Returns the header string if found, or null if not. */ public static function header($message, $header) { $match = self::headerLines($message, $header); return $match ? implode(', ', $match) : null; } /** * Returns the first header value from a message as a string or null. If * a header line contains multiple values separated by a comma, then this * function will return the first value in the list. * * @param array $message Request or response hash. * @param string $header Header to retrieve * * @return string|null Returns the value as a string if found. */ public static function firstHeader($message, $header) { if (!empty($message['headers'])) { foreach ($message['headers'] as $name => $value) { if (!strcasecmp($name, $header)) { // Return the match itself if it is a single value. $pos = strpos($value[0], ','); return $pos ? substr($value[0], 0, $pos) : $value[0]; } } } return null; } /** * Returns true if a message has the provided case-insensitive header. * * @param array $message Request or response hash. * @param string $header Header to check * * @return bool */ public static function hasHeader($message, $header) { if (!empty($message['headers'])) { foreach ($message['headers'] as $name => $value) { if (!strcasecmp($name, $header)) { return true; } } } return false; } /** * 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 */ public static function headersFromLines($lines) { $headers = []; foreach ($lines as $line) { $parts = explode(':', $line, 2); $headers[trim($parts[0])][] = isset($parts[1]) ? trim($parts[1]) : null; } return $headers; } /** * Removes a header from a message using a case-insensitive comparison. * * @param array $message Message that contains 'headers' * @param string $header Header to remove * * @return array */ public static function removeHeader(array $message, $header) { if (isset($message['headers'])) { foreach (array_keys($message['headers']) as $key) { if (!strcasecmp($header, $key)) { unset($message['headers'][$key]); } } } return $message; } /** * Replaces any existing case insensitive headers with the given value. * * @param array $message Message that contains 'headers' * @param string $header Header to set. * @param array $value Value to set. * * @return array */ public static function setHeader(array $message, $header, array $value) { $message = self::removeHeader($message, $header); $message['headers'][$header] = $value; return $message; } /** * Creates a URL string from a request. * * If the "url" key is present on the request, it is returned, otherwise * the url is built up based on the scheme, host, uri, and query_string * request values. * * @param array $request Request to get the URL from * * @return string Returns the request URL as a string. * @throws \InvalidArgumentException if no Host header is present. */ public static function url(array $request) { if (isset($request['url'])) { return $request['url']; } $uri = (isset($request['scheme']) ? $request['scheme'] : 'http') . '://'; if ($host = self::header($request, 'host')) { $uri .= $host; } else { throw new \InvalidArgumentException('No Host header was provided'); } if (isset($request['uri'])) { $uri .= $request['uri']; } if (isset($request['query_string'])) { $uri .= '?' . $request['query_string']; } return $uri; } /** * Reads the body of a message into a string. * * @param array|FutureArrayInterface $message Array containing a "body" key * * @return null|string Returns the body as a string or null if not set. * @throws \InvalidArgumentException if a request body is invalid. */ public static function body($message) { if (!isset($message['body'])) { return null; } if ($message['body'] instanceof StreamInterface) { return (string) $message['body']; } switch (gettype($message['body'])) { case 'string': return $message['body']; case 'resource': return stream_get_contents($message['body']); case 'object': if ($message['body'] instanceof \Iterator) { return implode('', iterator_to_array($message['body'])); } elseif (method_exists($message['body'], '__toString')) { return (string) $message['body']; } default: throw new \InvalidArgumentException('Invalid request body: ' . self::describeType($message['body'])); } } /** * Rewind the body of the provided message if possible. * * @param array $message Message that contains a 'body' field. * * @return bool Returns true on success, false on failure */ public static function rewindBody($message) { if ($message['body'] instanceof StreamInterface) { return $message['body']->seek(0); } if ($message['body'] instanceof \Generator) { return false; } if ($message['body'] instanceof \Iterator) { $message['body']->rewind(); return true; } if (is_resource($message['body'])) { return rewind($message['body']); } return is_string($message['body']) || (is_object($message['body']) && method_exists($message['body'], '__toString')); } /** * 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. */ public static function describeType($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())); } } /** * Sleep for the specified amount of time specified in the request's * ['client']['delay'] option if present. * * This function should only be used when a non-blocking sleep is not * possible. * * @param array $request Request to sleep */ public static function doSleep(array $request) { if (isset($request['client']['delay'])) { usleep($request['client']['delay'] * 1000); } } /** * Returns a proxied future that modifies the dereferenced value of another * future using a promise. * * @param FutureArrayInterface $future Future to wrap with a new future * @param callable $onFulfilled Invoked when the future fulfilled * @param callable $onRejected Invoked when the future rejected * @param callable $onProgress Invoked when the future progresses * * @return FutureArray */ public static function proxy( FutureArrayInterface $future, callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null ) { return new FutureArray( $future->then($onFulfilled, $onRejected, $onProgress), [$future, 'wait'], [$future, 'cancel'] ); } /** * Returns a debug stream based on the provided variable. * * @param mixed $value Optional value * * @return resource */ public static function getDebugResource($value = null) { if (is_resource($value)) { return $value; } elseif (defined('STDOUT')) { return STDOUT; } else { return fopen('php://output', 'w'); } } } src/Exception/CancelledFutureAccessException.php000077700000000210151323632020016045 0ustar00<?php namespace GuzzleHttp\Ring\Exception; class CancelledFutureAccessException extends RingException implements CancelledException {} src/Exception/ConnectException.php000077700000000212151323632020013251 0ustar00<?php namespace GuzzleHttp\Ring\Exception; /** * Occurs when the connection failed. */ class ConnectException extends RingException {} src/Exception/CancelledException.php000077700000000202151323632020013531 0ustar00<?php namespace GuzzleHttp\Ring\Exception; /** * Marker interface for cancelled exceptions. */ interface CancelledException {} src/Exception/RingException.php000077700000000136151323632020012564 0ustar00<?php namespace GuzzleHttp\Ring\Exception; class RingException extends \RuntimeException {}; src/Exception/.htaccess000077700000000177151323632020011100 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>src/.htaccess000077700000000177151323632020007142 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>src/Future/FutureInterface.php000077700000002651151323632020012421 0ustar00<?php namespace GuzzleHttp\Ring\Future; use React\Promise\PromiseInterface; use React\Promise\PromisorInterface; /** * Represents the result of a computation that may not have completed yet. * * You can use the future in a blocking manner using the wait() function, or * you can use a promise from the future to receive the result when the future * has been resolved. * * When the future is dereferenced using wait(), the result of the computation * is cached and returned for subsequent calls to wait(). If the result of the * computation has not yet completed when wait() is called, the call to wait() * will block until the future has completed. */ interface FutureInterface extends PromiseInterface, PromisorInterface { /** * Returns the result of the future either from cache or by blocking until * it is complete. * * This method must block until the future has a result or is cancelled. * Throwing an exception in the wait() method will mark the future as * realized and will throw the exception each time wait() is called. * Throwing an instance of GuzzleHttp\Ring\CancelledException will mark * the future as realized, will not throw immediately, but will throw the * exception if the future's wait() method is called again. * * @return mixed */ public function wait(); /** * Cancels the future, if possible. */ public function cancel(); } src/Future/BaseFutureTrait.php000077700000006734151323632020012405 0ustar00<?php namespace GuzzleHttp\Ring\Future; use GuzzleHttp\Ring\Exception\CancelledFutureAccessException; use GuzzleHttp\Ring\Exception\RingException; use React\Promise\PromiseInterface; /** * Implements common future functionality built on top of promises. */ trait BaseFutureTrait { /** @var callable */ private $waitfn; /** @var callable */ private $cancelfn; /** @var PromiseInterface */ private $wrappedPromise; /** @var \Exception Error encountered. */ private $error; /** @var mixed Result of the future */ private $result; private $isRealized = false; /** * @param PromiseInterface $promise Promise to shadow with the future. * @param callable $wait Function that blocks until the deferred * computation has been resolved. This * function MUST resolve the deferred value * associated with the supplied promise. * @param callable $cancel If possible and reasonable, provide a * function that can be used to cancel the * future from completing. */ public function __construct( PromiseInterface $promise, callable $wait = null, callable $cancel = null ) { $this->wrappedPromise = $promise; $this->waitfn = $wait; $this->cancelfn = $cancel; } public function wait() { if (!$this->isRealized) { $this->addShadow(); if (!$this->isRealized && $this->waitfn) { $this->invokeWait(); } if (!$this->isRealized) { $this->error = new RingException('Waiting did not resolve future'); } } if ($this->error) { throw $this->error; } return $this->result; } public function promise() { return $this->wrappedPromise; } public function then( callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null ) { return $this->wrappedPromise->then($onFulfilled, $onRejected, $onProgress); } public function cancel() { if (!$this->isRealized) { $cancelfn = $this->cancelfn; $this->waitfn = $this->cancelfn = null; $this->isRealized = true; $this->error = new CancelledFutureAccessException(); if ($cancelfn) { $cancelfn($this); } } } private function addShadow() { // Get the result and error when the promise is resolved. Note that // calling this function might trigger the resolution immediately. $this->wrappedPromise->then( function ($value) { $this->isRealized = true; $this->result = $value; $this->waitfn = $this->cancelfn = null; }, function ($error) { $this->isRealized = true; $this->error = $error; $this->waitfn = $this->cancelfn = null; } ); } private function invokeWait() { try { $wait = $this->waitfn; $this->waitfn = null; $wait(); } catch (\Exception $e) { // Defer can throw to reject. $this->error = $e; $this->isRealized = true; } } } src/Future/CompletedFutureValue.php000077700000002541151323632020013430 0ustar00<?php namespace GuzzleHttp\Ring\Future; use React\Promise\FulfilledPromise; use React\Promise\RejectedPromise; /** * Represents a future value that has been resolved or rejected. */ class CompletedFutureValue implements FutureInterface { protected $result; protected $error; private $cachedPromise; /** * @param mixed $result Resolved result * @param \Exception $e Error. Pass a GuzzleHttp\Ring\Exception\CancelledFutureAccessException * to mark the future as cancelled. */ public function __construct($result, \Exception $e = null) { $this->result = $result; $this->error = $e; } public function wait() { if ($this->error) { throw $this->error; } return $this->result; } public function cancel() {} public function promise() { if (!$this->cachedPromise) { $this->cachedPromise = $this->error ? new RejectedPromise($this->error) : new FulfilledPromise($this->result); } return $this->cachedPromise; } public function then( callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null ) { return $this->promise()->then($onFulfilled, $onRejected, $onProgress); } } src/Future/FutureArray.php000077700000001376151323632020011602 0ustar00<?php namespace GuzzleHttp\Ring\Future; /** * Represents a future array value that when dereferenced returns an array. */ class FutureArray implements FutureArrayInterface { use MagicFutureTrait; public function offsetExists($offset) { return isset($this->_value[$offset]); } public function offsetGet($offset) { return $this->_value[$offset]; } public function offsetSet($offset, $value) { $this->_value[$offset] = $value; } public function offsetUnset($offset) { unset($this->_value[$offset]); } public function count() { return count($this->_value); } public function getIterator() { return new \ArrayIterator($this->_value); } } src/Future/CompletedFutureArray.php000077700000001542151323632020013432 0ustar00<?php namespace GuzzleHttp\Ring\Future; /** * Represents a future array that has been completed successfully. */ class CompletedFutureArray extends CompletedFutureValue implements FutureArrayInterface { public function __construct(array $result) { parent::__construct($result); } public function offsetExists($offset) { return isset($this->result[$offset]); } public function offsetGet($offset) { return $this->result[$offset]; } public function offsetSet($offset, $value) { $this->result[$offset] = $value; } public function offsetUnset($offset) { unset($this->result[$offset]); } public function count() { return count($this->result); } public function getIterator() { return new \ArrayIterator($this->result); } } src/Future/FutureArrayInterface.php000077700000000325151323632020013414 0ustar00<?php namespace GuzzleHttp\Ring\Future; /** * Future that provides array-like access. */ interface FutureArrayInterface extends FutureInterface, \ArrayAccess, \Countable, \IteratorAggregate {}; src/Future/MagicFutureTrait.php000077700000001613151323632020012542 0ustar00<?php namespace GuzzleHttp\Ring\Future; /** * Implements common future functionality that is triggered when the result * property is accessed via a magic __get method. * * @property mixed $_value Actual data used by the future. Accessing this * property will cause the future to block if needed. */ trait MagicFutureTrait { use BaseFutureTrait; /** * This function handles retrieving the dereferenced result when requested. * * @param string $name Should always be "data" or an exception is thrown. * * @return mixed Returns the dereferenced data. * @throws \RuntimeException * @throws \GuzzleHttp\Ring\Exception\CancelledException */ public function __get($name) { if ($name !== '_value') { throw new \RuntimeException("Class has no {$name} property"); } return $this->_value = $this->wait(); } } src/Future/.htaccess000077700000000177151323632020010414 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>src/Future/FutureValue.php000077700000000446151323632020011575 0ustar00<?php namespace GuzzleHttp\Ring\Future; /** * Represents a future value that responds to wait() to retrieve the promised * value, but can also return promises that are delivered the value when it is * available. */ class FutureValue implements FutureInterface { use BaseFutureTrait; } .gitignore000077700000000063151323632020006537 0ustar00vendor build/artifacts/ composer.lock docs/_build/ LICENSE000077700000002127151323632020005557 0ustar00Copyright (c) 2014 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. .travis.yml000077700000001621151323632020006661 0ustar00language: php cache: directories: - $HOME/.composer/cache/files php: - 5.4 - 5.5 - 5.6 - 7.0 - 7.1 - 7.2 - hhvm - nightly env: global: - TEST_COMMAND="composer test" matrix: allow_failures: - php: hhvm - php: nightly fast_finish: true include: - php: 5.4 env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" before_install: - if [[ $COVERAGE != true ]]; then phpenv config-rm xdebug.ini || true; fi install: # To be removed when this issue will be resolved: https://github.com/composer/composer/issues/5355 - if [[ "$COMPOSER_FLAGS" == *"--prefer-lowest"* ]]; then travis_retry composer update --prefer-dist --no-interaction --prefer-stable --quiet; fi - travis_retry composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction before_script: - ~/.nvm/nvm.sh install v0.6.14 - ~/.nvm/nvm.sh run v0.6.14 script: - $TEST_COMMAND .editorconfig000077700000000271151323632020007225 0ustar00root = true [*] charset = utf-8 end_of_line = lf indent_size = 4 indent_style = space insert_final_newline = true trim_trailing_whitespace = true [{Makefile,*.mk}] indent_style = tab Makefile000077700000001603151323632020006210 0ustar00all: clean coverage docs docs: cd docs && make html view-docs: open docs/_build/html/index.html start-server: stop-server node tests/Client/server.js &> /dev/null & stop-server: @PID=$(shell ps axo pid,command \ | grep 'tests/Client/server.js' \ | grep -v grep \ | cut -f 1 -d " "\ ) && [ -n "$$PID" ] && kill $$PID || true test: start-server vendor/bin/phpunit $(TEST) $(MAKE) stop-server coverage: start-server vendor/bin/phpunit --coverage-html=build/artifacts/coverage $(TEST) $(MAKE) stop-server view-coverage: open build/artifacts/coverage/index.html clean: rm -rf build/artifacts/* cd docs && make clean tag: $(if $(TAG),,$(error TAG is not defined. Pass via "make tag TAG=4.2.1")) @echo Tagging $(TAG) chag update -m '$(TAG) ()' git add -A git commit -m '$(TAG) release' chag tag perf: start-server php tests/perf.php $(MAKE) stop-server .PHONY: docs CHANGELOG.md000077700000005474151323632020006373 0ustar00# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [1.1.1] - 2018-07-31 ### Fixed - `continue` keyword usage on PHP 7.3 ## [1.1.0] - 2015-05-19 ### Added - Added `CURL_HTTP_VERSION_2_0` ### Changed - The PHP stream wrapper handler now sets `allow_self_signed` to `false` to match the cURL handler when `verify` is set to `true` or a certificate file. - Ensuring that a directory exists before using the `save_to` option. - Response protocol version is now correctly extracted from a response. ### Fixed - Fixed a bug in which the result of `CurlFactory::retryFailedRewind` did not return an array. ## [1.0.7] - 2015-03-29 ### Fixed - PHP 7 fixes. ## [1.0.6] - 2015-02-26 ### Changed - The multi handle of the CurlMultiHandler is now created lazily. ### Fixed - Bug fix: futures now extend from React's PromiseInterface to ensure that they are properly forwarded down the promise chain. ## [1.0.5] - 2014-12-10 ### Added - Adding more error information to PHP stream wrapper exceptions. - Added digest auth integration test support to test server. ## [1.0.4] - 2014-12-01 ### Added - Added support for older versions of cURL that do not have CURLOPT_TIMEOUT_MS. - Added a fix to the StreamHandler to return a `FutureArrayInterface` when an ### Changed - Setting debug to `false` does not enable debug output. error occurs. ## [1.0.3] - 2014-11-03 ### Fixed - Setting the `header` stream option as a string to be compatible with GAE. - Header parsing now ensures that header order is maintained in the parsed message. ## [1.0.2] - 2014-10-28 ### Fixed - Now correctly honoring a `version` option is supplied in a request. See https://github.com/guzzle/RingPHP/pull/8 ## [1.0.1] - 2014-10-26 ### Fixed - Fixed a header parsing issue with the `CurlHandler` and `CurlMultiHandler` that caused cURL requests with multiple responses to merge repsonses together (e.g., requests with digest authentication). ## 1.0.0 - 2014-10-12 - Initial release [Unreleased]: https://github.com/guzzle/RingPHP/compare/1.1.1...HEAD [1.1.1]: https://github.com/guzzle/RingPHP/compare/1.1.0...1.1.1 [1.1.0]: https://github.com/guzzle/RingPHP/compare/1.0.7...1.1.0 [1.0.7]: https://github.com/guzzle/RingPHP/compare/1.0.6...1.0.7 [1.0.6]: https://github.com/guzzle/RingPHP/compare/1.0.5...1.0.6 [1.0.5]: https://github.com/guzzle/RingPHP/compare/1.0.4...1.0.5 [1.0.4]: https://github.com/guzzle/RingPHP/compare/1.0.3...1.0.4 [1.0.3]: https://github.com/guzzle/RingPHP/compare/1.0.2...1.0.3 [1.0.2]: https://github.com/guzzle/RingPHP/compare/1.0.1...1.0.2 [1.0.1]: https://github.com/guzzle/RingPHP/compare/1.0.0...1.0.1 composer.json000077700000002001151323632020007263 0ustar00{ "name": "ezimuel/ringphp", "description": "Fork of guzzle/RingPHP (abandoned) to be used with elasticsearch-php", "license": "MIT", "authors": [ { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" } ], "require": { "php": ">=5.4.0", "ezimuel/guzzlestreams": "^3.0.1", "react/promise": "~2.0" }, "require-dev": { "ext-curl": "*", "phpunit/phpunit": "~4.0" }, "suggest": { "ext-curl": "Guzzle will use specific adapters if cURL is present" }, "autoload": { "psr-4": { "GuzzleHttp\\Ring\\": "src/" } }, "autoload-dev": { "psr-4": { "GuzzleHttp\\Tests\\Ring\\": "tests/" } }, "scripts": { "test": "make test", "test-ci": "make coverage" }, "extra": { "branch-alias": { "dev-master": "1.1-dev" } } } docs/testing.rst000077700000004057151323632020007715 0ustar00======= Testing ======= RingPHP tests client handlers using `PHPUnit <https://phpunit.de/>`_ and a built-in node.js web server. Running Tests ------------- First, install the dependencies using `Composer <https://getcomposer.org>`_. composer.phar install Next, run the unit tests using ``Make``. make test The tests are also run on Travis-CI on each commit: https://travis-ci.org/guzzle/guzzle-ring Test Server ----------- Testing client handlers usually involves actually sending HTTP requests. RingPHP provides a node.js web server that returns canned responses and keep a list of the requests that have been received. The server can then be queried to get a list of the requests that were sent by the client so that you can ensure that the client serialized and transferred requests as intended. The server keeps a list of queued responses and returns responses that are popped off of the queue as HTTP requests are received. When there are not more responses to serve, the server returns a 500 error response. The test server uses the ``GuzzleHttp\Tests\Ring\Client\Server`` class to control the server. .. code-block:: php use GuzzleHttp\Ring\Client\StreamHandler; use GuzzleHttp\Tests\Ring\Client\Server; // First return a 200 followed by a 404 response. Server::enqueue([ ['status' => 200], ['status' => 404] ]); $handler = new StreamHandler(); $response = $handler([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'uri' => '/' ]); assert(200 == $response['status']); $response = $handler([ 'http_method' => 'HEAD', 'headers' => ['host' => [Server::$host]], 'uri' => '/' ]); assert(404 == $response['status']); After requests have been sent, you can get a list of the requests as they were sent over the wire to ensure they were sent correctly. .. code-block:: php $received = Server::received(); assert('GET' == $received[0]['http_method']); assert('HEAD' == $received[1]['http_method']); docs/spec.rst000077700000027412151323632020007172 0ustar00============= Specification ============= RingPHP applications consist of handlers, requests, responses, and middleware. Handlers -------- Handlers are implemented as a PHP ``callable`` that accept a request array and return a response array (``GuzzleHttp\Ring\Future\FutureArrayInterface``). For example: .. code-block:: php use GuzzleHttp\Ring\Future\CompletedFutureArray; $mockHandler = function (array $request) { return new CompletedFutureArray([ 'status' => 200, 'headers' => ['X-Foo' => ['Bar']], 'body' => 'Hello!' ]); }; This handler returns the same response each time it is invoked. All RingPHP handlers must return a ``GuzzleHttp\Ring\Future\FutureArrayInterface``. Use ``GuzzleHttp\Ring\Future\CompletedFutureArray`` when returning a response that has already completed. Requests -------- A request array is a PHP associative array that contains the configuration settings need to send a request. .. code-block:: php $request = [ 'http_method' => 'GET', 'scheme' => 'http', 'uri' => '/', 'body' => 'hello!', 'client' => ['timeout' => 1.0], 'headers' => [ 'host' => ['httpbin.org'], 'X-Foo' => ['baz', 'bar'] ] ]; The request array contains the following key value pairs: request_method (string, required) The HTTP request method, must be all caps corresponding to a HTTP request method, such as ``GET`` or ``POST``. scheme (string) The transport protocol, must be one of ``http`` or ``https``. Defaults to ``http``. uri (string, required) The request URI excluding the query string. Must start with "/". query_string (string) The query string, if present (e.g., ``foo=bar``). version (string) HTTP protocol version. Defaults to ``1.1``. headers (required, array) Associative array of headers. Each key represents the header name. Each value contains an array of strings where each entry of the array SHOULD be sent over the wire on a separate header line. body (string, fopen resource, ``Iterator``, ``GuzzleHttp\Stream\StreamInterface``) The body of the request, if present. Can be a string, resource returned from fopen, an ``Iterator`` that yields chunks of data, an object that implemented ``__toString``, or a ``GuzzleHttp\Stream\StreamInterface``. future (bool, string) Controls the asynchronous behavior of a response. Set to ``true`` or omit the ``future`` option to *request* that a request will be completed asynchronously. Keep in mind that your request might not necessarily be completed asynchronously based on the handler you are using. Set the ``future`` option to ``false`` to request that a synchronous response be provided. You can provide a string value to specify fine-tuned future behaviors that may be specific to the underlying handlers you are using. There are, however, some common future options that handlers should implement if possible. lazy Requests that the handler does not open and send the request immediately, but rather only opens and sends the request once the future is dereferenced. This option is often useful for sending a large number of requests concurrently to allow handlers to take better advantage of non-blocking transfers by first building up a pool of requests. If an handler does not implement or understand a provided string value, then the request MUST be treated as if the user provided ``true`` rather than the string value. Future responses created by asynchronous handlers MUST attempt to complete any outstanding future responses when they are destructed. Asynchronous handlers MAY choose to automatically complete responses when the number of outstanding requests reaches an handler-specific threshold. Client Specific Options ~~~~~~~~~~~~~~~~~~~~~~~ The following options are only used in ring client handlers. .. _client-options: client (array) Associative array of client specific transfer options. The ``client`` request key value pair can contain the following keys: 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. connect_timeout (float) Float describing the number of seconds to wait while trying to connect to a server. Use ``0`` to wait indefinitely (the default behavior). debug (bool, fopen() resource) Set to true or set to a PHP stream returned by fopen() to enable debug output with the handler used to send a request. If set to ``true``, the output is written to PHP's STDOUT. If a PHP ``fopen`` resource handle is provided, the output is written to the stream. "Debug output" is handler specific: different handlers will yield different output and various various level of detail. For example, when using cURL to transfer requests, cURL's `CURLOPT_VERBOSE <http://curl.haxx.se/libcurl/c/CURLOPT_VERBOSE.html>`_ will be used. When using the PHP stream wrapper, `stream notifications <http://php.net/manual/en/function.stream-notification-callback.php>`_ will be emitted. decode_content (bool) Specify whether or not ``Content-Encoding`` responses (gzip, deflate, etc.) are automatically decoded. Set to ``true`` to automatically decode encoded responses. Set to ``false`` to not decode responses. By default, content is *not* decoded automatically. delay (int) The number of milliseconds to delay before sending the request. This is often used for delaying before retrying a request. Handlers SHOULD implement this if possible, but it is not a strict requirement. progress (function) Defines a function to invoke when transfer progress is made. The function accepts the following arguments: 1. The total number of bytes expected to be downloaded 2. The number of bytes downloaded so far 3. The number of bytes expected to be uploaded 4. The number of bytes uploaded so far proxy (string, array) Pass a string to specify an HTTP proxy, or an associative array to specify different proxies for different protocols where the scheme is the key and the value is the proxy address. .. code-block:: php $request = [ 'http_method' => 'GET', 'headers' => ['host' => ['httpbin.org']], 'client' => [ // Use different proxies for different URI schemes. 'proxy' => [ 'http' => 'http://proxy.example.com:5100', 'https' => 'https://proxy.example.com:6100' ] ] ]; ssl_key (string, array) 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. save_to (string, fopen resource, ``GuzzleHttp\Stream\StreamInterface``) Specifies where the body of the response is downloaded. Pass a string to open a local file on disk and save the output to the file. Pass an fopen resource to save the output to a PHP stream resource. Pass a ``GuzzleHttp\Stream\StreamInterface`` to save the output to a Guzzle StreamInterface. Omitting this option will typically save the body of a response to a PHP temp stream. stream (bool) Set to true to stream a response rather than download it all up-front. This option will only be utilized when the corresponding handler supports it. timeout (float) Float describing the timeout of the request in seconds. Use 0 to wait indefinitely (the default behavior). verify (bool, string) 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. version (string) HTTP protocol version to use with the request. Server Specific Options ~~~~~~~~~~~~~~~~~~~~~~~ The following options are only used in ring server handlers. server_port (integer) The port on which the request is being handled. This is only used with ring servers, and is required. server_name (string) The resolved server name, or the server IP address. Required when using a Ring server. remote_addr (string) The IP address of the client or the last proxy that sent the request. Required when using a Ring server. Responses --------- A response is an array-like object that implements ``GuzzleHttp\Ring\Future\FutureArrayInterface``. Responses contain the following key value pairs: body (string, fopen resource, ``Iterator``, ``GuzzleHttp\Stream\StreamInterface``) The body of the response, if present. Can be a string, resource returned from fopen, an ``Iterator`` that yields chunks of data, an object that implemented ``__toString``, or a ``GuzzleHttp\Stream\StreamInterface``. effective_url (string) The URL that returned the resulting response. error (``\Exception``) Contains an exception describing any errors that were encountered during the transfer. headers (Required, array) Associative array of headers. Each key represents the header name. Each value contains an array of strings where each entry of the array is a header line. The headers array MAY be an empty array in the event an error occurred before a response was received. reason (string) Optional reason phrase. This option should be provided when the reason phrase does not match the typical reason phrase associated with the ``status`` code. See `RFC 7231 <http://tools.ietf.org/html/rfc7231#section-6.1>`_ for a list of HTTP reason phrases mapped to status codes. status (Required, integer) The HTTP status code. The status code MAY be set to ``null`` in the event an error occurred before a response was received (e.g., a networking error). transfer_stats (array) Provides an associative array of arbitrary transfer statistics if provided by the underlying handler. version (string) HTTP protocol version. Defaults to ``1.1``. Middleware ---------- Ring middleware augments the functionality of handlers by invoking them in the process of generating responses. Middleware is typically implemented as a higher-order function that takes one or more handlers as arguments followed by an optional associative array of options as the last argument, returning a new handler with the desired compound behavior. Here's an example of a middleware that adds a Content-Type header to each request. .. code-block:: php use GuzzleHttp\Ring\Client\CurlHandler; use GuzzleHttp\Ring\Core; $contentTypeHandler = function(callable $handler, $contentType) { return function (array $request) use ($handler, $contentType) { return $handler(Core::setHeader('Content-Type', $contentType)); }; }; $baseHandler = new CurlHandler(); $wrappedHandler = $contentTypeHandler($baseHandler, 'text/html'); $response = $wrappedHandler([/** request hash **/]); docs/index.rst000077700000002641151323632020007344 0ustar00======= RingPHP ======= Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function. RingPHP be used to power HTTP clients and servers through a PHP function that accepts a request hash and returns a response hash that is fulfilled using a `promise <https://github.com/reactphp/promise>`_, allowing RingPHP to support both synchronous and asynchronous workflows. By abstracting the implementation details of different HTTP clients and servers, RingPHP allows you to utilize pluggable HTTP clients and servers without tying your application to a specific implementation. .. toctree:: :maxdepth: 2 spec futures client_middleware client_handlers testing .. code-block:: php <?php require 'vendor/autoload.php'; use GuzzleHttp\Ring\Client\CurlHandler; $handler = new CurlHandler(); $response = $handler([ 'http_method' => 'GET', 'uri' => '/', 'headers' => [ 'host' => ['www.google.com'], 'x-foo' => ['baz'] ] ]); $response->then(function (array $response) { echo $response['status']; }); $response->wait(); RingPHP is inspired by Clojure's `Ring <https://github.com/ring-clojure/ring>`_, which, in turn, was inspired by Python's WSGI and Ruby's Rack. RingPHP is utilized as the handler layer in `Guzzle <http://guzzlephp.org>`_ 5.0+ to send HTTP requests. docs/conf.py000077700000001145151323632020007000 0ustar00import sys, os import sphinx_rtd_theme from sphinx.highlighting import lexers from pygments.lexers.web import PhpLexer lexers['php'] = PhpLexer(startinline=True, linenos=1) lexers['php-annotations'] = PhpLexer(startinline=True, linenos=1) primary_domain = 'php' extensions = [] templates_path = ['_templates'] source_suffix = '.rst' master_doc = 'index' project = u'RingPHP' copyright = u'2014, Michael Dowling' version = '1.0.0-alpha' exclude_patterns = ['_build'] html_title = "RingPHP" html_short_title = "RingPHP" html_theme = "sphinx_rtd_theme" html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] docs/client_handlers.rst000077700000012571151323632020011376 0ustar00=============== Client Handlers =============== Client handlers accept a request array and return a future response array that can be used synchronously as an array or asynchronously using a promise. Built-In Handlers ----------------- RingPHP comes with three built-in client handlers. Stream Handler ~~~~~~~~~~~~~~ The ``GuzzleHttp\Ring\Client\StreamHandler`` uses PHP's `http stream wrapper <http://php.net/manual/en/wrappers.http.php>`_ to send requests. .. note:: This handler cannot send requests concurrently. You can provide an associative array of custom stream context options to the StreamHandler using the ``stream_context`` key of the ``client`` request option. .. code-block:: php use GuzzleHttp\Ring\Client\StreamHandler; $response = $handler([ 'http_method' => 'GET', 'uri' => '/', 'headers' => ['host' => ['httpbin.org']], 'client' => [ 'stream_context' => [ 'http' => [ 'request_fulluri' => true, 'method' => 'HEAD' ], 'socket' => [ 'bindto' => '127.0.0.1:0' ], 'ssl' => [ 'verify_peer' => false ] ] ] ]); // Even though it's already completed, you can still use a promise $response->then(function ($response) { echo $response['status']; // 200 }); // Or access the response using the future interface echo $response['status']; // 200 cURL Handler ~~~~~~~~~~~~ The ``GuzzleHttp\Ring\Client\CurlHandler`` can be used with PHP 5.5+ to send requests using cURL easy handles. This handler is great for sending requests one at a time because the execute and select loop is implemented in C code which executes faster and consumes less memory than using PHP's ``curl_multi_*`` interface. .. note:: This handler cannot send requests concurrently. When using the CurlHandler, custom curl options can be specified as an associative array of `cURL option constants <http://php.net/manual/en/curl.constants.php>`_ mapping to values in the ``client`` option of a requst using the **curl** key. .. code-block:: php use GuzzleHttp\Ring\Client\CurlHandler; $handler = new CurlHandler(); $request = [ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'client' => ['curl' => [CURLOPT_LOW_SPEED_LIMIT => 10]] ]; $response = $handler($request); // The response can be used directly as an array. echo $response['status']; // 200 // Or, it can be used as a promise (that has already fulfilled). $response->then(function ($response) { echo $response['status']; // 200 }); cURL Multi Handler ~~~~~~~~~~~~~~~~~~ The ``GuzzleHttp\Ring\Client\CurlMultiHandler`` transfers requests using cURL's `multi API <http://curl.haxx.se/libcurl/c/libcurl-multi.html>`_. The ``CurlMultiHandler`` is great for sending requests concurrently. .. code-block:: php use GuzzleHttp\Ring\Client\CurlMultiHandler; $handler = new CurlMultiHandler(); $request = [ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]] ]; // this call returns a future array immediately. $response = $handler($request); // Ideally, you should use the promise API to not block. $response ->then(function ($response) { // Got the response at some point in the future echo $response['status']; // 200 // Don't break the chain return $response; })->then(function ($response) { // ... }); // If you really need to block, then you can use the response as an // associative array. This will block until it has completed. echo $response['status']; // 200 Just like the ``CurlHandler``, the ``CurlMultiHandler`` accepts custom curl option in the ``curl`` key of the ``client`` request option. Mock Handler ~~~~~~~~~~~~ The ``GuzzleHttp\Ring\Client\MockHandler`` is used to return mock responses. When constructed, the handler can be configured to return the same response array over and over, a future response, or a the evaluation of a callback function. .. code-block:: php use GuzzleHttp\Ring\Client\MockHandler; // Return a canned response. $mock = new MockHandler(['status' => 200]); $response = $mock([]); assert(200 == $response['status']); assert([] == $response['headers']); Implementing Handlers --------------------- Client handlers are just PHP callables (functions or classes that have the ``__invoke`` magic method). The callable accepts a request array and MUST return an instance of ``GuzzleHttp\Ring\Future\FutureArrayInterface`` so that the response can be used by both blocking and non-blocking consumers. Handlers need to follow a few simple rules: 1. Do not throw exceptions. If an error is encountered, return an array that contains the ``error`` key that maps to an ``\Exception`` value. 2. If the request has a ``delay`` client option, then the handler should only send the request after the specified delay time in seconds. Blocking handlers may find it convenient to just let the ``GuzzleHttp\Ring\Core::doSleep($request)`` function handle this for them. 3. Always return an instance of ``GuzzleHttp\Ring\Future\FutureArrayInterface``. 4. Complete any outstanding requests when the handler is destructed. docs/requirements.txt000077700000000021151323632020010755 0ustar00sphinx_rtd_theme docs/futures.rst000077700000013037151323632020007733 0ustar00======= Futures ======= Futures represent a computation that may have not yet completed. RingPHP uses hybrid of futures and promises to provide a consistent API that can be used for both blocking and non-blocking consumers. Promises -------- You can get the result of a future when it is ready using the promise interface of a future. Futures expose a promise API via a ``then()`` method that utilizes `React's promise library <https://github.com/reactphp/promise>`_. You should use this API when you do not wish to block. .. code-block:: php use GuzzleHttp\Ring\Client\CurlMultiHandler; $request = [ 'http_method' => 'GET', 'uri' => '/', 'headers' => ['host' => ['httpbin.org']] ]; $response = $handler($request); // Use the then() method to use the promise API of the future. $response->then(function ($response) { echo $response['status']; }); You can get the promise used by a future, an instance of ``React\Promise\PromiseInterface``, by calling the ``promise()`` method. .. code-block:: php $response = $handler($request); $promise = $response->promise(); $promise->then(function ($response) { echo $response['status']; }); This promise value can be used with React's `aggregate promise functions <https://github.com/reactphp/promise#functions>`_. Waiting ------- You can wait on a future to complete and retrieve the value, or *dereference* the future, using the ``wait()`` method. Calling the ``wait()`` method of a future will block until the result is available. The result is then returned or an exception is thrown if and exception was encountered while waiting on the the result. Subsequent calls to dereference a future will return the previously completed result or throw the previously encountered exception. Futures can be cancelled, which stops the computation if possible. .. code-block:: php use GuzzleHttp\Ring\Client\CurlMultiHandler; $response = $handler([ 'http_method' => 'GET', 'uri' => '/', 'headers' => ['host' => ['httpbin.org']] ]); // You can explicitly call block to wait on a result. $realizedResponse = $response->wait(); // Future responses can be used like a regular PHP array. echo $response['status']; In addition to explicitly calling the ``wait()`` function, using a future like a normal value will implicitly trigger the ``wait()`` function. Future Responses ---------------- RingPHP uses futures to return asynchronous responses immediately. Client handlers always return future responses that implement ``GuzzleHttp\Ring\Future\ArrayFutureInterface``. These future responses act just like normal PHP associative arrays for blocking access and provide a promise interface for non-blocking access. .. code-block:: php use GuzzleHttp\Ring\Client\CurlMultiHandler; $handler = new CurlMultiHandler(); $request = [ 'http_method' => 'GET', 'uri' => '/', 'headers' => ['Host' => ['www.google.com']] ]; $response = $handler($request); // Use the promise API for non-blocking access to the response. The actual // response value will be delivered to the promise. $response->then(function ($response) { echo $response['status']; }); // You can wait (block) until the future is completed. $response->wait(); // This will implicitly call wait(), and will block too! $response['status']; .. important:: Futures that are not completed by the time the underlying handler is destructed will be completed when the handler is shutting down. Cancelling ---------- Futures can be cancelled if they have not already been dereferenced. RingPHP futures are typically implemented with the ``GuzzleHttp\Ring\Future\BaseFutureTrait``. This trait provides the cancellation functionality that should be common to most implementations. Cancelling a future response will try to prevent the request from sending over the wire. When a future is cancelled, the cancellation function is invoked and performs the actual work needed to cancel the request from sending if possible (e.g., telling an event loop to stop sending a request or to close a socket). If no cancellation function is provided, then a request cannot be cancelled. If a cancel function is provided, then it should accept the future as an argument and return true if the future was successfully cancelled or false if it could not be cancelled. Wrapping an existing Promise ---------------------------- You can easily create a future from any existing promise using the ``GuzzleHttp\Ring\Future\FutureValue`` class. This class's constructor accepts a promise as the first argument, a wait function as the second argument, and a cancellation function as the third argument. The dereference function is used to force the promise to resolve (for example, manually ticking an event loop). The cancel function is optional and is used to tell the thing that created the promise that it can stop computing the result (for example, telling an event loop to stop transferring a request). .. code-block:: php use GuzzleHttp\Ring\Future\FutureValue; use React\Promise\Deferred; $deferred = new Deferred(); $promise = $deferred->promise(); $f = new FutureValue( $promise, function () use ($deferred) { // This function is responsible for blocking and resolving the // promise. Here we pass in a reference to the deferred so that // it can be resolved or rejected. $deferred->resolve('foo'); } ); docs/Makefile000077700000012714151323632020007145 0ustar00# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make <target>' where <target> is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/GuzzleRing.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/GuzzleRing.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/GuzzleRing" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/GuzzleRing" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." docs/client_middleware.rst000077700000012501151323632020011704 0ustar00================= Client Middleware ================= Middleware intercepts requests before they are sent over the wire and can be used to add functionality to handlers. Modifying Requests ------------------ Let's say you wanted to modify requests before they are sent over the wire so that they always add specific headers. This can be accomplished by creating a function that accepts a handler and returns a new function that adds the composed behavior. .. code-block:: php use GuzzleHttp\Ring\Client\CurlHandler; $handler = new CurlHandler(); $addHeaderHandler = function (callable $handler, array $headers = []) { return function (array $request) use ($handler, $headers) { // Add our custom headers foreach ($headers as $key => $value) { $request['headers'][$key] = $value; } // Send the request using the handler and return the response. return $handler($request); } }; // Create a new handler that adds headers to each request. $handler = $addHeaderHandler($handler, [ 'X-AddMe' => 'hello', 'Authorization' => 'Basic xyz' ]); $response = $handler([ 'http_method' => 'GET', 'headers' => ['Host' => ['httpbin.org']] ]); Modifying Responses ------------------- You can change a response as it's returned from a middleware. Remember that responses returned from an handler (including middleware) must implement ``GuzzleHttp\Ring\Future\FutureArrayInterface``. In order to be a good citizen, you should not expect that the responses returned through your middleware will be completed synchronously. Instead, you should use the ``GuzzleHttp\Ring\Core::proxy()`` function to modify the response when the underlying promise is resolved. This function is a helper function that makes it easy to create a new instance of ``FutureArrayInterface`` that wraps an existing ``FutureArrayInterface`` object. Let's say you wanted to add headers to a response as they are returned from your middleware, but you want to make sure you aren't causing future responses to be dereferenced right away. You can achieve this by modifying the incoming request and using the ``Core::proxy`` function. .. code-block:: php use GuzzleHttp\Ring\Core; use GuzzleHttp\Ring\Client\CurlHandler; $handler = new CurlHandler(); $responseHeaderHandler = function (callable $handler, array $headers) { return function (array $request) use ($handler, $headers) { // Send the request using the wrapped handler. return Core::proxy($handler($request), function ($response) use ($headers) { // Add the headers to the response when it is available. foreach ($headers as $key => $value) { $response['headers'][$key] = (array) $value; } // Note that you can return a regular response array when using // the proxy method. return $response; }); } }; // Create a new handler that adds headers to each response. $handler = $responseHeaderHandler($handler, ['X-Header' => 'hello!']); $response = $handler([ 'http_method' => 'GET', 'headers' => ['Host' => ['httpbin.org']] ]); assert($response['headers']['X-Header'] == 'hello!'); Built-In Middleware ------------------- RingPHP comes with a few basic client middlewares that modify requests and responses. Streaming Middleware ~~~~~~~~~~~~~~~~~~~~ If you want to send all requests with the ``streaming`` option to a specific handler but other requests to a different handler, then use the streaming middleware. .. code-block:: php use GuzzleHttp\Ring\Client\CurlHandler; use GuzzleHttp\Ring\Client\StreamHandler; use GuzzleHttp\Ring\Client\Middleware; $defaultHandler = new CurlHandler(); $streamingHandler = new StreamHandler(); $streamingHandler = Middleware::wrapStreaming( $defaultHandler, $streamingHandler ); // Send the request using the streaming handler. $response = $streamingHandler([ 'http_method' => 'GET', 'headers' => ['Host' => ['www.google.com']], 'stream' => true ]); // Send the request using the default handler. $response = $streamingHandler([ 'http_method' => 'GET', 'headers' => ['Host' => ['www.google.com']] ]); Future Middleware ~~~~~~~~~~~~~~~~~ If you want to send all requests with the ``future`` option to a specific handler but other requests to a different handler, then use the future middleware. .. code-block:: php use GuzzleHttp\Ring\Client\CurlHandler; use GuzzleHttp\Ring\Client\CurlMultiHandler; use GuzzleHttp\Ring\Client\Middleware; $defaultHandler = new CurlHandler(); $futureHandler = new CurlMultiHandler(); $futureHandler = Middleware::wrapFuture( $defaultHandler, $futureHandler ); // Send the request using the blocking CurlHandler. $response = $futureHandler([ 'http_method' => 'GET', 'headers' => ['Host' => ['www.google.com']] ]); // Send the request using the non-blocking CurlMultiHandler. $response = $futureHandler([ 'http_method' => 'GET', 'headers' => ['Host' => ['www.google.com']], 'future' => true ]); docs/.htaccess000077700000000177151323632020007303 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>.htaccess000077700000000177151323632020006353 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>tests/CoreTest.php000077700000021512151323632020010154 0ustar00<?php namespace GuzzleHttp\Tests\Ring; use GuzzleHttp\Ring\Core; use GuzzleHttp\Ring\Future\CompletedFutureArray; use GuzzleHttp\Ring\Future\FutureArray; use GuzzleHttp\Stream\Stream; use React\Promise\Deferred; class CoreTest extends \PHPUnit_Framework_TestCase { public function testReturnsNullNoHeadersAreSet() { $this->assertNull(Core::header([], 'Foo')); $this->assertNull(Core::firstHeader([], 'Foo')); } public function testChecksIfHasHeader() { $message = [ 'headers' => [ 'Foo' => ['Bar', 'Baz'], 'foo' => ['hello'], 'bar' => ['1'] ] ]; $this->assertTrue(Core::hasHeader($message, 'Foo')); $this->assertTrue(Core::hasHeader($message, 'foo')); $this->assertTrue(Core::hasHeader($message, 'FoO')); $this->assertTrue(Core::hasHeader($message, 'bar')); $this->assertFalse(Core::hasHeader($message, 'barr')); } public function testReturnsFirstHeaderWhenSimple() { $this->assertEquals('Bar', Core::firstHeader([ 'headers' => ['Foo' => ['Bar', 'Baz']], ], 'Foo')); } public function testReturnsFirstHeaderWhenMultiplePerLine() { $this->assertEquals('Bar', Core::firstHeader([ 'headers' => ['Foo' => ['Bar, Baz']], ], 'Foo')); } public function testExtractsCaseInsensitiveHeader() { $this->assertEquals( 'hello', Core::header(['headers' => ['foo' => ['hello']]], 'FoO') ); } public function testExtractsCaseInsensitiveHeaderLines() { $this->assertEquals( ['a', 'b', 'c', 'd'], Core::headerLines([ 'headers' => [ 'foo' => ['a', 'b'], 'Foo' => ['c', 'd'] ] ], 'foo') ); } public function testExtractsHeaderLines() { $this->assertEquals( ['bar', 'baz'], Core::headerLines([ 'headers' => [ 'Foo' => ['bar', 'baz'], ], ], 'Foo') ); } public function testExtractsHeaderAsString() { $this->assertEquals( 'bar, baz', Core::header([ 'headers' => [ 'Foo' => ['bar', 'baz'], ], ], 'Foo', true) ); } public function testReturnsNullWhenHeaderNotFound() { $this->assertNull(Core::header(['headers' => []], 'Foo')); } public function testRemovesHeaders() { $message = [ 'headers' => [ 'foo' => ['bar'], 'Foo' => ['bam'], 'baz' => ['123'], ], ]; $this->assertSame($message, Core::removeHeader($message, 'bam')); $this->assertEquals([ 'headers' => ['baz' => ['123']], ], Core::removeHeader($message, 'foo')); } public function testCreatesUrl() { $req = [ 'scheme' => 'http', 'headers' => ['host' => ['foo.com']], 'uri' => '/', ]; $this->assertEquals('http://foo.com/', Core::url($req)); } /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage No Host header was provided */ public function testEnsuresHostIsAvailableWhenCreatingUrls() { Core::url([]); } public function testCreatesUrlWithQueryString() { $req = [ 'scheme' => 'http', 'headers' => ['host' => ['foo.com']], 'uri' => '/', 'query_string' => 'foo=baz', ]; $this->assertEquals('http://foo.com/?foo=baz', Core::url($req)); } public function testUsesUrlIfSet() { $req = ['url' => 'http://foo.com']; $this->assertEquals('http://foo.com', Core::url($req)); } public function testReturnsNullWhenNoBody() { $this->assertNull(Core::body([])); } public function testReturnsStreamAsString() { $this->assertEquals( 'foo', Core::body(['body' => Stream::factory('foo')]) ); } public function testReturnsString() { $this->assertEquals('foo', Core::body(['body' => 'foo'])); } public function testReturnsResourceContent() { $r = fopen('php://memory', 'w+'); fwrite($r, 'foo'); rewind($r); $this->assertEquals('foo', Core::body(['body' => $r])); fclose($r); } public function testReturnsIteratorContent() { $a = new \ArrayIterator(['a', 'b', 'cd', '']); $this->assertEquals('abcd', Core::body(['body' => $a])); } public function testReturnsObjectToString() { $this->assertEquals('foo', Core::body(['body' => new StrClass])); } /** * @expectedException \InvalidArgumentException */ public function testEnsuresBodyIsValid() { Core::body(['body' => false]); } public function testParsesHeadersFromLines() { $lines = ['Foo: bar', 'Foo: baz', 'Abc: 123', 'Def: a, b']; $this->assertEquals([ 'Foo' => ['bar', 'baz'], 'Abc' => ['123'], 'Def' => ['a, b'], ], Core::headersFromLines($lines)); } public function testParsesHeadersFromLinesWithMultipleLines() { $lines = ['Foo: bar', 'Foo: baz', 'Foo: 123']; $this->assertEquals([ 'Foo' => ['bar', 'baz', '123'], ], Core::headersFromLines($lines)); } public function testCreatesArrayCallFunctions() { $called = []; $a = function ($a, $b) use (&$called) { $called['a'] = func_get_args(); }; $b = function ($a, $b) use (&$called) { $called['b'] = func_get_args(); }; $c = Core::callArray([$a, $b]); $c(1, 2); $this->assertEquals([1, 2], $called['a']); $this->assertEquals([1, 2], $called['b']); } public function testRewindsGuzzleStreams() { $str = Stream::factory('foo'); $this->assertTrue(Core::rewindBody(['body' => $str])); } public function testRewindsStreams() { $str = Stream::factory('foo')->detach(); $this->assertTrue(Core::rewindBody(['body' => $str])); } public function testRewindsIterators() { $iter = new \ArrayIterator(['foo']); $this->assertTrue(Core::rewindBody(['body' => $iter])); } public function testRewindsStrings() { $this->assertTrue(Core::rewindBody(['body' => 'hi'])); } public function testRewindsToStrings() { $this->assertTrue(Core::rewindBody(['body' => new StrClass()])); } public function typeProvider() { return [ ['foo', 'string(3) "foo"'], [true, 'bool(true)'], [false, 'bool(false)'], [10, 'int(10)'], [1.0, 'float(1)'], [new StrClass(), 'object(GuzzleHttp\Tests\Ring\StrClass)'], [['foo'], 'array(1)'] ]; } /** * @dataProvider typeProvider */ public function testDescribesType($input, $output) { $this->assertEquals($output, Core::describeType($input)); } public function testDoesSleep() { $t = microtime(true); $expected = $t + (100 / 1000); Core::doSleep(['client' => ['delay' => 100]]); $this->assertGreaterThanOrEqual($expected, microtime(true)); } public function testProxiesFuture() { $f = new CompletedFutureArray(['status' => 200]); $res = null; $proxied = Core::proxy($f, function ($value) use (&$res) { $value['foo'] = 'bar'; $res = $value; return $value; }); $this->assertNotSame($f, $proxied); $this->assertEquals(200, $f->wait()['status']); $this->assertArrayNotHasKey('foo', $f->wait()); $this->assertEquals('bar', $proxied->wait()['foo']); $this->assertEquals(200, $proxied->wait()['status']); } public function testProxiesDeferredFuture() { $d = new Deferred(); $f = new FutureArray($d->promise()); $f2 = Core::proxy($f); $d->resolve(['foo' => 'bar']); $this->assertEquals('bar', $f['foo']); $this->assertEquals('bar', $f2['foo']); } public function testProxiesDeferredFutureFailure() { $d = new Deferred(); $f = new FutureArray($d->promise()); $f2 = Core::proxy($f); $d->reject(new \Exception('foo')); try { $f2['hello?']; $this->fail('did not throw'); } catch (\Exception $e) { $this->assertEquals('foo', $e->getMessage()); } } } final class StrClass { public function __toString() { return 'foo'; } } tests/bootstrap.php000077700000000331151323632020010435 0ustar00<?php require __DIR__ . '/../vendor/autoload.php'; require __DIR__ . '/Client/Server.php'; use GuzzleHttp\Tests\Ring\Client\Server; Server::start(); register_shutdown_function(function () { Server::stop(); }); tests/Client/CurlHandlerTest.php000077700000005243151323632020012710 0ustar00<?php namespace GuzzleHttp\Tests\Ring\Client; use GuzzleHttp\Ring\Client\CurlHandler; class CurlHandlerTest extends \PHPUnit_Framework_TestCase { protected function setUp() { if (!function_exists('curl_reset')) { $this->markTestSkipped('curl_reset() is not available'); } } protected function getHandler($factory = null, $options = []) { return new CurlHandler($options); } public function testCanSetMaxHandles() { $a = new CurlHandler(['max_handles' => 10]); $this->assertEquals(10, $this->readAttribute($a, 'maxHandles')); } public function testCreatesCurlErrors() { $handler = new CurlHandler(); $response = $handler([ 'http_method' => 'GET', 'uri' => '/', 'headers' => ['host' => ['localhost:123']], 'client' => ['timeout' => 0.001, 'connect_timeout' => 0.001], ]); $this->assertNull($response['status']); $this->assertNull($response['reason']); $this->assertEquals([], $response['headers']); $this->assertInstanceOf( 'GuzzleHttp\Ring\Exception\RingException', $response['error'] ); $this->assertEquals( 1, preg_match('/^cURL error \d+: .*$/', $response['error']->getMessage()) ); } public function testReleasesAdditionalEasyHandles() { Server::flush(); $response = [ 'status' => 200, 'headers' => ['Content-Length' => [4]], 'body' => 'test', ]; Server::enqueue([$response, $response, $response, $response]); $a = new CurlHandler(['max_handles' => 2]); $fn = function () use (&$calls, $a, &$fn) { if (++$calls < 4) { $a([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'client' => ['progress' => $fn], ]); } }; $request = [ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'client' => [ 'progress' => $fn, ], ]; $a($request); $this->assertCount(2, $this->readAttribute($a, 'handles')); } public function testReusesHandles() { Server::flush(); $response = ['status' => 200]; Server::enqueue([$response, $response]); $a = new CurlHandler(); $request = [ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], ]; $a($request); $a($request); } } tests/Client/CurlFactoryTest.php000077700000063553151323632020012752 0ustar00<?php // Override curl_setopt_array() to get the last set curl options namespace GuzzleHttp\Ring\Client { function curl_setopt_array($handle, array $options) { if (!empty($_SERVER['curl_test'])) { $_SERVER['_curl'] = $options; } else { unset($_SERVER['_curl']); } \curl_setopt_array($handle, $options); } } namespace GuzzleHttp\Tests\Ring\Client { use GuzzleHttp\Ring\Client\CurlFactory; use GuzzleHttp\Ring\Client\CurlMultiHandler; use GuzzleHttp\Ring\Client\MockHandler; use GuzzleHttp\Ring\Core; use GuzzleHttp\Stream\FnStream; use GuzzleHttp\Stream\NoSeekStream; use GuzzleHttp\Stream\Stream; class CurlFactoryTest extends \PHPUnit_Framework_TestCase { public static function setUpBeforeClass() { $_SERVER['curl_test'] = true; unset($_SERVER['_curl']); } public static function tearDownAfterClass() { unset($_SERVER['_curl'], $_SERVER['curl_test']); } public function testCreatesCurlHandle() { Server::flush(); Server::enqueue([[ 'status' => 200, 'headers' => [ 'Foo' => ['Bar'], 'Baz' => ['bam'], 'Content-Length' => [2], ], 'body' => 'hi', ]]); $stream = Stream::factory(); $request = [ 'http_method' => 'PUT', 'headers' => [ 'host' => [Server::$url], 'Hi' => [' 123'], ], 'body' => 'testing', 'client' => ['save_to' => $stream], ]; $f = new CurlFactory(); $result = $f($request); $this->assertInternalType('array', $result); $this->assertCount(3, $result); $this->assertInternalType('resource', $result[0]); $this->assertInternalType('array', $result[1]); $this->assertSame($stream, $result[2]); curl_close($result[0]); $this->assertEquals('PUT', $_SERVER['_curl'][CURLOPT_CUSTOMREQUEST]); $this->assertEquals( 'http://http://127.0.0.1:8125/', $_SERVER['_curl'][CURLOPT_URL] ); // Sends via post fields when the request is small enough $this->assertEquals('testing', $_SERVER['_curl'][CURLOPT_POSTFIELDS]); $this->assertEquals(0, $_SERVER['_curl'][CURLOPT_RETURNTRANSFER]); $this->assertEquals(0, $_SERVER['_curl'][CURLOPT_HEADER]); $this->assertEquals(150, $_SERVER['_curl'][CURLOPT_CONNECTTIMEOUT]); $this->assertInstanceOf('Closure', $_SERVER['_curl'][CURLOPT_HEADERFUNCTION]); if (defined('CURLOPT_PROTOCOLS')) { $this->assertEquals( CURLPROTO_HTTP | CURLPROTO_HTTPS, $_SERVER['_curl'][CURLOPT_PROTOCOLS] ); } $this->assertContains('Expect:', $_SERVER['_curl'][CURLOPT_HTTPHEADER]); $this->assertContains('Accept:', $_SERVER['_curl'][CURLOPT_HTTPHEADER]); $this->assertContains('Content-Type:', $_SERVER['_curl'][CURLOPT_HTTPHEADER]); $this->assertContains('Hi: 123', $_SERVER['_curl'][CURLOPT_HTTPHEADER]); $this->assertContains('host: http://127.0.0.1:8125/', $_SERVER['_curl'][CURLOPT_HTTPHEADER]); } public function testSendsHeadRequests() { Server::flush(); Server::enqueue([['status' => 200]]); $a = new CurlMultiHandler(); $response = $a([ 'http_method' => 'HEAD', 'headers' => ['host' => [Server::$host]], ]); $response->wait(); $this->assertEquals(true, $_SERVER['_curl'][CURLOPT_NOBODY]); $checks = [CURLOPT_WRITEFUNCTION, CURLOPT_READFUNCTION, CURLOPT_FILE, CURLOPT_INFILE]; foreach ($checks as $check) { $this->assertArrayNotHasKey($check, $_SERVER['_curl']); } $this->assertEquals('HEAD', Server::received()[0]['http_method']); } public function testCanAddCustomCurlOptions() { Server::flush(); Server::enqueue([['status' => 200]]); $a = new CurlMultiHandler(); $a([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'client' => ['curl' => [CURLOPT_LOW_SPEED_LIMIT => 10]], ]); $this->assertEquals(10, $_SERVER['_curl'][CURLOPT_LOW_SPEED_LIMIT]); } /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage SSL CA bundle not found: /does/not/exist */ public function testValidatesVerify() { $f = new CurlFactory(); $f([ 'http_method' => 'GET', 'headers' => ['host' => ['foo.com']], 'client' => ['verify' => '/does/not/exist'], ]); } public function testCanSetVerifyToFile() { $f = new CurlFactory(); $f([ 'http_method' => 'GET', 'headers' => ['host' => ['foo.com']], 'client' => ['verify' => __FILE__], ]); $this->assertEquals(__FILE__, $_SERVER['_curl'][CURLOPT_CAINFO]); $this->assertEquals(2, $_SERVER['_curl'][CURLOPT_SSL_VERIFYHOST]); $this->assertEquals(true, $_SERVER['_curl'][CURLOPT_SSL_VERIFYPEER]); } public function testAddsVerifyAsTrue() { $f = new CurlFactory(); $f([ 'http_method' => 'GET', 'headers' => ['host' => ['foo.com']], 'client' => ['verify' => true], ]); $this->assertEquals(2, $_SERVER['_curl'][CURLOPT_SSL_VERIFYHOST]); $this->assertEquals(true, $_SERVER['_curl'][CURLOPT_SSL_VERIFYPEER]); $this->assertArrayNotHasKey(CURLOPT_CAINFO, $_SERVER['_curl']); } public function testCanDisableVerify() { $f = new CurlFactory(); $f([ 'http_method' => 'GET', 'headers' => ['host' => ['foo.com']], 'client' => ['verify' => false], ]); $this->assertEquals(0, $_SERVER['_curl'][CURLOPT_SSL_VERIFYHOST]); $this->assertEquals(false, $_SERVER['_curl'][CURLOPT_SSL_VERIFYPEER]); } public function testAddsProxy() { $f = new CurlFactory(); $f([ 'http_method' => 'GET', 'headers' => ['host' => ['foo.com']], 'client' => ['proxy' => 'http://bar.com'], ]); $this->assertEquals('http://bar.com', $_SERVER['_curl'][CURLOPT_PROXY]); } public function testAddsViaScheme() { $f = new CurlFactory(); $f([ 'http_method' => 'GET', 'scheme' => 'http', 'headers' => ['host' => ['foo.com']], 'client' => [ 'proxy' => ['http' => 'http://bar.com', 'https' => 'https://t'], ], ]); $this->assertEquals('http://bar.com', $_SERVER['_curl'][CURLOPT_PROXY]); } /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage SSL private key not found: /does/not/exist */ public function testValidatesSslKey() { $f = new CurlFactory(); $f([ 'http_method' => 'GET', 'headers' => ['host' => ['foo.com']], 'client' => ['ssl_key' => '/does/not/exist'], ]); } public function testAddsSslKey() { $f = new CurlFactory(); $f([ 'http_method' => 'GET', 'headers' => ['host' => ['foo.com']], 'client' => ['ssl_key' => __FILE__], ]); $this->assertEquals(__FILE__, $_SERVER['_curl'][CURLOPT_SSLKEY]); } public function testAddsSslKeyWithPassword() { $f = new CurlFactory(); $f([ 'http_method' => 'GET', 'headers' => ['host' => ['foo.com']], 'client' => ['ssl_key' => [__FILE__, 'test']], ]); $this->assertEquals(__FILE__, $_SERVER['_curl'][CURLOPT_SSLKEY]); $this->assertEquals('test', $_SERVER['_curl'][CURLOPT_SSLKEYPASSWD]); } /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage SSL certificate not found: /does/not/exist */ public function testValidatesCert() { $f = new CurlFactory(); $f([ 'http_method' => 'GET', 'headers' => ['host' => ['foo.com']], 'client' => ['cert' => '/does/not/exist'], ]); } public function testAddsCert() { $f = new CurlFactory(); $f([ 'http_method' => 'GET', 'headers' => ['host' => ['foo.com']], 'client' => ['cert' => __FILE__], ]); $this->assertEquals(__FILE__, $_SERVER['_curl'][CURLOPT_SSLCERT]); } public function testAddsCertWithPassword() { $f = new CurlFactory(); $f([ 'http_method' => 'GET', 'headers' => ['host' => ['foo.com']], 'client' => ['cert' => [__FILE__, 'test']], ]); $this->assertEquals(__FILE__, $_SERVER['_curl'][CURLOPT_SSLCERT]); $this->assertEquals('test', $_SERVER['_curl'][CURLOPT_SSLCERTPASSWD]); } /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage progress client option must be callable */ public function testValidatesProgress() { $f = new CurlFactory(); $f([ 'http_method' => 'GET', 'headers' => ['host' => ['foo.com']], 'client' => ['progress' => 'foo'], ]); } public function testEmitsDebugInfoToStream() { $res = fopen('php://memory', 'r+'); Server::flush(); Server::enqueue([['status' => 200]]); $a = new CurlMultiHandler(); $response = $a([ 'http_method' => 'HEAD', 'headers' => ['host' => [Server::$host]], 'client' => ['debug' => $res], ]); $response->wait(); rewind($res); $output = str_replace("\r", '', stream_get_contents($res)); $this->assertContains( "> HEAD / HTTP/1.1\nhost: 127.0.0.1:8125\n\n", $output ); $this->assertContains("< HTTP/1.1 200", $output); fclose($res); } public function testEmitsProgressToFunction() { Server::flush(); Server::enqueue([['status' => 200]]); $a = new CurlMultiHandler(); $called = []; $response = $a([ 'http_method' => 'HEAD', 'headers' => ['host' => [Server::$host]], 'client' => [ 'progress' => function () use (&$called) { $called[] = func_get_args(); }, ], ]); $response->wait(); $this->assertNotEmpty($called); foreach ($called as $call) { $this->assertCount(4, $call); } } private function addDecodeResponse($withEncoding = true) { $content = gzencode('test'); $response = [ 'status' => 200, 'reason' => 'OK', 'headers' => ['Content-Length' => [strlen($content)]], 'body' => $content, ]; if ($withEncoding) { $response['headers']['Content-Encoding'] = ['gzip']; } Server::flush(); Server::enqueue([$response]); return $content; } public function testDecodesGzippedResponses() { $this->addDecodeResponse(); $handler = new CurlMultiHandler(); $response = $handler([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'client' => ['decode_content' => true], ]); $response->wait(); $this->assertEquals('test', Core::body($response)); $this->assertEquals('', $_SERVER['_curl'][CURLOPT_ENCODING]); $sent = Server::received()[0]; $this->assertNull(Core::header($sent, 'Accept-Encoding')); } public function testDecodesGzippedResponsesWithHeader() { $this->addDecodeResponse(); $handler = new CurlMultiHandler(); $response = $handler([ 'http_method' => 'GET', 'headers' => [ 'host' => [Server::$host], 'Accept-Encoding' => ['gzip'], ], 'client' => ['decode_content' => true], ]); $response->wait(); $this->assertEquals('gzip', $_SERVER['_curl'][CURLOPT_ENCODING]); $sent = Server::received()[0]; $this->assertEquals('gzip', Core::header($sent, 'Accept-Encoding')); $this->assertEquals('test', Core::body($response)); } public function testDoesNotForceDecode() { $content = $this->addDecodeResponse(); $handler = new CurlMultiHandler(); $response = $handler([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'client' => ['decode_content' => false], ]); $response->wait(); $sent = Server::received()[0]; $this->assertNull(Core::header($sent, 'Accept-Encoding')); $this->assertEquals($content, Core::body($response)); } public function testProtocolVersion() { Server::flush(); Server::enqueue([['status' => 200]]); $a = new CurlMultiHandler(); $a([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'version' => 1.0, ]); $this->assertEquals(CURL_HTTP_VERSION_1_0, $_SERVER['_curl'][CURLOPT_HTTP_VERSION]); } /** * @expectedException \InvalidArgumentException */ public function testValidatesSaveTo() { $handler = new CurlMultiHandler(); $handler([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'client' => ['save_to' => true], ]); } public function testSavesToStream() { $stream = fopen('php://memory', 'r+'); $this->addDecodeResponse(); $handler = new CurlMultiHandler(); $response = $handler([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'client' => [ 'decode_content' => true, 'save_to' => $stream, ], ]); $response->wait(); rewind($stream); $this->assertEquals('test', stream_get_contents($stream)); } public function testSavesToGuzzleStream() { $stream = Stream::factory(); $this->addDecodeResponse(); $handler = new CurlMultiHandler(); $response = $handler([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'client' => [ 'decode_content' => true, 'save_to' => $stream, ], ]); $response->wait(); $this->assertEquals('test', (string) $stream); } public function testSavesToFileOnDisk() { $tmpfile = tempnam(sys_get_temp_dir(), 'testfile'); $this->addDecodeResponse(); $handler = new CurlMultiHandler(); $response = $handler([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'client' => [ 'decode_content' => true, 'save_to' => $tmpfile, ], ]); $response->wait(); $this->assertEquals('test', file_get_contents($tmpfile)); unlink($tmpfile); } /** * @expectedException \InvalidArgumentException */ public function testValidatesBody() { $handler = new CurlMultiHandler(); $handler([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'body' => false, ]); } public function testAddsLargePayloadFromStreamWithNoSizeUsingChunked() { $stream = Stream::factory('foo'); $stream = FnStream::decorate($stream, [ 'getSize' => function () { return null; } ]); $this->addDecodeResponse(); $handler = new CurlMultiHandler(); $response = $handler([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'body' => $stream, ]); $response->wait(); $sent = Server::received()[0]; $this->assertEquals('chunked', Core::header($sent, 'Transfer-Encoding')); $this->assertNull(Core::header($sent, 'Content-Length')); $this->assertEquals('foo', $sent['body']); } public function testAddsPayloadFromIterator() { $iter = new \ArrayIterator(['f', 'o', 'o']); $this->addDecodeResponse(); $handler = new CurlMultiHandler(); $response = $handler([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'body' => $iter, ]); $response->wait(); $sent = Server::received()[0]; $this->assertEquals('chunked', Core::header($sent, 'Transfer-Encoding')); $this->assertNull(Core::header($sent, 'Content-Length')); $this->assertEquals('foo', $sent['body']); } public function testAddsPayloadFromResource() { $res = fopen('php://memory', 'r+'); $data = str_repeat('.', 1000000); fwrite($res, $data); rewind($res); $this->addDecodeResponse(); $handler = new CurlMultiHandler(); $response = $handler([ 'http_method' => 'GET', 'headers' => [ 'host' => [Server::$host], 'content-length' => [1000000], ], 'body' => $res, ]); $response->wait(); $sent = Server::received()[0]; $this->assertNull(Core::header($sent, 'Transfer-Encoding')); $this->assertEquals(1000000, Core::header($sent, 'Content-Length')); $this->assertEquals($data, $sent['body']); } public function testAddsContentLengthFromStream() { $stream = Stream::factory('foo'); $this->addDecodeResponse(); $handler = new CurlMultiHandler(); $response = $handler([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'body' => $stream, ]); $response->wait(); $sent = Server::received()[0]; $this->assertEquals(3, Core::header($sent, 'Content-Length')); $this->assertNull(Core::header($sent, 'Transfer-Encoding')); $this->assertEquals('foo', $sent['body']); } public function testDoesNotAddMultipleContentLengthHeaders() { $this->addDecodeResponse(); $handler = new CurlMultiHandler(); $response = $handler([ 'http_method' => 'GET', 'headers' => [ 'host' => [Server::$host], 'content-length' => [3], ], 'body' => 'foo', ]); $response->wait(); $sent = Server::received()[0]; $this->assertEquals(3, Core::header($sent, 'Content-Length')); $this->assertNull(Core::header($sent, 'Transfer-Encoding')); $this->assertEquals('foo', $sent['body']); } public function testSendsPostWithNoBodyOrDefaultContentType() { Server::flush(); Server::enqueue([['status' => 200]]); $handler = new CurlMultiHandler(); $response = $handler([ 'http_method' => 'POST', 'uri' => '/', 'headers' => ['host' => [Server::$host]], ]); $response->wait(); $received = Server::received()[0]; $this->assertEquals('POST', $received['http_method']); $this->assertNull(Core::header($received, 'content-type')); $this->assertSame('0', Core::firstHeader($received, 'content-length')); } public function testParseProtocolVersion() { $res = CurlFactory::createResponse( function () {}, [], ['curl' => ['errno' => null]], ['HTTP/1.1 200 Ok'], null ); $this->assertSame('1.1', $res['version']); } public function testFailsWhenNoResponseAndNoBody() { $res = CurlFactory::createResponse(function () {}, [], [], [], null); $this->assertInstanceOf('GuzzleHttp\Ring\Exception\RingException', $res['error']); $this->assertContains( 'No response was received for a request with no body', $res['error']->getMessage() ); } public function testFailsWhenCannotRewindRetry() { $res = CurlFactory::createResponse(function () {}, [ 'body' => new NoSeekStream(Stream::factory('foo')) ], [], [], null); $this->assertInstanceOf('GuzzleHttp\Ring\Exception\RingException', $res['error']); $this->assertContains( 'rewind the request body failed', $res['error']->getMessage() ); } public function testRetriesWhenBodyCanBeRewound() { $callHandler = $called = false; $res = CurlFactory::createResponse(function () use (&$callHandler) { $callHandler = true; return ['status' => 200]; }, [ 'body' => FnStream::decorate(Stream::factory('test'), [ 'seek' => function () use (&$called) { $called = true; return true; } ]) ], [], [], null); $this->assertTrue($callHandler); $this->assertTrue($called); $this->assertEquals('200', $res['status']); } public function testFailsWhenRetryMoreThanThreeTimes() { $call = 0; $mock = new MockHandler(function (array $request) use (&$mock, &$call) { $call++; return CurlFactory::createResponse($mock, $request, [], [], null); }); $response = $mock([ 'http_method' => 'GET', 'body' => 'test', ]); $this->assertEquals(3, $call); $this->assertArrayHasKey('error', $response); $this->assertContains( 'The cURL request was retried 3 times', $response['error']->getMessage() ); } public function testHandles100Continue() { Server::flush(); Server::enqueue([ [ 'status' => '200', 'reason' => 'OK', 'headers' => [ 'Test' => ['Hello'], 'Content-Length' => ['4'], ], 'body' => 'test', ], ]); $request = [ 'http_method' => 'PUT', 'headers' => [ 'Host' => [Server::$host], 'Expect' => ['100-Continue'], ], 'body' => 'test', ]; $handler = new CurlMultiHandler(); $response = $handler($request)->wait(); $this->assertEquals(200, $response['status']); $this->assertEquals('OK', $response['reason']); $this->assertEquals(['Hello'], $response['headers']['Test']); $this->assertEquals(['4'], $response['headers']['Content-Length']); $this->assertEquals('test', Core::body($response)); } public function testCreatesConnectException() { $m = new \ReflectionMethod('GuzzleHttp\Ring\Client\CurlFactory', 'createErrorResponse'); $m->setAccessible(true); $response = $m->invoke( null, function () {}, [], [ 'err_message' => 'foo', 'curl' => [ 'errno' => CURLE_COULDNT_CONNECT, ] ] ); $this->assertInstanceOf('GuzzleHttp\Ring\Exception\ConnectException', $response['error']); } public function testParsesLastResponseOnly() { $response1 = [ 'status' => 301, 'headers' => [ 'Content-Length' => ['0'], 'Location' => ['/foo'] ] ]; $response2 = [ 'status' => 200, 'headers' => [ 'Content-Length' => ['0'], 'Foo' => ['bar'] ] ]; Server::flush(); Server::enqueue([$response1, $response2]); $a = new CurlMultiHandler(); $response = $a([ 'http_method' => 'GET', 'headers' => ['Host' => [Server::$host]], 'client' => [ 'curl' => [ CURLOPT_FOLLOWLOCATION => true ] ] ])->wait(); $this->assertEquals(1, $response['transfer_stats']['redirect_count']); $this->assertEquals('http://127.0.0.1:8125/foo', $response['effective_url']); $this->assertEquals(['bar'], $response['headers']['Foo']); $this->assertEquals(200, $response['status']); $this->assertFalse(Core::hasHeader($response, 'Location')); } public function testMaintainsMultiHeaderOrder() { Server::flush(); Server::enqueue([ [ 'status' => 200, 'headers' => [ 'Content-Length' => ['0'], 'Foo' => ['a', 'b'], 'foo' => ['c', 'd'], ] ] ]); $a = new CurlMultiHandler(); $response = $a([ 'http_method' => 'GET', 'headers' => ['Host' => [Server::$host]] ])->wait(); $this->assertEquals( ['a', 'b', 'c', 'd'], Core::headerLines($response, 'Foo') ); } /** * @expectedException \RuntimeException * @expectedExceptionMessage Directory /path/to/does/not does not exist for save_to value of /path/to/does/not/exist.txt */ public function testThrowsWhenDirNotFound() { $request = [ 'http_method' => 'GET', 'headers' => ['host' => [Server::$url]], 'client' => ['save_to' => '/path/to/does/not/exist.txt'], ]; $f = new CurlFactory(); $f($request); } } } tests/Client/CurlMultiHandlerTest.php000077700000013716151323632020013727 0ustar00<?php namespace GuzzleHttp\Tests\Ring\Client; use GuzzleHttp\Ring\Client\CurlMultiHandler; class CurlMultiHandlerTest extends \PHPUnit_Framework_TestCase { public function testSendsRequest() { Server::enqueue([['status' => 200]]); $a = new CurlMultiHandler(); $response = $a([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], ]); $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response); $this->assertEquals(200, $response['status']); $this->assertArrayHasKey('transfer_stats', $response); $realUrl = trim($response['transfer_stats']['url'], '/'); $this->assertEquals(trim(Server::$url, '/'), $realUrl); $this->assertArrayHasKey('effective_url', $response); $this->assertEquals( trim(Server::$url, '/'), trim($response['effective_url'], '/') ); } public function testCreatesErrorResponses() { $url = 'http://localhost:123/'; $a = new CurlMultiHandler(); $response = $a([ 'http_method' => 'GET', 'headers' => ['host' => ['localhost:123']], ]); $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response); $this->assertNull($response['status']); $this->assertNull($response['reason']); $this->assertEquals([], $response['headers']); $this->assertArrayHasKey('error', $response); $this->assertContains('cURL error ', $response['error']->getMessage()); $this->assertArrayHasKey('transfer_stats', $response); $this->assertEquals( trim($url, '/'), trim($response['transfer_stats']['url'], '/') ); $this->assertArrayHasKey('effective_url', $response); $this->assertEquals( trim($url, '/'), trim($response['effective_url'], '/') ); } public function testSendsFuturesWhenDestructed() { Server::enqueue([['status' => 200]]); $a = new CurlMultiHandler(); $response = $a([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], ]); $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response); $a->__destruct(); $this->assertEquals(200, $response['status']); } public function testCanSetMaxHandles() { $a = new CurlMultiHandler(['max_handles' => 2]); $this->assertEquals(2, $this->readAttribute($a, 'maxHandles')); } public function testCanSetSelectTimeout() { $a = new CurlMultiHandler(['select_timeout' => 2]); $this->assertEquals(2, $this->readAttribute($a, 'selectTimeout')); } public function testSendsFuturesWhenMaxHandlesIsReached() { $request = [ 'http_method' => 'PUT', 'headers' => ['host' => [Server::$host]], 'future' => 'lazy', // passing this to control the test ]; $response = ['status' => 200]; Server::flush(); Server::enqueue([$response, $response, $response]); $a = new CurlMultiHandler(['max_handles' => 3]); for ($i = 0; $i < 5; $i++) { $responses[] = $a($request); } $this->assertCount(3, Server::received()); $responses[3]->cancel(); $responses[4]->cancel(); } public function testCanCancel() { Server::flush(); $response = ['status' => 200]; Server::enqueue(array_fill_keys(range(0, 10), $response)); $a = new CurlMultiHandler(); $responses = []; for ($i = 0; $i < 10; $i++) { $response = $a([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'future' => 'lazy', ]); $response->cancel(); $responses[] = $response; } $this->assertCount(0, Server::received()); foreach ($responses as $response) { $this->assertTrue($this->readAttribute($response, 'isRealized')); } } public function testCannotCancelFinished() { Server::flush(); Server::enqueue([['status' => 200]]); $a = new CurlMultiHandler(); $response = $a([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], ]); $response->wait(); $response->cancel(); } public function testDelaysInParallel() { Server::flush(); Server::enqueue([['status' => 200]]); $a = new CurlMultiHandler(); $expected = microtime(true) + (100 / 1000); $response = $a([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'client' => ['delay' => 100], ]); $response->wait(); $this->assertGreaterThanOrEqual($expected, microtime(true)); } public function testSendsNonLazyFutures() { $request = [ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'future' => true, ]; Server::flush(); Server::enqueue([['status' => 202]]); $a = new CurlMultiHandler(); $response = $a($request); $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response); $this->assertEquals(202, $response['status']); } public function testExtractsErrors() { $request = [ 'http_method' => 'GET', 'headers' => ['host' => ['127.0.0.1:123']], 'future' => true, ]; Server::flush(); Server::enqueue([['status' => 202]]); $a = new CurlMultiHandler(); $response = $a($request); $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response); $this->assertEquals(CURLE_COULDNT_CONNECT, $response['curl']['errno']); $this->assertNotEmpty($response['curl']['error']); } } tests/Client/server.js000077700000020247151323632020011001 0ustar00/** * Guzzle node.js test server to return queued responses to HTTP requests and * expose a RESTful API for enqueueing responses and retrieving the requests * that have been received. * * - Delete all requests that have been received: * > DELETE /guzzle-server/requests * > Host: 127.0.0.1:8125 * * - Enqueue responses * > PUT /guzzle-server/responses * > Host: 127.0.0.1:8125 * > * > [{'status': 200, 'reason': 'OK', 'headers': {}, 'body': '' }] * * - Get the received requests * > GET /guzzle-server/requests * > Host: 127.0.0.1:8125 * * < HTTP/1.1 200 OK * < * < [{'http_method': 'GET', 'uri': '/', 'headers': {}, 'body': 'string'}] * * - Attempt access to the secure area * > GET /secure/by-digest/qop-auth/guzzle-server/requests * > Host: 127.0.0.1:8125 * * < HTTP/1.1 401 Unauthorized * < WWW-Authenticate: Digest realm="Digest Test", qop="auth", nonce="0796e98e1aeef43141fab2a66bf4521a", algorithm="MD5", stale="false" * < * < 401 Unauthorized * * - Shutdown the server * > DELETE /guzzle-server * > Host: 127.0.0.1:8125 * * @package Guzzle PHP <http://www.guzzlephp.org> * @license See the LICENSE file that was distributed with this source code. */ var http = require('http'); var url = require('url'); /** * Guzzle node.js server * @class */ var GuzzleServer = function(port, log) { this.port = port; this.log = log; this.responses = []; this.requests = []; var that = this; var md5 = function(input) { var crypto = require('crypto'); var hasher = crypto.createHash('md5'); hasher.update(input); return hasher.digest('hex'); } /** * Node.js HTTP server authentication module. * * It is only initialized on demand (by loadAuthentifier). This avoids * requiring the dependency to http-auth on standard operations, and the * performance hit at startup. */ var auth; /** * Provides authentication handlers (Basic, Digest). */ var loadAuthentifier = function(type, options) { var typeId = type; if (type == 'digest') { typeId += '.'+(options && options.qop ? options.qop : 'none'); } if (!loadAuthentifier[typeId]) { if (!auth) { try { auth = require('http-auth'); } catch (e) { if (e.code == 'MODULE_NOT_FOUND') { return; } } } switch (type) { case 'digest': var digestParams = { realm: 'Digest Test', login: 'me', password: 'test' }; if (options && options.qop) { digestParams.qop = options.qop; } loadAuthentifier[typeId] = auth.digest(digestParams, function(username, callback) { callback(md5(digestParams.login + ':' + digestParams.realm + ':' + digestParams.password)); }); break } } return loadAuthentifier[typeId]; }; var firewallRequest = function(request, req, res, requestHandlerCallback) { var securedAreaUriParts = request.uri.match(/^\/secure\/by-(digest)(\/qop-([^\/]*))?(\/.*)$/); if (securedAreaUriParts) { var authentifier = loadAuthentifier(securedAreaUriParts[1], { qop: securedAreaUriParts[2] }); if (!authentifier) { res.writeHead(501, 'HTTP authentication not implemented', { 'Content-Length': 0 }); res.end(); return; } authentifier.check(req, res, function(req, res) { req.url = securedAreaUriParts[4]; requestHandlerCallback(request, req, res); }); } else { requestHandlerCallback(request, req, res); } }; var controlRequest = function(request, req, res) { if (req.url == '/guzzle-server/perf') { res.writeHead(200, 'OK', {'Content-Length': 16}); res.end('Body of response'); } else if (req.method == 'DELETE') { if (req.url == '/guzzle-server/requests') { // Clear the received requests that.requests = []; res.writeHead(200, 'OK', { 'Content-Length': 0 }); res.end(); if (that.log) { console.log('Flushing requests'); } } else if (req.url == '/guzzle-server') { // Shutdown the server res.writeHead(200, 'OK', { 'Content-Length': 0, 'Connection': 'close' }); res.end(); if (that.log) { console.log('Shutting down'); } that.server.close(); } } else if (req.method == 'GET') { if (req.url === '/guzzle-server/requests') { if (that.log) { console.log('Sending received requests'); } // Get received requests var body = JSON.stringify(that.requests); res.writeHead(200, 'OK', { 'Content-Length': body.length }); res.end(body); } } else if (req.method == 'PUT' && req.url == '/guzzle-server/responses') { if (that.log) { console.log('Adding responses...'); } if (!request.body) { if (that.log) { console.log('No response data was provided'); } res.writeHead(400, 'NO RESPONSES IN REQUEST', { 'Content-Length': 0 }); } else { that.responses = eval('(' + request.body + ')'); for (var i = 0; i < that.responses.length; i++) { if (that.responses[i].body) { that.responses[i].body = new Buffer(that.responses[i].body, 'base64'); } } if (that.log) { console.log(that.responses); } res.writeHead(200, 'OK', { 'Content-Length': 0 }); } res.end(); } }; var receivedRequest = function(request, req, res) { if (req.url.indexOf('/guzzle-server') === 0) { controlRequest(request, req, res); } else if (req.url.indexOf('/guzzle-server') == -1 && !that.responses.length) { res.writeHead(500); res.end('No responses in queue'); } else { if (that.log) { console.log('Returning response from queue and adding request'); } that.requests.push(request); var response = that.responses.shift(); res.writeHead(response.status, response.reason, response.headers); res.end(response.body); } }; this.start = function() { that.server = http.createServer(function(req, res) { var parts = url.parse(req.url, false); var request = { http_method: req.method, scheme: parts.scheme, uri: parts.pathname, query_string: parts.query, headers: req.headers, version: req.httpVersion, body: '' }; // Receive each chunk of the request body req.addListener('data', function(chunk) { request.body += chunk; }); // Called when the request completes req.addListener('end', function() { firewallRequest(request, req, res, receivedRequest); }); }); that.server.listen(this.port, '127.0.0.1'); if (this.log) { console.log('Server running at http://127.0.0.1:8125/'); } }; }; // Get the port from the arguments port = process.argv.length >= 3 ? process.argv[2] : 8125; log = process.argv.length >= 4 ? process.argv[3] : false; // Start the server server = new GuzzleServer(port, log); server.start(); tests/Client/MockHandlerTest.php000077700000005266151323632020012701 0ustar00<?php namespace GuzzleHttp\Tests\Ring\Client; use GuzzleHttp\Ring\Client\MockHandler; use GuzzleHttp\Ring\Future\FutureArray; use React\Promise\Deferred; class MockHandlerTest extends \PHPUnit_Framework_TestCase { public function testReturnsArray() { $mock = new MockHandler(['status' => 200]); $response = $mock([]); $this->assertEquals(200, $response['status']); $this->assertEquals([], $response['headers']); $this->assertNull($response['body']); $this->assertNull($response['reason']); $this->assertNull($response['effective_url']); } public function testReturnsFutures() { $deferred = new Deferred(); $future = new FutureArray( $deferred->promise(), function () use ($deferred) { $deferred->resolve(['status' => 200]); } ); $mock = new MockHandler($future); $response = $mock([]); $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response); $this->assertEquals(200, $response['status']); } public function testReturnsFuturesWithThenCall() { $deferred = new Deferred(); $future = new FutureArray( $deferred->promise(), function () use ($deferred) { $deferred->resolve(['status' => 200]); } ); $mock = new MockHandler($future); $response = $mock([]); $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response); $this->assertEquals(200, $response['status']); $req = null; $promise = $response->then(function ($value) use (&$req) { $req = $value; $this->assertEquals(200, $req['status']); }); $this->assertInstanceOf('React\Promise\PromiseInterface', $promise); $this->assertEquals(200, $req['status']); } public function testReturnsFuturesAndProxiesCancel() { $c = null; $deferred = new Deferred(); $future = new FutureArray( $deferred->promise(), function () {}, function () use (&$c) { $c = true; return true; } ); $mock = new MockHandler($future); $response = $mock([]); $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response); $response->cancel(); $this->assertTrue($c); } /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage Response must be an array or FutureArrayInterface. Found */ public function testEnsuresMockIsValid() { $mock = new MockHandler('foo'); $mock([]); } } tests/Client/Server.php000077700000011775151323632020011122 0ustar00<?php namespace GuzzleHttp\Tests\Ring\Client; use GuzzleHttp\Ring\Client\StreamHandler; use GuzzleHttp\Ring\Core; /** * Class uses to control the test webserver. * * Queued responses will be served to requests using a FIFO order. All requests * received by the server are stored on the node.js server and can be retrieved * by calling {@see Server::received()}. * * Mock responses that don't require data to be transmitted over HTTP a great * for testing. Mock response, however, cannot test the actual sending of an * HTTP request using cURL. This test server allows the simulation of any * number of HTTP request response transactions to test the actual sending of * requests over the wire without having to leave an internal network. */ class Server { public static $started; public static $url = 'http://127.0.0.1:8125/'; public static $host = '127.0.0.1:8125'; public static $port = 8125; /** * Flush the received requests from the server * @throws \RuntimeException */ public static function flush() { self::send('DELETE', '/guzzle-server/requests'); } /** * Queue an array of responses or a single response on the server. * * Any currently queued responses will be overwritten. Subsequent requests * on the server will return queued responses in FIFO order. * * @param array $responses An array of responses. The shape of a response * is the shape described in the RingPHP spec. * @throws \Exception */ public static function enqueue(array $responses) { $data = []; foreach ($responses as $response) { if (!is_array($response)) { throw new \Exception('Each response must be an array'); } if (isset($response['body'])) { $response['body'] = base64_encode($response['body']); } $response += ['headers' => [], 'reason' => '', 'body' => '']; $data[] = $response; } self::send('PUT', '/guzzle-server/responses', json_encode($data)); } /** * Get all of the received requests as a RingPHP request structure. * * @return array * @throws \RuntimeException */ public static function received() { if (!self::$started) { return []; } $response = self::send('GET', '/guzzle-server/requests'); $body = Core::body($response); $result = json_decode($body, true); if ($result === false) { throw new \RuntimeException('Error decoding response: ' . json_last_error()); } foreach ($result as &$res) { if (isset($res['uri'])) { $res['resource'] = $res['uri']; } if (isset($res['query_string'])) { $res['resource'] .= '?' . $res['query_string']; } if (!isset($res['resource'])) { $res['resource'] = ''; } // Ensure that headers are all arrays if (isset($res['headers'])) { foreach ($res['headers'] as &$h) { $h = (array) $h; } unset($h); } } unset($res); return $result; } /** * Stop running the node.js server */ public static function stop() { if (self::$started) { self::send('DELETE', '/guzzle-server'); } self::$started = false; } public static function wait($maxTries = 20) { $tries = 0; while (!self::isListening() && ++$tries < $maxTries) { usleep(100000); } if (!self::isListening()) { throw new \RuntimeException('Unable to contact node.js server'); } } public static function start() { if (self::$started) { return; } try { self::wait(); } catch (\Exception $e) { exec('node ' . __DIR__ . \DIRECTORY_SEPARATOR . 'server.js ' . self::$port . ' >> /tmp/server.log 2>&1 &'); self::wait(); } self::$started = true; } private static function isListening() { $response = self::send('GET', '/guzzle-server/perf', null, [ 'connect_timeout' => 1, 'timeout' => 1 ]); return !isset($response['error']); } private static function send( $method, $path, $body = null, array $client = [] ) { $handler = new StreamHandler(); $request = [ 'http_method' => $method, 'uri' => $path, 'request_port' => 8125, 'headers' => ['host' => ['127.0.0.1:8125']], 'body' => $body, 'client' => $client, ]; if ($body) { $request['headers']['content-length'] = [strlen($body)]; } return $handler($request); } } tests/Client/MiddlewareTest.php000077700000004163151323632020012562 0ustar00<?php namespace GuzzleHttp\Tests\Ring\Client; use GuzzleHttp\Ring\Client\Middleware; use GuzzleHttp\Ring\Future\CompletedFutureArray; class MiddlewareTest extends \PHPUnit_Framework_TestCase { public function testFutureCallsDefaultHandler() { $future = new CompletedFutureArray(['status' => 200]); $calledA = false; $a = function (array $req) use (&$calledA, $future) { $calledA = true; return $future; }; $calledB = false; $b = function (array $req) use (&$calledB) { $calledB = true; }; $s = Middleware::wrapFuture($a, $b); $s([]); $this->assertTrue($calledA); $this->assertFalse($calledB); } public function testFutureCallsStreamingHandler() { $future = new CompletedFutureArray(['status' => 200]); $calledA = false; $a = function (array $req) use (&$calledA) { $calledA = true; }; $calledB = false; $b = function (array $req) use (&$calledB, $future) { $calledB = true; return $future; }; $s = Middleware::wrapFuture($a, $b); $result = $s(['client' => ['future' => true]]); $this->assertFalse($calledA); $this->assertTrue($calledB); $this->assertSame($future, $result); } public function testStreamingCallsDefaultHandler() { $calledA = false; $a = function (array $req) use (&$calledA) { $calledA = true; }; $calledB = false; $b = function (array $req) use (&$calledB) { $calledB = true; }; $s = Middleware::wrapStreaming($a, $b); $s([]); $this->assertTrue($calledA); $this->assertFalse($calledB); } public function testStreamingCallsStreamingHandler() { $calledA = false; $a = function (array $req) use (&$calledA) { $calledA = true; }; $calledB = false; $b = function (array $req) use (&$calledB) { $calledB = true; }; $s = Middleware::wrapStreaming($a, $b); $s(['client' => ['stream' => true]]); $this->assertFalse($calledA); $this->assertTrue($calledB); } } tests/Client/StreamHandlerTest.php000077700000037376151323632020013252 0ustar00<?php namespace GuzzleHttp\Tests\Ring\Client; use GuzzleHttp\Ring\Client\ClientUtils; use GuzzleHttp\Ring\Core; use GuzzleHttp\Ring\Client\StreamHandler; class StreamHandlerTest extends \PHPUnit_Framework_TestCase { public function testReturnsResponseForSuccessfulRequest() { $this->queueRes(); $handler = new StreamHandler(); $response = $handler([ 'http_method' => 'GET', 'uri' => '/', 'headers' => [ 'host' => [Server::$host], 'Foo' => ['Bar'], ], ]); $this->assertEquals('1.1', $response['version']); $this->assertEquals(200, $response['status']); $this->assertEquals('OK', $response['reason']); $this->assertEquals(['Bar'], $response['headers']['Foo']); $this->assertEquals(['8'], $response['headers']['Content-Length']); $this->assertEquals('hi there', Core::body($response)); $sent = Server::received()[0]; $this->assertEquals('GET', $sent['http_method']); $this->assertEquals('/', $sent['resource']); $this->assertEquals(['127.0.0.1:8125'], $sent['headers']['host']); $this->assertEquals('Bar', Core::header($sent, 'foo')); } public function testAddsErrorToResponse() { $handler = new StreamHandler(); $result = $handler([ 'http_method' => 'GET', 'headers' => ['host' => ['localhost:123']], 'client' => ['timeout' => 0.01], ]); $this->assertInstanceOf( 'GuzzleHttp\Ring\Future\CompletedFutureArray', $result ); $this->assertNull($result['status']); $this->assertNull($result['body']); $this->assertEquals([], $result['headers']); $this->assertInstanceOf( 'GuzzleHttp\Ring\Exception\RingException', $result['error'] ); } public function testEnsuresTheHttpProtocol() { $handler = new StreamHandler(); $result = $handler([ 'http_method' => 'GET', 'url' => 'ftp://localhost:123', ]); $this->assertArrayHasKey('error', $result); $this->assertContains( 'URL is invalid: ftp://localhost:123', $result['error']->getMessage() ); } public function testStreamAttributeKeepsStreamOpen() { $this->queueRes(); $handler = new StreamHandler(); $response = $handler([ 'http_method' => 'PUT', 'uri' => '/foo', 'query_string' => 'baz=bar', 'headers' => [ 'host' => [Server::$host], 'Foo' => ['Bar'], ], 'body' => 'test', 'client' => ['stream' => true], ]); $this->assertEquals(200, $response['status']); $this->assertEquals('OK', $response['reason']); $this->assertEquals('8', Core::header($response, 'Content-Length')); $body = $response['body']; $this->assertTrue(is_resource($body)); $this->assertEquals('http', stream_get_meta_data($body)['wrapper_type']); $this->assertEquals('hi there', stream_get_contents($body)); fclose($body); $sent = Server::received()[0]; $this->assertEquals('PUT', $sent['http_method']); $this->assertEquals('/foo', $sent['uri']); $this->assertEquals('baz=bar', $sent['query_string']); $this->assertEquals('/foo?baz=bar', $sent['resource']); $this->assertEquals('127.0.0.1:8125', Core::header($sent, 'host')); $this->assertEquals('Bar', Core::header($sent, 'foo')); } public function testDrainsResponseIntoTempStream() { $this->queueRes(); $handler = new StreamHandler(); $response = $handler([ 'http_method' => 'GET', 'uri' => '/', 'headers' => ['host' => [Server::$host]], ]); $body = $response['body']; $this->assertEquals('php://temp', stream_get_meta_data($body)['uri']); $this->assertEquals('hi', fread($body, 2)); fclose($body); } public function testDrainsResponseIntoSaveToBody() { $r = fopen('php://temp', 'r+'); $this->queueRes(); $handler = new StreamHandler(); $response = $handler([ 'http_method' => 'GET', 'uri' => '/', 'headers' => ['host' => [Server::$host]], 'client' => ['save_to' => $r], ]); $body = $response['body']; $this->assertEquals('php://temp', stream_get_meta_data($body)['uri']); $this->assertEquals('hi', fread($body, 2)); $this->assertEquals(' there', stream_get_contents($r)); fclose($r); } public function testDrainsResponseIntoSaveToBodyAtPath() { $tmpfname = tempnam('/tmp', 'save_to_path'); $this->queueRes(); $handler = new StreamHandler(); $response = $handler([ 'http_method' => 'GET', 'uri' => '/', 'headers' => ['host' => [Server::$host]], 'client' => ['save_to' => $tmpfname], ]); $body = $response['body']; $this->assertInstanceOf('GuzzleHttp\Stream\StreamInterface', $body); $this->assertEquals($tmpfname, $body->getMetadata('uri')); $this->assertEquals('hi', $body->read(2)); $body->close(); unlink($tmpfname); } public function testAutomaticallyDecompressGzip() { Server::flush(); $content = gzencode('test'); Server::enqueue([ [ 'status' => 200, 'reason' => 'OK', 'headers' => [ 'Content-Encoding' => ['gzip'], 'Content-Length' => [strlen($content)], ], 'body' => $content, ], ]); $handler = new StreamHandler(); $response = $handler([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'uri' => '/', 'client' => ['decode_content' => true], ]); $this->assertEquals('test', Core::body($response)); } public function testDoesNotForceGzipDecode() { Server::flush(); $content = gzencode('test'); Server::enqueue([ [ 'status' => 200, 'reason' => 'OK', 'headers' => [ 'Content-Encoding' => ['gzip'], 'Content-Length' => [strlen($content)], ], 'body' => $content, ], ]); $handler = new StreamHandler(); $response = $handler([ 'http_method' => 'GET', 'headers' => ['host' => [Server::$host]], 'uri' => '/', 'client' => ['stream' => true, 'decode_content' => false], ]); $this->assertSame($content, Core::body($response)); } public function testProtocolVersion() { $this->queueRes(); $handler = new StreamHandler(); $handler([ 'http_method' => 'GET', 'uri' => '/', 'headers' => ['host' => [Server::$host]], 'version' => 1.0, ]); $this->assertEquals(1.0, Server::received()[0]['version']); } protected function getSendResult(array $opts) { $this->queueRes(); $handler = new StreamHandler(); $opts['stream'] = true; return $handler([ 'http_method' => 'GET', 'uri' => '/', 'headers' => ['host' => [Server::$host]], 'client' => $opts, ]); } public function testAddsProxy() { $res = $this->getSendResult(['stream' => true, 'proxy' => '127.0.0.1:8125']); $opts = stream_context_get_options($res['body']); $this->assertEquals('127.0.0.1:8125', $opts['http']['proxy']); } public function testAddsTimeout() { $res = $this->getSendResult(['stream' => true, 'timeout' => 200]); $opts = stream_context_get_options($res['body']); $this->assertEquals(200, $opts['http']['timeout']); } public function testVerifiesVerifyIsValidIfPath() { $res = $this->getSendResult(['verify' => '/does/not/exist']); $this->assertContains( 'SSL CA bundle not found: /does/not/exist', (string) $res['error'] ); } public function testVerifyCanBeDisabled() { $res = $this->getSendResult(['verify' => false]); $this->assertArrayNotHasKey('error', $res); } public function testVerifiesCertIfValidPath() { $res = $this->getSendResult(['cert' => '/does/not/exist']); $this->assertContains( 'SSL certificate not found: /does/not/exist', (string) $res['error'] ); } public function testVerifyCanBeSetToPath() { $path = $path = ClientUtils::getDefaultCaBundle(); $res = $this->getSendResult(['verify' => $path]); $this->assertArrayNotHasKey('error', $res); $opts = stream_context_get_options($res['body']); $this->assertEquals(true, $opts['ssl']['verify_peer']); $this->assertEquals($path, $opts['ssl']['cafile']); $this->assertTrue(file_exists($opts['ssl']['cafile'])); } public function testUsesSystemDefaultBundle() { $path = $path = ClientUtils::getDefaultCaBundle(); $res = $this->getSendResult(['verify' => true]); $this->assertArrayNotHasKey('error', $res); $opts = stream_context_get_options($res['body']); if (PHP_VERSION_ID < 50600) { $this->assertEquals($path, $opts['ssl']['cafile']); } } public function testEnsuresVerifyOptionIsValid() { $res = $this->getSendResult(['verify' => 10]); $this->assertContains( 'Invalid verify request option', (string) $res['error'] ); } public function testCanSetPasswordWhenSettingCert() { $path = __FILE__; $res = $this->getSendResult(['cert' => [$path, 'foo']]); $opts = stream_context_get_options($res['body']); $this->assertEquals($path, $opts['ssl']['local_cert']); $this->assertEquals('foo', $opts['ssl']['passphrase']); } public function testDebugAttributeWritesToStream() { $this->queueRes(); $f = fopen('php://temp', 'w+'); $this->getSendResult(['debug' => $f]); fseek($f, 0); $contents = stream_get_contents($f); $this->assertContains('<GET http://127.0.0.1:8125/> [CONNECT]', $contents); $this->assertContains('<GET http://127.0.0.1:8125/> [FILE_SIZE_IS]', $contents); $this->assertContains('<GET http://127.0.0.1:8125/> [PROGRESS]', $contents); } public function testDebugAttributeWritesStreamInfoToBuffer() { $called = false; $this->queueRes(); $buffer = fopen('php://temp', 'r+'); $this->getSendResult([ 'progress' => function () use (&$called) { $called = true; }, 'debug' => $buffer, ]); fseek($buffer, 0); $contents = stream_get_contents($buffer); $this->assertContains('<GET http://127.0.0.1:8125/> [CONNECT]', $contents); $this->assertContains('<GET http://127.0.0.1:8125/> [FILE_SIZE_IS] message: "Content-Length: 8"', $contents); $this->assertContains('<GET http://127.0.0.1:8125/> [PROGRESS] bytes_max: "8"', $contents); $this->assertTrue($called); } public function testEmitsProgressInformation() { $called = []; $this->queueRes(); $this->getSendResult([ 'progress' => function () use (&$called) { $called[] = func_get_args(); }, ]); $this->assertNotEmpty($called); $this->assertEquals(8, $called[0][0]); $this->assertEquals(0, $called[0][1]); } public function testEmitsProgressInformationAndDebugInformation() { $called = []; $this->queueRes(); $buffer = fopen('php://memory', 'w+'); $this->getSendResult([ 'debug' => $buffer, 'progress' => function () use (&$called) { $called[] = func_get_args(); }, ]); $this->assertNotEmpty($called); $this->assertEquals(8, $called[0][0]); $this->assertEquals(0, $called[0][1]); rewind($buffer); $this->assertNotEmpty(stream_get_contents($buffer)); fclose($buffer); } public function testAddsProxyByProtocol() { $url = str_replace('http', 'tcp', Server::$url); $res = $this->getSendResult(['proxy' => ['http' => $url]]); $opts = stream_context_get_options($res['body']); $this->assertEquals($url, $opts['http']['proxy']); } public function testPerformsShallowMergeOfCustomContextOptions() { $res = $this->getSendResult([ 'stream_context' => [ 'http' => [ 'request_fulluri' => true, 'method' => 'HEAD', ], 'socket' => [ 'bindto' => '127.0.0.1:0', ], 'ssl' => [ 'verify_peer' => false, ], ], ]); $opts = stream_context_get_options($res['body']); $this->assertEquals('HEAD', $opts['http']['method']); $this->assertTrue($opts['http']['request_fulluri']); $this->assertFalse($opts['ssl']['verify_peer']); $this->assertEquals('127.0.0.1:0', $opts['socket']['bindto']); } public function testEnsuresThatStreamContextIsAnArray() { $res = $this->getSendResult(['stream_context' => 'foo']); $this->assertContains( 'stream_context must be an array', (string) $res['error'] ); } public function testDoesNotAddContentTypeByDefault() { $this->queueRes(); $handler = new StreamHandler(); $handler([ 'http_method' => 'PUT', 'uri' => '/', 'headers' => ['host' => [Server::$host], 'content-length' => [3]], 'body' => 'foo', ]); $req = Server::received()[0]; $this->assertEquals('', Core::header($req, 'Content-Type')); $this->assertEquals(3, Core::header($req, 'Content-Length')); } private function queueRes() { Server::flush(); Server::enqueue([ [ 'status' => 200, 'reason' => 'OK', 'headers' => [ 'Foo' => ['Bar'], 'Content-Length' => [8], ], 'body' => 'hi there', ], ]); } public function testSupports100Continue() { Server::flush(); Server::enqueue([ [ 'status' => '200', 'reason' => 'OK', 'headers' => [ 'Test' => ['Hello'], 'Content-Length' => ['4'], ], 'body' => 'test', ], ]); $request = [ 'http_method' => 'PUT', 'headers' => [ 'Host' => [Server::$host], 'Expect' => ['100-Continue'], ], 'body' => 'test', ]; $handler = new StreamHandler(); $response = $handler($request); $this->assertEquals(200, $response['status']); $this->assertEquals('OK', $response['reason']); $this->assertEquals(['Hello'], $response['headers']['Test']); $this->assertEquals(['4'], $response['headers']['Content-Length']); $this->assertEquals('test', Core::body($response)); } } tests/Client/.htaccess000077700000000177151323632020010733 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>tests/.htaccess000077700000000177151323632020007515 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>tests/Future/FutureArrayTest.php000077700000003140151323632020013004 0ustar00<?php namespace GuzzleHttp\Tests\Ring\Future; use GuzzleHttp\Ring\Future\FutureArray; use React\Promise\Deferred; class FutureArrayTest extends \PHPUnit_Framework_TestCase { public function testLazilyCallsDeref() { $c = false; $deferred = new Deferred(); $f = new FutureArray( $deferred->promise(), function () use (&$c, $deferred) { $c = true; $deferred->resolve(['status' => 200]); } ); $this->assertFalse($c); $this->assertFalse($this->readAttribute($f, 'isRealized')); $this->assertEquals(200, $f['status']); $this->assertTrue($c); } public function testActsLikeArray() { $deferred = new Deferred(); $f = new FutureArray( $deferred->promise(), function () use (&$c, $deferred) { $deferred->resolve(['status' => 200]); } ); $this->assertTrue(isset($f['status'])); $this->assertEquals(200, $f['status']); $this->assertEquals(['status' => 200], $f->wait()); $this->assertEquals(1, count($f)); $f['baz'] = 10; $this->assertEquals(10, $f['baz']); unset($f['baz']); $this->assertFalse(isset($f['baz'])); $this->assertEquals(['status' => 200], iterator_to_array($f)); } /** * @expectedException \RuntimeException */ public function testThrowsWhenAccessingInvalidProperty() { $deferred = new Deferred(); $f = new FutureArray($deferred->promise(), function () {}); $f->foo; } } tests/Future/.htaccess000077700000000177151323632020010767 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>tests/Future/CompletedFutureValueTest.php000077700000002221151323632020014636 0ustar00<?php namespace GuzzleHttp\Tests\Ring\Future; use GuzzleHttp\Ring\Exception\CancelledFutureAccessException; use GuzzleHttp\Ring\Future\CompletedFutureValue; class CompletedFutureValueTest extends \PHPUnit_Framework_TestCase { public function testReturnsValue() { $f = new CompletedFutureValue('hi'); $this->assertEquals('hi', $f->wait()); $f->cancel(); $a = null; $f->then(function ($v) use (&$a) { $a = $v; }); $this->assertSame('hi', $a); } public function testThrows() { $ex = new \Exception('foo'); $f = new CompletedFutureValue(null, $ex); $f->cancel(); try { $f->wait(); $this->fail('did not throw'); } catch (\Exception $e) { $this->assertSame($e, $ex); } } public function testMarksAsCancelled() { $ex = new CancelledFutureAccessException(); $f = new CompletedFutureValue(null, $ex); try { $f->wait(); $this->fail('did not throw'); } catch (\Exception $e) { $this->assertSame($e, $ex); } } } tests/Future/FutureValueTest.php000077700000005546151323632020013016 0ustar00<?php namespace GuzzleHttp\Tests\Ring\Future; use GuzzleHttp\Ring\Exception\CancelledFutureAccessException; use GuzzleHttp\Ring\Future\FutureValue; use React\Promise\Deferred; class FutureValueTest extends \PHPUnit_Framework_TestCase { public function testDerefReturnsValue() { $called = 0; $deferred = new Deferred(); $f = new FutureValue( $deferred->promise(), function () use ($deferred, &$called) { $called++; $deferred->resolve('foo'); } ); $this->assertEquals('foo', $f->wait()); $this->assertEquals(1, $called); $this->assertEquals('foo', $f->wait()); $this->assertEquals(1, $called); $f->cancel(); $this->assertTrue($this->readAttribute($f, 'isRealized')); } /** * @expectedException \GuzzleHttp\Ring\Exception\CancelledFutureAccessException */ public function testThrowsWhenAccessingCancelled() { $f = new FutureValue( (new Deferred())->promise(), function () {}, function () { return true; } ); $f->cancel(); $f->wait(); } /** * @expectedException \OutOfBoundsException */ public function testThrowsWhenDerefFailure() { $called = false; $deferred = new Deferred(); $f = new FutureValue( $deferred->promise(), function () use(&$called) { $called = true; } ); $deferred->reject(new \OutOfBoundsException()); $f->wait(); $this->assertFalse($called); } /** * @expectedException \GuzzleHttp\Ring\Exception\RingException * @expectedExceptionMessage Waiting did not resolve future */ public function testThrowsWhenDerefDoesNotResolve() { $deferred = new Deferred(); $f = new FutureValue( $deferred->promise(), function () use(&$called) { $called = true; } ); $f->wait(); } public function testThrowingCancelledFutureAccessExceptionCancels() { $deferred = new Deferred(); $f = new FutureValue( $deferred->promise(), function () use ($deferred) { throw new CancelledFutureAccessException(); } ); try { $f->wait(); $this->fail('did not throw'); } catch (CancelledFutureAccessException $e) {} } /** * @expectedException \Exception * @expectedExceptionMessage foo */ public function testThrowingExceptionInDerefMarksAsFailed() { $deferred = new Deferred(); $f = new FutureValue( $deferred->promise(), function () { throw new \Exception('foo'); } ); $f->wait(); } } tests/Future/CompletedFutureArrayTest.php000077700000001307151323632020014644 0ustar00<?php namespace GuzzleHttp\Tests\Ring\Future; use GuzzleHttp\Ring\Future\CompletedFutureArray; class CompletedFutureArrayTest extends \PHPUnit_Framework_TestCase { public function testReturnsAsArray() { $f = new CompletedFutureArray(['foo' => 'bar']); $this->assertEquals('bar', $f['foo']); $this->assertFalse(isset($f['baz'])); $f['abc'] = '123'; $this->assertTrue(isset($f['abc'])); $this->assertEquals(['foo' => 'bar', 'abc' => '123'], iterator_to_array($f)); $this->assertEquals(2, count($f)); unset($f['abc']); $this->assertEquals(1, count($f)); $this->assertEquals(['foo' => 'bar'], iterator_to_array($f)); } } phpunit.xml.dist000077700000000543151323632020007725 0ustar00<?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap="./tests/bootstrap.php" colors="true"> <testsuites> <testsuite> <directory>tests</directory> </testsuite> </testsuites> <filter> <whitelist> <directory suffix=".php">src</directory> </whitelist> </filter> </phpunit>
/var/www/html/dhandapani/e5964/../dev/../9da53/ringphp.tar