Completed
Push — h/multiple-servers ( 562c3a )
by Christian
06:27
created

Client::setHttpBasicAuth()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 9
rs 9.6666
cc 2
eloc 3
nc 2
nop 2
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
namespace Crate\PDO\Http;
24
25
use Crate\PDO\Exception\RuntimeException;
26
use Crate\PDO\Exception\UnsupportedException;
27
use Crate\Stdlib\Collection;
28
use GuzzleHttp\Exception\BadResponseException;
29
use GuzzleHttp\Exception\ConnectException;
30
use GuzzleHttp\Exception\ParseException;
31
32
33
class Client implements ClientInterface
34
{
35
36
    const DEFAULT_SERVER = "localhost:4200";
37
38
    /**
39
     * @var array
40
     */
41
    private $availableServers = [];
42
43
    /**
44
     * @var array
45
     */
46
    private $serverPool = [];
47
48
    /**
49
     * Client constructor.
50
     * @param array $servers
51
     * @param array $options
52
     */
53
    public function __construct(array $servers, array $options)
54
    {
55
        if ($servers == null || count($servers) == 0) {
56
            $this->serverPool[self::DEFAULT_SERVER] = new Server(self::DEFAULT_SERVER, $options);
57
            $this->availableServers[] = self::DEFAULT_SERVER;
58
        } else {
59
            foreach ($servers as &$server) {
60
                $this->serverPool[$server] = new Server($server, $options);
61
                $this->availableServers[] = $server;
62
            }
63
        }
64
    }
65
66
    /**
67
     * {@Inheritdoc}
68
     */
69
    public function execute($query, array $parameters)
70
    {
71
        $body = [
72
            'stmt' => $query,
73
            'args' => $parameters
74
        ];
75
76
        return $this->sqlRequest(null, $body);
77
    }
78
79
    /**
80
     * @param string $server
81
     * @param array  $body
82
     * @return Collection
83
     */
84
    private function sqlRequest($server, array $body)
85
    {
86
        while (true) {
87
            $nextServer = $server == null ? $this->nextServer() : $server;
88
            /** @var Server $s */
89
            $s = $this->serverPool[$nextServer];
90
91
            try {
92
93
                $response = $s->doRequest($body);
94
                $responseBody = $response->json();
95
96
                return new Collection(
97
                    $responseBody['rows'],
98
                    $responseBody['cols'],
99
                    $responseBody['duration'],
100
                    $responseBody['rowcount']
101
                );
102
103
            } catch (ConnectException $exception) {
104
105
                if ($server != null) {
106
                    throw $exception;
107
                } else {
108
                    $this->dropServer($server, $exception);
109
                }
110
111
            } catch (BadResponseException $exception) {
112
113
                try {
114
115
                    $json = $exception->getResponse()->json();
116
117
                    $errorCode    = $json['error']['code'];
118
                    $errorMessage = $json['error']['message'];
119
120
                    throw new RuntimeException($errorMessage, $errorCode, $exception);
121
122
                } catch (ParseException $e) {
123
                    throw new RuntimeException('Server returned non-JSON response.', 0, $exception);
124
                }
125
126
            }
127
        }
128
        return null;
129
    }
130
131
    /**
132
     * {@Inheritdoc}
133
     */
134
    public function getServerInfo()
135
    {
136
        throw new UnsupportedException('Not yet implemented');
137
    }
138
139
    /**
140
     * {@Inheritdoc}
141
     */
142
    public function getServerVersion()
143
    {
144
        throw new UnsupportedException('Not yet implemented');
145
    }
146
147
    /**
148
     * {@Inheritdoc}
149
     */
150
    public function setTimeout($timeout)
151
    {
152
        foreach ($this->serverPool as $k => &$s) {
153
            /**
154
             * @var $s Server
155
             */
156
            $s->setTimeout($timeout);
157
        }
158
    }
159
160
    /**
161
     * {@Inheritdoc}
162
     */
163
    public function setHttpBasicAuth($username, $passwd)
164
    {
165
        foreach ($this->serverPool as $k => &$s) {
166
            /**
167
             * @var $s Server
168
             */
169
            $s->setHttpBasicAuth($username, $passwd);
170
        }
171
    }
172
173
    /**
174
     * {@Inheritdoc}
175
     */
176
    public function setHttpHeader($name, $value)
177
    {
178
        foreach ($this->serverPool as $k => &$s) {
179
            /**
180
             * @var $s Server
181
             */
182
            $s->setHttpHeader($name, $value);
183
        }
184
    }
185
186
    /**
187
     * @return Server The next available server instance
188
     */
189
    private function nextServer()
190
    {
191
        $server = $this->availableServers[0];
192
        $this->roundRobin();
193
        return $server;
194
    }
195
196
    /**
197
     * Very simple round-robin implementation
198
     * Pops the first item of the availableServers array and appends it at the end.
199
     *
200
     * @return void
201
     */
202
    private function roundRobin()
203
    {
204
        /**
205
         * Performing round robin on the array only makes sense
206
         * if there are more than 1 available servers.
207
         */
208
        if (count($this->availableServers) > 1) {
209
            $this->availableServers[] = array_shift($this->availableServers);
210
        }
211
    }
212
213
    /**
214
     * @param string           $server
215
     * @param ConnectException $exception
216
     */
217
    private function dropServer($server, $exception)
218
    {
219
        if (($idx = array_search($server, $this->availableServers)) !== false) {
220
            unset($this->availableServers[$idx]);
221
        }
222
        if (count($this->availableServers) == 0) {
223
            throw new ConnectException("No more servers available, exception from last server: " . $exception->getMessage(),
224
                $exception->getRequest(), $exception->getResponse(), $exception);
225
        }
226
    }
227
228
}
229