3
namespace GuzzleHttp\Adapter\Curl;
5
use GuzzleHttp\Adapter\AdapterInterface;
6
use GuzzleHttp\Adapter\TransactionInterface;
7
use GuzzleHttp\Event\RequestEvents;
8
use GuzzleHttp\Exception\AdapterException;
9
use GuzzleHttp\Message\MessageFactoryInterface;
12
* HTTP adapter that uses cURL easy handles as a transport layer.
16
* When using the CurlAdapter, custom curl options can be specified as an
17
* associative array of curl option constants mapping to values in the
18
* **curl** key of a request's configuration options.
20
class CurlAdapter implements AdapterInterface
22
/** @var CurlFactory */
25
/** @var MessageFactoryInterface */
26
private $messageFactory;
28
/** @var array Array of curl easy handles */
29
private $handles = [];
31
/** @var array Array of owned curl easy handles */
32
private $ownedHandles = [];
34
/** @var int Total number of idle handles to keep in cache */
38
* Accepts an associative array of options:
40
* - handle_factory: Optional callable factory used to create cURL handles.
41
* The callable is invoked with the following arguments:
42
* TransactionInterface, MessageFactoryInterface, and an optional cURL
43
* handle to modify. The factory method must then return a cURL resource.
44
* - max_handles: Maximum number of idle handles (defaults to 5).
46
* @param MessageFactoryInterface $messageFactory
47
* @param array $options Array of options to use with the adapter
49
public function __construct(
50
MessageFactoryInterface $messageFactory,
53
$this->handles = $this->ownedHandles = [];
54
$this->messageFactory = $messageFactory;
55
$this->curlFactory = isset($options['handle_factory'])
56
? $options['handle_factory']
58
$this->maxHandles = isset($options['max_handles'])
59
? $options['max_handles']
63
public function __destruct()
65
foreach ($this->handles as $handle) {
66
if (is_resource($handle)) {
72
public function send(TransactionInterface $transaction)
74
RequestEvents::emitBefore($transaction);
75
if ($response = $transaction->getResponse()) {
79
$factory = $this->curlFactory;
82
$this->messageFactory,
83
$this->checkoutEasyHandle()
87
$info = curl_getinfo($handle);
88
$info['curl_result'] = curl_errno($handle);
90
if ($info['curl_result']) {
91
$this->handleError($transaction, $info, $handle);
93
$this->releaseEasyHandle($handle);
94
if ($body = $transaction->getResponse()->getBody()) {
97
RequestEvents::emitComplete($transaction, $info);
100
return $transaction->getResponse();
103
private function handleError(
104
TransactionInterface $transaction,
108
$error = curl_error($handle);
109
$this->releaseEasyHandle($handle);
110
RequestEvents::emitError(
112
new AdapterException("cURL error {$info['curl_result']}: {$error}"),
117
private function checkoutEasyHandle()
119
// Find an unused handle in the cache
120
if (false !== ($key = array_search(false, $this->ownedHandles, true))) {
121
$this->ownedHandles[$key] = true;
122
return $this->handles[$key];
126
$handle = curl_init();
128
$this->handles[$id] = $handle;
129
$this->ownedHandles[$id] = true;
134
private function releaseEasyHandle($handle)
137
if (count($this->ownedHandles) > $this->maxHandles) {
138
curl_close($this->handles[$id]);
139
unset($this->handles[$id], $this->ownedHandles[$id]);
141
// curl_reset doesn't clear these out for some reason
142
curl_setopt_array($handle, [
143
CURLOPT_HEADERFUNCTION => null,
144
CURLOPT_WRITEFUNCTION => null,
145
CURLOPT_READFUNCTION => null,
146
CURLOPT_PROGRESSFUNCTION => null
149
$this->ownedHandles[$id] = false;