Initial xentral_oss_20.3.c9ffacf
This commit is contained in:
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
```
|
||||
Reference in New Issue
Block a user