<?php
/**
- * @copyright Copyright (C) 2020, Friendica
+ * @copyright Copyright (C) 2010-2021, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
use FastRoute\Dispatcher;
use FastRoute\RouteCollector;
use FastRoute\RouteParser\Std;
-use Friendica\Core\Cache\Duration;
-use Friendica\Core\Cache\ICache;
+use Friendica\Core\Cache\Enum\Duration;
+use Friendica\Core\Cache\Capability\ICanCache;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
+use Friendica\Core\Lock\Capability\ICanLock;
use Friendica\Network\HTTPException;
/**
*/
class Router
{
- const POST = 'POST';
- const GET = 'GET';
+ const DELETE = 'DELETE';
+ const GET = 'GET';
+ const PATCH = 'PATCH';
+ const POST = 'POST';
+ const PUT = 'PUT';
+ const OPTIONS = 'OPTIONS';
const ALLOWED_METHODS = [
- self::POST,
+ self::DELETE,
self::GET,
+ self::PATCH,
+ self::POST,
+ self::PUT,
+ self::OPTIONS
];
/** @var RouteCollector */
/** @var L10n */
private $l10n;
- /** @var ICache */
+ /** @var ICanCache */
private $cache;
+ /** @var ICanLock */
+ private $lock;
+
/** @var string */
private $baseRoutesFilepath;
* @param array $server The $_SERVER variable
* @param string $baseRoutesFilepath The path to a base routes file to leverage cache, can be empty
* @param L10n $l10n
- * @param ICache $cache
+ * @param ICanCache $cache
* @param RouteCollector|null $routeCollector
*/
- public function __construct(array $server, string $baseRoutesFilepath, L10n $l10n, ICache $cache, RouteCollector $routeCollector = null)
+ public function __construct(array $server, string $baseRoutesFilepath, L10n $l10n, ICanCache $cache, ICanLock $lock, RouteCollector $routeCollector = null)
{
$this->baseRoutesFilepath = $baseRoutesFilepath;
$this->l10n = $l10n;
$this->cache = $cache;
+ $this->lock = $lock;
$httpMethod = $server['REQUEST_METHOD'] ?? self::GET;
$this->httpMethod = in_array($httpMethod, self::ALLOWED_METHODS) ? $httpMethod : self::GET;
$this->routeCollector = isset($routeCollector) ?
$routeCollector :
new RouteCollector(new Std(), new GroupCountBased());
+
+ if ($this->baseRoutesFilepath && !file_exists($this->baseRoutesFilepath)) {
+ throw new HTTPException\InternalServerErrorException('Routes file path does\'n exist.');
+ }
}
/**
{
$dispatchData = [];
- if ($this->baseRoutesFilepath && file_exists($this->baseRoutesFilepath)) {
+ if ($this->baseRoutesFilepath) {
$dispatchData = require $this->baseRoutesFilepath;
if (!is_array($dispatchData)) {
throw new HTTPException\InternalServerErrorException('Invalid base routes file');
* The cached "routerDispatchData" lasts for a day, and must be cleared manually when there
* is any changes in the enabled addons list.
*
+ * Additionally, we check for the base routes file last modification time to automatically
+ * trigger re-computing the dispatch data.
+ *
* @return array|mixed
* @throws HTTPException\InternalServerErrorException
*/
private function getCachedDispatchData()
{
$routerDispatchData = $this->cache->get('routerDispatchData');
+ $lastRoutesFileModifiedTime = $this->cache->get('lastRoutesFileModifiedTime');
+ $forceRecompute = false;
- if ($routerDispatchData) {
+ if ($this->baseRoutesFilepath) {
+ $routesFileModifiedTime = filemtime($this->baseRoutesFilepath);
+ $forceRecompute = $lastRoutesFileModifiedTime != $routesFileModifiedTime;
+ }
+
+ if (!$forceRecompute && $routerDispatchData) {
return $routerDispatchData;
}
+ if (!$this->lock->acquire('getCachedDispatchData', 0)) {
+ // Immediately return uncached data when we can't aquire a lock
+ return $this->getDispatchData();
+ }
+
$routerDispatchData = $this->getDispatchData();
$this->cache->set('routerDispatchData', $routerDispatchData, Duration::DAY);
+ if (!empty($routesFileModifiedTime)) {
+ $this->cache->set('lastRoutesFileModifiedTime', $routesFileModifiedTime, Duration::MONTH);
+ }
+
+ if ($this->lock->isLocked('getCachedDispatchData')) {
+ $this->lock->release('getCachedDispatchData');
+ }
return $routerDispatchData;
}