Completed
Push — master ( da510d...a5cc05 )
by Alexander
01:57
created

Remote::get()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 8
Ratio 100 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 2
dl 8
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace alkemann\h2l;
4
5
use alkemann\h2l\exceptions\CurlFailure;
6
7
/**
8
 * Class Remote
9
 *
10
 * Makes http requests using cURL. uses Message for both Request and Response description
11
 *
12
 * @TODO SSL verify optional
13
 * @TODO Proxy support?
14
 * @package alkemann\h2l
15
 */
16
class Remote
17
{
18
    private $config = [];
19
20
    public function __construct(array $config = [])
21
    {
22
        $this->config = $config + [
23
                // defaults
24
            ];
25
    }
26
27 View Code Duplication
    public function get(string $url, array $headers = []): Message
28
    {
29
        $request = (new Message)
30
            ->withType(Message::REQUEST)
31
            ->withUrl($url)
32
            ->withMethod(Request::GET)
33
            ->withHeaders($headers);
34
        return $this->http($request);
35
    }
36
37 View Code Duplication
    public function postJson(string $url, array $data, array $headers = [], string $method = Request::POST): Message
38
    {
39
        $headers['Content-Type'] = 'application/json; charset=utf-8';
40
        $headers['Accept'] = 'application/json';
41
        $data_string = json_encode($data);
42
        $headers['Content-Length'] = strlen($data_string);
43
        $request = (new Message)
44
            ->withType(Message::REQUEST)
45
            ->withUrl($url)
46
            ->withMethod($method)
47
            ->withBody($data_string)
48
            ->withHeaders($headers);
49
        return $this->http($request);
50
    }
51
52 View Code Duplication
    public function postForm(string $url, array $data, array $headers = [], string $method = Request::POST): Message
53
    {
54
        $headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
55
        $data_string = http_build_query($data);
56
        $headers['Content-Length'] = strlen($data_string);
57
        $request = (new Message)
58
            ->withType(Message::REQUEST)
59
            ->withUrl($url)
60
            ->withMethod($method)
61
            ->withBody($data_string)
62
            ->withHeaders($headers);
63
        return $this->http($request);
64
    }
65
66 View Code Duplication
    public function delete(string $url, array $headers = []): Message
67
    {
68
        $request = (new Message)
69
            ->withType(Message::REQUEST)
70
            ->withUrl($url)
71
            ->withMethod(Request::DELETE)
72
            ->withHeaders($headers);
73
        return $this->http($request);
74
    }
75
76
    public function http(Message $request): Message
77
    {
78
        $start = microtime(true);
79
80
        $curl_handler = $this->createCurlHandlerFromRequest($request);
81
82
        $meta = [];
83
        $headers = [];
84
85
        try {
86
            $content = curl_exec($curl_handler);
87
            if ($content === false) {
88
                throw new CurlFailure(curl_error($curl_handler), curl_errno($curl_handler));
89
            }
90
        } catch (\Exception $e) {
91
            Log::error("CURL exception : " . get_class($e) . " : " . $e->getMessage());
92
93
            $curl_failure = new CurlFailure($e->getMessage(), $e->getCode(), $e);
94
            $latency = microtime(true) - $start;
95
            $info = curl_getinfo($curl_handler);
96
            $curl_failure->setContext(compact('request', 'latency', 'info'));
97
98
            curl_close($curl_handler);
99
            unset($curl_handler);
100
101
            throw $curl_failure;
102
        }
103
104
        $meta['latency'] = microtime(true) - $start;
105
        $meta['info'] = curl_getinfo($curl_handler);
106
        $code = $meta['info']['http_code'];
107
108
        $header_size = curl_getinfo($curl_handler, CURLINFO_HEADER_SIZE);
109
        $header = substr($content, 0, $header_size);
110
        if ($header) {
111
            $headers = $this->extractHeaders($header);
112
        }
113
        $content = substr($content, $header_size);
114
115
        // Curl handler no longer needed, let's close it
116
117
        curl_close($curl_handler);
118
        unset($curl_handler);
119
120
        return (new Message)
121
            ->withType(Message::RESPONSE)
122
            ->withUrl($request->url())
123
            ->withMethod($request->method())
124
            ->withBody($content)
125
            ->withCode($code)
126
            ->withHeaders($headers)
127
            ->withMeta($meta);
128
    }
129
130
    /**
131
     * return resource a hurl handler
132
     */
133
    private function createCurlHandlerFromRequest(Message $request)
134
    {
135
        $curl_handler = curl_init();
136
137
        $options = $this->config['curl'] ?? [];
138
        $options += [
139
            CURLOPT_FOLLOWLOCATION => true,
140
            CURLOPT_MAXREDIRS => 10,
141
            CURLOPT_CONNECTTIMEOUT => 5,
142
            CURLOPT_TIMEOUT => 5,
143
            CURLOPT_URL => $request->url(),
144
            CURLOPT_USERAGENT => 'alkemann\h2l\Remote',
145
            CURLOPT_RETURNTRANSFER => true,
146
            CURLOPT_HEADER => true,
147
            CURLOPT_CUSTOMREQUEST => $request->method(),
148
        ];
149
150
        if (!isset($options[CURLOPT_HTTPHEADER])) {
151
            $options[CURLOPT_HTTPHEADER] = [];
152
        }
153
        $body = $request->body();
154
        if ($body !== null) {
155
            $options[CURLOPT_POSTFIELDS] = $body;
156
        }
157
158
        foreach ($request->headers() as $header_name => $header_value) {
159
            $options[CURLOPT_HTTPHEADER][] = "{$header_name}: {$header_value}";
160
        }
161
162
        $headers_to_set_to_blank_if_not_set = [
163
            'Content-Type',
164
            'Expect',
165
            'Accept',
166
            'Accept-Encoding'
167
        ];
168
        foreach ($headers_to_set_to_blank_if_not_set as $name) {
169
            if ($request->header($name) === null) {
170
                $conf[CURLOPT_HTTPHEADER][] = "{$name}:";
171
            }
172
        }
173
174
        curl_setopt_array($curl_handler, $options);
175
176
        return $curl_handler;
177
    }
178
179
    private function extractHeaders(string $header): array
180
    {
181
        $parts = explode("\n", $header);
182
        $result = [];
183
        foreach ($parts as $part) {
184
            $part = trim($part);
185
            if (empty($part)) {
186
                continue;
187
            }
188
189
            if (strpos($part, ': ') !== false) {
190
                list($key, $value) = explode(": ", $part);
191
                $result[$key] = trim($value);
192
                continue;
193
            }
194
195
            $regex = '#^HTTP/(\d\.\d) (\d{3})(.*)#';
196
            if (preg_match($regex, $part, $matches)) {
197
                if ($result) {
198
                    $result = ['redirected' => $result];
199
                }
200
201
                $result['Http-Version'] = $matches[1];
202
                $result['Http-Code'] = $matches[2];
203
                $result['Http-Message'] = $matches[3] ? trim($matches[3]) : '';
204
            }
205
        }
206
        return $result;
207
    }
208
}
209