Initial xentral_oss_20.3.c9ffacf

This commit is contained in:
Alex
2021-05-21 08:49:41 +02:00
parent 5406e4a551
commit 34e5ac43d9
18884 changed files with 2109867 additions and 0 deletions
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient;
final class Bootstrap
{
/**
* @return array
*/
public static function registerServices(): array
{
return [
'HttpClientFactory' => 'onInitHttpClientFactory',
];
}
/**
* @return HttpClientFactory
*/
public static function onInitHttpClientFactory(): HttpClientFactory
{
return new HttpClientFactory();
}
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient\Exception;
/**
* Exception for 4xx HTTP status errors
*/
class ClientErrorException extends TransferErrorException
{
}
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient\Exception;
use Xentral\Components\HttpClient\Response\ServerResponseInterface;
/**
* HTTP connect failed, e.g. timeout
*
* Response is not available
*/
class ConnectionFailedException extends TransferErrorException
{
/**
* @inheritDoc
*/
public function hasResponse(): bool
{
return false;
}
/**
* @inheritDoc
*/
public function getResponse(): ?ServerResponseInterface
{
return null;
}
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient\Exception;
use Xentral\Core\Exception\ComponentExceptionInterface;
interface HttpClientExceptionInterface extends ComponentExceptionInterface
{
}
@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient\Exception;
final class InvalidArgumentException extends \InvalidArgumentException implements HttpClientExceptionInterface
{
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient\Exception;
use LogicException;
final class InvalidRequestOptionsException extends LogicException implements HttpClientExceptionInterface
{
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient\Exception;
use LogicException;
final class InvalidResponseException extends LogicException implements HttpClientExceptionInterface
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient\Exception;
/**
* Exception for 5xx HTTP status errors
*/
class ServerErrorException extends TransferErrorException
{
}
@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient\Exception;
class TooManyRedirectsException extends TransferErrorException
{
}
@@ -0,0 +1,135 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient\Exception;
use GuzzleHttp\Exception\ClientException as GuzzleClientException;
use GuzzleHttp\Exception\ConnectException as GuzzleConnectException;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\ServerException as GuzzleServerException;
use GuzzleHttp\Exception\TooManyRedirectsException as GuzzleRedirectsException;
use RuntimeException;
use Throwable;
use Xentral\Components\HttpClient\Request\ClientRequest;
use Xentral\Components\HttpClient\Request\ClientRequestInterface;
use Xentral\Components\HttpClient\Response\ServerResponse;
use Xentral\Components\HttpClient\Response\ServerResponseInterface;
class TransferErrorException extends RuntimeException implements TransferErrorExceptionInterface
{
/** @var ClientRequestInterface $request */
protected $request;
/** @var ServerResponseInterface|null $response */
protected $response;
/**
* @param string $message
* @param int $code
* @param Throwable|null $previous
* @param ClientRequestInterface|null $request
* @param ServerResponseInterface|null $response
*/
public function __construct(
$message = '',
$code = 0,
Throwable $previous = null,
ClientRequestInterface $request = null,
ServerResponseInterface $response = null
) {
parent::__construct($message, $code, $previous);
if ($request !== null) {
$this->request = $request;
}
if ($response !== null) {
$this->response = $response;
}
}
/**
* @param GuzzleException $exception
*
* @return TransferErrorExceptionInterface
*/
public static function fromGuzzleException(GuzzleException $exception): TransferErrorExceptionInterface
{
switch (get_class($exception)) {
case GuzzleConnectException::class:
$exceptionClass = ConnectionFailedException::class;
break;
case GuzzleClientException::class: // HTTP 4xx
$exceptionClass = ClientErrorException::class;
break;
case GuzzleServerException::class: // HTTP 5xx
$exceptionClass = ServerErrorException::class;
break;
case GuzzleRedirectsException::class:
$exceptionClass = TooManyRedirectsException::class;
break;
default:
$exceptionClass = TransferErrorException::class;
break;
}
$self = new $exceptionClass(
$exception->getMessage(),
$exception->getCode(),
$exception
);
// Request anhängen; immer verfügbar
$psrRequest = $exception->getRequest();
$request = ClientRequest::fromGuzzleRequest($psrRequest);
$self->request = $request;
// Response anhängen; NICHT immer verfügbar
if ($exception->hasResponse()) {
$psrResponse = $exception->getResponse();
$response = ServerResponse::fromGuzzleResponse($psrResponse);
$self->response = $response;
}
return $self;
}
/**
* @param ClientRequestInterface $request
* @param ServerResponseInterface $response
*
* @return TransferErrorExceptionInterface
*/
public static function fromClientRequest(
ClientRequestInterface $request,
ServerResponseInterface $response = null
): TransferErrorExceptionInterface {
$message = sprintf('Error Communicating with Server: %s %s', $request->getMethod(), $request->getUri());
return new self($message, 0, null, $request, $response);
}
/**
* @return bool
*/
public function hasResponse(): bool
{
return $this->response !== null;
}
/**
* @return ServerResponseInterface|null
*/
public function getResponse(): ?ServerResponseInterface
{
return $this->response;
}
/**
* @return ClientRequestInterface
*/
public function getRequest(): ClientRequestInterface
{
return $this->request;
}
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient\Exception;
use Xentral\Components\HttpClient\Request\ClientRequestInterface;
use Xentral\Components\HttpClient\Response\ServerResponseInterface;
interface TransferErrorExceptionInterface extends HttpClientExceptionInterface
{
/**
* @return bool
*/
public function hasResponse(): bool;
/**
* @return ServerResponseInterface|null
*/
public function getResponse(): ?ServerResponseInterface;
/**
* @return ClientRequestInterface
*/
public function getRequest(): ClientRequestInterface;
}
@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Exception\GuzzleException;
use Xentral\Components\HttpClient\Exception\TransferErrorException;
use Xentral\Components\HttpClient\Exception\TransferErrorExceptionInterface;
use Xentral\Components\HttpClient\Request\ClientRequest;
use Xentral\Components\HttpClient\Request\ClientRequestInterface;
use Xentral\Components\HttpClient\Response\ServerResponse;
use Xentral\Components\HttpClient\Response\ServerResponseInterface;
use Xentral\Components\HttpClient\Stream\StreamInterface;
use Xentral\Components\HttpClient\Uri\UriInterface;
final class HttpClient implements HttpClientInterface
{
/** @var RequestOptions $options */
private $options;
/**
* @param RequestOptions|null $options
*/
public function __construct(RequestOptions $options = null)
{
$this->options = $options === null ? new RequestOptions() : clone $options;
}
/**
* @param string $method HTTP method
* @param string|UriInterface $uri URI
* @param array $headers Request headers
* @param string|null|resource|StreamInterface $body Request body
* @param string $version Protocol version
*
* @throws TransferErrorExceptionInterface
*
* @return ServerResponseInterface
*/
public function request($method, $uri, array $headers = [], $body = null, $version = '1.1'): ServerResponseInterface
{
$request = new ClientRequest($method, $uri, $headers, $body, $version);
return $this->sendRequest($request);
}
/**
* @param ClientRequestInterface $request
* @param RequestOptions|null $options
*
* @throws TransferErrorExceptionInterface
*
* @return ServerResponseInterface
*/
public function sendRequest(
ClientRequestInterface $request,
RequestOptions $options = null
): ServerResponseInterface {
$optionsArray = $options === null ? $this->options->toArray() : $options->toArray();
try {
$client = $this->createClient();
$response = $client->send($request, $optionsArray);
return ServerResponse::fromGuzzleResponse($response);
//
} catch (GuzzleException $exception) {
throw TransferErrorException::fromGuzzleException($exception);
}
}
/**
* @return GuzzleClient
*/
private function createClient(): GuzzleClient
{
return new GuzzleClient($this->options->toArray());
}
}
@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient;
final class HttpClientFactory
{
/**
* @param RequestOptions|null $options
*
* @return HttpClientInterface
*/
public function createClient(RequestOptions $options = null): HttpClientInterface
{
if ($options === null) {
$options = new RequestOptions();
}
return new HttpClient($options);
}
}
@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient;
use Xentral\Components\HttpClient\Exception\TransferErrorExceptionInterface;
use Xentral\Components\HttpClient\Request\ClientRequestInterface;
use Xentral\Components\HttpClient\Response\ServerResponseInterface;
use Xentral\Components\HttpClient\Stream\StreamInterface;
use Xentral\Components\HttpClient\Uri\UriInterface;
interface HttpClientInterface
{
/**
* @param string $method HTTP method
* @param string|UriInterface $uri URI
* @param array $headers Request headers
* @param string|null|resource|StreamInterface $body Request body
* @param string $version Protocol version
*
* @throws TransferErrorExceptionInterface
*
* @return ServerResponseInterface
*/
public function request(
$method,
$uri,
array $headers = [],
$body = null,
$version = '1.1'
): ServerResponseInterface;
/**
* @param ClientRequestInterface $request
* @param RequestOptions|null $options
*
* @return ServerResponseInterface
*/
public function sendRequest(
ClientRequestInterface $request,
RequestOptions $options = null
): ServerResponseInterface;
}
@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient\Request;
use GuzzleHttp\Psr7\Request as GuzzleRequest;
use Psr\Http\Message\RequestInterface as PsrRequestInterface;
use Xentral\Components\HttpClient\Uri\Uri;
use Xentral\Components\HttpClient\Uri\UriInterface;
final class ClientRequest extends GuzzleRequest implements ClientRequestInterface
{
/**
* @param PsrRequestInterface $guzzleRequest
*
* @return ClientRequestInterface
*/
public static function fromGuzzleRequest(PsrRequestInterface $guzzleRequest): ClientRequestInterface
{
return new self(
$guzzleRequest->getMethod(),
Uri::fromGuzzleUri($guzzleRequest->getUri()),
$guzzleRequest->getHeaders(),
$guzzleRequest->getBody(),
$guzzleRequest->getProtocolVersion()
);
}
/**
* @return UriInterface|string|void
*/
public function getUri()
{
$guzzleUri = parent::getUri();
return Uri::fromGuzzleUri($guzzleUri);
}
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient\Request;
use Psr\Http\Message\RequestInterface as PsrRequestInterface;
interface ClientRequestInterface extends PsrRequestInterface
{
}
@@ -0,0 +1,467 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient;
use GuzzleHttp\RequestOptions as GuzzleOptionKeys;
use Iterator;
use Xentral\Components\HttpClient\Exception\InvalidArgumentException;
use Xentral\Components\HttpClient\Exception\InvalidRequestOptionsException;
use Xentral\Components\HttpClient\Stream\StreamInterface;
final class RequestOptions
{
/** @var array $options */
private $options;
/**
* @internal Use setters and public methods instead
*
* @param array $options
*/
public function __construct(array $options = [])
{
$this->options = array_merge($this->getDefaultOptions(), $options);
}
/**
* @return array
*/
public function toArray(): array
{
return $this->options;
}
/**
* Sets or overwrites all headers for the current client
*
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#headers
*
* @param array|null $headers
*
* @return self
*/
public function setHeaders(array $headers = null): self
{
$this->options[GuzzleOptionKeys::HEADERS] = $headers;
return $this;
}
/**
* Sets or overwrites a specific header
*
* Headers added here are defaults for the created client. Headers can be overwritten for single requests.
*
* @example setHeader('Accept', 'text/html')
* @example setHeader('Accept', ['text/html', 'text'/plain'])
*
* @param string $headerType
* @param string|string[]|null ...$headerValue `null` to remove a header
*
* @return self
*/
public function setHeader(string $headerType, ...$headerValue): self
{
if ($headerValue === null) {
unset($this->options[GuzzleOptionKeys::HEADERS][$headerType]);
} else {
$this->options[GuzzleOptionKeys::HEADERS][$headerType] = $headerValue;
}
return $this;
}
/**
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#expect
*
* @return self
*/
public function enableExpectHeader(): self
{
$this->options[GuzzleOptionKeys::EXPECT] = true;
return $this;
}
/**
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#expect
*
* @return self
*/
public function disableExpectHeader(): self
{
$this->options[GuzzleOptionKeys::EXPECT] = false;
return $this;
}
/**
* Sets the body of the request
*
* @param resource|string|null|int|float|StreamInterface|callable|Iterator $body
*
* @return self
*/
public function setBody($body): self
{
$this->options[GuzzleOptionKeys::BODY] = $body;
return $this;
}
/**
* Used to send an application/x-www-form-urlencoded POST request
*
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#form-params
*
* @param array $formParams
*
* @throws InvalidRequestOptionsException
*
* @return self
*/
public function setBodyFromFormParams(array $formParams): self
{
if (isset($this->options[GuzzleOptionKeys::BODY])) {
throw new InvalidRequestOptionsException('Form params body can not be set. Body is already set.');
}
if (isset($this->options[GuzzleOptionKeys::MULTIPART])) {
throw new InvalidRequestOptionsException('Form params body can not be set. Multipart body is already set.');
}
$this->options[GuzzleOptionKeys::FORM_PARAMS] = $formParams;
return $this;
}
/**
* Used to send an multipart/form-data requests
*
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#multipart
*
* @param array $multipartParams
*
* @throws InvalidRequestOptionsException
*
* @return self
*/
public function setBodyFromMultipartParams(array $multipartParams): self
{
if (isset($this->options[GuzzleOptionKeys::BODY])) {
throw new InvalidRequestOptionsException('Multipart body can not be set. Body is already set.');
}
if (isset($this->options[GuzzleOptionKeys::FORM_PARAMS])) {
throw new InvalidRequestOptionsException('Multipart body can not be set. Form params body is already set.');
}
$this->options[GuzzleOptionKeys::MULTIPART] = $multipartParams;
return $this;
}
/**
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#auth
*
* @param string $username
* @param string $password
*
* @return self
*/
public function setAuthBasic(string $username, string $password): self
{
$this->options[GuzzleOptionKeys::AUTH] = [$username, $password];
return $this;
}
/**
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#auth
*
* @param string $username
* @param string $password
*
* @return self
*/
public function setAuthDigest(string $username, string $password): self
{
$this->options[GuzzleOptionKeys::AUTH] = [$username, $password, 'digest'];
return $this;
}
/**
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#auth
*
* @param string $username
* @param string $password
*
* @return self
*/
public function setAuthNtlm(string $username, string $password): self
{
$this->options[GuzzleOptionKeys::AUTH] = [$username, $password, 'ntlm'];
return $this;
}
/**
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#auth
*
* @param array|string|null $value
*
* @return self
*/
public function setAuthCustom($value): self
{
$this->options[GuzzleOptionKeys::AUTH] = $value;
return $this;
}
/**
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#debug
*
* @param resource $resource
*
* @throws InvalidArgumentException
*
* @return self
*/
public function setDebugResource($resource): self
{
if (!is_resource($resource)) {
throw new InvalidArgumentException('Debug resource is not a valid resource.');
}
$this->options[GuzzleOptionKeys::DEBUG] = $resource;
return $this;
}
/**
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#version
*
* @param float|string $version Default '1.1'
*
* @return self
*/
public function setProtocolVersion($version): self
{
$this->options[GuzzleOptionKeys::VERSION] = $version;
return $this;
}
/**
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#allow-redirects
*
* @return self
*/
public function allowRedirects(): self
{
$this->options[GuzzleOptionKeys::ALLOW_REDIRECTS] = $this->getDefaultRedirectOptions();
return $this;
}
/**
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#allow-redirects
*
* @return self
*/
public function disallowRedirects(): self
{
$this->options[GuzzleOptionKeys::ALLOW_REDIRECTS] = false;
return $this;
}
/**
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#allow-redirects
*
* @param int $redirectCount
*
* @return self
*/
public function setMaxRedirectsCount(int $redirectCount): self
{
$redirectOptions = $this->getDefaultRedirectOptions();
if ($redirectCount <= 0) {
$redirectOptions = false;
}
if ($redirectCount > 0) {
$redirectOptions['max'] = $redirectCount;
}
$this->options[GuzzleOptionKeys::ALLOW_REDIRECTS] = $redirectOptions;
return $this;
}
/**
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#sink
*
* @param string|resource $location
*
* @return self
*/
public function setStorageLocation($location): self
{
$this->options[GuzzleOptionKeys::SINK] = $location;
return $this;
}
/**
* Attempt to stream a response rather than download it all up-front.
*
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#stream
*
* @return self
*/
public function enableStream(): self
{
$this->options[GuzzleOptionKeys::STREAM] = true;
return $this;
}
/**
* (Default behavior)
*
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#stream
*
* @return self
*/
public function disableStream(): self
{
$this->options[GuzzleOptionKeys::STREAM] = false;
return $this;
}
/**
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#ssl-key
*
* @param string $path
* @param string|null $passphrase
*
* @return self
*/
public function setSslKey(string $path, string $passphrase = null): self
{
if ($passphrase !== null) {
$this->options[GuzzleOptionKeys::SSL_KEY] = [$path, $passphrase];
} else {
$this->options[GuzzleOptionKeys::SSL_KEY] = $path;
}
return $this;
}
/**
* Default behaviour
*
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#verify
*
* @return self
*/
public function enableSslVerification(): self
{
$this->options[GuzzleOptionKeys::VERIFY] = true;
return $this;
}
/**
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#verify
*
* @return self
*/
public function disableSslVerification(): self
{
$this->options[GuzzleOptionKeys::VERIFY] = false;
return $this;
}
/**
* Enable SSL verification using a custom certificate
*
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#verify
*
* @param string $certificatePath Path to SSL certificate on disk
*
* @return self
*/
public function setCustomSslVerification(string $certificatePath): self
{
$this->options[GuzzleOptionKeys::VERIFY] = $certificatePath;
return $this;
}
/**
* Sets the timeout of the request in seconds. Use 0 to wait indefinitely (default behavior).
*
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#timeout
*
* @param float $seconds
*
* @return self
*/
public function setTimeout(float $seconds = 0.0): self
{
$this->options[GuzzleOptionKeys::TIMEOUT] = $seconds;
return $this;
}
/**
* Disables exceptions on HTTP protocol errors (4xx and 5xx status)
*
* By default, exceptions will be thrown on HTTP protocol errors
*
* @see http://docs.guzzlephp.org/en/6.5/request-options.html#http_errors
*
* @return self
*/
public function disableHttpErrorExceptions(): self
{
$this->options[GuzzleOptionKeys::HTTP_ERRORS] = false;
return $this;
}
/**
* @return array
*/
private function getDefaultOptions(): array
{
return [
GuzzleOptionKeys::ALLOW_REDIRECTS => $this->getDefaultRedirectOptions(),
GuzzleOptionKeys::DEBUG => false,
GuzzleOptionKeys::TIMEOUT => 0,
GuzzleOptionKeys::VERIFY => true,
GuzzleOptionKeys::VERSION => 1.1,
GuzzleOptionKeys::STREAM => false,
];
}
/**
* @return array
*/
private function getDefaultRedirectOptions(): array
{
return [
'max' => 5,
'strict' => false,
'referer' => false,
'protocols' => ['http', 'https'],
'track_redirects' => false,
];
}
}
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient\Response;
use GuzzleHttp\Psr7\Response as GuzzleResponse;
use Psr\Http\Message\ResponseInterface as PsrResponseInterface;
use Xentral\Components\HttpClient\Exception\InvalidResponseException;
use Xentral\Components\HttpClient\Stream\StreamDecorator;
final class ServerResponse extends GuzzleResponse implements ServerResponseInterface
{
/**
* @param PsrResponseInterface $response
*
* @throws InvalidResponseException
*
* @return ServerResponseInterface
*/
public static function fromGuzzleResponse(PsrResponseInterface $response): ServerResponseInterface
{
$resource = $response->getBody()->detach();
if (!is_resource($resource)) {
throw new InvalidResponseException('Response body is invalid.');
}
return new self(
$response->getStatusCode(),
$response->getHeaders(),
new StreamDecorator($resource),
$response->getProtocolVersion(),
$response->getReasonPhrase()
);
}
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient\Response;
use Psr\Http\Message\ResponseInterface as PsrResponseInterface;
interface ServerResponseInterface extends PsrResponseInterface
{
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient\Stream;
use GuzzleHttp\Psr7\Stream;
final class StreamDecorator extends Stream implements StreamInterface
{
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient\Stream;
use Psr\Http\Message\StreamInterface as PsrStreamInterface;
interface StreamInterface extends PsrStreamInterface
{
}
+24
View File
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient\Uri;
use GuzzleHttp\Psr7\Uri as GuzzleUri;
final class Uri extends GuzzleUri implements UriInterface
{
/**
* @param GuzzleUri|string $uri
*
* @return Uri|UriInterface
*/
public static function fromGuzzleUri($uri)
{
if (!$uri instanceof UriInterface) {
return new self((string)$uri);
}
return $uri;
}
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\HttpClient\Uri;
use Psr\Http\Message\UriInterface as PsrUriInterface;
interface UriInterface extends PsrUriInterface
{
}
+126
View File
@@ -0,0 +1,126 @@
# HTTP-Client
## Neue HttpClient-Instanz erzeugen
```php
/** @var \Xentral\Components\HttpClient\HttpClientFactory $factory */
$factory = $container->get('HttpClientFactory');
$client = $factory->createClient();
```
## Requests abschicken
```php
/** @var \Xentral\Components\HttpClient\HttpClientFactory $factory */
/** @var \Xentral\Components\HttpClient\HttpClientInterface $client */
$client = $factory->createClient();
$uri = 'https://httpbin.org/json';
$headers = ['Accept' => 'application/json'];
$request = new \Xentral\Components\HttpClient\Request\ClientRequest('GET', $uri, $headers);
try {
/** @var \Xentral\Components\HttpClient\Response\ServerResponseInterface $response */
$response = $client->sendRequest($request);
} catch (\Xentral\Components\HttpClient\Exception\TransferErrorExceptionInterface $exception) {
$request = $exception->getRequest();
$response = $exception->hasResponse() ? $exception->getResponse() : null;
// ...
}
```
## Request-Optionen
Mit den Request-Optionen kann das Standard-Verhalten des Http-Clients beeinflusst werden.
### Standard-Optionen
* Timeout: 0 Sekunden (kein Timeout)
* Umleitungen folgen: Maximal fünf Redirects
* Streaming-Verhalten: Deaktiviert (alles am Stück downloaden)
* SSL-Zertifikatsverifizierung: Aktiv
* HTTP-Protokollversion: 1.1
### Request-Optionen übergeben
Es gibt zwei Möglichkeiten Request-Optionen zu übergeben:
1. Beim Erzeugen der HttpClient-Instanz (`HttpClientFactory::createClient($options)`)
* Die Optionen gelten dann für alle Requests dieser Instanz.
2. Beim Abschicken eines Requests (`HttpClient::sendRequest($request, $options)`)
* Die Optionen werden nur für diesen Request angewendet.
Wenn an beiden Stellen Optionen übergeben werden, werden die Optionen zusammengefasst. Bei Konflikten haben die
Optionen Vorrang die beim Abschicken des Request übergeben werden (2. Möglichkeit).
### Beispiele
```php
$options = new \Xentral\Components\HttpClient\RequestOptions();
$options->setTimeout(5); // Maximal fünf Sekunden auf eine Antwort warten
$options->disallowRedirects(); // Keinen Umleitungen folgen
$options->setAuthDigest('username', 'password'); // Per DigestAuth authentifizieren
$options->setHeader('Accept', ['text/html', 'text/plain']); // Accept-Header setzen
```
#### Große Dateien downloaden
Für den Download von großen Dateien empfiehlt es sich die `setStorageLocation()`-Option zu verwenden.
Die Methode nimmt entweder einen Dateipfad oder ein `resource`-Objekt als Parameter entgegen.
Der Vorteil dieser Option besteht darin, dass der Response-Body direkt in eine Datei gestreamt wird.
Das führt zu einem sehr geringen Arbeitsspeicherbedarf.
```php
$options = new \Xentral\Components\HttpClient\RequestOptions();
$options->setStorageLocation('/tmp/large_file');
$response = $client->sendRequest($request, $options);
```
## Fehlerbehandlung
Im Fehlerfall wird standardmäßig eine Exception geworfen. Alle Exceptions implementieren
`\Xentral\Components\HttpClient\Exception\HttpClientExceptionInterface`.
Desweiteren gibt es spezielle Exceptions die bei HTTP-Protokollfehlern geworfen werden. Diese sind
alle von `\Xentral\Components\HttpClient\Exception\TransferErrorException` abgeleitet.
### Transfer-Fehler
* Verbindungsaufbau ist fehlgeschlagen
* Mögliche Ursachen: Gegenstelle ist nicht vorhanden (URL falsch/fehlerhaft), Gegenstelle momentan nicht
erreichbar, Routing-Fehler
* Klasse: `\Xentral\Components\HttpClient\Exception\ConnectionFailedException`
* HTTP-Client-Fehler (HTTP-Status 4xx)
* Die Ursache des Scheiterns liegt eher im Verantwortungsbereich des HTTP-Clients.
* Beispiel: Zugriff auf Resource ist nicht erlaubt (HTTP-Status 403)
* Klasse: `\Xentral\Components\HttpClient\Exception\ClientErrorException`
* HTTP-Server-Fehler (HTTP-Status 5xx)
* Die Ursache des Scheiterns liegt eher im Verantwortungsbereich der Gegenstelle (Server).
* Beispiel: Internal Server Error (HTTP-Status 500)
* Klasse: `\Xentral\Components\HttpClient\Exception\ServerErrorException`
* Zu viele Umleitungen
* In der Standard-Einstellung sind fünf Weiterleitungen erlaubt. Wird diese Zahl überschritten wird die
`TooManyRedirectsException` geworfen. In den Request-Optionen lässt sich die Anzahl der maximal erlaubten
Weiterleitungen festlegen.
* Klasse: `\Xentral\Components\HttpClient\Exception\TooManyRedirectsException`
### Transfer-Fehler-Exceptions deaktivieren
Über die Request-Optionen können Exceptions für HTTP-Protokollfehler deaktiviert werden.
```php
$options = new \Xentral\Components\HttpClient\RequestOptions();
$options->disableHttpErrorExceptions();
/** @var \Xentral\Components\HttpClient\HttpClientFactory $factory */
$factory = $container->get('HttpClientFactory');
$client = $factory->createClient($options);
$response = $client->request('GET', 'http://not_existing');
echo $response->getStatusCode(); // Ausgabe: 404
```