Skip to content

Commit

Permalink
[FEATURE] Provide implementation for PSR-18 HTTP Client
Browse files Browse the repository at this point in the history
The implementation of the PSR-18 ClientInterface is provided
as an adapter to the existing GuzzleHTTP Client. Therefore
existing configuraton settings will be reused.

As our current Guzzle wrapper (RequestFactory->request)
has support for passing custom guzzle per-request options,
we do not deprecate this method but add the PSR-18 implementation
as a more generic alternative.

Once GuzzleHTTP supports PSR-18 natively we can (and will)
drop our adapter and point to Guzzles native implementation
in our dependency injection configuration.
Therefore, this adapter is marked as internal and extensions
are being instructed to depend on the PSR-18 interfaces
only.

composer require psr/http-client:^1.0

Releases: master
Resolves: #89216
Change-Id: I0f2c81916a2f5e4b40abd6f0b146440ef155cf00
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/61567
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Benni Mack <benni@typo3.org>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
  • Loading branch information
bnf authored and maddy2101 committed Sep 24, 2019
1 parent d9db040 commit ba9ac6f
Show file tree
Hide file tree
Showing 12 changed files with 577 additions and 26 deletions.
2 changes: 2 additions & 0 deletions composer.json
Expand Up @@ -47,6 +47,7 @@
"phpdocumentor/reflection-docblock": "^4.3",
"psr/container": "^1.0",
"psr/event-dispatcher": "^1.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"psr/http-message": "~1.0",
"psr/http-server-middleware": "^1.0",
Expand Down Expand Up @@ -95,6 +96,7 @@
"phpdocumentor/reflection-docblock": ">= 4.3.2"
},
"provide": {
"psr/http-client-implementation": "1.0",
"psr/http-factory-implementation": "1.0",
"psr/http-message-implementation": "1.0"
},
Expand Down
51 changes: 50 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 73 additions & 0 deletions typo3/sysext/core/Classes/Http/Client.php
@@ -0,0 +1,73 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Core\Http;

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

use GuzzleHttp\ClientInterface as GuzzleClientInterface;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\RequestOptions;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Client\RequestExceptionInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
* PSR-18 adapter for Guzzle\ClientInterface
*
* Will be removed once GuzzleHTTP implements PSR-18.
*
* @internal
*/
class Client implements ClientInterface
{
/**
* @var GuzzleClientInterface
*/
private $guzzle;

public function __construct(GuzzleClientInterface $guzzle)
{
$this->guzzle = $guzzle;
}

/**
* Sends a PSR-7 request and returns a PSR-7 response.
*
* @param RequestInterface $request
* @return ResponseInterface
* @throws ClientExceptionInterface If an error happens while processing the request.
* @throws NetworkExceptionInterface If the request cannot be sent due to a network failure of any kind
* @throws RequestExceptionInterface If the request message is not a well-formed HTTP request
*/
public function sendRequest(RequestInterface $request): ResponseInterface
{
try {
return $this->guzzle->send($request, [
RequestOptions::HTTP_ERRORS => false,
RequestOptions::ALLOW_REDIRECTS => false,
]);
} catch (ConnectException $e) {
throw new Client\NetworkException($e->getMessage(), 1566909446, $e->getRequest(), $e);
} catch (RequestException $e) {
throw new Client\RequestException($e->getMessage(), 1566909447, $e->getRequest(), $e);
} catch (GuzzleException $e) {
throw new Client\ClientException($e->getMessage(), 1566909448, $e);
}
}
}
32 changes: 32 additions & 0 deletions typo3/sysext/core/Classes/Http/Client/ClientException.php
@@ -0,0 +1,32 @@
<?php
declare(strict_types = 1);

namespace TYPO3\CMS\Core\Http\Client;

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

use GuzzleHttp\Exception\GuzzleException;
use Psr\Http\Client\ClientExceptionInterface;
use RuntimeException;

/**
* @internal
*/
class ClientException extends RuntimeException implements ClientExceptionInterface, GuzzleException
{
public function __construct(string $message, int $code, GuzzleException $previous)
{
parent::__construct($message, $code, $previous);
}
}
47 changes: 47 additions & 0 deletions typo3/sysext/core/Classes/Http/Client/GuzzleClientFactory.php
@@ -0,0 +1,47 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Core\Http\Client;

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\HandlerStack;
use TYPO3\CMS\Core\Utility\GeneralUtility;

/**
* @internal
*/
class GuzzleClientFactory
{
/**
* Creates the client to do requests
* @return ClientInterface
*/
public static function getClient(): ClientInterface
{
$httpOptions = $GLOBALS['TYPO3_CONF_VARS']['HTTP'];
$httpOptions['verify'] = filter_var($httpOptions['verify'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? $httpOptions['verify'];

if (isset($GLOBALS['TYPO3_CONF_VARS']['HTTP']['handler']) && is_array($GLOBALS['TYPO3_CONF_VARS']['HTTP']['handler'])) {
$stack = HandlerStack::create();
foreach ($GLOBALS['TYPO3_CONF_VARS']['HTTP']['handler'] ?? [] as $handler) {
$stack->push($handler);
}
$httpOptions['handler'] = $stack;
}

return GeneralUtility::makeInstance(Client::class, $httpOptions);
}
}
42 changes: 42 additions & 0 deletions typo3/sysext/core/Classes/Http/Client/NetworkException.php
@@ -0,0 +1,42 @@
<?php
declare(strict_types = 1);

namespace TYPO3\CMS\Core\Http\Client;

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

use GuzzleHttp\Exception\ConnectException as GuzzleConnectException;
use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Message\RequestInterface;

/**
* @internal
*/
class NetworkException extends GuzzleConnectException implements NetworkExceptionInterface
{
public function __construct(
string $message,
int $code,
RequestInterface $request,
GuzzleConnectException $previous
) {
parent::__construct($message, $request, $previous);
$this->code = $code;
}

public function getRequest(): RequestInterface
{
parent::getRequest();
}
}
42 changes: 42 additions & 0 deletions typo3/sysext/core/Classes/Http/Client/RequestException.php
@@ -0,0 +1,42 @@
<?php
declare(strict_types = 1);

namespace TYPO3\CMS\Core\Http\Client;

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

use GuzzleHttp\Exception\RequestException as GuzzleRequestException;
use Psr\Http\Client\RequestExceptionInterface;
use Psr\Http\Message\RequestInterface;

/**
* @internal
*/
class RequestException extends GuzzleRequestException implements RequestExceptionInterface
{
public function __construct(
string $message,
int $code,
RequestInterface $request,
GuzzleRequestException $previous
) {
parent::__construct($message, $request, null, $previous);
$this->code = $code;
}

public function getRequest(): RequestInterface
{
parent::getRequest();
}
}
27 changes: 2 additions & 25 deletions typo3/sysext/core/Classes/Http/RequestFactory.php
Expand Up @@ -15,14 +15,11 @@
* The TYPO3 project - inspiring people to share!
*/

use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\HandlerStack;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Http\Client\GuzzleClientFactory;

/**
* Class RequestFactory to create Request objects
Expand Down Expand Up @@ -52,27 +49,7 @@ public function createRequest(string $method, $uri): RequestInterface
*/
public function request(string $uri, string $method = 'GET', array $options = []): ResponseInterface
{
$client = $this->getClient();
$client = GuzzleClientFactory::getClient();
return $client->request($method, $uri, $options);
}

/**
* Creates the client to do requests
* @return ClientInterface
*/
protected function getClient(): ClientInterface
{
$httpOptions = $GLOBALS['TYPO3_CONF_VARS']['HTTP'];
$httpOptions['verify'] = filter_var($httpOptions['verify'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? $httpOptions['verify'];

if (isset($GLOBALS['TYPO3_CONF_VARS']['HTTP']['handler']) && is_array($GLOBALS['TYPO3_CONF_VARS']['HTTP']['handler'])) {
$stack = HandlerStack::create();
foreach ($GLOBALS['TYPO3_CONF_VARS']['HTTP']['handler'] ?? [] as $handler) {
$stack->push($handler);
}
$httpOptions['handler'] = $stack;
}

return GeneralUtility::makeInstance(Client::class, $httpOptions);
}
}
10 changes: 10 additions & 0 deletions typo3/sysext/core/Configuration/Services.yaml
Expand Up @@ -77,6 +77,9 @@ services:
Psr\EventDispatcher\EventDispatcherInterface:
alias: TYPO3\CMS\Core\EventDispatcher\EventDispatcher
public: true
Psr\Http\Client\ClientInterface:
alias: TYPO3\CMS\Core\Http\Client
public: true
Psr\Http\Message\RequestFactoryInterface:
alias: TYPO3\CMS\Core\Http\RequestFactory
public: true
Expand All @@ -95,3 +98,10 @@ services:
Psr\Http\Message\UriFactoryInterface:
alias: TYPO3\CMS\Core\Http\UriFactory
public: true
GuzzleHttp\ClientInterface:
alias: GuzzleHttp\Client
public: true

# External dependencies
GuzzleHttp\Client:
factory: ['TYPO3\CMS\Core\Http\Client\GuzzleClientFactory', 'getClient']

0 comments on commit ba9ac6f

Please sign in to comment.