Skip to content

Commit bbe103b

Browse files
[HttpClient] add HttplugClient for compat with libs that need httplug v1 or v2
1 parent 1c0baf6 commit bbe103b

File tree

5 files changed

+196
-1
lines changed

5 files changed

+196
-1
lines changed

composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@
111111
"monolog/monolog": "~1.11",
112112
"nyholm/psr7": "^1.0",
113113
"ocramius/proxy-manager": "^2.1",
114+
"php-http/httplug": "^1.0|^2.0",
114115
"predis/predis": "~1.1",
115116
"psr/http-client": "^1.0",
116117
"psr/simple-cache": "^1.0",

src/Symfony/Component/HttpClient/CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ CHANGELOG
55
-----
66

77
* made `Psr18Client` implement relevant PSR-17 factories
8-
* added `$response->cancel()`
8+
* added `HttplugClient`
99

1010
4.3.0
1111
-----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpClient;
13+
14+
use Http\Client\Exception\NetworkException;
15+
use Http\Client\Exception\RequestException;
16+
use Http\Client\HttpClient;
17+
use Http\Message\RequestFactory;
18+
use Http\Message\StreamFactory;
19+
use Http\Message\UriFactory;
20+
use Psr\Http\Client\ClientInterface;
21+
use Psr\Http\Client\NetworkExceptionInterface;
22+
use Psr\Http\Client\RequestExceptionInterface;
23+
use Psr\Http\Message\RequestInterface;
24+
use Psr\Http\Message\ResponseFactoryInterface;
25+
use Psr\Http\Message\ResponseInterface;
26+
use Psr\Http\Message\StreamFactoryInterface;
27+
use Psr\Http\Message\StreamInterface;
28+
use Psr\Http\Message\UriInterface;
29+
use Symfony\Contracts\HttpClient\HttpClientInterface;
30+
31+
if (!interface_exists(HttpClient::class)) {
32+
throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/httplug" package is not installed. Try running "composer require php-http/httplug".');
33+
}
34+
35+
if (!interface_exists(ClientInterface::class)) {
36+
throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "psr/http-client" package is not installed. Try running "composer require psr/http-client".');
37+
}
38+
39+
if (!interface_exists(RequestFactory::class)) {
40+
throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/message-factory" package is not installed. Try running "composer require nyholm/psr7".');
41+
}
42+
43+
/**
44+
* An adapter to turn a Symfony HttpClientInterface into an Httplug client.
45+
*
46+
* Run "composer require psr/http-client" to install the base ClientInterface. Run
47+
* "composer require nyholm/psr7" to install an efficient implementation of response
48+
* and stream factories with flex-provided autowiring aliases.
49+
*
50+
* @author Nicolas Grekas <p@tchwork.com>
51+
*/
52+
final class HttplugClient implements HttpClient, RequestFactory, StreamFactory, UriFactory
53+
{
54+
private $client;
55+
56+
public function __construct(HttpClientInterface $client = null, ResponseFactoryInterface $responseFactory = null, StreamFactoryInterface $streamFactory = null)
57+
{
58+
$this->client = new Psr18Client($client, $responseFactory, $streamFactory);
59+
}
60+
61+
/**
62+
* {@inheritdoc}
63+
*/
64+
public function sendRequest(RequestInterface $request): ResponseInterface
65+
{
66+
try {
67+
return $this->client->sendRequest($request);
68+
} catch (RequestExceptionInterface $e) {
69+
throw new RequestException($e->getMessage(), $request, $e);
70+
} catch (NetworkExceptionInterface $e) {
71+
throw new NetworkException($e->getMessage(), $request, $e);
72+
}
73+
}
74+
75+
/**
76+
* {@inheritdoc}
77+
*/
78+
public function createRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1'): RequestInterface
79+
{
80+
$request = $this->client
81+
->createRequest($method, $uri)
82+
->withProtocolVersion($protocolVersion)
83+
->withBody($this->createStream($body))
84+
;
85+
86+
foreach ($headers as $name => $value) {
87+
$request = $request->withAddedHeader($name, $value);
88+
}
89+
90+
return $request;
91+
}
92+
93+
/**
94+
* {@inheritdoc}
95+
*/
96+
public function createStream($body = null): StreamInterface
97+
{
98+
if ($body instanceof StreamInterface) {
99+
return $body;
100+
}
101+
102+
if (\is_string($body ?? '')) {
103+
return $this->client->createStream($body ?? '');
104+
}
105+
106+
if (\is_resource($body)) {
107+
return $this->client->createStreamFromResource($body);
108+
}
109+
110+
throw new \InvalidArgumentException(sprintf('%s() expects string, resource or StreamInterface, %s given.', __METHOD__, \gettype($body)));
111+
}
112+
113+
/**
114+
* {@inheritdoc}
115+
*/
116+
public function createUri($uri = ''): UriInterface
117+
{
118+
return $uri instanceof UriInterface ? $uri : $this->client->createUri($uri);
119+
}
120+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpClient\Tests;
13+
14+
use Http\Client\Exception\NetworkException;
15+
use Http\Client\Exception\RequestException;
16+
use PHPUnit\Framework\TestCase;
17+
use Symfony\Component\HttpClient\HttplugClient;
18+
use Symfony\Component\HttpClient\NativeHttpClient;
19+
use Symfony\Contracts\HttpClient\Test\TestHttpServer;
20+
21+
class HttplugClientTest extends TestCase
22+
{
23+
private static $server;
24+
25+
public static function setUpBeforeClass()
26+
{
27+
TestHttpServer::start();
28+
}
29+
30+
public function testSendRequest()
31+
{
32+
$client = new HttplugClient(new NativeHttpClient());
33+
34+
$response = $client->sendRequest($client->createRequest('GET', 'http://localhost:8057'));
35+
36+
$this->assertSame(200, $response->getStatusCode());
37+
$this->assertSame('application/json', $response->getHeaderLine('content-type'));
38+
39+
$body = json_decode((string) $response->getBody(), true);
40+
41+
$this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']);
42+
}
43+
44+
public function testPostRequest()
45+
{
46+
$client = new HttplugClient(new NativeHttpClient());
47+
48+
$request = $client->createRequest('POST', 'http://localhost:8057/post')
49+
->withBody($client->createStream('foo=0123456789'));
50+
51+
$response = $client->sendRequest($request);
52+
$body = json_decode((string) $response->getBody(), true);
53+
54+
$this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $body);
55+
}
56+
57+
public function testNetworkException()
58+
{
59+
$client = new HttplugClient(new NativeHttpClient());
60+
61+
$this->expectException(NetworkException::class);
62+
$client->sendRequest($client->createRequest('GET', 'http://localhost:8058'));
63+
}
64+
65+
public function testRequestException()
66+
{
67+
$client = new HttplugClient(new NativeHttpClient());
68+
69+
$this->expectException(RequestException::class);
70+
$client->sendRequest($client->createRequest('BAD.METHOD', 'http://localhost:8057'));
71+
}
72+
}

src/Symfony/Component/HttpClient/composer.json

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
}
1616
],
1717
"provide": {
18+
"php-http/client-implementation": "*",
1819
"psr/http-client-implementation": "1.0",
1920
"symfony/http-client-implementation": "1.1"
2021
},
@@ -26,6 +27,7 @@
2627
},
2728
"require-dev": {
2829
"nyholm/psr7": "^1.0",
30+
"php-http/httplug": "^1.0|^2.0",
2931
"psr/http-client": "^1.0",
3032
"symfony/http-kernel": "^4.3|^5.0",
3133
"symfony/process": "^4.2|^5.0"

0 commit comments

Comments
 (0)