summaryrefslogtreecommitdiff
path: root/inc/mailgun/php-http/curl-client/src/Client.php
diff options
context:
space:
mode:
Diffstat (limited to 'inc/mailgun/php-http/curl-client/src/Client.php')
-rw-r--r--inc/mailgun/php-http/curl-client/src/Client.php371
1 files changed, 371 insertions, 0 deletions
diff --git a/inc/mailgun/php-http/curl-client/src/Client.php b/inc/mailgun/php-http/curl-client/src/Client.php
new file mode 100644
index 0000000..5696ab3
--- /dev/null
+++ b/inc/mailgun/php-http/curl-client/src/Client.php
@@ -0,0 +1,371 @@
+<?php
+namespace Http\Client\Curl;
+
+use Http\Client\Exception;
+use Http\Client\HttpAsyncClient;
+use Http\Client\HttpClient;
+use Http\Discovery\MessageFactoryDiscovery;
+use Http\Discovery\StreamFactoryDiscovery;
+use Http\Message\MessageFactory;
+use Http\Message\StreamFactory;
+use Http\Promise\Promise;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * PSR-7 compatible cURL based HTTP client
+ *
+ * @license http://opensource.org/licenses/MIT MIT
+ *
+ * @author Михаил Красильников <m.krasilnikov@yandex.ru>
+ * @author Blake Williams <github@shabbyrobe.org>
+ *
+ * @api
+ * @since 1.0
+ */
+class Client implements HttpClient, HttpAsyncClient
+{
+ /**
+ * cURL options
+ *
+ * @var array
+ */
+ private $options;
+
+ /**
+ * PSR-7 message factory
+ *
+ * @var MessageFactory
+ */
+ private $messageFactory;
+
+ /**
+ * PSR-7 stream factory
+ *
+ * @var StreamFactory
+ */
+ private $streamFactory;
+
+ /**
+ * cURL synchronous requests handle
+ *
+ * @var resource|null
+ */
+ private $handle = null;
+
+ /**
+ * Simultaneous requests runner
+ *
+ * @var MultiRunner|null
+ */
+ private $multiRunner = null;
+
+ /**
+ * Create new client
+ *
+ * @param MessageFactory|null $messageFactory HTTP Message factory
+ * @param StreamFactory|null $streamFactory HTTP Stream factory
+ * @param array $options cURL options (see http://php.net/curl_setopt)
+ *
+ * @throws \Http\Discovery\Exception\NotFoundException If factory discovery failed.
+ *
+ * @since 1.0
+ */
+ public function __construct(
+ MessageFactory $messageFactory = null,
+ StreamFactory $streamFactory = null,
+ array $options = []
+ ) {
+ $this->messageFactory = $messageFactory ?: MessageFactoryDiscovery::find();
+ $this->streamFactory = $streamFactory ?: StreamFactoryDiscovery::find();
+ $this->options = $options;
+ }
+
+ /**
+ * Release resources if still active
+ */
+ public function __destruct()
+ {
+ if (is_resource($this->handle)) {
+ curl_close($this->handle);
+ }
+ }
+
+ /**
+ * Sends a PSR-7 request.
+ *
+ * @param RequestInterface $request
+ *
+ * @return ResponseInterface
+ *
+ * @throws \Http\Client\Exception\NetworkException In case of network problems.
+ * @throws \Http\Client\Exception\RequestException On invalid request.
+ * @throws \InvalidArgumentException For invalid header names or values.
+ * @throws \RuntimeException If creating the body stream fails.
+ *
+ * @since 1.6 \UnexpectedValueException replaced with RequestException.
+ * @since 1.6 Throw NetworkException on network errors.
+ * @since 1.0
+ */
+ public function sendRequest(RequestInterface $request)
+ {
+ $responseBuilder = $this->createResponseBuilder();
+ $options = $this->createCurlOptions($request, $responseBuilder);
+
+ if (is_resource($this->handle)) {
+ curl_reset($this->handle);
+ } else {
+ $this->handle = curl_init();
+ }
+
+ curl_setopt_array($this->handle, $options);
+ curl_exec($this->handle);
+
+ $errno = curl_errno($this->handle);
+ switch ($errno) {
+ case CURLE_OK:
+ // All OK, no actions needed.
+ break;
+ case CURLE_COULDNT_RESOLVE_PROXY:
+ case CURLE_COULDNT_RESOLVE_HOST:
+ case CURLE_COULDNT_CONNECT:
+ case CURLE_OPERATION_TIMEOUTED:
+ case CURLE_SSL_CONNECT_ERROR:
+ throw new Exception\NetworkException(curl_error($this->handle), $request);
+ default:
+ throw new Exception\RequestException(curl_error($this->handle), $request);
+ }
+
+ $response = $responseBuilder->getResponse();
+ $response->getBody()->seek(0);
+
+ return $response;
+ }
+
+ /**
+ * Sends a PSR-7 request in an asynchronous way.
+ *
+ * @param RequestInterface $request
+ *
+ * @return Promise
+ *
+ * @throws \Http\Client\Exception\RequestException On invalid request.
+ * @throws \InvalidArgumentException For invalid header names or values.
+ * @throws \RuntimeException If creating the body stream fails.
+ *
+ * @since 1.6 \UnexpectedValueException replaced with RequestException.
+ * @since 1.0
+ */
+ public function sendAsyncRequest(RequestInterface $request)
+ {
+ if (!$this->multiRunner instanceof MultiRunner) {
+ $this->multiRunner = new MultiRunner();
+ }
+
+ $handle = curl_init();
+ $responseBuilder = $this->createResponseBuilder();
+ $options = $this->createCurlOptions($request, $responseBuilder);
+ curl_setopt_array($handle, $options);
+
+ $core = new PromiseCore($request, $handle, $responseBuilder);
+ $promise = new CurlPromise($core, $this->multiRunner);
+ $this->multiRunner->add($core);
+
+ return $promise;
+ }
+
+ /**
+ * Generates cURL options
+ *
+ * @param RequestInterface $request
+ * @param ResponseBuilder $responseBuilder
+ *
+ * @throws \Http\Client\Exception\RequestException On invalid request.
+ * @throws \InvalidArgumentException For invalid header names or values.
+ * @throws \RuntimeException if can not read body
+ *
+ * @return array
+ */
+ private function createCurlOptions(RequestInterface $request, ResponseBuilder $responseBuilder)
+ {
+ $options = $this->options;
+
+ $options[CURLOPT_HEADER] = false;
+ $options[CURLOPT_RETURNTRANSFER] = false;
+ $options[CURLOPT_FOLLOWLOCATION] = false;
+
+ try {
+ $options[CURLOPT_HTTP_VERSION]
+ = $this->getProtocolVersion($request->getProtocolVersion());
+ } catch (\UnexpectedValueException $e) {
+ throw new Exception\RequestException($e->getMessage(), $request);
+ }
+ $options[CURLOPT_URL] = (string) $request->getUri();
+
+ $options = $this->addRequestBodyOptions($request, $options);
+
+ $options[CURLOPT_HTTPHEADER] = $this->createHeaders($request, $options);
+
+ if ($request->getUri()->getUserInfo()) {
+ $options[CURLOPT_USERPWD] = $request->getUri()->getUserInfo();
+ }
+
+ $options[CURLOPT_HEADERFUNCTION] = function ($ch, $data) use ($responseBuilder) {
+ $str = trim($data);
+ if ('' !== $str) {
+ if (strpos(strtolower($str), 'http/') === 0) {
+ $responseBuilder->setStatus($str)->getResponse();
+ } else {
+ $responseBuilder->addHeader($str);
+ }
+ }
+
+ return strlen($data);
+ };
+
+ $options[CURLOPT_WRITEFUNCTION] = function ($ch, $data) use ($responseBuilder) {
+ return $responseBuilder->getResponse()->getBody()->write($data);
+ };
+
+ return $options;
+ }
+
+ /**
+ * Return cURL constant for specified HTTP version
+ *
+ * @param string $requestVersion
+ *
+ * @throws \UnexpectedValueException if unsupported version requested
+ *
+ * @return int
+ */
+ private function getProtocolVersion($requestVersion)
+ {
+ switch ($requestVersion) {
+ case '1.0':
+ return CURL_HTTP_VERSION_1_0;
+ case '1.1':
+ return CURL_HTTP_VERSION_1_1;
+ case '2.0':
+ if (defined('CURL_HTTP_VERSION_2_0')) {
+ return CURL_HTTP_VERSION_2_0;
+ }
+ throw new \UnexpectedValueException('libcurl 7.33 needed for HTTP 2.0 support');
+ }
+
+ return CURL_HTTP_VERSION_NONE;
+ }
+
+ /**
+ * Add request body related cURL options.
+ *
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return array
+ */
+ private function addRequestBodyOptions(RequestInterface $request, array $options)
+ {
+ /*
+ * Some HTTP methods cannot have payload:
+ *
+ * - GET — cURL will automatically change method to PUT or POST if we set CURLOPT_UPLOAD or
+ * CURLOPT_POSTFIELDS.
+ * - HEAD — cURL treats HEAD as GET request with a same restrictions.
+ * - TRACE — According to RFC7231: a client MUST NOT send a message body in a TRACE request.
+ */
+ if (!in_array($request->getMethod(), ['GET', 'HEAD', 'TRACE'], true)) {
+ $body = $request->getBody();
+ $bodySize = $body->getSize();
+ if ($bodySize !== 0) {
+ if ($body->isSeekable()) {
+ $body->rewind();
+ }
+
+ // Message has non empty body.
+ if (null === $bodySize || $bodySize > 1024 * 1024) {
+ // Avoid full loading large or unknown size body into memory
+ $options[CURLOPT_UPLOAD] = true;
+ if (null !== $bodySize) {
+ $options[CURLOPT_INFILESIZE] = $bodySize;
+ }
+ $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
+ return $body->read($length);
+ };
+ } else {
+ // Small body can be loaded into memory
+ $options[CURLOPT_POSTFIELDS] = (string) $body;
+ }
+ }
+ }
+
+ if ($request->getMethod() === 'HEAD') {
+ // This will set HTTP method to "HEAD".
+ $options[CURLOPT_NOBODY] = true;
+ } elseif ($request->getMethod() !== 'GET') {
+ // GET is a default method. Other methods should be specified explicitly.
+ $options[CURLOPT_CUSTOMREQUEST] = $request->getMethod();
+ }
+
+ return $options;
+ }
+
+ /**
+ * Create headers array for CURLOPT_HTTPHEADER
+ *
+ * @param RequestInterface $request
+ * @param array $options cURL options
+ *
+ * @return string[]
+ */
+ private function createHeaders(RequestInterface $request, array $options)
+ {
+ $curlHeaders = [];
+ $headers = $request->getHeaders();
+ foreach ($headers as $name => $values) {
+ $header = strtolower($name);
+ if ('expect' === $header) {
+ // curl-client does not support "Expect-Continue", so dropping "expect" headers
+ continue;
+ }
+ if ('content-length' === $header) {
+ if (array_key_exists(CURLOPT_POSTFIELDS, $options)) {
+ // Small body content length can be calculated here.
+ $values = [strlen($options[CURLOPT_POSTFIELDS])];
+ } elseif (!array_key_exists(CURLOPT_READFUNCTION, $options)) {
+ // Else if there is no body, forcing "Content-length" to 0
+ $values = [0];
+ }
+ }
+ foreach ($values as $value) {
+ $curlHeaders[] = $name . ': ' . $value;
+ }
+ }
+ /*
+ * curl-client does not support "Expect-Continue", but cURL adds "Expect" header by default.
+ * We can not suppress it, but we can set it to empty.
+ */
+ $curlHeaders[] = 'Expect:';
+
+ return $curlHeaders;
+ }
+
+ /**
+ * Create new ResponseBuilder instance
+ *
+ * @return ResponseBuilder
+ *
+ * @throws \RuntimeException If creating the stream from $body fails.
+ */
+ private function createResponseBuilder()
+ {
+ try {
+ $body = $this->streamFactory->createStream(fopen('php://temp', 'w+b'));
+ } catch (\InvalidArgumentException $e) {
+ throw new \RuntimeException('Can not create "php://temp" stream.');
+ }
+ $response = $this->messageFactory->createResponse(200, null, [], $body);
+
+ return new ResponseBuilder($response);
+ }
+}