Skip to content

Commit 6277d2c

Browse files
authored
Merge pull request #408 from qiniu/feat/middleware-and-backup-domains
add http client middleware and getting region hosts with backup domains
2 parents a678c26 + 3c47321 commit 6277d2c

File tree

13 files changed

+444
-13
lines changed

13 files changed

+444
-13
lines changed

src/Qiniu/Config.php

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ final class Config
3232
private $regionCache;
3333
// UC Host
3434
private $ucHost;
35+
// backup UC Hosts
36+
private $backupUcHosts;
37+
// backup UC Hosts max retry time
38+
public $backupUcHostsRetryTimes;
3539

3640
// 构造函数
3741
public function __construct(Region $z = null)
@@ -41,11 +45,17 @@ public function __construct(Region $z = null)
4145
$this->useCdnDomains = false;
4246
$this->regionCache = array();
4347
$this->ucHost = Config::UC_HOST;
48+
$this->backupUcHosts = array(
49+
"kodo-config.qiniuapi.com",
50+
"api.qiniu.com"
51+
);
52+
$this->backupUcHostsRetryTimes = 2;
4453
}
4554

46-
public function setUcHost($ucHost)
55+
public function setUcHost($ucHost, $backupUcHosts = array())
4756
{
4857
$this->ucHost = $ucHost;
58+
$this->backupUcHosts = $backupUcHosts;
4959
}
5060

5161
public function getUcHost()
@@ -59,6 +69,21 @@ public function getUcHost()
5969
return $scheme . $this->ucHost;
6070
}
6171

72+
public function appendBackupUcHosts($hosts)
73+
{
74+
$this->backupUcHosts = array_merge($this->backupUcHosts, $hosts);
75+
}
76+
77+
public function prependBackupUcHosts($hosts)
78+
{
79+
$this->backupUcHosts = array_merge($hosts, $this->backupUcHosts);
80+
}
81+
82+
public function getBackupUcHosts()
83+
{
84+
return $this->backupUcHosts;
85+
}
86+
6287
public function getUpHost($accessKey, $bucket)
6388
{
6489
$region = $this->getRegion($accessKey, $bucket);
@@ -308,7 +333,13 @@ private function getRegion($accessKey, $bucket)
308333
return $regionCache;
309334
}
310335

311-
$region = Zone::queryZone($accessKey, $bucket, $this->getUcHost());
336+
$region = Zone::queryZone(
337+
$accessKey,
338+
$bucket,
339+
$this->getUcHost(),
340+
$this->getBackupUcHosts(),
341+
$this->backupUcHostsRetryTimes
342+
);
312343
if (is_array($region)) {
313344
list($region, $err) = $region;
314345
if ($err != null) {
@@ -332,7 +363,13 @@ private function getRegionV2($accessKey, $bucket)
332363
return array($regionCache, null);
333364
}
334365

335-
$region = Zone::queryZone($accessKey, $bucket, $this->getUcHost());
366+
$region = Zone::queryZone(
367+
$accessKey,
368+
$bucket,
369+
$this->getUcHost(),
370+
$this->getBackupUcHosts(),
371+
$this->backupUcHostsRetryTimes
372+
);
336373
if (is_array($region)) {
337374
list($region, $err) = $region;
338375
return array($region, $err);

src/Qiniu/Http/Client.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
namespace Qiniu\Http;
33

44
use Qiniu\Config;
5+
use Qiniu\Http\Middleware;
56

67
final class Client
78
{
@@ -14,7 +15,7 @@ final class Client
1415
public static function get($url, array $headers = array(), $opt = null)
1516
{
1617
$request = new Request('GET', $url, $headers, null, $opt);
17-
return self::sendRequest($request);
18+
return self::sendRequestWithMiddleware($request);
1819
}
1920

2021
/**
@@ -119,6 +120,19 @@ private static function userAgent()
119120
return $ua;
120121
}
121122

123+
/**
124+
* @param Request $request
125+
* @return Response
126+
*/
127+
public static function sendRequestWithMiddleware($request)
128+
{
129+
$middlewares = $request->opt->middlewares;
130+
$handle = Middleware\compose($middlewares, function ($req) {
131+
return Client::sendRequest($req);
132+
});
133+
return $handle($request);
134+
}
135+
122136
/**
123137
* @param Request $request
124138
* @return Response
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
namespace Qiniu\Http\Middleware;
3+
4+
use Qiniu\Http\Request;
5+
use Qiniu\Http\Response;
6+
7+
interface Middleware
8+
{
9+
/**
10+
* @param Request $request
11+
* @param callable(Request): Response $next
12+
* @return Response
13+
*/
14+
public function send($request, $next);
15+
}
16+
17+
/**
18+
* @param array<Middleware> $middlewares
19+
* @param callable(Request): Response $handler
20+
* @return callable(Request): Response
21+
*/
22+
function compose($middlewares, $handler)
23+
{
24+
$next = $handler;
25+
foreach (array_reverse($middlewares) as $middleware) {
26+
$next = function ($request) use ($middleware, $next) {
27+
return $middleware->send($request, $next);
28+
};
29+
}
30+
return $next;
31+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
namespace Qiniu\Http\Middleware;
3+
4+
use Qiniu\Http\Request;
5+
use Qiniu\Http\Response;
6+
7+
class RetryDomainsMiddleware implements Middleware
8+
{
9+
/**
10+
* @var array<string> backup domains.
11+
*/
12+
private $backupDomains;
13+
14+
/**
15+
* @var numeric max retry times for each backup domains.
16+
*/
17+
private $maxRetryTimes;
18+
19+
/**
20+
* @var callable args response and request; returns bool; If true will retry with backup domains.
21+
*/
22+
private $retryCondition;
23+
24+
/**
25+
* @param array<string> $backupDomains
26+
* @param numeric $maxRetryTimes
27+
*/
28+
public function __construct($backupDomains, $maxRetryTimes = 2, $retryCondition = null)
29+
{
30+
$this->backupDomains = $backupDomains;
31+
$this->maxRetryTimes = $maxRetryTimes;
32+
$this->retryCondition = $retryCondition;
33+
}
34+
35+
private function shouldRetry($resp, $req)
36+
{
37+
if (is_callable($this->retryCondition)) {
38+
return call_user_func($this->retryCondition, $resp, $req);
39+
}
40+
41+
return !$resp || $resp->needRetry();
42+
}
43+
44+
/**
45+
* @param Request $request
46+
* @param callable(Request): Response $next
47+
* @return Response
48+
*/
49+
public function send($request, $next)
50+
{
51+
$response = null;
52+
$urlComponents = parse_url($request->url);
53+
54+
foreach (array_merge(array($urlComponents["host"]), $this->backupDomains) as $backupDomain) {
55+
$urlComponents["host"] = $backupDomain;
56+
$request->url = \Qiniu\unparse_url($urlComponents);
57+
$retriedTimes = 0;
58+
59+
while ($retriedTimes < $this->maxRetryTimes) {
60+
$response = $next($request);
61+
62+
$retriedTimes += 1;
63+
64+
if (!$this->shouldRetry($response, $request)) {
65+
return $response;
66+
}
67+
}
68+
}
69+
70+
if (!$response) {
71+
$response = $next($request);
72+
}
73+
74+
return $response;
75+
}
76+
}

src/Qiniu/Http/Request.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,26 @@
33

44
final class Request
55
{
6+
/**
7+
* @var string
8+
*/
69
public $url;
10+
11+
/**
12+
* @var array<string, string>
13+
*/
714
public $headers;
15+
16+
/**
17+
* @var mixed|null
18+
*/
819
public $body;
20+
21+
/**
22+
* @var string
23+
*/
924
public $method;
25+
1026
/**
1127
* @var RequestOptions
1228
*/

src/Qiniu/Http/RequestOptions.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Qiniu\Http;
44

5+
use Qiniu\Http\Middleware\Middleware;
6+
57
final class RequestOptions
68
{
79

@@ -30,16 +32,23 @@ final class RequestOptions
3032
*/
3133
public $timeout_ms;
3234

35+
/**
36+
* @var array<Middleware>
37+
*/
38+
public $middlewares;
39+
3340
public function __construct(
3441
$connection_timeout = null,
3542
$connection_timeout_ms = null,
3643
$timeout = null,
37-
$timeout_ms = null
44+
$timeout_ms = null,
45+
$middlewares = array()
3846
) {
3947
$this->connection_timeout = $connection_timeout;
4048
$this->connection_timeout_ms = $connection_timeout_ms;
4149
$this->timeout = $timeout;
4250
$this->timeout_ms = $timeout_ms;
51+
$this->middlewares = $middlewares;
4352
}
4453

4554
public function getCurlOpt()

src/Qiniu/Http/Response.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,18 @@ public function ok()
199199

200200
public function needRetry()
201201
{
202-
$code = $this->statusCode;
203-
if ($code < 0 || ($code / 100 === 5 and $code !== 579) || $code === 996) {
204-
return true;
202+
if ($this->statusCode > 0 && $this->statusCode < 500) {
203+
return false;
205204
}
205+
206+
// https://developer.qiniu.com/fusion/kb/1352/the-http-request-return-a-status-code
207+
if (in_array($this->statusCode, array(
208+
501, 509, 573, 579, 608, 612, 614, 616, 618, 630, 631, 632, 640, 701
209+
))) {
210+
return false;
211+
}
212+
213+
return true;
206214
}
207215

208216
private static function isJson($headers)

src/Qiniu/Region.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
use Qiniu\Http\Client;
55
use Qiniu\Http\Error;
6+
use Qiniu\Http\Middleware\RetryDomainsMiddleware;
7+
use Qiniu\Http\RequestOptions;
68

79
class Region
810
{
@@ -169,16 +171,23 @@ public static function regionSeoul()
169171
}
170172

171173
/*
172-
* GET /v2/query?ak=<ak>&bucket=<bucket>
174+
* GET /v4/query?ak=<ak>&bucket=<bucket>
173175
**/
174-
public static function queryRegion($ak, $bucket, $ucHost = null)
176+
public static function queryRegion($ak, $bucket, $ucHost = null, $backupUcHosts = array(), $retryTimes = 2)
175177
{
176178
$region = new Region();
177179
if (!$ucHost) {
178180
$ucHost = "https://" . Config::UC_HOST;
179181
}
180182
$url = $ucHost . '/v4/query' . "?ak=$ak&bucket=$bucket";
181-
$ret = Client::Get($url);
183+
$reqOpt = new RequestOptions();
184+
$reqOpt->middlewares = array(
185+
new RetryDomainsMiddleware(
186+
$backupUcHosts,
187+
$retryTimes
188+
)
189+
);
190+
$ret = Client::Get($url, array(), $reqOpt);
182191
if (!$ret->ok()) {
183192
return array(null, new Error($url, $ret));
184193
}

src/Qiniu/Zone.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ public static function qvmZonez1()
5050
return parent::qvmRegionHuabei();
5151
}
5252

53-
public static function queryZone($ak, $bucket, $ucHost = null)
53+
public static function queryZone($ak, $bucket, $ucHost = null, $backupUcHosts = array(), $retryTimes = 2)
5454
{
55-
return parent::queryRegion($ak, $bucket, $ucHost);
55+
return parent::queryRegion($ak, $bucket, $ucHost, $backupUcHosts, $retryTimes);
5656
}
5757
}

src/Qiniu/functions.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,4 +302,35 @@ function ucwords($str, $delimiters)
302302
return \ucwords($str, $delimiters);
303303
}
304304
}
305+
306+
/**
307+
* 将 parse_url 的结果转换回字符串
308+
* TODO: add unit test
309+
*
310+
* @param $parsed_url - parse_url 的结果
311+
* @return string
312+
*/
313+
function unparse_url($parsed_url)
314+
{
315+
316+
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
317+
318+
$host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
319+
320+
$port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
321+
322+
$user = isset($parsed_url['user']) ? $parsed_url['user'] : '';
323+
324+
$pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
325+
326+
$pass = ($user || $pass) ? "$pass@" : '';
327+
328+
$path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
329+
330+
$query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
331+
332+
$fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
333+
334+
return "$scheme$user$pass$host$port$path$query$fragment";
335+
}
305336
}

0 commit comments

Comments
 (0)