Skip to content

Commit

Permalink
Merge pull request #355 from mesilov/354-add-x-request-id-support
Browse files Browse the repository at this point in the history
add x-request-id support
  • Loading branch information
mesilov authored Nov 25, 2023
2 parents 8ec5051 + f62e2db commit 187ce78
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 21 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* add [crm item support](https://github.com/mesilov/bitrix24-php-sdk/issues/330)
* add enum `DealStageSemanticId`
* add Duplicate search support for `Bitrix24\SDK\Services\CRM\Duplicates\Service\Duplicate`
* add `x-request-id` [header support](https://github.com/mesilov/bitrix24-php-sdk/issues/354)

### Changed

Expand Down
46 changes: 37 additions & 9 deletions src/Core/ApiClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
namespace Bitrix24\SDK\Core;

use Bitrix24\SDK\Core\Contracts\ApiClientInterface;
use Bitrix24\SDK\Core\Credentials\Credentials;
use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
use Bitrix24\SDK\Core\Exceptions\TransportException;
use Bitrix24\SDK\Core\Response\DTO\RenewedAccessToken;
use Bitrix24\SDK\Infrastructure\HttpClient\RequestId\RequestIdGeneratorInterface;
use Fig\Http\Message\StatusCodeInterface;
use Psr\Log\LoggerInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
Expand All @@ -18,7 +20,8 @@ class ApiClient implements ApiClientInterface
{
protected HttpClientInterface $client;
protected LoggerInterface $logger;
protected Credentials\Credentials $credentials;
protected Credentials $credentials;
protected RequestIdGeneratorInterface $requestIdGenerator;
/**
* @const string
*/
Expand All @@ -32,14 +35,20 @@ class ApiClient implements ApiClientInterface
/**
* ApiClient constructor.
*
* @param Credentials\Credentials $credentials
* @param Credentials $credentials
* @param HttpClientInterface $client
* @param RequestIdGeneratorInterface $requestIdGenerator
* @param LoggerInterface $logger
*/
public function __construct(Credentials\Credentials $credentials, HttpClientInterface $client, LoggerInterface $logger)
public function __construct(
Credentials $credentials,
HttpClientInterface $client,
RequestIdGeneratorInterface $requestIdGenerator,
LoggerInterface $logger)
{
$this->credentials = $credentials;
$this->client = $client;
$this->requestIdGenerator = $requestIdGenerator;
$this->logger = $logger;
$this->logger->debug(
'ApiClient.init',
Expand All @@ -64,9 +73,9 @@ protected function getDefaultHeaders(): array
}

/**
* @return Credentials\Credentials
* @return Credentials
*/
public function getCredentials(): Credentials\Credentials
public function getCredentials(): Credentials
{
return $this->credentials;
}
Expand All @@ -80,7 +89,10 @@ public function getCredentials(): Credentials\Credentials
*/
public function getNewAccessToken(): RenewedAccessToken
{
$this->logger->debug('getNewAccessToken.start');
$requestId = $this->requestIdGenerator->getRequestId();
$this->logger->debug('getNewAccessToken.start', [
'requestId' => $requestId
]);
if ($this->getCredentials()->getApplicationProfile() === null) {
throw new InvalidArgumentException('application profile not set');
}
Expand All @@ -103,14 +115,21 @@ public function getNewAccessToken(): RenewedAccessToken
);

$requestOptions = [
'headers' => $this->getDefaultHeaders(),
'headers' => array_merge(
$this->getDefaultHeaders(),
[
$this->requestIdGenerator->getHeaderFieldName() => $requestId
]
),
];
$response = $this->client->request($method, $url, $requestOptions);
$responseData = $response->toArray(false);
if ($response->getStatusCode() === StatusCodeInterface::STATUS_OK) {
$newAccessToken = RenewedAccessToken::initFromArray($responseData);

$this->logger->debug('getNewAccessToken.finish');
$this->logger->debug('getNewAccessToken.finish', [
'requestId' => $requestId
]);
return $newAccessToken;
}
if ($response->getStatusCode() === StatusCodeInterface::STATUS_BAD_REQUEST) {
Expand All @@ -129,12 +148,14 @@ public function getNewAccessToken(): RenewedAccessToken
*/
public function getResponse(string $apiMethod, array $parameters = []): ResponseInterface
{
$requestId = $this->requestIdGenerator->getRequestId();
$this->logger->info(
'getResponse.start',
[
'apiMethod' => $apiMethod,
'domainUrl' => $this->credentials->getDomainUrl(),
'parameters' => $parameters,
'requestId' => $requestId
]
);

Expand All @@ -150,9 +171,15 @@ public function getResponse(string $apiMethod, array $parameters = []): Response
$parameters['auth'] = $this->getCredentials()->getAccessToken()->getAccessToken();
}


$requestOptions = [
'json' => $parameters,
'headers' => $this->getDefaultHeaders(),
'headers' => array_merge(
$this->getDefaultHeaders(),
[
$this->requestIdGenerator->getHeaderFieldName() => $requestId
]
),
// disable redirects, try to catch portal change domain name event
'max_redirects' => 0,
];
Expand All @@ -163,6 +190,7 @@ public function getResponse(string $apiMethod, array $parameters = []): Response
[
'apiMethod' => $apiMethod,
'responseInfo' => $response->getInfo(),
'requestId' => $requestId
]
);

Expand Down
22 changes: 16 additions & 6 deletions src/Core/CoreBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
use Bitrix24\SDK\Core\Credentials\Credentials;
use Bitrix24\SDK\Core\Credentials\WebhookUrl;
use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
use Bitrix24\SDK\Infrastructure\HttpClient\RequestId\DefaultRequestIdGenerator;
use Bitrix24\SDK\Infrastructure\HttpClient\RequestId\RequestIdGeneratorInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\EventDispatcher\EventDispatcher;
Expand All @@ -23,12 +25,13 @@
*/
class CoreBuilder
{
protected ?ApiClientInterface $apiClient;
protected HttpClientInterface $httpClient;
protected EventDispatcherInterface $eventDispatcher;
protected LoggerInterface $logger;
protected ?Credentials $credentials;
protected ApiLevelErrorHandler $apiLevelErrorHandler;
private ?ApiClientInterface $apiClient;
private HttpClientInterface $httpClient;
private EventDispatcherInterface $eventDispatcher;
private LoggerInterface $logger;
private ?Credentials $credentials;
private ApiLevelErrorHandler $apiLevelErrorHandler;
private RequestIdGeneratorInterface $requestIdGenerator;

/**
* CoreBuilder constructor.
Expand All @@ -46,6 +49,12 @@ public function __construct()
$this->credentials = null;
$this->apiClient = null;
$this->apiLevelErrorHandler = new ApiLevelErrorHandler($this->logger);
$this->requestIdGenerator = new DefaultRequestIdGenerator();
}

public function withRequestIdGenerator(RequestIdGeneratorInterface $requestIdGenerator): void
{
$this->requestIdGenerator = $requestIdGenerator;
}

/**
Expand Down Expand Up @@ -101,6 +110,7 @@ public function build(): CoreInterface
$this->apiClient = new ApiClient(
$this->credentials,
$this->httpClient,
$this->requestIdGenerator,
$this->logger
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace Bitrix24\SDK\Infrastructure\HttpClient\RequestId;

use Symfony\Component\Uid\Uuid;

class DefaultRequestIdGenerator implements RequestIdGeneratorInterface
{
private const DEFAULT_REQUEST_ID_FIELD_NAME = 'X-Request-ID';
private const KEY_NAME_VARIANTS = [
'REQUEST_ID',
'HTTP_X_REQUEST_ID',
'UNIQUE_ID'
];

private function generate(): string
{
return Uuid::v7()->toRfc4122();
}

private function findExists(): ?string
{
$candidate = null;
foreach(self::KEY_NAME_VARIANTS as $key)
{
if(!empty($_SERVER[$key]))
{
$candidate = $_SERVER[$key];
break;
}
}
return $candidate;
}

public function getRequestId(): string
{
$reqId = $this->findExists();
if ($reqId === null) {
$reqId = $this->generate();
}
return $reqId;
}

public function getHeaderFieldName(): string
{
return self::DEFAULT_REQUEST_ID_FIELD_NAME;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Bitrix24\SDK\Infrastructure\HttpClient\RequestId;

interface RequestIdGeneratorInterface
{
public function getRequestId(): string;

public function getHeaderFieldName(): string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Bitrix24\SDK\Tests\Unit\Infrastructure\HttpClient\RequestId;

use Bitrix24\SDK\Infrastructure\HttpClient\RequestId\DefaultRequestIdGenerator;
use Generator;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Uid\Uuid;

class DefaultRequestIdGeneratorTest extends TestCase
{
/**
* @param $requestIdKey
* @param $requestId
* @return void
* @dataProvider requestIdKeyDataProvider
* @covers \Bitrix24\SDK\Infrastructure\HttpClient\RequestId\DefaultRequestIdGenerator::getRequestId
*/
public function testExistsRequestId($requestIdKey, $requestId): void
{
$_SERVER[$requestIdKey] = $requestId;
$gen = new DefaultRequestIdGenerator();
$this->assertEquals($requestId, $gen->getRequestId());
unset($_SERVER[$requestIdKey]);
}

public function requestIdKeyDataProvider(): Generator
{
yield 'REQUEST_ID' => [
'REQUEST_ID',
Uuid::v7()->toRfc4122()
];
yield 'HTTP_X_REQUEST_ID' => [
'HTTP_X_REQUEST_ID',
Uuid::v7()->toRfc4122()
];
yield 'UNIQUE_ID' => [
'UNIQUE_ID',
Uuid::v7()->toRfc4122()
];
}
}
12 changes: 6 additions & 6 deletions tests/Unit/Stubs/NullCore.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,11 @@
use Bitrix24\SDK\Core\Credentials\Credentials;
use Bitrix24\SDK\Core\Credentials\WebhookUrl;
use Bitrix24\SDK\Core\Response\Response;
use Bitrix24\SDK\Infrastructure\HttpClient\RequestId\DefaultRequestIdGenerator;
use Psr\Log\NullLogger;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;

/**
* Class NullCore
*
* @package Bitrix24\SDK\Tests\Unit\Stubs
*/
class NullCore implements CoreInterface
{
/**
Expand All @@ -37,6 +33,10 @@ public function call(string $apiMethod, array $parameters = []): Response

public function getApiClient(): ApiClientInterface
{
return new ApiClient(Credentials::createFromWebhook(new WebhookUrl('')), new MockHttpClient(), new NullLogger());
return new ApiClient(
Credentials::createFromWebhook(new WebhookUrl('')),
new MockHttpClient(),
new DefaultRequestIdGenerator(),
new NullLogger());
}
}

0 comments on commit 187ce78

Please sign in to comment.