Http::getJson()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.1406

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 1
dl 0
loc 17
ccs 6
cts 8
cp 0.75
crap 3.1406
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Packagist Mirror.
7
 *
8
 * For the full license information, please view the LICENSE.md
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Webs\Mirror;
13
14
use GuzzleHttp\Client;
15
use GuzzleHttp\Pool;
16
use GuzzleHttp\Psr7\Request;
17
use stdClass;
18
use Exception;
19
use Generator;
20
use Closure;
21
22
/**
23
 * Middleware to http operations.
24
 *
25
 * @author Webysther Nunes <[email protected]>
26
 */
27
class Http
28
{
29
    use Gzip;
30
31
    /**
32
     * @var Client
33
     */
34
    protected $client;
35
36
    /**
37
     * @var Mirror
38
     */
39
    protected $mirror;
40
41
    /**
42
     * @var array
43
     */
44
    protected $poolErrors;
45
46
    /**
47
     * @var array
48
     */
49
    protected $poolErrorsCount = [];
50
51
    /**
52
     * @var array
53
     */
54
    protected $config = [
55
        'base_uri' => '',
56
        'headers' => ['Accept-Encoding' => 'gzip'],
57
        'decode_content' => false,
58
        'timeout' => 30,
59
        'connect_timeout' => 15,
60
    ];
61
62
    /**
63
     * @var int
64
     */
65
    protected $maxConnections;
66
67
    /**
68
     * @var int
69
     */
70
    protected $connections;
71
72
    /**
73
     * @var bool
74
     */
75
    protected $usingMirrors = false;
76
77
    /**
78
     * @param Mirror $mirror
79
     * @param int    $maxConnections
80
     */
81 1
    public function __construct(Mirror $mirror, int $maxConnections)
82
    {
83 1
        // fixed when /something is passed is converted to /
84 1
        // now we put a / as suffix and is resolved as /something/
85 1
        $this->config['base_uri'] = trim($mirror->getMaster(), '/').'/';
86 1
        $this->client = new Client($this->config);
87 1
        $this->maxConnections = $maxConnections;
88
        $this->mirror = $mirror;
89
    }
90
91
    /**
92 1
     * @return string
93
     */
94 1
    public function getBaseUri():string
95
    {
96
        return $this->config['base_uri'];
97
    }
98
99
    /**
100
     * Client get with transparent gz decode.
101
     *
102 1
     * @see Client::get
103
     */
104 1
    public function getJson(string $uri):stdClass
105
    {
106
        $response = $this->client->get($uri);
107 1
108
        // Maybe 4xx or 5xx
109
        if ($response->getStatusCode() >= 400) {
110
            throw new Exception("Error download $uri", 1);
111 1
        }
112 1
113
        $json = $this->decode((string) $response->getBody());
114 1
        $decoded = json_decode($json);
115
116
        if (json_last_error() !== JSON_ERROR_NONE) {
117
            throw new Exception("Response not a json: $json", 1);
118 1
        }
119
120
        return $decoded;
121
    }
122
123
    /**
124
     * Create a new get request.
125
     *
126
     * @param string $uri
127
     *
128 1
     * @return Request
129
     */
130 1
    public function getRequest(string $uri):Request
131 1
    {
132 1
        $base = $this->getBaseUri();
133
        if ($this->usingMirrors) {
134
            $base = $this->mirror->getNext();
135 1
        }
136
137
        // when uri is doubled // is misleading interpreted
138
        $request_uri = trim($base, '/').'/'.trim($uri, '/');
139
        // debug every uri:
140
        // print($request_uri.PHP_EOL);
141
        return new Request('GET', $request_uri);
142
    }
143
144
    /**
145 1
     * @param Generator $requests
146
     * @param Closure   $success
147 1
     * @param Closure   $complete
148 1
     *
149 1
     * @return Http
150 1
     */
151
    public function pool(Generator $requests, Closure $success, Closure $complete):Http
152
    {
153 1
        $this->connections = $this->maxConnections;
154 1
        if ($this->usingMirrors) {
155 1
            $mirrors = $this->mirror->getAll()->count();
156 1
            $this->connections = $this->maxConnections * $mirrors;
157 1
        }
158
159 1
        $fulfilled = function($response, $path) use ($success, $complete) {
160 1
            $body = (string) $response->getBody();
161 1
            $success($body, $path);
162
            $complete();
163 1
        };
164 1
165
        $rejected = function($reason, $path) use ($complete) {
166 1
            $uri = $reason->getRequest()->getUri();
167 1
            $host = $uri->getScheme().'://'.$uri->getHost();
168 1
            $uri = (string) $uri;
169 1
170
            $wordwrap = wordwrap($reason->getMessage());
171
            $message = current(explode("\n", $wordwrap)).'...';
172 1
173 1
            $this->poolErrors[$path] = [
174 1
                'code' => $reason->getCode(),
175
                'host' => dirname($uri),
176 1
                'message' => $message,
177 1
            ];
178 1
179 1
            if (!isset($this->poolErrorsCount[$host])) {
180
                $this->poolErrorsCount[$host] = 0;
181 1
            }
182 1
            ++$this->poolErrorsCount[$host];
183 1
            $complete();
184
        };
185
186 1
        $this->poolErrors = [];
187
        $pool = new Pool(
188
            $this->client,
189 1
            $requests,
190
            [
191 1
                'concurrency' => $this->connections,
192
                'fulfilled' => $fulfilled,
193
                'rejected' => $rejected,
194
            ]
195
        );
196
        $pool->promise()->wait();
197 1
198
        // Reset to use only max connections for one mirror
199 1
        $this->usingMirrors = false;
200
201 1
        return $this;
202
    }
203
204
    /**
205
     * @return Http
206
     */
207 1
    public function useMirrors():Http
208
    {
209 1
        $this->usingMirrors = true;
210
211
        return $this;
212
    }
213
214
    /**
215
     * @return array
216
     */
217
    public function getPoolErrors():array
218
    {
219
        return $this->poolErrors;
220
    }
221
222
    /**
223
     * @param string $mirror
224
     *
225
     * @return int
226
     */
227
    public function getTotalErrorByMirror(string $mirror):int
228
    {
229
        if (!isset($this->poolErrorsCount[$mirror])) {
230
            return 0;
231
        }
232
233
        return $this->poolErrorsCount[$mirror];
234
    }
235
236
    /**
237
     * @return Mirror
238
     */
239
    public function getMirror():Mirror
240
    {
241
        return $this->mirror;
242
    }
243
}
244