Completed
Push — feature/decoupled ( ef93d4...6a89b6 )
by Webysther
02:32
created

Http::useMirrors()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 5
ccs 0
cts 4
cp 0
crap 2
rs 9.4285
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
    public function __construct(Mirror $mirror, int $maxConnections)
82
    {
83
        $this->config['base_uri'] = $mirror->getMaster();
84
        $this->client = new Client($this->config);
85
        $this->maxConnections = $maxConnections;
86
        $this->mirror = $mirror;
87
    }
88
89
    /**
90
     * @return string
91
     */
92
    public function getBaseUri():string
93
    {
94
        return $this->config['base_uri'];
95
    }
96
97
    /**
98
     * Client get with transparent gz decode.
99
     *
100
     * @see Client::get
101
     */
102
    public function getJson(string $uri):stdClass
103
    {
104
        $response = $this->client->get($uri);
105
106
        // Maybe 4xx or 5xx
107
        if ($response->getStatusCode() >= 400) {
108
            throw new Exception("Error download $uri", 1);
109
        }
110
111
        $json = $this->decode((string) $response->getBody());
112
        $decoded = json_decode($json);
113
114
        if (json_last_error() !== JSON_ERROR_NONE) {
115
            throw new Exception("Response not a json: $json", 1);
116
        }
117
118
        return $decoded;
119
    }
120
121
    /**
122
     * Create a new get request.
123
     *
124
     * @param string $uri
125
     *
126
     * @return Request
127
     */
128
    public function getRequest(string $uri):Request
129
    {
130
        $base = $this->getBaseUri();
131
        if ($this->usingMirrors) {
132
            $base = $this->mirror->getNext();
133
        }
134
135
        return new Request('GET', $base.'/'.$uri);
136
    }
137
138
    /**
139
     * @param Generator $requests
140
     * @param Closure   $success
141
     * @param Closure   $complete
142
     *
143
     * @return Http
144
     */
145
    public function pool(Generator $requests, Closure $success, Closure $complete):Http
146
    {
147
        $this->connections = $this->maxConnections;
148
        if ($this->usingMirrors) {
149
            $mirrors = $this->mirror->getAll()->count();
150
            $this->connections = $this->maxConnections * $mirrors;
151
        }
152
153
        $fulfilled = function ($response, $path) use ($success, $complete) {
154
            $body = (string) $response->getBody();
155
            $success($body, $path);
156
            $complete();
157
        };
158
159
        $rejected = function ($reason, $path) use ($complete) {
160
            $uri = $reason->getRequest()->getUri();
161
            $host = $uri->getScheme().'://'.$uri->getHost();
162
163
            $wordwrap = wordwrap($reason->getMessage());
164
            $message = current(explode("\n", $wordwrap)).'...';
165
166
            $this->poolErrors[$path] = [
167
                'code' => $reason->getCode(),
168
                'host' => $host,
169
                'message' => $message
170
            ];
171
172
            $this->poolErrorsCount[$host]++;
173
            $complete();
174
        };
175
176
        $this->poolErrors = [];
177
        $pool = new Pool(
178
            $this->client,
179
            $requests,
180
            [
181
                'concurrency' => $this->connections,
182
                'fulfilled' => $fulfilled,
183
                'rejected' => $rejected,
184
            ]
185
        );
186
        $pool->promise()->wait();
187
188
        // Reset to use only max connections for one mirror
189
        $this->usingMirrors = false;
190
191
        return $this;
192
    }
193
194
    /**
195
     * @return Http
196
     */
197
    public function useMirrors():Http
198
    {
199
        $this->usingMirrors = true;
200
201
        return $this;
202
    }
203
204
    /**
205
     * @return array
206
     */
207
    public function getPoolErrors():array
208
    {
209
        return $this->poolErrors;
210
    }
211
212
    /**
213
     * @param  string $mirror
214
     * @return int
215
     */
216
    public function getTotalErrorByMirror(string $mirror):int
217
    {
218
        if(!isset($this->poolErrorsCount[$mirror])){
219
            return 0;
220
        }
221
222
        return $this->poolErrorsCount[$mirror];
223
    }
224
225
    /**
226
     * @return Mirror
227
     */
228
    public function getMirror():Mirror
229
    {
230
        return $this->mirror;
231
    }
232
}
233