Completed
Push — master ( 362a13...64ee51 )
by WEBEWEB
01:29
created

AbstractCurlRequest::parseHeader()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 8.5386
c 0
b 0
f 0
cc 7
nc 7
nop 1
1
<?php
2
3
/*
4
 * This file is part of the core-library package.
5
 *
6
 * (c) 2018 WEBEWEB
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace WBW\Library\Core\Network\CURL\Request;
13
14
use DateTime;
15
use Exception;
16
use InvalidArgumentException;
17
use WBW\Library\Core\Argument\ArgumentHelper;
18
use WBW\Library\Core\Network\CURL\API\CurlRequestInterface;
19
use WBW\Library\Core\Network\CURL\Configuration\CurlConfiguration;
20
use WBW\Library\Core\Network\CURL\Exception\CurlRequestCallException;
21
use WBW\Library\Core\Network\CURL\Factory\CurlFactory;
22
use WBW\Library\Core\Network\HTTP\HttpInterface;
23
24
/**
25
 * Abstract cURL request.
26
 *
27
 * @author webeweb <https://github.com/webeweb/>
28
 * @package WBW\Library\Core\Network\CURL\Request
29
 * @abstract
30
 */
31
abstract class AbstractCurlRequest implements CurlRequestInterface, HttpInterface {
32
33
    /**
34
     * Content-Type "application/x-www-form-urlencoded".
35
     * @var string
36
     */
37
    const CONTENT_TYPE_X_WWW_FORM_URLENCODED = "Content-Type: application/x-www-form-urlencoded";
38
39
    /**
40
     * Configuration.
41
     *
42
     * @var CurlConfiguration
43
     */
44
    private $configuration;
45
46
    /**
47
     * Headers.
48
     *
49
     * @var array
50
     */
51
    private $headers = [];
52
53
    /**
54
     * Method.
55
     *
56
     * @var string
57
     */
58
    private $method = self::HTTP_METHOD_GET;
59
60
    /**
61
     * POST data.
62
     *
63
     * @var array
64
     */
65
    private $postData = [];
66
67
    /**
68
     * Query data.
69
     *
70
     * @var array
71
     */
72
    private $queryData = [];
73
74
    /**
75
     * Resource path.
76
     *
77
     * @var string
78
     */
79
    private $resourcePath;
80
81
    /**
82
     * Constructor.
83
     *
84
     * @param string $method The Method.
85
     * @param CurlConfiguration $configuration The configuration.
86
     * @param string $resourcePath The resource path.
87
     * @throws InvalidArgumentException Throws an invalid argument exception if the method is invalid.
88
     */
89
    protected function __construct($method, CurlConfiguration $configuration, $resourcePath) {
90
        $this->setConfiguration($configuration);
91
        $this->setMethod($method);
92
        $this->setResourcePath($resourcePath);
93
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98
    public function addHeader($name, $value) {
99
        ArgumentHelper::isTypeOf($name, ArgumentHelper::ARGUMENT_STRING);
100
        $this->headers[$name] = $value;
101
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106
    public function addPostData($name, $value) {
107
        ArgumentHelper::isTypeOf($name, ArgumentHelper::ARGUMENT_STRING);
108
        $this->postData[$name] = $value;
109
    }
110
111
    /**
112
     * {@inheritdoc}
113
     */
114
    public function addQueryData($name, $value) {
115
        ArgumentHelper::isTypeOf($name, ArgumentHelper::ARGUMENT_STRING);
116
        $this->queryData[$name] = $value;
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122
    public function call() {
123
124
        $curlHeaders  = $this->mergeHeaders();
125
        $curlPOSTData = http_build_query($this->getPostData());
126
127
        if (true === in_array("Content-Type: application/json", $curlHeaders)) {
128
            $curlPOSTData = json_encode($this->getPostData());
129
        }
130
131
        $curlURL = $this->mergeURL();
132
        if (0 < count($this->getQueryData())) {
133
            $curlURL = implode("?", [$curlURL, http_build_query($this->getQueryData())]);
134
        }
135
136
        $stream = curl_init();
137
138
        if (0 < $this->getConfiguration()->getConnectTimeout()) {
139
            curl_setopt($stream, CURLOPT_CONNECTTIMEOUT, $this->getConfiguration()->getConnectTimeout());
140
        }
141
142
        if (true === $this->getConfiguration()->getAllowEncoding()) {
143
            curl_setopt($stream, CURLOPT_ENCODING, "");
144
        }
145
146
        curl_setopt($stream, CURLOPT_HEADER, 1);
147
        curl_setopt($stream, CURLOPT_HTTPHEADER, $curlHeaders);
148
149
        $this->setCurlPost($stream, $curlPOSTData);
150
        $this->setCURLProxy($stream);
151
        $this->setCurlReturnTransfer($stream);
152
        $this->setCurlSsl($stream);
153
        $this->setCurlTimeout($stream);
154
155
        curl_setopt($stream, CURLOPT_URL, $curlURL);
156
157
        $this->setCurlUserAgent($stream);
158
        $this->setCurlVerbose($stream, $curlURL, $curlPOSTData);
159
160
        $curlExec     = curl_exec($stream);
161
        $httpHeadSize = curl_getinfo($stream, CURLINFO_HEADER_SIZE);
162
        $httpHead     = $this->parseheader(substr($curlExec, 0, $httpHeadSize));
163
        $httpBody     = substr($curlExec, $httpHeadSize);
164
        $curlInfo     = curl_getinfo($stream);
165
166
        if (true === $this->getConfiguration()->getDebug()) {
167
            $msg = (new DateTime())->format("c") . " [DEBUG] " . $curlURL . PHP_EOL . "HTTP response body ~BEGIN~" . PHP_EOL . print_r($httpBody, true) . PHP_EOL . "~END~" . PHP_EOL;
168
            error_log($msg, 3, $this->getConfiguration()->getDebugFile());
169
        }
170
171
        $response = CurlFactory::newCURLResponse();
172
        $response->setRequestBody($curlPOSTData);
173
        $response->setRequestHeader($curlHeaders);
174
        $response->setRequestURL($curlURL);
175
        $response->setResponseBody($httpBody);
176
        $response->setResponseHeader($httpHead);
177
        $response->setResponseInfo($curlInfo);
178
179
        if (200 <= $curlInfo["http_code"] && $curlInfo["http_code"] <= 299) {
180
            return $response;
181
        }
182
183
        $cde = $curlInfo["http_code"];
184
        $msg = curl_errno($stream);
185
186
        if (0 === $curlInfo["http_code"]) {
187
            if (false === empty(curl_error($stream))) {
188
                $msg = "Call to ${curlURL} failed : " . curl_error($stream);
189
            } else {
190
                $msg = "Call to ${curlURL} failed, but for an unknown reason. This could happen if you are disconnected from the network.";
191
            }
192
        }
193
194
        throw new CurlRequestCallException($msg, $cde, $response);
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     */
200
    public function clearHeaders() {
201
        return $this->setHeaders([]);
202
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207
    public function clearPostData() {
208
        return $this->setPostData([]);
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     */
214
    public function clearQueryData() {
215
        return $this->setQueryData([]);
216
    }
217
218
    /**
219
     * {@inheritdoc}
220
     */
221
    public function getConfiguration() {
222
        return $this->configuration;
223
    }
224
225
    /**
226
     * {@inheritdoc}
227
     */
228
    public function getHeaders() {
229
        return $this->headers;
230
    }
231
232
    /**
233
     * {@inheritdoc}
234
     */
235
    public function getMethod() {
236
        return $this->method;
237
    }
238
239
    /**
240
     * {@inheritdoc}
241
     */
242
    public function getPostData() {
243
        return $this->postData;
244
    }
245
246
    /**
247
     * {@inheritdoc}
248
     */
249
    public function getQueryData() {
250
        return $this->queryData;
251
    }
252
253
    /**
254
     * {@inheritdoc}
255
     */
256
    public function getResourcePath() {
257
        return $this->resourcePath;
258
    }
259
260
    /**
261
     * Merge the headers.
262
     *
263
     * @return array Returns the merged headers.
264
     */
265
    private function mergeHeaders() {
266
267
        $mergedHeaders = [];
268
        foreach (array_merge($this->getConfiguration()->getHeaders(), $this->getHeaders()) as $key => $value) {
269
            $mergedHeaders[] = implode(": ", [$key, $value]);
270
        }
271
272
        return $mergedHeaders;
273
    }
274
275
    /**
276
     * Merge the URL.
277
     *
278
     * @return string Returns the merged URL.
279
     */
280
    private function mergeURL() {
281
282
        $mergedURL = [
283
            $this->getConfiguration()->getHost(),
284
        ];
285
286
        if (null !== $this->getResourcePath() && "" !== $this->getResourcePath()) {
287
            $mergedURL[] = $this->getResourcePath();
288
        }
289
290
        return implode("/", $mergedURL);
291
    }
292
293
    /**
294
     * Parse the raw header.
295
     *
296
     * @param string $rawHeader The raw header.
297
     * @return array Returns the headers.
298
     */
299
    private function parseHeader($rawHeader) {
300
301
        $headers = [];
302
        $key     = "";
303
304
        foreach (explode("\n", $rawHeader) as $h) {
305
            $h = explode(":", $h, 2);
306
            if (true === isset($h[1])) {
307
                if (false === isset($headers[$h[0]])) {
308
                    $headers[$h[0]] = trim($h[1]);
309
                } else if (true === is_array($headers[$h[0]])) {
310
                    $headers[$h[0]] = array_merge($headers[$h[0]], [trim($h[1])]);
311
                } else {
312
                    $headers[$h[0]] = array_merge([$headers[$h[0]]], [trim($h[1])]);
313
                }
314
                $key = $h[0];
315
            } else {
316
                if ("\t" === substr($h[0], 0, 1)) {
317
                    $headers[$key] .= "\r\n\t" . trim($h[0]);
318
                } else if (!$key) {
319
                    $headers[0] = trim($h[0]);
320
                }
321
                trim($h[0]);
322
            }
323
        }
324
325
        return $headers;
326
    }
327
328
    /**
329
     * {@inheritdoc}
330
     */
331
    public function removeHeader($name) {
332
        if (true === array_key_exists($name, $this->headers)) {
333
            unset($this->headers[$name]);
334
        }
335
        return $this;
336
    }
337
338
    /**
339
     * {@inheritdoc}
340
     */
341
    public function removePostData($name) {
342
        if (true === array_key_exists($name, $this->postData)) {
343
            unset($this->postData[$name]);
344
        }
345
        return $this;
346
    }
347
348
    /**
349
     * {@inheritdoc}
350
     */
351
    public function removeQueryData($name) {
352
        if (true === array_key_exists($name, $this->queryData)) {
353
            unset($this->queryData[$name]);
354
        }
355
        return $this;
356
    }
357
358
    /**
359
     * Set the configuration.
360
     *
361
     * @param CurlConfiguration $configuration The configuration.
362
     * @return CurlRequestInterface Returns this cURL request.
363
     */
364
    protected function setConfiguration(CurlConfiguration $configuration) {
365
        $this->configuration = $configuration;
366
        return $this;
367
    }
368
369
    /**
370
     * Set cURL POST.
371
     *
372
     * @param resource $stream The stream.
373
     * @param string $postData The POST data.
374
     * @return CurlRequestInterface Returns this cURL request.
375
     */
376
    protected function setCurlPost($stream, $postData) {
377
378
        switch ($this->getMethod()) {
379
380
            case self::HTTP_METHOD_DELETE:
381
            case self::HTTP_METHOD_OPTIONS:
382
            case self::HTTP_METHOD_PATCH:
383
            case self::HTTP_METHOD_PUT:
384
                curl_setopt($stream, CURLOPT_CUSTOMREQUEST, $this->getMethod());
385
                curl_setopt($stream, CURLOPT_POSTFIELDS, $postData);
386
                break;
387
388
            case self::HTTP_METHOD_HEAD:
389
                curl_setopt($stream, CURLOPT_NOBODY, true);
390
                break;
391
392
            case self::HTTP_METHOD_POST:
393
                curl_setopt($stream, CURLOPT_POST, true);
394
                curl_setopt($stream, CURLOPT_POSTFIELDS, $postData);
395
                break;
396
        }
397
398
        return $this;
399
    }
400
401
    /**
402
     * Set cURL proxy.
403
     *
404
     * @param resource $stream The stream.
405
     * @return CurlRequestInterface Returns this cURL request.
406
     */
407
    protected function setCurlProxy($stream) {
408
409
        if (null !== $this->getConfiguration()->getProxyHost()) {
410
            curl_setopt($stream, CURLOPT_PROXY, $this->getConfiguration()->getProxyHost());
411
        }
412
413
        if (null !== $this->getConfiguration()->getProxyPort()) {
414
            curl_setopt($stream, CURLOPT_PROXYPORT, $this->getConfiguration()->getProxyPort());
415
        }
416
417
        if (null !== $this->getConfiguration()->getProxyType()) {
418
            curl_setopt($stream, CURLOPT_PROXYTYPE, $this->getConfiguration()->getProxyType());
419
        }
420
421
        if (null !== $this->getConfiguration()->getProxyUsername()) {
422
            curl_setopt($stream, CURLOPT_PROXYUSERPWD, implode(":", [$this->getConfiguration()->getProxyUsername(), $this->getConfiguration()->getProxyPassword()]));
423
        }
424
425
        return $this;
426
    }
427
428
    /**
429
     * Set cURL return transfer.
430
     *
431
     * @param resource $stream The stream.
432
     * @return CurlRequestInterface Returns this cURL request.
433
     */
434
    protected function setCurlReturnTransfer($stream) {
435
        curl_setopt($stream, CURLOPT_RETURNTRANSFER, true);
436
        return $this;
437
    }
438
439
    /**
440
     * Set cURL SSL.
441
     *
442
     * @param resource $stream The stream.
443
     * @return CurlRequestInterface Returns this cURL request.
444
     */
445
    protected function setCurlSsl($stream) {
446
447
        if (false === $this->getConfiguration()->getSslVerification()) {
448
            curl_setopt($stream, CURLOPT_SSL_VERIFYHOST, 0);
449
            curl_setopt($stream, CURLOPT_SSL_VERIFYPEER, 0);
450
        }
451
452
        return $this;
453
    }
454
455
    /**
456
     * Set cURL timeout.
457
     *
458
     * @param resource $stream The stream.
459
     * @return CurlRequestInterface Returns this cURL request.
460
     */
461
    protected function setCurlTimeout($stream) {
462
463
        if (0 < $this->getConfiguration()->getRequestTimeout()) {
464
            curl_setopt($stream, CURLOPT_TIMEOUT, $this->getConfiguration()->getRequestTimeout());
465
        }
466
467
        return $this;
468
    }
469
470
    /**
471
     * Set cURL user agent.
472
     *
473
     * @param resource $stream The stream.
474
     * @return CurlRequestInterface Returns this cURL request.
475
     */
476
    protected function setCurlUserAgent($stream) {
477
        curl_setopt($stream, CURLOPT_USERAGENT, $this->getConfiguration()->getUserAgent());
478
        return $this;
479
    }
480
481
    /**
482
     * Set cURL verbose.
483
     *
484
     * @param resource $stream The stream.
485
     * @param string $url The URL.
486
     * @param string $postData The POST data.
487
     * @return CurlRequestInterface Returns this cURL request.
488
     * @throws Exception Throws an exception if an error occurs.
489
     */
490
    protected function setCurlVerbose($stream, $url, $postData) {
491
492
        if (true === $this->getConfiguration()->getDebug()) {
493
494
            curl_setopt($stream, CURLOPT_STDERR, fopen($this->getConfiguration()->getDebugFile(), "a"));
495
            curl_setopt($stream, CURLOPT_VERBOSE, 0);
496
497
            $msg = (new DateTime())->format("c") . " [DEBUG] " . $url . PHP_EOL . "HTTP request body ~BEGIN~" . PHP_EOL . print_r($postData, true) . PHP_EOL . "~END~" . PHP_EOL;
498
            error_log($msg, 3, $this->getConfiguration()->getDebugFile());
499
        } else {
500
501
            if (true === $this->getConfiguration()->getVerbose()) {
502
                curl_setopt($stream, CURLOPT_VERBOSE, 1);
503
            } else {
504
                curl_setopt($stream, CURLOPT_VERBOSE, 0);
505
            }
506
        }
507
508
        return $this;
509
    }
510
511
    /**
512
     * Set the headers.
513
     *
514
     * @param array $headers The headers.
515
     * @return CurlRequestInterface Returns this cURL request.
516
     */
517
    protected function setHeaders(array $headers) {
518
        $this->headers = $headers;
519
        return $this;
520
    }
521
522
    /**
523
     * Set the method.
524
     *
525
     * @param string $method The method.
526
     * @return CurlRequestInterface Returns this cURL request.
527
     * @throws InvalidArgumentException Throws an invalid argument exception if the method is invalid.
528
     */
529
    protected function setMethod($method) {
530
        switch ($method) {
531
            case self::HTTP_METHOD_DELETE:
532
            case self::HTTP_METHOD_GET:
533
            case self::HTTP_METHOD_HEAD:
534
            case self::HTTP_METHOD_OPTIONS:
535
            case self::HTTP_METHOD_PATCH:
536
            case self::HTTP_METHOD_POST:
537
            case self::HTTP_METHOD_PUT:
538
                $this->method = $method;
539
                break;
540
            default:
541
                throw new InvalidArgumentException(sprintf("The HTTP method \"%s\" is invalid", $method));
542
        }
543
        return $this;
544
    }
545
546
    /**
547
     * Set the POST data.
548
     *
549
     * @param array $postData The POST data.
550
     * @return CurlRequestInterface Returns this cURL request.
551
     */
552
    protected function setPostData(array $postData) {
553
        $this->postData = $postData;
554
        return $this;
555
    }
556
557
    /**
558
     * Set the query data.
559
     *
560
     * @param array $queryData The query data.
561
     * @return CurlRequestInterface Returns this cURL request.
562
     */
563
    protected function setQueryData(array $queryData) {
564
        $this->queryData = $queryData;
565
        return $this;
566
    }
567
568
    /**
569
     * {@inheritdoc}
570
     */
571
    public function setResourcePath($resourcePath) {
572
        $this->resourcePath = preg_replace("/^\//", "", trim($resourcePath));
573
        return $this;
574
    }
575
}
576