8000 [HttpClient] Add Psr18Client - aka a PSR-18 adapter · symfony/symfony@d9622bf · GitHub
[go: up one dir, main page]

Skip to content

Commit d9622bf

Browse files
[HttpClient] Add Psr18Client - aka a PSR-18 adapter
1 parent de0ad70 commit d9622bf

File tree

4 files changed

+190
-0
lines changed

4 files changed

+190
-0
lines changed

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,10 @@
102102
"doctrine/reflection": "~1.0",
103103
"doctrine/doctrine-bundle": "~1.4",
104104
"monolog/monolog": "~1.11",
105+
"nyholm/psr7": "^1.0",
105106
"ocramius/proxy-manager": "~0.4|~1.0|~2.0",
106107
"predis/predis": "~1.1",
108+
"psr/http-client": "^1.0",
107109
"egulias/email-validator": "~1.2,>=1.2.8|~2.0",
108110
"symfony/phpunit-bridge": "~3.4|~4.0",
109111
"symfony/security-acl": "~2.8|~3.0",
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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 Psr\Http\Client\ClientInterface;
15+
use Psr\Http\Client\NetworkExceptionInterface;
16+
use Psr\Http\Client\RequestExceptionInterface;
17+
use Psr\Http\Message\RequestInterface;
18+
use Psr\Http\Message\ResponseFactoryInterface;
19+
use Psr\Http\Message\ResponseInterface;
20+
use Psr\Http\Message\StreamFactoryInterface;
21+
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
22+
use Symfony\Contracts\HttpClient\HttpClientInterface;
23+
24+
/**
25+
* An adapter to turn a Symfony HttpClientInterface into a PSR-18 ClientInterface.
26+
*
27+
* Run "composer require psr/http-client" to install the base ClientInterface. Run
28+
* "composer require nyholm/psr7" to install an efficient implementation of response
29+
* and stream factories with flex-provided autowiring aliases.
30+
*
31+
* @author Nicolas Grekas <p@tchwork.com>
32+
*/
33+
final class Psr18Client implements ClientInterface
34+
{
35+
private $client;
36+
private $responseFactory;
37+
private $streamFactory;
38+
39+
public function __construct(HttpClientInterface $client, ResponseFactoryInterface $responseFactory, StreamFactoryInterface $streamFactory)
40+
{
41+
$this->client = $client;
42+
$this->responseFactory = $responseFactory;
43+
$this->streamFactory = $streamFactory;
44+
}
45+
46+
public function sendRequest(RequestInterface $request): ResponseInterface
47+
{
48+
try {
49+
$response = $this->client->request($request->getMethod(), (string) $request->getUri(), [
50+
'headers' => $request->getHeaders(),
51+
'body' => (string) $request->getBody(),
52+
'http_version' => '1.0' === $request->getProtocolVersion() ? 1.0 : null,
53+
]);
54+
55+
$psrResponse = $this->responseFactory->createResponse($response->getStatusCode());
56+
57+
foreach ($response->getHeaders() as $name => $values) {
58+
foreach ($values as $value) {
59+
$psrResponse = $psrResponse->withAddedHeader($name, $value);
60+
}
61+
}
62+
63+
return $psrResponse->withBody($this->streamFactory->createStream($response->getContent()));
64+
} catch (TransportExceptionInterface $e) {
65+
if ($e instanceof \InvalidArgumentException) {
66+
throw new Psr18RequestException($e, $request);
67+
}
68+
69+
throw new Psr18NetworkException($e, $request);
70+
}
71+
}
72+
}
73+
74+
/**
75+
* @internal
76+
*/
77+
trait Psr18ExceptionTrait
78+
{
79+
private $request;
80+
81+
public function __construct(TransportExceptionInterface $e, RequestInterface $request)
82+
{
83+
parent::__construct($e->getMessage(), 0, $e);
84+
$this->request = $request;
85+
}
86+
87+
public function getRequest(): RequestInterface
88+
{
89+
return $this->request;
90+
}
91+
}
92+
93+
/**
94+
* @internal
95+
*/
96+
class Psr18NetworkException extends \RuntimeException implements NetworkExceptionInterface
97+
{
98+
use Psr18ExceptionTrait;
99+
}
100+
101+
/**
102+
* @internal
103+
*/
104+
class Psr18RequestException extends \InvalidArgumentException implements RequestExceptionInterface
105+
{
106+
use Psr18ExceptionTrait;
107+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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 Nyholm\Psr7\Factory\Psr17Factory;
15+
use PHPUnit\Framework\TestCase;
16+
use Symfony\Component\HttpClient\NativeHttpClient;
17+
use Symfony\Component\HttpClient\Psr18Client;
18+
use Symfony\Component\HttpClient\Psr18NetworkException;
19+
use Symfony\Component\HttpClient\Psr18RequestException;
20+
use Symfony\Contracts\HttpClient\Test\TestHttpServer;
21+
22+
class Psr18ClientTest extends TestCase
23+
{
24+
private static $server;
25+
26+
public static function setUpBeforeClass()
27+
{
28+
TestHttpServer::start();
29+
}
30+
31+
public function testSendRequest()
32+
{
33+
$factory = new Psr17Factory();
34+
$client = new Psr18Client(new NativeHttpClient(), $factory, $factory);
35+
36+
$response = $client->sendRequest($factory->createRequest('GET', 'http://localhost:8057'));
37+
38+
$this->assertSame(200, $response->getStatusCode());
39+
$this->assertSame('application/json', $response->getHeaderLine('content-type'));
40+
41+
$body = json_decode((string) $response->getBody(), true);
42+
43+
$this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']);
44+
}
45+
46+
public function testPostRequest()
47+
{
48+
$factory = new Psr17Factory();
49+
$client = new Psr18Client(new NativeHttpClient(), $factory, $factory);
50+
51+
$request = $factory->createRequest('POST', 'http://localhost:8057/post')
52+
->withBody($factory->createStream('foo=0123456789'));
53+
54+
$response = $client->sendRequest($request);
55+
$body = json_decode((string) $response->getBody(), true);
56+
57+
$this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $body);
58+
}
59+
60+
public function testNetworkException()
61+
{
62+
$factory = new Psr17Factory();
63+
$client = new Psr18Client(new NativeHttpClient(), $factory, $factory);
64+
65+
$this->expectException(Psr18NetworkException::class);
66+
$client->sendRequest($factory->createRequest('GET', 'http://localhost:8058'));
67+
}
68+
69+
public function testRequestException()
70+
{
71+
$factory = new Psr17Factory();
72+
$client = new Psr18Client(new NativeHttpClient(), $factory, $factory);
73+
74+
$this->expectException(Psr18RequestException::class);
75+
$client->sendRequest($factory->createRequest('BAD.METHOD', 'http://localhost:8057'));
76+
}
77+
}

src/Symfony/Component/HttpClient/composer.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
"php": "^7.1.3",
2323
"symfony/contracts": "^1.1"
2424
},
25+
"require-dev": {
26+
"nyholm/psr7": "^1.0",
27+
"psr/http-client": "^1.0"
28+
},
2529
"autoload": {
2630
"psr-4": { "Symfony\\Component\\HttpClient\\": "" },
2731
"exclude-from-classmap": [

0 commit comments

Comments
 (0)
0