Skip to content

Commit 556647f

Browse files
committed
Merge pull request #14 from php-http/plugin_client
Add Plugin Client
2 parents 3e25b9f + 9c1c93b commit 556647f

40 files changed

+2837
-1
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
- Add a flexible http client providing both contract, and only emulating what's necessary
88
- HTTP Client Router: route requests to underlying clients
9+
- Plugin client and core plugins moved here from `php-http/plugins`
910

1011
### Deprecated
1112

composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"php": ">=5.4",
1515
"php-http/httplug": "^1.0",
1616
"php-http/message-factory": "^1.0",
17-
"php-http/message": "^1.2"
17+
"php-http/message": "^1.2",
18+
"symfony/options-resolver": "^2.6|^3.0"
1819
},
1920
"require-dev": {
2021
"phpspec/phpspec": "^2.4",

spec/FlexibleHttpClientSpec.php

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ function it_does_not_emulate_a_client($client, RequestInterface $syncRequest, Re
7979
{
8080
$client->implement('Http\Client\HttpClient');
8181
$client->implement('Http\Client\HttpAsyncClient');
82+
8283
$client->sendRequest($syncRequest)->shouldBeCalled();
8384
$client->sendRequest($asyncRequest)->shouldNotBeCalled();
8485
$client->sendAsyncRequest($asyncRequest)->shouldBeCalled();

spec/Plugin/AddHostPluginSpec.php

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
namespace spec\Http\Client\Common\Plugin;
4+
5+
use Http\Message\StreamFactory;
6+
use Http\Message\UriFactory;
7+
use Http\Promise\FulfilledPromise;
8+
use Psr\Http\Message\RequestInterface;
9+
use Psr\Http\Message\ResponseInterface;
10+
use Psr\Http\Message\StreamInterface;
11+
use Psr\Http\Message\UriInterface;
12+
use PhpSpec\ObjectBehavior;
13+
14+
class AddHostPluginSpec extends ObjectBehavior
15+
{
16+
function let(UriInterface $uri)
17+
{
18+
$this->beConstructedWith($uri);
19+
}
20+
21+
function it_is_initializable(UriInterface $uri)
22+
{
23+
$uri->getHost()->shouldBeCalled()->willReturn('example.com');
24+
25+
$this->shouldHaveType('Http\Client\Common\Plugin\AddHostPlugin');
26+
}
27+
28+
function it_is_a_plugin(UriInterface $uri)
29+
{
30+
$uri->getHost()->shouldBeCalled()->willReturn('example.com');
31+
32+
$this->shouldImplement('Http\Client\Common\Plugin');
33+
}
34+
35+
function it_adds_domain(
36+
RequestInterface $request,
37+
UriInterface $host,
38+
UriInterface $uri
39+
) {
40+
$host->getScheme()->shouldBeCalled()->willReturn('http://');
41+
$host->getHost()->shouldBeCalled()->willReturn('example.com');
42+
43+
$request->getUri()->shouldBeCalled()->willReturn($uri);
44+
$request->withUri($uri)->shouldBeCalled()->willReturn($request);
45+
46+
$uri->withScheme('http://')->shouldBeCalled()->willReturn($uri);
47+
$uri->withHost('example.com')->shouldBeCalled()->willReturn($uri);
48+
$uri->getHost()->shouldBeCalled()->willReturn('');
49+
50+
$this->beConstructedWith($host);
51+
$this->handleRequest($request, function () {}, function () {});
52+
}
53+
54+
function it_replaces_domain(
55+
RequestInterface $request,
56+
UriInterface $host,
57+
UriInterface $uri
58+
) {
59+
$host->getScheme()->shouldBeCalled()->willReturn('http://');
60+
$host->getHost()->shouldBeCalled()->willReturn('example.com');
61+
62+
$request->getUri()->shouldBeCalled()->willReturn($uri);
63+
$request->withUri($uri)->shouldBeCalled()->willReturn($request);
64+
65+
$uri->withScheme('http://')->shouldBeCalled()->willReturn($uri);
66+
$uri->withHost('example.com')->shouldBeCalled()->willReturn($uri);
67+
68+
69+
$this->beConstructedWith($host, ['replace' => true]);
70+
$this->handleRequest($request, function () {}, function () {});
71+
}
72+
73+
function it_does_nothing_when_domain_exists(
74+
RequestInterface $request,
75+
UriInterface $host,
76+
UriInterface $uri
77+
) {
78+
$request->getUri()->shouldBeCalled()->willReturn($uri);
79+
$uri->getHost()->shouldBeCalled()->willReturn('default.com');
80+
81+
$this->beConstructedWith($host);
82+
$this->handleRequest($request, function () {}, function () {});
83+
}
84+
}
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace spec\Http\Client\Common\Plugin;
4+
5+
use Http\Message\Authentication;
6+
use Http\Promise\Promise;
7+
use Psr\Http\Message\RequestInterface;
8+
use PhpSpec\ObjectBehavior;
9+
use Prophecy\Argument;
10+
11+
class AuthenticationPluginSpec extends ObjectBehavior
12+
{
13+
function let(Authentication $authentication)
14+
{
15+
$this->beConstructedWith($authentication);
16+
}
17+
18+
function it_is_initializable(Authentication $authentication)
19+
{
20+
$this->shouldHaveType('Http\Client\Common\Plugin\AuthenticationPlugin');
21+
}
22+
23+
function it_is_a_plugin()
24+
{
25+
$this->shouldImplement('Http\Client\Common\Plugin');
26+
}
27+
28+
function it_sends_an_authenticated_request(Authentication $authentication, RequestInterface $notAuthedRequest, RequestInterface $authedRequest, Promise $promise)
29+
{
30+
$authentication->authenticate($notAuthedRequest)->willReturn($authedRequest);
31+
32+
$next = function (RequestInterface $request) use($authedRequest, $promise) {
33+
if (Argument::is($authedRequest->getWrappedObject())->scoreArgument($request)) {
34+
return $promise->getWrappedObject();
35+
}
36+
};
37+
38+
$this->handleRequest($notAuthedRequest, $next, function () {})->shouldReturn($promise);
39+
}
40+
}
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace spec\Http\Client\Common\Plugin;
4+
5+
use PhpSpec\Exception\Example\SkippingException;
6+
use Psr\Http\Message\RequestInterface;
7+
use Psr\Http\Message\StreamInterface;
8+
use PhpSpec\ObjectBehavior;
9+
use Prophecy\Argument;
10+
11+
class ContentLengthPluginSpec extends ObjectBehavior
12+
{
13+
function it_is_initializable()
14+
{
15+
$this->shouldHaveType('Http\Client\Common\Plugin\ContentLengthPlugin');
16+
}
17+
18+
function it_is_a_plugin()
19+
{
20+
$this->shouldImplement('Http\Client\Common\Plugin');
21+
}
22+
23+
function it_adds_content_length_header(RequestInterface $request, StreamInterface $stream)
24+
{
25+
$request->hasHeader('Content-Length')->shouldBeCalled()->willReturn(false);
26+
$request->getBody()->shouldBeCalled()->willReturn($stream);
27+
$stream->getSize()->shouldBeCalled()->willReturn(100);
28+
$request->withHeader('Content-Length', 100)->shouldBeCalled()->willReturn($request);
29+
30+
$this->handleRequest($request, function () {}, function () {});
31+
}
32+
33+
function it_streams_chunked_if_no_size(RequestInterface $request, StreamInterface $stream)
34+
{
35+
if(defined('HHVM_VERSION')) {
36+
throw new SkippingException('Skipping test on hhvm, as there is no chunk encoding on hhvm');
37+
}
38+
39+
$request->hasHeader('Content-Length')->shouldBeCalled()->willReturn(false);
40+
$request->getBody()->shouldBeCalled()->willReturn($stream);
41+
42+
$stream->getSize()->shouldBeCalled()->willReturn(null);
43+
$request->withBody(Argument::type('Http\Message\Encoding\ChunkStream'))->shouldBeCalled()->willReturn($request);
44+
$request->withAddedHeader('Transfer-Encoding', 'chunked')->shouldBeCalled()->willReturn($request);
45+
46+
$this->handleRequest($request, function () {}, function () {});
47+
}
48+
}

spec/Plugin/CookiePluginSpec.php

+183
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
<?php
2+
3+
namespace spec\Http\Client\Common\Plugin;
4+
5+
use Http\Promise\FulfilledPromise;
6+
use Http\Message\Cookie;
7+
use Http\Message\CookieJar;
8+
use Http\Promise\Promise;
9+
use Psr\Http\Message\RequestInterface;
10+
use Psr\Http\Message\ResponseInterface;
11+
use Psr\Http\Message\UriInterface;
12+
use PhpSpec\ObjectBehavior;
13+
use Prophecy\Argument;
14+
15+
class CookiePluginSpec extends ObjectBehavior
16+
{
17+
private $cookieJar;
18+
19+
function let()
20+
{
21+
$this->cookieJar = new CookieJar();
22+
23+
$this->beConstructedWith($this->cookieJar);
24+
}
25+
26+
function it_is_initializable()
27+
{
28+
$this->shouldHaveType('Http\Client\Common\Plugin\CookiePlugin');
29+
}
30+
31+
function it_is_a_plugin()
32+
{
33+
$this->shouldImplement('Http\Client\Common\Plugin');
34+
}
35+
36+
function it_loads_cookie(RequestInterface $request, UriInterface $uri, Promise $promise)
37+
{
38+
$cookie = new Cookie('name', 'value', 86400, 'test.com');
39+
$this->cookieJar->addCookie($cookie);
40+
41+
$request->getUri()->willReturn($uri);
42+
$uri->getHost()->willReturn('test.com');
43+
$uri->getPath()->willReturn('/');
44+
45+
$request->withAddedHeader('Cookie', 'name=value')->willReturn($request);
46+
47+
$this->handleRequest($request, function (RequestInterface $requestReceived) use ($request, $promise) {
48+
if (Argument::is($requestReceived)->scoreArgument($request->getWrappedObject())) {
49+
return $promise->getWrappedObject();
50+
}
51+
}, function () {});
52+
}
53+
54+
function it_does_not_load_cookie_if_expired(RequestInterface $request, UriInterface $uri, Promise $promise)
55+
{
56+
$cookie = new Cookie('name', 'value', null, 'test.com', false, false, null, (new \DateTime())->modify('-1 day'));
57+
$this->cookieJar->addCookie($cookie);
58+
59+
$request->withAddedHeader('Cookie', 'name=value')->shouldNotBeCalled();
60+
61+
$this->handleRequest($request, function (RequestInterface $requestReceived) use ($request, $promise) {
62+
if (Argument::is($requestReceived)->scoreArgument($request->getWrappedObject())) {
63+
return $promise->getWrappedObject();
64+
}
65+
}, function () {});
66+
}
67+
68+
function it_does_not_load_cookie_if_domain_does_not_match(RequestInterface $request, UriInterface $uri, Promise $promise)
69+
{
70+
$cookie = new Cookie('name', 'value', 86400, 'test2.com');
71+
$this->cookieJar->addCookie($cookie);
72+
73+
$request->getUri()->willReturn($uri);
74+
$uri->getHost()->willReturn('test.com');
75+
76+
$request->withAddedHeader('Cookie', 'name=value')->shouldNotBeCalled();
77+
78+
$this->handleRequest($request, function (RequestInterface $requestReceived) use ($request, $promise) {
79+
if (Argument::is($requestReceived)->scoreArgument($request->getWrappedObject())) {
80+
return $promise->getWrappedObject();
81+
}
82+
}, function () {});
83+
}
84+
85+
function it_does_not_load_cookie_if_path_does_not_match(RequestInterface $request, UriInterface $uri, Promise $promise)
86+
{
87+
$cookie = new Cookie('name', 'value', 86400, 'test.com', '/sub');
88+
$this->cookieJar->addCookie($cookie);
89+
90+
$request->getUri()->willReturn($uri);
91+
$uri->getHost()->willReturn('test.com');
92+
$uri->getPath()->willReturn('/');
93+
94+
$request->withAddedHeader('Cookie', 'name=value')->shouldNotBeCalled();
95+
96+
$this->handleRequest($request, function (RequestInterface $requestReceived) use ($request, $promise) {
97+
if (Argument::is($requestReceived)->scoreArgument($request->getWrappedObject())) {
98+
return $promise->getWrappedObject();
99+
}
100+
}, function () {});
101+
}
102+
103+
function it_does_not_load_cookie_when_cookie_is_secure(RequestInterface $request, UriInterface $uri, Promise $promise)
104+
{
105+
$cookie = new Cookie('name', 'value', 86400, 'test.com', null, true);
106+
$this->cookieJar->addCookie($cookie);
107+
108+
$request->getUri()->willReturn($uri);
109+
$uri->getHost()->willReturn('test.com');
110+
$uri->getPath()->willReturn('/');
111+
$uri->getScheme()->willReturn('http');
112+
113+
$request->withAddedHeader('Cookie', 'name=value')->shouldNotBeCalled();
114+
115+
$this->handleRequest($request, function (RequestInterface $requestReceived) use ($request, $promise) {
116+
if (Argument::is($requestReceived)->scoreArgument($request->getWrappedObject())) {
117+
return $promise->getWrappedObject();
118+
}
119+
}, function () {});
120+
}
121+
122+
function it_loads_cookie_when_cookie_is_secure(RequestInterface $request, UriInterface $uri, Promise $promise)
123+
{
124+
$cookie = new Cookie('name', 'value', 86400, 'test.com', null, true);
125+
$this->cookieJar->addCookie($cookie);
126+
127+
$request->getUri()->willReturn($uri);
128+
$uri->getHost()->willReturn('test.com');
129+
$uri->getPath()->willReturn('/');
130+
$uri->getScheme()->willReturn('https');
131+
132+
$request->withAddedHeader('Cookie', 'name=value')->willReturn($request);
133+
134+
$this->handleRequest($request, function (RequestInterface $requestReceived) use ($request, $promise) {
135+
if (Argument::is($requestReceived)->scoreArgument($request->getWrappedObject())) {
136+
return $promise->getWrappedObject();
137+
}
138+
}, function () {});
139+
}
140+
141+
function it_saves_cookie(RequestInterface $request, ResponseInterface $response, UriInterface $uri)
142+
{
143+
$next = function () use ($response) {
144+
return new FulfilledPromise($response->getWrappedObject());
145+
};
146+
147+
$response->hasHeader('Set-Cookie')->willReturn(true);
148+
$response->getHeader('Set-Cookie')->willReturn([
149+
'cookie=value; expires=Tuesday, 31-Mar-99 07:42:12 GMT; Max-Age=60; path=/; domain=test.com; secure; HttpOnly'
150+
]);
151+
152+
$request->getUri()->willReturn($uri);
153+
$uri->getHost()->willReturn('test.com');
154+
$uri->getPath()->willReturn('/');
155+
156+
$promise = $this->handleRequest($request, $next, function () {});
157+
$promise->shouldHaveType('Http\Promise\Promise');
158+
$promise->wait()->shouldReturnAnInstanceOf('Psr\Http\Message\ResponseInterface');
159+
}
160+
161+
function it_throws_exception_on_invalid_expires_date(
162+
RequestInterface $request,
163+
ResponseInterface $response,
164+
UriInterface $uri
165+
) {
166+
$next = function () use ($response) {
167+
return new FulfilledPromise($response->getWrappedObject());
168+
};
169+
170+
$response->hasHeader('Set-Cookie')->willReturn(true);
171+
$response->getHeader('Set-Cookie')->willReturn([
172+
'cookie=value; expires=i-am-an-invalid-date;'
173+
]);
174+
175+
$request->getUri()->willReturn($uri);
176+
$uri->getHost()->willReturn('test.com');
177+
$uri->getPath()->willReturn('/');
178+
179+
$promise = $this->handleRequest($request, $next, function () {});
180+
$promise->shouldReturnAnInstanceOf('Http\Promise\RejectedPromise');
181+
$promise->shouldThrow('Http\Client\Exception\TransferException')->duringWait();
182+
}
183+
}

0 commit comments

Comments
 (0)