Passed
Branch main (45b422)
by Andreas
01:40
created

ServerPool::configure()   B

Complexity

Conditions 10
Paths 108

Size

Total Lines 54
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 10

Importance

Changes 0
Metric Value
eloc 33
c 0
b 0
f 0
dl 0
loc 54
ccs 29
cts 29
cp 1
rs 7.6
cc 10
nc 108
nop 1
crap 10

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Licensed to CRATE Technology GmbH("Crate") under one or more contributor
4
 * license agreements.  See the NOTICE file distributed with this work for
5
 * additional information regarding copyright ownership.  Crate licenses
6
 * this file to you under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.  You may
8
 * obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
15
 * License for the specific language governing permissions and limitations
16
 * under the License.
17
 *
18
 * However, if you have executed another commercial license agreement
19
 * with Crate these terms will supersede the license and you may use the
20
 * software solely pursuant to the terms of the relevant commercial agreement.
21
 */
22
23
declare(strict_types=1);
24
25
namespace Crate\PDO\Http;
26
27
use Crate\PDO\Exception\RuntimeException;
28
use Crate\PDO\PDO;
29
use Crate\PDO\PDOInterface;
30
use Crate\Stdlib\Collection;
31
use Crate\Stdlib\CollectionInterface;
32
use GuzzleHttp\Client;
33
use GuzzleHttp\ClientInterface;
34
use GuzzleHttp\Exception\BadResponseException;
35
use GuzzleHttp\Exception\ConnectException;
36
use GuzzleHttp\RequestOptions;
37
38
/**
39
 * Class ServerPool
40
 *
41
 * Very basic round robin implementation
42
 */
43
final class ServerPool implements ServerInterface
44
{
45
    private const DEFAULT_SERVER = 'localhost:4200';
46
47
    /**
48
     * @var string
49
     */
50
    private $protocol = 'http';
51
52
    /**
53
     * @var array
54
     */
55
    private $httpOptions = [];
56
57
    /**
58
     * @var string[]
59
     */
60
    private $availableServers = [];
61
62
    /**
63
     * @var Client
64
     */
65
    private $httpClient;
66
67
    /**
68
     * Client constructor.
69
     *
70
     * @param array                $servers
71
     * @param ClientInterface|null $client
72
     */
73 13
    public function __construct(array $servers, ClientInterface $client = null)
74
    {
75 13
        if (\count($servers) === 0) {
76
            $servers = [self::DEFAULT_SERVER];
77
        }
78
79
        // micro optimization so we don't always hit the same server first
80 13
        shuffle($servers);
81
82 13
        foreach ($servers as $server) {
83 13
            $this->availableServers[] = $server;
84
        }
85
86 13
        $this->httpClient = $client ?: new Client();
87 13
    }
88
89
    /**
90
     * {@Inheritdoc}
91
     * @throws \GuzzleHttp\Exception\ConnectException
92
     */
93 13
    public function execute(string $query, array $parameters): CollectionInterface
94
    {
95 13
        $numServers = count($this->availableServers) - 1;
96
97 13
        for ($i = 0; $i <= $numServers; $i++) {
98
            // always get the first available server
99 13
            $server = $this->availableServers[0];
100
101
            // Move the selected server to the end of the stack
102 13
            $this->availableServers[] = array_shift($this->availableServers);
103
104 13
            $options = array_merge($this->httpOptions, [
105 13
                'base_uri' => sprintf('%s://%s', $this->protocol, $server),
106
                'json'     => [
107 13
                    'stmt' => $query,
108 13
                    'args' => $parameters,
109
                ],
110
            ]);
111
112
            try {
113 13
                $response     = $this->httpClient->request('POST', '/_sql', $options);
114 11
                $responseBody = json_decode((string)$response->getBody(), true);
115
116 11
                return new Collection(
117 11
                    $responseBody['rows'],
118 11
                    $responseBody['cols'],
119 11
                    $responseBody['duration'],
120 11
                    $responseBody['rowcount']
121
                );
122 3
            } catch (ConnectException $exception) {
123
                // Catch it before the BadResponseException but do nothing.
124 2
                continue;
125 1
            } catch (BadResponseException $exception) {
126 1
                $body = (string)$exception->getResponse()->getBody();
127 1
                $json = json_decode($body, true);
128
129 1
                if ($json === null && json_last_error() !== JSON_ERROR_NONE) {
130
                    throw new RuntimeException(sprintf('Server returned non-JSON response: %s', $body), 0, $exception);
131
                }
132
133 1
                $errorCode    = $json['error']['code'];
134 1
                $errorMessage = $json['error']['message'];
135
136 1
                throw new RuntimeException($errorMessage, $errorCode, $exception);
137
            }
138
        }
139
140 1
        throw new ConnectException(
141 1
            sprintf('No more servers available, exception from last server: %s', $exception->getMessage()),
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $exception does not seem to be defined for all execution paths leading up to this point.
Loading history...
142 1
            $exception->getRequest(),
143
            $exception
144
        );
145
    }
146
147
    /**
148
     * {@Inheritdoc}
149
     */
150
    public function getServerInfo(): array
151
    {
152
        return [
153
            'serverVersion' => $this->getServerVersion(),
154
        ];
155
    }
156
157
    /**
158
     * {@Inheritdoc}
159
     */
160
    public function getServerVersion(): string
161
    {
162
        $result = $this->execute("select version['number'] from sys.nodes limit 1", []);
163
164
        if (count($result->getRows()) !== 1) {
165
            throw new RuntimeException('Failed to determine server version');
166
        }
167
168
        return $result->getRows()[0][0];
169
    }
170
171
    /**
172
     * Reconfigure the the server pool based on the attributes in PDO
173
     *
174
     * @param PDOInterface $pdo
175
     */
176 13
    public function configure(PDOInterface $pdo): void
177
    {
178 13
        $sslMode = $pdo->getAttribute(PDO::CRATE_ATTR_SSL_MODE);
179
180 13
        $protocol = $sslMode === PDO::CRATE_ATTR_SSL_MODE_DISABLED ? 'http' : 'https';
181
182
        $options = [
183 13
            RequestOptions::TIMEOUT         => $pdo->getAttribute(PDO::ATTR_TIMEOUT),
184 13
            RequestOptions::CONNECT_TIMEOUT => $pdo->getAttribute(PDO::ATTR_TIMEOUT),
185 13
            RequestOptions::AUTH            => $pdo->getAttribute(PDO::CRATE_ATTR_HTTP_BASIC_AUTH) ?: null,
186
            RequestOptions::HEADERS         => [
187 13
                'Default-Schema' => $pdo->getAttribute(PDO::CRATE_ATTR_DEFAULT_SCHEMA),
188
            ],
189
        ];
190
191 13
        if ($sslMode === PDO::CRATE_ATTR_SSL_MODE_ENABLED_BUT_WITHOUT_HOST_VERIFICATION) {
192 2
            $options['verify'] = false;
193
        }
194
195 13
        $ca         = $pdo->getAttribute(PDO::CRATE_ATTR_SSL_CA_PATH);
196 13
        $caPassword = $pdo->getAttribute(PDO::CRATE_ATTR_SSL_CA_PASSWORD);
197
198 13
        if ($ca) {
199 2
            if ($caPassword) {
200 1
                $options[RequestOptions::VERIFY] = [$ca, $caPassword];
201
            } else {
202 2
                $options[RequestOptions::VERIFY] = $ca;
203
            }
204
        }
205
206 13
        $cert         = $pdo->getAttribute(PDO::CRATE_ATTR_SSL_CERT_PATH);
207 13
        $certPassword = $pdo->getAttribute(PDO::CRATE_ATTR_SSL_CERT_PASSWORD);
208
209 13
        if ($cert) {
210 2
            if ($certPassword) {
211 1
                $options[RequestOptions::CERT] = [$cert, $certPassword];
212
            } else {
213 2
                $options[RequestOptions::CERT] = $cert;
214
            }
215
        }
216
217 13
        $key         = $pdo->getAttribute(PDO::CRATE_ATTR_SSL_KEY_PATH);
218 13
        $keyPassword = $pdo->getAttribute(PDO::CRATE_ATTR_SSL_KEY_PASSWORD);
219
220 13
        if ($key) {
221 2
            if ($keyPassword) {
222 1
                $options[RequestOptions::SSL_KEY] = [$key, $keyPassword];
223
            } else {
224 2
                $options[RequestOptions::SSL_KEY] = $key;
225
            }
226
        }
227
228 13
        $this->protocol    = $protocol;
229 13
        $this->httpOptions = $options;
230 13
    }
231
}
232