From 0f706e203eac794dd832c4af675cdc7ae9af3a20 Mon Sep 17 00:00:00 2001 From: landrok Date: Sat, 6 Apr 2024 17:41:11 +0200 Subject: [PATCH] [Feat] Server: implement retries and sleep HTTP configurations When other servers fails, the server instance will make 2 more attempts with 5 seconds between each. --- .github/workflows/php.yml | 6 +- docs/server/server-usage.md | 25 ++++++- .../Configuration/HttpConfiguration.php | 16 ++++- src/ActivityPhp/Server/Helper.php | 30 ++++---- src/ActivityPhp/Server/Http/Request.php | 72 ++++++++++++++++--- 5 files changed, 119 insertions(+), 30 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 60819a5..ad7637f 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -16,19 +16,19 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: "${{ matrix.php-versions }}" - coverage: xdebug + coverage: xdebug2 ini-values: "memory_limit=-1" tools: phpunit, composer - name: Cache Composer packages id: composer-cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: vendor key: ${{ runner.os }}-${{ matrix.php-versions }}-php-${{ hashFiles('**/composer.lock') }} diff --git a/docs/server/server-usage.md b/docs/server/server-usage.md index 55e0c4e..32a4b34 100644 --- a/docs/server/server-usage.md +++ b/docs/server/server-usage.md @@ -206,7 +206,7 @@ ________________________________________________________________________ **timeout** -The default timeout for HTTP requests is `10s`.. +The default timeout for HTTP requests is `10s`. ```php use ActivityPhp\Server; @@ -236,6 +236,29 @@ $server = new Server([ ]); ``` +**retries** and **sleep** + +Other federated servers might have some problems and responds with HTTP errors (5xx). + +The server instance may retry to reach another instance. +By default, it will make 2 more attempts with 5 seconds between each before failing. + +Setting to `-1` would make it endlessly attempt to transmit its message (Not recommended). +Setting to `0` would make it never retry to transmit its message. + +```php +use ActivityPhp\Server; + +// Setting to 5 maximum retries with 10 seconds between each +$server = new Server([ + 'http' => [ + 'retries' => 5, + 'sleep' => 10, + ], +]); +``` + + ________________________________________________________________________ diff --git a/src/ActivityPhp/Server/Configuration/HttpConfiguration.php b/src/ActivityPhp/Server/Configuration/HttpConfiguration.php index df83ebb..43e7389 100644 --- a/src/ActivityPhp/Server/Configuration/HttpConfiguration.php +++ b/src/ActivityPhp/Server/Configuration/HttpConfiguration.php @@ -16,7 +16,7 @@ /** * Server HTTP configuration stack - */ + */ class HttpConfiguration extends AbstractConfiguration { /** @@ -29,9 +29,19 @@ class HttpConfiguration extends AbstractConfiguration */ protected $agent; + /** + * @var int Max number of retries + */ + protected $retries = 2; + + /** + * @var int Number of seconds to sleep before retrying + */ + protected $sleep = 5; + /** * Dispatch configuration parameters - * + * * @param array $params */ public function __construct(array $params = []) @@ -67,7 +77,7 @@ private function getUserAgent(): string $host, is_null($port) ? '' : ":{$port}" ); - + return sprintf( '%s/%s (+%s)', Version::getRootNamespace(), diff --git a/src/ActivityPhp/Server/Helper.php b/src/ActivityPhp/Server/Helper.php index 2d60dd7..030d0f6 100644 --- a/src/ActivityPhp/Server/Helper.php +++ b/src/ActivityPhp/Server/Helper.php @@ -11,12 +11,13 @@ namespace ActivityPhp\Server; -use ActivityPhp\Server\Http\Request as HttpRequest; -use ActivityPhp\Type; -use ActivityPhp\Type\Util; -use DateInterval; use DateTime; use Exception; +use DateInterval; +use ActivityPhp\Type; +use ActivityPhp\Server; +use ActivityPhp\Type\Util; +use ActivityPhp\Server\Http\Request as HttpRequest; /** * \ActivityPhp\Server\Helper provides global helper methods for a server @@ -26,9 +27,9 @@ abstract class Helper { /** * An array of allowed Accept HTTP headers - * + * * @see https://www.w3.org/TR/activitypub/#client-to-server-interactions - * + * * @var string[] */ protected static $acceptHeaders = [ @@ -39,7 +40,7 @@ abstract class Helper /** * Validate HTTP Accept headers - * + * * @param null|string|array $accept * @param bool $strict Strict mode * @return bool @@ -47,7 +48,7 @@ abstract class Helper */ public static function validateAcceptHeader($accept, $strict = false) { - if (is_string($accept) + if (is_string($accept) && in_array($accept, self::$acceptHeaders) ) { return true; @@ -58,8 +59,8 @@ public static function validateAcceptHeader($accept, $strict = false) ) { return true; } - - if (!$strict) { + + if (! $strict) { return false; } @@ -73,7 +74,7 @@ public static function validateAcceptHeader($accept, $strict = false) /** * Fetch JSON content from an URL - * + * * @param string $url * @param float|int $timeout * @return array @@ -81,7 +82,12 @@ public static function validateAcceptHeader($accept, $strict = false) public static function fetch($url, $timeout = 10.0) { return Util::decodeJson( - (new HttpRequest($timeout))->get($url) + (new HttpRequest($timeout)) + ->setMaxRetries( + Server::server()->config('http')->get('retries'), + Server::server()->config('http')->get('sleep') + ) + ->get($url) ); } } diff --git a/src/ActivityPhp/Server/Http/Request.php b/src/ActivityPhp/Server/Http/Request.php index d2805d0..121ad92 100644 --- a/src/ActivityPhp/Server/Http/Request.php +++ b/src/ActivityPhp/Server/Http/Request.php @@ -11,13 +11,14 @@ namespace ActivityPhp\Server\Http; +use ActivityPhp\Server; use ActivityPhp\Server\Cache\CacheHelper; use Exception; use GuzzleHttp\Client; /** * Request handler - */ + */ class Request { const HTTP_HEADER_ACCEPT = 'application/activity+json,application/ld+json,application/json'; @@ -29,7 +30,7 @@ class Request /** * Allowed HTTP methods - * + * * @var array */ protected $allowedMethods = [ @@ -38,21 +39,40 @@ class Request /** * HTTP client - * + * * @var \GuzzleHttp\Client */ protected $client; + /** + * Number of allowed retries + * + * -1: unlimited + * 0 : never retry + * >0: throw exception after this number of retries + */ + protected $maxRetries = 0; + + /** + * Number of seconds to wait before retrying + */ + protected $sleepBeforeRetry = 5; + + /** + * Current retries counter + */ + protected $retryCounter = 0; + /** * Set HTTP client - * + * * @param float|int $timeout * @param string $agent */ public function __construct($timeout = 10.0, $agent = '') { - $headers = ['Accept' => self::HTTP_HEADER_ACCEPT]; + if ($agent) { $headers['User-Agent'] = $agent; } @@ -63,9 +83,20 @@ public function __construct($timeout = 10.0, $agent = '') ]); } + /** + * Set Max retries after a sleeping time + */ + public function setMaxRetries(int $maxRetries, int $sleepBeforeRetry = 5): self + { + $this->maxRetries = $maxRetries; + $this->sleepBeforeRetry = $sleepBeforeRetry; + + return $this; + } + /** * Set HTTP methods - * + * * @param string $method */ protected function setMethod(string $method) @@ -77,17 +108,17 @@ protected function setMethod(string $method) /** * Get HTTP methods - * + * * @return string */ protected function getMethod() { return $this->method; } - + /** * Execute a GET request - * + * * @param string $url * @return string */ @@ -96,14 +127,33 @@ public function get(string $url) if (CacheHelper::has($url)) { return CacheHelper::get($url); } + try { $content = $this->client->get($url)->getBody()->getContents(); - } catch (\GuzzleHttp\Exception\ClientException $exception) { - throw new Exception($exception->getMessage()); + } catch (Exception $e) { + Server::server()->logger()->error( + __METHOD__ . ':failure', + ['url' => $url, 'message' => $e->getMessage()] + ); + if ($this->maxRetries === -1 + || $this->retryCounter < $this->maxRetries + ) { + $this->retryCounter++; + Server::server()->logger()->info( + __METHOD__ . ':retry#' . $this->retryCounter, + ['url' => $url] + ); + sleep($this->sleepBeforeRetry); + return $this->get($url); + } + + throw new Exception($e->getMessage()); } CacheHelper::set($url, $content); + $this->retryCounter = 0; + return $content; } }