Passed
Push — master ( 49eccb...69f727 )
by Thomas
02:32
created

Request::buildRequestOptions()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 30
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 19
c 2
b 0
f 0
nc 4
nop 3
dl 0
loc 30
rs 9.6333
1
<?php
2
3
namespace Sulao\BaiduBos;
4
5
use GuzzleHttp;
6
use GuzzleHttp\Exception\BadResponseException;
7
use Psr\Http\Message\ResponseInterface;
8
9
/**
10
 * Class Client
11
 *
12
 * @package Sulao\BaiduBos
13
 */
14
abstract class Request
15
{
16
    const BOS_HOST = 'bcebos.com';
17
18
    /**
19
     * @var array
20
     */
21
    protected $config;
22
23
    /**
24
     * @var array Guzzle request options
25
     */
26
    protected $options = ['connect_timeout' => 10];
27
28
    /**
29
     * @var Authorizer
30
     */
31
    protected $authorizer;
32
33
    /**
34
     * Client constructor.
35
     *
36
     * @param array $config
37
     *
38
     * @throws Exception
39
     */
40
    public function __construct(array $config)
41
    {
42
        $keys = array_diff(
43
            ['access_key', 'secret_key', 'bucket', 'region'],
44
            array_keys($config)
45
        );
46
        if (!empty($keys)) {
47
            throw new Exception(
48
                'Invalid config, missing: ' . implode(',', $keys)
49
            );
50
        }
51
52
        if (isset($config['options']) && is_array($config['options'])) {
53
            $this->options = $config['options'] + $this->options;
54
        }
55
56
        $this->authorizer = new Authorizer(
57
            $config['access_key'],
58
            $config['secret_key']
59
        );
60
61
        $this->config = $config;
62
    }
63
64
    /**
65
     * Request BOS api
66
     *
67
     * @param string $method
68
     * @param string $path
69
     * @param array  $options
70
     *
71
     * @return array|string|mixed
72
     * @throws Exception
73
     */
74
    protected function request($method, $path, array $options = [])
75
    {
76
        list($endpoint, $requestOptions) = $this
77
            ->buildRequestOptions($method, $path, $options);
78
79
        $httpClient = new GuzzleHttp\Client($this->options);
80
81
        try {
82
            $response = $httpClient
83
                ->request($method, $endpoint, $requestOptions);
84
        } catch (\Exception $exception) {
85
            throw $this->handleRquestException($exception);
86
        }
87
88
        return $this->processReturn(
89
            $response,
90
            isset($options['return']) ? $options['return'] : null
91
        );
92
    }
93
94
    /**
95
     * Handle http request exception
96
     *
97
     * @param \Exception $exception
98
     *
99
     * @return Exception
100
     */
101
    protected function handleRquestException(\Exception $exception)
102
    {
103
        if ($exception instanceof BadResponseException) {
104
            $content = $exception->getResponse()->getBody()->getContents();
105
            $response = json_decode($content, true);
106
            if (isset($response['code']) && isset($response['message'])) {
107
                $newException = new Exception(
108
                    $response['message'],
109
                    $exception->getCode(),
110
                    $exception
111
                );
112
113
                $newException->bosCode = $response['code'];
114
115
                if (isset($response['requestId'])) {
116
                    $newException->requestId = $response['requestId'];
117
                }
118
119
                return $newException;
120
            }
121
        }
122
123
        return new Exception(
124
            $exception->getMessage(),
125
            $exception->getCode(),
126
            $exception
127
        );
128
    }
129
130
    /**
131
     * Build endpoint and request options for guzzle to request
132
     *
133
     * @param string $method
134
     * @param string $path
135
     * @param array  $options
136
     *
137
     * @return array
138
     */
139
    protected function buildRequestOptions($method, $path, array $options = [])
140
    {
141
        $path = '/' . ltrim($path, '/');
142
143
        $options += [
144
            'query' => [], 'headers' => [], 'body' => null,
145
            'request' => [], 'authorize' => [],
146
        ];
147
        $requestOptions = $options['request'];
148
149
        $headers = $this->buildHeaders(
150
            $method,
151
            $path,
152
            $options['query'],
153
            $options['headers'],
154
            $options['body'],
155
            $options['authorize']
156
        );
157
        $requestOptions['headers'] = $headers;
158
159
        if (!is_null($options['body'])) {
160
            $requestOptions['body'] = $options['body'];
161
        }
162
163
        $endpoint = 'https://' . $headers['Host'] . $path;
164
        if (!empty($options['query'])) {
165
            $endpoint .= '?' . $this->buildQuery($options['query']);
166
        }
167
168
        return [$endpoint, $requestOptions];
169
    }
170
171
    /**
172
     * Build BOS api request header
173
     *
174
     * @param string      $method
175
     * @param string      $path
176
     * @param array       $query
177
     * @param array       $headers
178
     * @param string|null $body
179
     * @param array       $options
180
     *
181
     * @return array
182
     */
183
    protected function buildHeaders(
184
        $method,
185
        $path,
186
        array $query = [],
187
        array $headers = [],
188
        $body = null,
189
        array $options = []
190
    ) {
191
        $keys = array_map('strtolower', array_keys($headers));
192
193
        $headers['Host'] = $this->config['bucket'] . '.'
194
            . $this->config['region'] . '.' . self::BOS_HOST;
195
196
        if (!array_key_exists('date', $keys)) {
197
            $headers['Date'] = gmdate('D, d M Y H:i:s e');
198
        }
199
200
        $contentLength = strlen((string) $body);
201
        if (!array_key_exists('content-length', $keys)) {
202
            $headers['Content-Length'] = $contentLength ;
203
        }
204
205
        if (!array_key_exists('content-md5', $keys) && $contentLength) {
206
            $headers['Content-MD5'] = base64_encode(md5($body, true));
0 ignored issues
show
Bug introduced by
It seems like $body can also be of type null; however, parameter $string of md5() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

206
            $headers['Content-MD5'] = base64_encode(md5(/** @scrutinizer ignore-type */ $body, true));
Loading history...
207
        }
208
209
        if (!array_key_exists('authorization', $keys)) {
210
            $headers['Authorization'] = $this->authorizer
211
                ->getAuthorization($method, $path, $query, $headers, $options);
212
        }
213
214
        return $headers;
215
    }
216
217
    /**
218
     *  Build query string
219
     *
220
     * @param array $query
221
     *
222
     * @return string
223
     */
224
    protected function buildQuery(array $query)
225
    {
226
        $arr = [];
227
        foreach ($query as $key => $value) {
228
            $arr[] = rawurlencode($key)
229
                . (isset($value) ? '=' . rawurlencode($value) : '');
230
        }
231
232
        return implode('&', $arr);
233
    }
234
235
    /**
236
     * Process the return base on format
237
     *
238
     * @param ResponseInterface $response
239
     * @param string|null       $format
240
     *
241
     * @return array|string|mixed
242
     */
243
    protected function processReturn(
244
        ResponseInterface $response,
245
        $format = null
246
    ) {
247
        if ($format === 'body-json') {
248
            $return = json_decode($response->getBody()->getContents(), true);
249
        } elseif ($format === 'headers') {
250
            $return = $this->parseHeaders($response->getHeaders());
251
        } elseif ($format === 'both') {
252
            $return = [
253
                'headers' => $this->parseHeaders($response->getHeaders()),
254
                'body' => $response->getBody()->getContents(),
255
            ];
256
        } else {
257
            $return = $response->getBody()->getContents();
258
        }
259
260
        return $return;
261
    }
262
263
    /**
264
     * Parse response headers
265
     *
266
     * @param array $responseHeaders
267
     *
268
     * @return array
269
     */
270
    protected function parseHeaders(array $responseHeaders)
271
    {
272
        $headers = array();
273
        foreach ($responseHeaders as $name => $values) {
274
            $headers[$name] = count($values) == 1 ? reset($values) : $values;
275
        }
276
277
        return $headers;
278
    }
279
}
280