Skip to content

Commit ec69827

Browse files
committed
Add random client pool, better tests, add protected method for inheritance in client pool item
1 parent df40da7 commit ec69827

File tree

5 files changed

+195
-28
lines changed

5 files changed

+195
-28
lines changed

spec/HttpClientPool/LeastUsedClientPoolSpec.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,21 @@ public function it_reenable_client(HttpClient $client, RequestInterface $request
6868
$this->shouldThrow('Http\Client\Exception\HttpException')->duringSendRequest($request);
6969
$this->shouldThrow('Http\Client\Exception\HttpException')->duringSendRequest($request);
7070
}
71+
72+
public function it_uses_the_lowest_request_client(HttpClientPoolItem $client1, HttpClientPoolItem $client2, RequestInterface $request, ResponseInterface $response)
73+
{
74+
$this->addHttpClient($client1);
75+
$this->addHttpClient($client2);
76+
77+
$client1->getSendingRequestCount()->willReturn(10);
78+
$client2->getSendingRequestCount()->willReturn(2);
79+
80+
$client1->isDisabled()->willReturn(false);
81+
$client2->isDisabled()->willReturn(false);
82+
83+
$client1->sendRequest($request)->shouldNotBeCalled();
84+
$client2->sendRequest($request)->willReturn($response);
85+
86+
$this->sendRequest($request)->shouldReturn($response);
87+
}
7188
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
namespace spec\Http\Client\Common\HttpClientPool;
4+
5+
use Http\Client\Common\HttpClientPoolItem;
6+
use Http\Client\HttpAsyncClient;
7+
use Http\Client\HttpClient;
8+
use Http\Promise\Promise;
9+
use PhpSpec\ObjectBehavior;
10+
use Prophecy\Argument;
11+
use Psr\Http\Message\RequestInterface;
12+
use Psr\Http\Message\ResponseInterface;
13+
14+
class RandomClientPoolSpec extends ObjectBehavior
15+
{
16+
public function it_is_initializable()
17+
{
18+
$this->shouldHaveType('Http\Client\Common\HttpClientPool\RandomClientPool');
19+
}
20+
21+
public function it_is_an_http_client()
22+
{
23+
$this->shouldImplement('Http\Client\HttpClient');
24+
}
25+
26+
public function it_is_an_async_http_client()
27+
{
28+
$this->shouldImplement('Http\Client\HttpAsyncClient');
29+
}
30+
31+
public function it_throw_exception_with_no_client(RequestInterface $request)
32+
{
33+
$this->shouldThrow('Http\Client\Common\Exception\HttpClientNotFoundException')->duringSendRequest($request);
34+
$this->shouldThrow('Http\Client\Common\Exception\HttpClientNotFoundException')->duringSendAsyncRequest($request);
35+
}
36+
37+
public function it_sends_request(HttpClient $httpClient, RequestInterface $request, ResponseInterface $response)
38+
{
39+
$this->addHttpClient($httpClient);
40+
$httpClient->sendRequest($request)->willReturn($response);
41+
42+
$this->sendRequest($request)->shouldReturn($response);
43+
}
44+
45+
public function it_sends_async_request(HttpAsyncClient $httpAsyncClient, RequestInterface $request, Promise $promise)
46+
{
47+
$this->addHttpClient($httpAsyncClient);
48+
$httpAsyncClient->sendAsyncRequest($request)->willReturn($promise);
49+
$promise->then(Argument::type('callable'), Argument::type('callable'))->willReturn($promise);
50+
51+
$this->sendAsyncRequest($request)->shouldReturn($promise);
52+
}
53+
54+
public function it_throw_exception_if_no_more_enable_client(HttpClient $client, RequestInterface $request)
55+
{
56+
$this->addHttpClient($client);
57+
$client->sendRequest($request)->willThrow('Http\Client\Exception\HttpException');
58+
59+
$this->shouldThrow('Http\Client\Exception\HttpException')->duringSendRequest($request);
60+
$this->shouldThrow('Http\Client\Common\Exception\HttpClientNotFoundException')->duringSendRequest($request);
61+
}
62+
63+
public function it_reenable_client(HttpClient $client, RequestInterface $request)
64+
{
65+
$this->addHttpClient(new HttpClientPoolItem($client->getWrappedObject(), 0));
66+
$client->sendRequest($request)->willThrow('Http\Client\Exception\HttpException');
67+
68+
$this->shouldThrow('Http\Client\Exception\HttpException')->duringSendRequest($request);
69+
$this->shouldThrow('Http\Client\Exception\HttpException')->duringSendRequest($request);
70+
}
71+
}

spec/HttpClientPool/RoundRobinClientPoolSpec.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,16 @@ public function it_reenable_client(HttpClient $client, RequestInterface $request
6868
$this->shouldThrow('Http\Client\Exception\HttpException')->duringSendRequest($request);
6969
$this->shouldThrow('Http\Client\Exception\HttpException')->duringSendRequest($request);
7070
}
71+
72+
public function it_round_between_clients(HttpClient $client1, HttpClient $client2, RequestInterface $request, ResponseInterface $response)
73+
{
74+
$this->addHttpClient($client1);
75+
$this->addHttpClient($client2);
76+
77+
$client1->sendRequest($request)->willReturn($response);
78+
$client2->sendRequest($request)->willReturn($response);
79+
80+
$this->sendRequest($request)->shouldReturn($response);
81+
$this->sendRequest($request)->shouldReturn($response);
82+
}
7183
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Http\Client\Common\HttpClientPool;
4+
5+
use Http\Client\Common\Exception\HttpClientNotFoundException;
6+
use Http\Client\Common\HttpClientPool;
7+
use Http\Client\Common\HttpClientPoolItem;
8+
9+
/**
10+
* RoundRobinClientPool will choose the next client in the pool.
11+
*
12+
* @author Joel Wurtz <[email protected]>
13+
*/
14+
class RandomClientPool extends HttpClientPool
15+
{
16+
/**
17+
* {@inheritdoc}
18+
*/
19+
public function chooseHttpClient()
20+
{
21+
$clientPool = array_filter($this->clientPool, function (HttpClientPoolItem $clientPoolItem) {
22+
return !$clientPoolItem->isDisabled();
23+
});
24+
25+
if (0 === count($clientPool)) {
26+
throw new HttpClientNotFoundException('Cannot choose a http client as there is no one present in the pool');
27+
}
28+
29+
return $clientPool[array_rand($clientPool)];
30+
}
31+
}

src/HttpClientPoolItem.php

Lines changed: 64 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@ class HttpClientPoolItem implements HttpClient, HttpAsyncClient
2020
/** @var int Number of request this client is currently sending */
2121
private $sendingRequestCount = 0;
2222

23-
/** @var bool Status of the http client */
24-
private $disabled = false;
25-
26-
/** @var \DateTime Time when this client has been disabled */
23+
/** @var \DateTime|null Time when this client has been disabled or null if enable */
2724
private $disabledAt;
2825

2926
/** @var int|null Number of seconds after this client is reenable, by default null: never reenable this client */
@@ -53,12 +50,12 @@ public function sendRequest(RequestInterface $request)
5350
}
5451

5552
try {
56-
++$this->sendingRequestCount;
53+
$this->incrementRequestCount();
5754
$response = $this->client->sendRequest($request);
58-
--$this->sendingRequestCount;
55+
$this->decrementRequestCount();
5956
} catch (Exception $e) {
6057
$this->disable();
61-
--$this->sendingRequestCount;
58+
$this->decrementRequestCount();
6259

6360
throw $e;
6461
}
@@ -75,20 +72,47 @@ public function sendAsyncRequest(RequestInterface $request)
7572
throw new Exception\RequestException('Cannot send the request as this client has been disabled', $request);
7673
}
7774

78-
++$this->sendingRequestCount;
75+
$this->incrementRequestCount();
7976

8077
return $this->client->sendAsyncRequest($request)->then(function ($response) {
81-
--$this->sendingRequestCount;
78+
$this->decrementRequestCount();
8279

8380
return $response;
8481
}, function ($exception) {
8582
$this->disable();
86-
--$this->sendingRequestCount;
83+
$this->decrementRequestCount();
8784

8885
throw $exception;
8986
});
9087
}
9188

89+
/**
90+
* Whether this client is disabled or not.
91+
*
92+
* Will also reactivate this client if possible
93+
*
94+
* @return bool
95+
*/
96+
public function isDisabled()
97+
{
98+
$disabledAt = $this->getDisabledAt();
99+
100+
if (null !== $this->reenableAfter && null !== $disabledAt) {
101+
// Reenable after a certain time
102+
$now = new \DateTime();
103+
104+
if (($now->getTimestamp() - $disabledAt->getTimestamp()) >= $this->reenableAfter) {
105+
$this->enable();
106+
107+
return false;
108+
}
109+
110+
return true;
111+
}
112+
113+
return null !== $disabledAt;
114+
}
115+
92116
/**
93117
* Get current number of request that is send by the underlying http client.
94118
*
@@ -100,32 +124,44 @@ public function getSendingRequestCount()
100124
}
101125

102126
/**
103-
* Disable the current client.
127+
* Return when this client has been disabled or null if it's enabled.
128+
*
129+
* @return \DateTime|null
104130
*/
105-
protected function disable()
131+
protected function getDisabledAt()
106132
{
107-
$this->disabled = true;
108-
$this->disabledAt = new \DateTime('now');
133+
return $this->disabledAt;
109134
}
110135

111136
/**
112-
* Whether this client is disabled or not.
113-
*
114-
* Will also reactivate this client if possible
115-
*
116-
* @return bool
137+
* Increment the request count.
117138
*/
118-
public function isDisabled()
139+
protected function incrementRequestCount()
119140
{
120-
if ($this->disabled && null !== $this->reenableAfter) {
121-
// Reenable after a certain time
122-
$now = new \DateTime();
141+
++$this->sendingRequestCount;
142+
}
123143

124-
if (($now->getTimestamp() - $this->disabledAt->getTimestamp()) >= $this->reenableAfter) {
125-
$this->disabled = false;
126-
}
127-
}
144+
/**
145+
* Decrement the request count.
146+
*/
147+
protected function decrementRequestCount()
148+
{
149+
--$this->sendingRequestCount;
150+
}
151+
152+
/**
153+
* Enable the current client.
154+
*/
155+
protected function enable()
156+
{
157+
$this->disabledAt = null;
158+
}
128159

129-
return $this->disabled;
160+
/**
161+
* Disable the current client.
162+
*/
163+
protected function disable()
164+
{
165+
$this->disabledAt = new \DateTime('now');
130166
}
131167
}

0 commit comments

Comments
 (0)