Skip to content

Commit

Permalink
[HttpClient] add HttplugClient for compat with libs that need httplug…
Browse files Browse the repository at this point in the history
… v1 or v2
  • Loading branch information
nicolas-grekas committed Jun 10, 2019
1 parent 1c0baf6 commit bbe103b
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 1 deletion.
1 change: 1 addition & 0 deletions composer.json
Expand Up @@ -111,6 +111,7 @@
"monolog/monolog": "~1.11",
"nyholm/psr7": "^1.0",
"ocramius/proxy-manager": "^2.1",
"php-http/httplug": "^1.0|^2.0",
"predis/predis": "~1.1",
"psr/http-client": "^1.0",
"psr/simple-cache": "^1.0",
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/HttpClient/CHANGELOG.md
Expand Up @@ -5,7 +5,7 @@ CHANGELOG
-----

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

4.3.0
-----
Expand Down
120 changes: 120 additions & 0 deletions src/Symfony/Component/HttpClient/HttplugClient.php
@@ -0,0 +1,120 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpClient;

use Http\Client\Exception\NetworkException;
use Http\Client\Exception\RequestException;
use Http\Client\HttpClient;
use Http\Message\RequestFactory;
use Http\Message\StreamFactory;
use Http\Message\UriFactory;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Client\RequestExceptionInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

if (!interface_exists(HttpClient::class)) {
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".');
}

if (!interface_exists(ClientInterface::class)) {
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".');
}

if (!interface_exists(RequestFactory::class)) {
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".');
}

/**
* An adapter to turn a Symfony HttpClientInterface into an Httplug client.
*
* Run "composer require psr/http-client" to install the base ClientInterface. Run
* "composer require nyholm/psr7" to install an efficient implementation of response
* and stream factories with flex-provided autowiring aliases.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class HttplugClient implements HttpClient, RequestFactory, StreamFactory, UriFactory
{
private $client;

public function __construct(HttpClientInterface $client = null, ResponseFactoryInterface $responseFactory = null, StreamFactoryInterface $streamFactory = null)
{
$this->client = new Psr18Client($client, $responseFactory, $streamFactory);
}

/**
* {@inheritdoc}
*/
public function sendRequest(RequestInterface $request): ResponseInterface
{
try {
return $this->client->sendRequest($request);
} catch (RequestExceptionInterface $e) {
throw new RequestException($e->getMessage(), $request, $e);
} catch (NetworkExceptionInterface $e) {
throw new NetworkException($e->getMessage(), $request, $e);
}
}

/**
* {@inheritdoc}
*/
public function createRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1'): RequestInterface
{
$request = $this->client
->createRequest($method, $uri)
->withProtocolVersion($protocolVersion)
->withBody($this->createStream($body))
;

foreach ($headers as $name => $value) {
$request = $request->withAddedHeader($name, $value);
}

return $request;
}

/**
* {@inheritdoc}
*/
public function createStream($body = null): StreamInterface
{
if ($body instanceof StreamInterface) {
return $body;
}

if (\is_string($body ?? '')) {
return $this->client->createStream($body ?? '');
}

if (\is_resource($body)) {
return $this->client->createStreamFromResource($body);
}

throw new \InvalidArgumentException(sprintf('%s() expects string, resource or StreamInterface, %s given.', __METHOD__, \gettype($body)));
}

/**
* {@inheritdoc}
*/
public function createUri($uri = ''): UriInterface
{
return $uri instanceof UriInterface ? $uri : $this->client->createUri($uri);
}
}
72 changes: 72 additions & 0 deletions src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php
@@ -0,0 +1,72 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpClient\Tests;

use Http\Client\Exception\NetworkException;
use Http\Client\Exception\RequestException;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpClient\HttplugClient;
use Symfony\Component\HttpClient\NativeHttpClient;
use Symfony\Contracts\HttpClient\Test\TestHttpServer;

class HttplugClientTest extends TestCase
{
private static $server;

public static function setUpBeforeClass()
{
TestHttpServer::start();
}

public function testSendRequest()
{
$client = new HttplugClient(new NativeHttpClient());

$response = $client->sendRequest($client->createRequest('GET', 'http://localhost:8057'));

$this->assertSame(200, $response->getStatusCode());
$this->assertSame('application/json', $response->getHeaderLine('content-type'));

$body = json_decode((string) $response->getBody(), true);

$this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']);
}

public function testPostRequest()
{
$client = new HttplugClient(new NativeHttpClient());

$request = $client->createRequest('POST', 'http://localhost:8057/post')
->withBody($client->createStream('foo=0123456789'));

$response = $client->sendRequest($request);
$body = json_decode((string) $response->getBody(), true);

$this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $body);
}

public function testNetworkException()
{
$client = new HttplugClient(new NativeHttpClient());

$this->expectException(NetworkException::class);
$client->sendRequest($client->createRequest('GET', 'http://localhost:8058'));
}

public function testRequestException()
{
$client = new HttplugClient(new NativeHttpClient());

$this->expectException(RequestException::class);
$client->sendRequest($client->createRequest('BAD.METHOD', 'http://localhost:8057'));
}
}
2 changes: 2 additions & 0 deletions src/Symfony/Component/HttpClient/composer.json
Expand Up @@ -15,6 +15,7 @@
}
],
"provide": {
"php-http/client-implementation": "*",
"psr/http-client-implementation": "1.0",
"symfony/http-client-implementation": "1.1"
},
Expand All @@ -26,6 +27,7 @@
},
"require-dev": {
"nyholm/psr7": "^1.0",
"php-http/httplug": "^1.0|^2.0",
"psr/http-client": "^1.0",
"symfony/http-kernel": "^4.3|^5.0",
"symfony/process": "^4.2|^5.0"
Expand Down

0 comments on commit bbe103b

Please sign in to comment.