-
Notifications
You must be signed in to change notification settings - Fork 27
Corrected a problem with memory #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1ff99d8
b37f2d6
abbf8fa
32ab33f
0e5cfd0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
<?php | ||
|
||
namespace Http\Client\Curl; | ||
|
||
use Http\Client\Exception; | ||
|
@@ -18,39 +19,42 @@ | |
* | ||
* @author Михаил Красильников <[email protected]> | ||
* @author Blake Williams <[email protected]> | ||
* @author Dmitry Arhitector <[email protected]> | ||
* | ||
* @api | ||
* @since 1.0 | ||
*/ | ||
class Client implements HttpClient, HttpAsyncClient | ||
{ | ||
/** | ||
|
||
/** | ||
* cURL options | ||
* | ||
* @var array | ||
*/ | ||
private $options; | ||
protected $options; | ||
|
||
/** | ||
* cURL response parser | ||
* | ||
* @var ResponseParser | ||
*/ | ||
private $responseParser; | ||
protected $responseParser; | ||
|
||
/** | ||
* cURL synchronous requests handle | ||
* | ||
* @var resource|null | ||
*/ | ||
private $handle = null; | ||
protected $handle = null; | ||
|
||
/** | ||
* Simultaneous requests runner | ||
* | ||
* @var MultiRunner|null | ||
*/ | ||
private $multiRunner = null; | ||
protected $multiRunner = null; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why the extra line. |
||
|
||
/** | ||
* Create new client | ||
|
@@ -68,29 +72,17 @@ class Client implements HttpClient, HttpAsyncClient | |
* | ||
* @since 1.0 | ||
*/ | ||
public function __construct( | ||
MessageFactory $messageFactory, | ||
StreamFactory $streamFactory, | ||
array $options = [] | ||
) { | ||
public function __construct(MessageFactory $messageFactory, StreamFactory $streamFactory, array $options = []) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please revert this, this is because PSR conforming. |
||
{ | ||
$this->responseParser = new ResponseParser($messageFactory, $streamFactory); | ||
$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 | ||
* @param array $options custom curl options | ||
* | ||
* @return ResponseInterface | ||
* | ||
|
@@ -99,27 +91,24 @@ public function __destruct() | |
* | ||
* @since 1.0 | ||
*/ | ||
public function sendRequest(RequestInterface $request) | ||
public function sendRequest(RequestInterface $request, array $options = []) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please don't add this. Use the constructor. |
||
{ | ||
$options = $this->createCurlOptions($request); | ||
|
||
if (is_resource($this->handle)) { | ||
curl_reset($this->handle); | ||
} else { | ||
$this->handle = curl_init(); | ||
} | ||
|
||
$options = $this->createCurlOptions($request, $options); | ||
|
||
curl_setopt_array($this->handle, $options); | ||
$raw = curl_exec($this->handle); | ||
|
||
if (curl_errno($this->handle) > 0) { | ||
if ( ! curl_exec($this->handle)) { | ||
throw new RequestException(curl_error($this->handle), $request); | ||
} | ||
|
||
$info = curl_getinfo($this->handle); | ||
|
||
try { | ||
$response = $this->responseParser->parse($raw, $info); | ||
$response = $this->responseParser->parse($options[CURLOPT_FILE], curl_getinfo($this->handle)); | ||
} catch (\Exception $e) { | ||
throw new RequestException($e->getMessage(), $request, $e); | ||
} | ||
|
@@ -130,6 +119,7 @@ public function sendRequest(RequestInterface $request) | |
* Sends a PSR-7 request in an asynchronous way. | ||
* | ||
* @param RequestInterface $request | ||
* @param array $options custom curl options | ||
* | ||
* @return Promise | ||
* | ||
|
@@ -138,66 +128,111 @@ public function sendRequest(RequestInterface $request) | |
* | ||
* @since 1.0 | ||
*/ | ||
public function sendAsyncRequest(RequestInterface $request) | ||
public function sendAsyncRequest(RequestInterface $request, array $options = []) | ||
{ | ||
if (!$this->multiRunner instanceof MultiRunner) { | ||
if ( ! $this->multiRunner instanceof MultiRunner) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please revert this. No spaces (PSR) |
||
$this->multiRunner = new MultiRunner($this->responseParser); | ||
} | ||
|
||
$handle = curl_init(); | ||
$options = $this->createCurlOptions($request); | ||
$options = $this->createCurlOptions($request, $options); | ||
|
||
curl_setopt_array($handle, $options); | ||
|
||
$core = new PromiseCore($request, $handle); | ||
$promise = new CurlPromise($core, $this->multiRunner); | ||
|
||
$this->multiRunner->add($core); | ||
|
||
return $promise; | ||
} | ||
|
||
/** | ||
* Get parser | ||
* | ||
* @return ResponseParser | ||
*/ | ||
public function getResponseParser() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't necessary. |
||
{ | ||
return $this->responseParser; | ||
} | ||
|
||
/** | ||
* Release resources if still active | ||
*/ | ||
public function __destruct() | ||
{ | ||
if (is_resource($this->handle)) { | ||
curl_close($this->handle); | ||
} | ||
} | ||
|
||
/** | ||
* Generates cURL options | ||
* | ||
* @param RequestInterface $request | ||
* @param array $options custom curl options | ||
* | ||
* @throws \UnexpectedValueException if unsupported HTTP version requested | ||
* | ||
* @return array | ||
*/ | ||
private function createCurlOptions(RequestInterface $request) | ||
{ | ||
$options = $this->options; | ||
|
||
$options[CURLOPT_HEADER] = true; | ||
$options[CURLOPT_RETURNTRANSFER] = true; | ||
$options[CURLOPT_FOLLOWLOCATION] = false; | ||
|
||
protected function createCurlOptions(RequestInterface $request, array $options = []) | ||
{ | ||
// Invalid overwrite Curl options. | ||
$options = array_diff_key($options + $this->options, array_flip([CURLOPT_INFILE, CURLOPT_INFILESIZE])); | ||
$options[CURLOPT_HTTP_VERSION] = $this->getProtocolVersion($request->getProtocolVersion()); | ||
$options[CURLOPT_HEADERFUNCTION] = [$this->getResponseParser(), 'headerHandler']; | ||
$options[CURLOPT_URL] = (string) $request->getUri(); | ||
$options[CURLOPT_RETURNTRANSFER] = false; | ||
$options[CURLOPT_FILE] = $this->getResponseParser()->getTemporaryStream(); | ||
$options[CURLOPT_HEADER] = false; | ||
|
||
if (in_array($request->getMethod(), ['OPTIONS', 'POST', 'PUT'], true)) { | ||
// cURL allows request body only for these methods. | ||
$body = (string) $request->getBody(); | ||
if ('' !== $body) { | ||
$options[CURLOPT_POSTFIELDS] = $body; | ||
} | ||
} | ||
// These methods do not transfer body. | ||
// You can specify any method you'd like, including a custom method that might not be part of RFC 7231 (like "MOVE"). | ||
if (in_array($request->getMethod(), ['GET', 'HEAD', 'TRACE', 'CONNECT'])) { | ||
if ($request->getMethod() == 'HEAD') { | ||
$options[CURLOPT_NOBODY] = true; | ||
|
||
if ($request->getMethod() === '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(); | ||
} | ||
unset($options[CURLOPT_READFUNCTION], $options[CURLOPT_WRITEFUNCTION]); | ||
} | ||
} else { | ||
$body = clone $request->getBody(); | ||
$size = $body->getSize(); | ||
|
||
$options[CURLOPT_HTTPHEADER] = $this->createHeaders($request, $options); | ||
if ($size === null || $size > 1048576) { | ||
$body->rewind(); | ||
$options[CURLOPT_UPLOAD] = true; | ||
|
||
if ($request->getUri()->getUserInfo()) { | ||
$options[CURLOPT_USERPWD] = $request->getUri()->getUserInfo(); | ||
} | ||
// Avoid full loading large or unknown size body into memory. Not replace CURLOPT_READFUNCTION. | ||
if (isset($options[CURLOPT_READFUNCTION]) && is_callable($options[CURLOPT_READFUNCTION])) { | ||
$body = $body->detach(); | ||
$options[CURLOPT_READFUNCTION] = function ($curlHandler, $handler, $length) use ($body, $options) { | ||
return call_user_func($options[CURLOPT_READFUNCTION], $curlHandler, $body, $length); | ||
}; | ||
} else { | ||
$options[CURLOPT_READFUNCTION] = function ($curl, $handler, $length) use ($body) { | ||
return $body->read($length); | ||
}; | ||
} | ||
} else { | ||
// Send the body as a string if the size is less than 1MB. | ||
$options[CURLOPT_POSTFIELDS] = (string) $request->getBody(); | ||
} | ||
} | ||
|
||
return $options; | ||
} | ||
if ($request->getMethod() != 'GET') { | ||
$options[CURLOPT_CUSTOMREQUEST] = $request->getMethod(); | ||
} | ||
|
||
$options[CURLOPT_HTTPHEADER] = $this->createHeaders($request, $options); | ||
|
||
if ($request->getUri()->getUserInfo()) { | ||
$options[CURLOPT_USERPWD] = $request->getUri()->getUserInfo(); | ||
} | ||
|
||
return $options; | ||
} | ||
|
||
/** | ||
* Return cURL constant for specified HTTP version | ||
|
@@ -208,7 +243,7 @@ private function createCurlOptions(RequestInterface $request) | |
* | ||
* @return int | ||
*/ | ||
private function getProtocolVersion($requestVersion) | ||
protected function getProtocolVersion($requestVersion) | ||
{ | ||
switch ($requestVersion) { | ||
case '1.0': | ||
|
@@ -219,8 +254,10 @@ private function getProtocolVersion($requestVersion) | |
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; | ||
} | ||
|
||
|
@@ -232,23 +269,36 @@ private function getProtocolVersion($requestVersion) | |
* | ||
* @return string[] | ||
*/ | ||
private function createHeaders(RequestInterface $request, array $options) | ||
protected function createHeaders(RequestInterface $request, array $options) | ||
{ | ||
$curlHeaders = []; | ||
$headers = array_keys($request->getHeaders()); | ||
|
||
if ( ! $request->hasHeader('Expect')) { | ||
$curlHeaders[] = 'Expect:'; | ||
} | ||
|
||
if ( ! $request->hasHeader('Accept')) { | ||
$curlHeaders[] = 'Accept: */*'; | ||
} | ||
|
||
foreach ($headers as $name) { | ||
if (strtolower($name) === 'content-length') { | ||
$values = [0]; | ||
|
||
if (array_key_exists(CURLOPT_POSTFIELDS, $options)) { | ||
$values = [strlen($options[CURLOPT_POSTFIELDS])]; | ||
} | ||
} else { | ||
$values = $request->getHeader($name); | ||
} | ||
|
||
foreach ($values as $value) { | ||
$curlHeaders[] = $name . ': ' . $value; | ||
$curlHeaders[] = $name. ': '.$value; | ||
} | ||
} | ||
|
||
return $curlHeaders; | ||
} | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the extra line?