Skip to content

Commit

Permalink
[Feat] Server: implement retries and sleep HTTP configurations
Browse files Browse the repository at this point in the history
When other servers fails, the server instance will make 2 more attempts with 5 seconds between each.
  • Loading branch information
landrok committed Apr 6, 2024
1 parent 359568a commit 0f706e2
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 30 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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') }}
Expand Down
25 changes: 24 additions & 1 deletion docs/server/server-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
],
]);
```


________________________________________________________________________


Expand Down
16 changes: 13 additions & 3 deletions src/ActivityPhp/Server/Configuration/HttpConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

/**
* Server HTTP configuration stack
*/
*/
class HttpConfiguration extends AbstractConfiguration
{
/**
Expand All @@ -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 = [])
Expand Down Expand Up @@ -67,7 +77,7 @@ private function getUserAgent(): string
$host,
is_null($port) ? '' : ":{$port}"
);

return sprintf(
'%s/%s (+%s)',
Version::getRootNamespace(),
Expand Down
30 changes: 18 additions & 12 deletions src/ActivityPhp/Server/Helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 = [
Expand All @@ -39,15 +40,15 @@ abstract class Helper

/**
* Validate HTTP Accept headers
*
*
* @param null|string|array $accept
* @param bool $strict Strict mode
* @return bool
* @throws \Exception when strict mode enabled
*/
public static function validateAcceptHeader($accept, $strict = false)
{
if (is_string($accept)
if (is_string($accept)
&& in_array($accept, self::$acceptHeaders)
) {
return true;
Expand All @@ -58,8 +59,8 @@ public static function validateAcceptHeader($accept, $strict = false)
) {
return true;
}
if (!$strict) {

if (! $strict) {
return false;
}

Expand All @@ -73,15 +74,20 @@ public static function validateAcceptHeader($accept, $strict = false)

/**
* Fetch JSON content from an URL
*
*
* @param string $url
* @param float|int $timeout
* @return array
*/
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)
);
}
}
72 changes: 61 additions & 11 deletions src/ActivityPhp/Server/Http/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -29,7 +30,7 @@ class Request

/**
* Allowed HTTP methods
*
*
* @var array
*/
protected $allowedMethods = [
Expand All @@ -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;
}
Expand All @@ -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)
Expand All @@ -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
*/
Expand All @@ -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;
}
}

0 comments on commit 0f706e2

Please sign in to comment.