Skip to content

Commit

Permalink
feat(RequestBlockingServiceContract): extract blocking to class
Browse files Browse the repository at this point in the history
  • Loading branch information
h4kuna committed Jun 27, 2024
1 parent e6837e4 commit c2e24b9
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 83 deletions.
17 changes: 17 additions & 0 deletions src/Contracts/RequestBlockingServiceContract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php declare(strict_types=1);

namespace h4kuna\Fio\Contracts;

use Closure;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Message\ResponseInterface;

interface RequestBlockingServiceContract
{
/**
* @param Closure(): ?ResponseInterface $callback
*
* @throws ClientExceptionInterface
*/
function synchronize(string $token, Closure $callback): ?ResponseInterface;
}
12 changes: 9 additions & 3 deletions src/FioFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use h4kuna\Dir\Dir;
use h4kuna\Dir\TempDir;
use h4kuna\Fio;
use h4kuna\Fio\Contracts\RequestBlockingServiceContract;
use Psr\Http\Client\ClientInterface;

class FioFactory
Expand All @@ -28,7 +29,7 @@ public function __construct(

$this->accountCollection = $this->createAccountCollection($accounts);
$this->queue = $this->createQueue(
is_string($temp) ? new TempDir($temp) : $temp,
$this->createRequestBlockingService(is_string($temp) ? new TempDir($temp) : $temp),
$client ?? self::createClientInterface(),
$fioRequestFactory ?? self::createRequestFactory()
);
Expand All @@ -47,9 +48,14 @@ public function createFioPay(string $name = ''): Fio\FioPay
}


protected function createQueue(Dir $tempDir, ClientInterface $client, Utils\FioRequestFactory $fioRequestFactory): Utils\Queue
protected function createQueue(RequestBlockingServiceContract $requestBlockingService, ClientInterface $client, Utils\FioRequestFactory $fioRequestFactory): Utils\Queue
{
return new Utils\Queue($tempDir->create(), $client, $fioRequestFactory);
return new Utils\Queue($client, $fioRequestFactory, $requestBlockingService);
}

protected function createRequestBlockingService(Dir $tempDir): RequestBlockingServiceContract
{
return new Fio\Utils\FileRequestBlockingService($tempDir->create());
}


Expand Down
80 changes: 80 additions & 0 deletions src/Utils/FileRequestBlockingService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php declare(strict_types=1);

namespace h4kuna\Fio\Utils;

use Closure;
use h4kuna\Dir\Dir;
use h4kuna\Fio\Contracts\RequestBlockingServiceContract;
use h4kuna\Fio\Exceptions\InvalidState;
use Nette\SafeStream\Wrapper;
use Psr\Http\Message\ResponseInterface;

final class FileRequestBlockingService implements RequestBlockingServiceContract
{
/** @var array<string, string> */
private static array $tokens = [];

/**
* @param int $waitTime - seconds
*/
public function __construct(
private Dir $tempDir,
private int $waitTime = 31,
) {
}

public function synchronize(string $token, Closure $callback): ?ResponseInterface
{
$tempFile = $this->loadFileName($token);
$file = self::createFileResource($tempFile);
$this->sleep($tempFile);

try {
$response = $callback();
} finally {
fclose($file);
touch($tempFile);
}

return $response;
}

/**
* @return resource
*/
private static function createFileResource(string $filePath)
{
$file = fopen(self::safeProtocol($filePath), 'w');
if ($file === false) {
throw new InvalidState('Open file is failed ' . $filePath);
}

return $file;
}


private function sleep(string $filename): void
{
$criticalTime = time() - intval(filemtime($filename));
if ($criticalTime < $this->waitTime) {
sleep($this->waitTime - $criticalTime);
}
}


private function loadFileName(string $token): string
{
$key = substr($token, 10, -10);
if (!isset(self::$tokens[$key])) {
self::$tokens[$key] = $this->tempDir->filename(md5($key));
}

return self::$tokens[$key];
}


private static function safeProtocol(string $filename): string
{
return Wrapper::Protocol . "://$filename";
}
}
84 changes: 19 additions & 65 deletions src/Utils/Queue.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

namespace h4kuna\Fio\Utils;

use h4kuna\Dir\Dir;
use h4kuna\Fio\Contracts\RequestBlockingServiceContract;
use h4kuna\Fio\Exceptions;
use h4kuna\Fio\Pay\Response;
use h4kuna\Fio\Pay\XMLResponse;
use Nette\SafeStream;
use Nette\Utils\Strings;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
Expand All @@ -15,24 +14,21 @@

class Queue
{
/** @var int [s] */
protected const WAIT_TIME = 30;
private const HEADER_CONFLICT = 409;

/** @var array<string, string> */
private static array $tokens = [];

private int $limitLoop = 3;


public function __construct(
private Dir $tempDir,
private ClientInterface $client,
private FioRequestFactory $requestFactory,
private RequestBlockingServiceContract $requestBlockingService
) {
}


/**
* @param positive-int $limitLoop
*/
public function setLimitLoop(int $limitLoop): void
{
$this->limitLoop = $limitLoop;
Expand Down Expand Up @@ -83,72 +79,30 @@ public function import(array $params, string $content): Response

private function request(string $token, RequestInterface $request): ResponseInterface
{
$tempFile = $this->loadFileName($token);
$i = 0;

request:
$file = self::createFileResource($tempFile);
++$i;
$request = $request->withHeader('X-Powered-By', 'h4kuna/fio');
$response = null;
try {
$response = $this->client->sendRequest($request->withHeader('X-Powered-By', 'h4kuna/fio'));

if ($response->getStatusCode() === self::HEADER_CONFLICT) {
if ($i >= $this->limitLoop) {
throw new Exceptions\QueueLimit(sprintf('You have limit up requests to server "%s". Too many requests in short time interval.', $i));
}
self::sleep($tempFile);
goto request;
}
for ($i = 0; $i < $this->limitLoop && $response === null; ++$i) {
$response = $this->requestBlockingService->synchronize($token, function () use ($request) {
$response = $this->client->sendRequest($request);

touch($tempFile);
if ($response->getStatusCode() === self::HEADER_CONFLICT) {
return null;
}

return $response;
return $response;
});
}
} catch (ClientExceptionInterface $e) {
$message = str_replace($token, Strings::truncate($token, 10), $e->getMessage());
throw new Exceptions\ServiceUnavailable($message, $e->getCode()); // in url is token, don't need keep previous exception
} finally {
fclose($file);
}
}


/**
* @return resource
*/
private static function createFileResource(string $filePath)
{
$file = fopen(self::safeProtocol($filePath), 'w');
if ($file === false) {
throw new Exceptions\InvalidState('Open file is failed ' . $filePath);
}

return $file;
}


private static function sleep(string $filename): void
{
$criticalTime = time() - intval(filemtime($filename));
if ($criticalTime < static::WAIT_TIME) {
sleep(static::WAIT_TIME - $criticalTime);
}
}


private function loadFileName(string $token): string
{
$key = substr($token, 10, -10);
if (!isset(self::$tokens[$key])) {
self::$tokens[$key] = $this->tempDir->filename(md5($key));
if ($response === null) {
throw new Exceptions\QueueLimit(sprintf('You have limit up requests to server "%s". Too many requests in short time interval.', $this->limitLoop));
}

return self::$tokens[$key];
}


private static function safeProtocol(string $filename): string
{
return SafeStream\Wrapper::Protocol . "://$filename";
return $response;
}


Expand Down
4 changes: 2 additions & 2 deletions tests/src/Fixtures/FioFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace h4kuna\Fio\Tests\Fixtures;

use h4kuna\Dir\Dir;
use h4kuna\Fio\Contracts\RequestBlockingServiceContract;
use h4kuna\Fio\Pay\XMLFile;
use h4kuna\Fio\Read\Json;
use h4kuna\Fio\Utils\FioRequestFactory;
Expand Down Expand Up @@ -39,7 +39,7 @@ public function getReader(): Json
}


protected function createQueue(Dir $tempDir, ClientInterface $client, FioRequestFactory $fioRequestFactory): Queue
protected function createQueue(RequestBlockingServiceContract $requestBlockingService, ClientInterface $client, FioRequestFactory $fioRequestFactory): Queue
{
return new Queue();
}
Expand Down
20 changes: 7 additions & 13 deletions tests/src/Unit/Utils/QueueTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
namespace h4kuna\Fio\Tests\Unit\Utils;

use GuzzleHttp;
use h4kuna\Dir\TempDir;
use h4kuna;
use h4kuna\Dir\TempDir;
use h4kuna\Fio\Tests\Fixtures\ClientMock;
use h4kuna\Fio\Tests\Fixtures\RequestFactoryMock;
use h4kuna\Fio\Tests\Fixtures\Response;
use h4kuna\Fio\Tests\Fixtures\TestCase;
use h4kuna\Fio\Utils\FileRequestBlockingService;
use h4kuna\Fio\Utils\Queue;
use Psr\Http\Client\ClientExceptionInterface;
use Tester\Assert;
use function h4kuna\Fio\Tests\loadResult;

Expand Down Expand Up @@ -55,18 +54,16 @@ public function testDownloadOk(): void
public function testDownloadThrowQueueNoLimit(): void
{
$queue = self::createQueue();
$queue->setLimitLoop(0);
Assert::exception(fn (
) => $queue->download(self::TOKEN, 'http://www.example.com/?status=409'), h4kuna\Fio\Exceptions\QueueLimit::class, 'You have limit up requests to server "1". Too many requests in short time interval.');
$queue->setLimitLoop(1);
Assert::exception(fn () => $queue->download(self::TOKEN, 'http://www.example.com/?status=409'), h4kuna\Fio\Exceptions\QueueLimit::class, 'You have limit up requests to server "1". Too many requests in short time interval.');
}


public function testDownloadThrowQueueLimit(): void
{
$queue = self::createQueue();
$queue->setLimitLoop(2);
Assert::exception(fn (
) => $queue->download(self::TOKEN, 'http://www.example.com/?status=409'), h4kuna\Fio\Exceptions\QueueLimit::class, 'You have limit up requests to server "2". Too many requests in short time interval.');
Assert::exception(fn () => $queue->download(self::TOKEN, 'http://www.example.com/?status=409'), h4kuna\Fio\Exceptions\QueueLimit::class, 'You have limit up requests to server "2". Too many requests in short time interval.');
}


Expand All @@ -87,11 +84,8 @@ private static function createQueue(): Queue
{
$tempDir = new TempDir(__DIR__ . '/../../../temp');

$q = new class ($tempDir, new ClientMock(), new RequestFactoryMock()) extends Queue {
protected const WAIT_TIME = 2;

};
$q->setLimitLoop(0);
$q = new Queue(new ClientMock(), new RequestFactoryMock(), new FileRequestBlockingService($tempDir, 2));
$q->setLimitLoop(1);

return $q;
}
Expand Down

0 comments on commit c2e24b9

Please sign in to comment.