Completed
Push — master ( 83b55d...472d84 )
by Justin
03:47 queued 36s
created

CurlSender::__construct()   B

Complexity

Conditions 8
Paths 64

Size

Total Lines 27
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 18
dl 0
loc 27
rs 8.4444
c 0
b 0
f 0
cc 8
nc 64
nop 1
1
<?php namespace Rollbar\Senders;
2
3
/**
4
 * Adapted from:
5
 * https://github.com/segmentio/analytics-php/blob/master/lib/Segment/Consumer/Socket.php
6
 */
7
8
use Rollbar\Response;
9
use Rollbar\Payload\Payload;
10
use Rollbar\Payload\EncodedPayload;
11
12
class CurlSender implements SenderInterface
13
{
14
    private $utilities;
15
    private $endpoint;
16
    private $timeout;
17
    private $proxy = null;
18
    private $verifyPeer = true;
19
    private $caCertPath = null;
20
    private $multiHandle = null;
21
    private $maxBatchRequests = 75;
22
    private $batchRequests = array();
23
    private $inflightRequests = array();
24
25
    public function __construct($opts)
26
    {
27
        $this->endpoint = \Rollbar\Defaults::get()->endpoint() . 'item/';
28
        $this->timeout = \Rollbar\Defaults::get()->timeout();
29
        
30
        $this->utilities = new \Rollbar\Utilities();
31
        if (isset($_ENV['ROLLBAR_ENDPOINT']) && !isset($opts['endpoint'])) {
32
            $opts['endpoint'] = $_ENV['ROLLBAR_ENDPOINT'];
33
        }
34
        if (array_key_exists('endpoint', $opts)) {
35
            $this->utilities->validateString($opts['endpoint'], 'opts["endpoint"]', null, false);
36
            $this->endpoint = $opts['endpoint'];
37
        }
38
        if (array_key_exists('timeout', $opts)) {
39
            $this->utilities->validateInteger($opts['timeout'], 'opts["timeout"]', 0, null, false);
40
            $this->timeout = $opts['timeout'];
41
        }
42
        if (array_key_exists('proxy', $opts)) {
43
            $this->proxy = $opts['proxy'];
44
        }
45
46
        if (array_key_exists('verifyPeer', $opts)) {
47
            $this->utilities->validateBoolean($opts['verifyPeer'], 'opts["verifyPeer"]', false);
48
            $this->verifyPeer = $opts['verifyPeer'];
49
        }
50
        if (array_key_exists('ca_cert_path', $opts)) {
51
            $this->caCertPath = $opts['ca_cert_path'];
52
        }
53
    }
54
    
55
    public function getEndpoint()
56
    {
57
        return $this->endpoint;
58
    }
59
60
    public function send(EncodedPayload $payload, $accessToken)
61
    {
62
        $handle = curl_init();
63
64
        $this->setCurlOptions($handle, $payload, $accessToken);
65
        $result = curl_exec($handle);
66
        $statusCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
67
        
68
        $result = $result === false ?
69
                    curl_error($handle) :
70
                    json_decode($result, true);
71
        
72
        curl_close($handle);
73
74
        $data = $payload->data();
75
        $uuid = $data['data']['uuid'];
76
        
77
        return new Response($statusCode, $result, $uuid);
78
    }
79
80
    public function sendBatch($batch, $accessToken)
81
    {
82
        if ($this->multiHandle === null) {
83
            $this->multiHandle = curl_multi_init();
84
        }
85
86
        if ($this->maxBatchRequests > 0) {
87
            $this->wait($accessToken, $this->maxBatchRequests);
88
        }
89
90
        $this->batchRequests = array_merge($this->batchRequests, $batch);
91
        $this->maybeSendMoreBatchRequests($accessToken);
92
        $this->checkForCompletedRequests($accessToken);
93
    }
94
95
    public function wait($accessToken, $max = 0)
96
    {
97
        if (count($this->inflightRequests) <= $max) {
98
            return;
99
        }
100
        while (1) {
101
            $this->checkForCompletedRequests($accessToken);
102
            if (count($this->inflightRequests) <= $max) {
103
                break;
104
            }
105
            curl_multi_select($this->multiHandle); // or do: usleep(10000);
106
        }
107
    }
108
109
    private function maybeSendMoreBatchRequests($accessToken)
110
    {
111
        $max = $this->maxBatchRequests - count($this->inflightRequests);
112
        if ($max <= 0) {
113
            return;
114
        }
115
        $idx = 0;
116
        $len = count($this->batchRequests);
117
        for (; $idx < $len && $idx < $max; $idx++) {
118
            $payload = $this->batchRequests[$idx];
119
            $handle = curl_init();
120
            $this->setCurlOptions($handle, $payload, $accessToken);
121
            curl_multi_add_handle($this->multiHandle, $handle);
122
            $handleArrayKey = (int)$handle;
123
            $this->inflightRequests[$handleArrayKey] = true;
124
        }
125
        $this->batchRequests = array_slice($this->batchRequests, $idx);
126
    }
127
128
    public function setCurlOptions($handle, EncodedPayload $payload, $accessToken)
129
    {
130
        curl_setopt($handle, CURLOPT_URL, $this->endpoint);
131
        curl_setopt($handle, CURLOPT_POST, true);
132
        curl_setopt($handle, CURLOPT_POSTFIELDS, $payload->encoded());
133
        curl_setopt($handle, CURLOPT_VERBOSE, false);
134
        curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, $this->verifyPeer);
135
        curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
136
        curl_setopt($handle, CURLOPT_TIMEOUT, $this->timeout);
137
        curl_setopt($handle, CURLOPT_HTTPHEADER, array('X-Rollbar-Access-Token: ' . $accessToken));
138
        curl_setopt($handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
139
140
        if (!is_null($this->caCertPath)) {
141
            curl_setopt($handle, CURLOPT_CAINFO, $this->caCertPath);
142
        }
143
144
        if ($this->proxy) {
145
            $proxy = is_array($this->proxy) ? $this->proxy : array('address' => $this->proxy);
146
            if (isset($proxy['address'])) {
147
                curl_setopt($handle, CURLOPT_PROXY, $proxy['address']);
148
                curl_setopt($handle, CURLOPT_FOLLOWLOCATION, true);
149
            }
150
            if (isset($proxy['username']) && isset($proxy['password'])) {
151
                curl_setopt($handle, CURLOPT_PROXYUSERPWD, $proxy['username'] . ':' . $proxy['password']);
152
            }
153
        }
154
    }
155
156
    private function checkForCompletedRequests($accessToken)
157
    {
158
        do {
159
            $curlResponse = curl_multi_exec($this->multiHandle, $active);
160
        } while ($curlResponse == CURLM_CALL_MULTI_PERFORM);
161
        while ($active && $curlResponse == CURLM_OK) {
162
            if (curl_multi_select($this->multiHandle, 0.01) == -1) {
163
                $this->maybeSendMoreBatchRequests($accessToken);
164
                return;
165
            }
166
            do {
167
                $curlResponse = curl_multi_exec($this->multiHandle, $active);
168
            } while ($curlResponse == CURLM_CALL_MULTI_PERFORM);
169
        }
170
        $this->removeFinishedRequests($accessToken);
171
    }
172
173
    private function removeFinishedRequests($accessToken)
174
    {
175
        while ($info = curl_multi_info_read($this->multiHandle)) {
176
            $handle = $info['handle'];
177
            $handleArrayKey = (int)$handle;
178
            if (isset($this->inflightRequests[$handleArrayKey])) {
179
                unset($this->inflightRequests[$handleArrayKey]);
180
                curl_multi_remove_handle($this->multiHandle, $handle);
181
            }
182
            curl_close($handle);
183
        }
184
        $this->maybeSendMoreBatchRequests($accessToken);
185
    }
186
    
187
    public function toString()
188
    {
189
        return "Rollbar API endpoint: " . $this->getEndpoint();
190
    }
191
}
192