X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FApp%2FRouter.php;h=78d8ab629f8fd27afb31e3f7c8ca637bf6dd4e3e;hb=199f72ee3cd3c2ef7edfafa1d5a36a365bad93ad;hp=6ae42bb14191d30f372dbe87115f3ef828940f9d;hpb=174129af946b912f842cffc474b7ee5119f76247;p=friendica.git diff --git a/src/App/Router.php b/src/App/Router.php index 6ae42bb141..78d8ab629f 100644 --- a/src/App/Router.php +++ b/src/App/Router.php @@ -1,4 +1,23 @@ . + * + */ namespace Friendica\App; @@ -7,8 +26,10 @@ use FastRoute\DataGenerator\GroupCountBased; use FastRoute\Dispatcher; use FastRoute\RouteCollector; use FastRoute\RouteParser\Std; +use Friendica\Core\Cache\Duration; +use Friendica\Core\Cache\ICache; use Friendica\Core\Hook; -use Friendica\DI; +use Friendica\Core\L10n; use Friendica\Network\HTTPException; /** @@ -23,12 +44,18 @@ 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 ALLOWED_METHODS = [ - self::POST, + self::DELETE, self::GET, + self::PATCH, + self::POST, + self::PUT, ]; /** @var RouteCollector */ @@ -44,21 +71,44 @@ class Router */ private $parameters = []; + /** @var L10n */ + private $l10n; + + /** @var ICache */ + private $cache; + + /** @var string */ + private $baseRoutesFilepath; + /** - * @param array $server The $_SERVER variable - * @param RouteCollector|null $routeCollector Optional the loaded Route collector + * @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 RouteCollector|null $routeCollector */ - public function __construct(array $server, RouteCollector $routeCollector = null) + public function __construct(array $server, string $baseRoutesFilepath, L10n $l10n, ICache $cache, RouteCollector $routeCollector = null) { + $this->baseRoutesFilepath = $baseRoutesFilepath; + $this->l10n = $l10n; + $this->cache = $cache; + $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.'); + } } /** + * This will be called either automatically if a base routes file path was submitted, + * or can be called manually with a custom route array. + * * @param array $routes The routes to add to the Router * * @return self The router instance with the loaded routes @@ -75,6 +125,9 @@ class Router $this->routeCollector = $routeCollector; + // Add routes from addons + Hook::callAll('route_collection', $this->routeCollector); + return $this; } @@ -166,12 +219,9 @@ class Router */ public function getModuleClass($cmd) { - // Add routes from addons - Hook::callAll('route_collection', $this->routeCollector); - $cmd = '/' . ltrim($cmd, '/'); - $dispatcher = new Dispatcher\GroupCountBased($this->routeCollector->getData()); + $dispatcher = new Dispatcher\GroupCountBased($this->getCachedDispatchData()); $moduleClass = null; $this->parameters = []; @@ -181,9 +231,9 @@ class Router $moduleClass = $routeInfo[1]; $this->parameters = $routeInfo[2]; } elseif ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new HTTPException\MethodNotAllowedException(DI::l10n()->t('Method not allowed for this module. Allowed method(s): %s', implode(', ', $routeInfo[1]))); + throw new HTTPException\MethodNotAllowedException($this->l10n->t('Method not allowed for this module. Allowed method(s): %s', implode(', ', $routeInfo[1]))); } else { - throw new HTTPException\NotFoundException(DI::l10n()->t('Page not found.')); + throw new HTTPException\NotFoundException($this->l10n->t('Page not found.')); } return $moduleClass; @@ -198,4 +248,64 @@ class Router { return $this->parameters; } + + /** + * If a base routes file path has been provided, we can load routes from it if the cache misses. + * + * @return array + * @throws HTTPException\InternalServerErrorException + */ + private function getDispatchData() + { + $dispatchData = []; + + if ($this->baseRoutesFilepath) { + $dispatchData = require $this->baseRoutesFilepath; + if (!is_array($dispatchData)) { + throw new HTTPException\InternalServerErrorException('Invalid base routes file'); + } + } + + $this->loadRoutes($dispatchData); + + return $this->routeCollector->getData(); + } + + /** + * We cache the dispatch data for speed, as computing the current routes (version 2020.09) + * takes about 850ms for each requests. + * + * 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 ($this->baseRoutesFilepath) { + $routesFileModifiedTime = filemtime($this->baseRoutesFilepath); + $forceRecompute = $lastRoutesFileModifiedTime != $routesFileModifiedTime; + } + + if (!$forceRecompute && $routerDispatchData) { + return $routerDispatchData; + } + + $routerDispatchData = $this->getDispatchData(); + + $this->cache->set('routerDispatchData', $routerDispatchData, Duration::DAY); + if (!empty($routesFileModifiedTime)) { + $this->cache->set('lastRoutesFileMtime', $routesFileModifiedTime, Duration::MONTH); + } + + return $routerDispatchData; + } }