-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
feat: Cache compiled routes #52793
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Cache compiled routes #52793
Changes from all commits
ee3abdd
01575b7
5c9092b
216da3a
446b22a
0d78c60
e8370bf
a2fdecc
c6f2eff
89f51a8
12e7f46
bb485fb
ba53147
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -15,10 +15,16 @@ | |||
| use OCP\IRequest; | ||||
| use Psr\Container\ContainerInterface; | ||||
| use Psr\Log\LoggerInterface; | ||||
| use Symfony\Component\Routing\Exception\ResourceNotFoundException; | ||||
| use Symfony\Component\Routing\Matcher\CompiledUrlMatcher; | ||||
| use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper; | ||||
| use Symfony\Component\Routing\RouteCollection; | ||||
|
|
||||
| class CachingRouter extends Router { | ||||
| protected ICache $cache; | ||||
|
|
||||
| protected array $legacyCreatedRoutes = []; | ||||
|
|
||||
| public function __construct( | ||||
| ICacheFactory $cacheFactory, | ||||
| LoggerInterface $logger, | ||||
|
|
@@ -54,4 +60,98 @@ public function generate($name, $parameters = [], $absolute = false) { | |||
| return $url; | ||||
| } | ||||
| } | ||||
|
|
||||
| private function serializeRouteCollection(RouteCollection $collection): array { | ||||
| $dumper = new CompiledUrlMatcherDumper($collection); | ||||
| return $dumper->getCompiledRoutes(); | ||||
| } | ||||
|
|
||||
| /** | ||||
| * Find the route matching $url | ||||
| * | ||||
| * @param string $url The url to find | ||||
| * @throws \Exception | ||||
| * @return array | ||||
| */ | ||||
| public function findMatchingRoute(string $url): array { | ||||
| $this->eventLogger->start('cacheroute:match', 'Match route'); | ||||
| $key = $this->context->getHost() . '#' . $this->context->getBaseUrl() . '#rootCollection'; | ||||
| $cachedRoutes = $this->cache->get($key); | ||||
| if (!$cachedRoutes) { | ||||
| parent::loadRoutes(); | ||||
| $cachedRoutes = $this->serializeRouteCollection($this->root); | ||||
| $this->cache->set($key, $cachedRoutes, 3600); | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The key does not include any version number, so this probably means its always cached also on updates?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, the cache have a global prefix with all apps version numbers, see Line 605 in acc2311
|
||||
| } | ||||
| $matcher = new CompiledUrlMatcher($cachedRoutes, $this->context); | ||||
| $this->eventLogger->start('cacheroute:url:match', 'Symfony URL match call'); | ||||
| try { | ||||
| $parameters = $matcher->match($url); | ||||
| } catch (ResourceNotFoundException $e) { | ||||
| if (!str_ends_with($url, '/')) { | ||||
| // We allow links to apps/files? for backwards compatibility reasons | ||||
| // However, since Symfony does not allow empty route names, the route | ||||
| // we need to match is '/', so we need to append the '/' here. | ||||
| try { | ||||
| $parameters = $matcher->match($url . '/'); | ||||
| } catch (ResourceNotFoundException $newException) { | ||||
| // If we still didn't match a route, we throw the original exception | ||||
| throw $e; | ||||
| } | ||||
| } else { | ||||
| throw $e; | ||||
| } | ||||
| } | ||||
| $this->eventLogger->end('cacheroute:url:match'); | ||||
|
|
||||
| $this->eventLogger->end('cacheroute:match'); | ||||
| return $parameters; | ||||
| } | ||||
|
|
||||
| /** | ||||
| * @param array{action:mixed, ...} $parameters | ||||
| */ | ||||
| protected function callLegacyActionRoute(array $parameters): void { | ||||
| /* | ||||
| * Closures cannot be serialized to cache, so for legacy routes calling an action we have to include the routes.php file again | ||||
| */ | ||||
| $app = $parameters['app']; | ||||
| $this->useCollection($app); | ||||
| parent::requireRouteFile($parameters['route-file'], $app); | ||||
| $collection = $this->getCollection($app); | ||||
| $parameters['action'] = $collection->get($parameters['_route'])?->getDefault('action'); | ||||
| parent::callLegacyActionRoute($parameters); | ||||
| } | ||||
|
|
||||
| /** | ||||
| * Create a \OC\Route\Route. | ||||
| * Deprecated | ||||
| * | ||||
| * @param string $name Name of the route to create. | ||||
| * @param string $pattern The pattern to match | ||||
| * @param array $defaults An array of default parameter values | ||||
| * @param array $requirements An array of requirements for parameters (regexes) | ||||
| */ | ||||
| public function create($name, $pattern, array $defaults = [], array $requirements = []): Route { | ||||
| $this->legacyCreatedRoutes[] = $name; | ||||
| return parent::create($name, $pattern, $defaults, $requirements); | ||||
| } | ||||
|
|
||||
| /** | ||||
| * Require a routes.php file | ||||
| */ | ||||
| protected function requireRouteFile(string $file, string $appName): void { | ||||
| $this->legacyCreatedRoutes = []; | ||||
| parent::requireRouteFile($file, $appName); | ||||
| foreach ($this->legacyCreatedRoutes as $routeName) { | ||||
| $route = $this->collection?->get($routeName); | ||||
| if ($route === null) { | ||||
| /* Should never happen */ | ||||
| throw new \Exception("Could not find route $routeName"); | ||||
| } | ||||
| if ($route->hasDefault('action')) { | ||||
| $route->setDefault('route-file', $file); | ||||
| $route->setDefault('app', $appName); | ||||
| } | ||||
| } | ||||
| } | ||||
| } | ||||
Uh oh!
There was an error while loading. Please reload this page.