Skip to content

Commit

Permalink
feat: add "Required Filters" and use PageCache and PerformanceMetrics
Browse files Browse the repository at this point in the history
Also changes benchmark points.
  • Loading branch information
kenjis committed Oct 17, 2023
1 parent e858581 commit d49ab26
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 49 deletions.
24 changes: 23 additions & 1 deletion app/Config/Filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use CodeIgniter\Filters\DebugToolbar;
use CodeIgniter\Filters\Honeypot;
use CodeIgniter\Filters\InvalidChars;
use CodeIgniter\Filters\PageCache;
use CodeIgniter\Filters\PerformanceMetrics;
use CodeIgniter\Filters\SecureHeaders;

class Filters extends BaseConfig
Expand All @@ -24,6 +26,27 @@ class Filters extends BaseConfig
'honeypot' => Honeypot::class,
'invalidchars' => InvalidChars::class,
'secureheaders' => SecureHeaders::class,
'pagecache' => PageCache::class,
'performance' => PerformanceMetrics::class,
];

/**
* List of special required filters.
*
* The filters listed here is special. They are applied before and after
* other kinds of filters, and always applied even if a route does not exist.
*
* @var array<string, array<int, string>>
*/
public array $required = [
'before' => [
'pagecache',
],
'after' => [
'pagecache',
'performance',
'toolbar',
],
];

/**
Expand All @@ -40,7 +63,6 @@ class Filters extends BaseConfig
// 'invalidchars',
],
'after' => [
'toolbar',
// 'honeypot',
// 'secureheaders',
],
Expand Down
120 changes: 76 additions & 44 deletions system/CodeIgniter.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use CodeIgniter\Events\Events;
use CodeIgniter\Exceptions\FrameworkException;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\Filters\Filters;
use CodeIgniter\HTTP\CLIRequest;
use CodeIgniter\HTTP\DownloadResponse;
use CodeIgniter\HTTP\Exceptions\RedirectException;
Expand Down Expand Up @@ -338,30 +339,86 @@ public function run(?RouteCollectionInterface $routes = null, bool $returnRespon
$this->getRequestObject();
$this->getResponseObject();

try {
$this->forceSecureAccess();
Events::trigger('pre_system');

$this->response = $this->handleRequest($routes, config(Cache::class), $returnResponse);
} catch (ResponsableInterface|DeprecatedRedirectException $e) {
$this->outputBufferingEnd();
if ($e instanceof DeprecatedRedirectException) {
$e = new RedirectException($e->getMessage(), $e->getCode(), $e);
}
$this->benchmark->stop('bootstrap');

$this->response = $e->getResponse();
} catch (PageNotFoundException $e) {
$this->response = $this->display404errors($e);
} catch (Throwable $e) {
$this->outputBufferingEnd();
$this->benchmark->start('required_before_filters');
// Start up the filters
$filters = Services::filters();
// Run required before filters
$possibleResponse = $this->runRequiredBeforeFilters($filters);

throw $e;
// If a ResponseInterface instance is returned then send it back to the client and stop
if ($possibleResponse instanceof ResponseInterface) {
$this->response = $possibleResponse;
} else {
try {
$this->forceSecureAccess();

$this->response = $this->handleRequest($routes, config(Cache::class), $returnResponse);
} catch (ResponsableInterface|DeprecatedRedirectException $e) {
$this->outputBufferingEnd();
if ($e instanceof DeprecatedRedirectException) {
$e = new RedirectException($e->getMessage(), $e->getCode(), $e);
}

$this->response = $e->getResponse();
} catch (PageNotFoundException $e) {
$this->response = $this->display404errors($e);
} catch (Throwable $e) {
$this->outputBufferingEnd();

throw $e;
}
}

$this->runRequiredAfterFilters($filters);

if ($returnResponse) {
return $this->response;
}

$this->sendResponse();

// Is there a post-system event?
Events::trigger('post_system');
}

/**
* Run required before filters.
*/
private function runRequiredBeforeFilters(Filters $filters): ?ResponseInterface
{
$possibleResponse = $filters->runRequired('before');
$this->benchmark->stop('required_before_filters');

// If a ResponseInterface instance is returned then send it back to the client and stop
if ($possibleResponse instanceof ResponseInterface) {
return $possibleResponse;
}

return null;
}

/**
* Run required after filters.
*/
private function runRequiredAfterFilters(Filters $filters): void
{
$filters->setResponse($this->response);

// After filter debug toolbar requires 'total_execution'.
$this->totalTime = $this->benchmark->getElapsedTime('total_execution');

// Run required after filters
$this->benchmark->start('required_after_filters');
$response = $filters->runRequired('after');
$this->benchmark->stop('required_after_filters');

if ($response instanceof ResponseInterface) {
$this->response = $response;
}
}

/**
Expand Down Expand Up @@ -404,20 +461,11 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
return $this->response->setStatusCode(405)->setBody('Method Not Allowed');
}

Events::trigger('pre_system');

// Check for a cached page. Execution will stop
// if the page has been cached.
if (($response = $this->displayCache($cacheConfig)) instanceof ResponseInterface) {
return $response;
}

$routeFilters = $this->tryToRouteIt($routes);

$uri = $this->request->getPath();

if ($this->enableFilters) {
// Start up the filters
$filters = Services::filters();

// If any filters were specified within the routes file,
Expand Down Expand Up @@ -477,9 +525,6 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
$filters = Services::filters();
$filters->setResponse($this->response);

// After filter debug toolbar requires 'total_execution'.
$this->totalTime = $this->benchmark->getElapsedTime('total_execution');

// Run "after" filters
$this->benchmark->start('after_filters');
$response = $filters->run($uri, 'after');
Expand All @@ -495,21 +540,13 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
! $this->response instanceof DownloadResponse
&& ! $this->response instanceof RedirectResponse
) {
// Cache it without the performance metrics replaced
// so that we can have live speed updates along the way.
// Must be run after filters to preserve the Response headers.
$this->pageCache->make($this->request, $this->response);

// Save our current URI as the previous URI in the session
// for safer, more accurate use with `previous_url()` helper function.
$this->storePreviousURL(current_url(true));
}

unset($uri);

// Is there a post-system event?
Events::trigger('post_system');

return $this->response;
}

Expand Down Expand Up @@ -667,6 +704,7 @@ protected function forceSecureAccess($duration = 31_536_000)
*
* @throws Exception
*
* @deprecated 4.5.0 PageCache required filter is used. No longer used.
* @deprecated 4.4.2 The parameter $config is deprecated. No longer used.
*/
public function displayCache(Cache $config)
Expand Down Expand Up @@ -749,6 +787,8 @@ protected function generateCacheName(Cache $config): string

/**
* Replaces the elapsed_time and memory_usage tag.
*
* @deprecated 4.5.0 PerformanceMetrics required filter is used. No longer used.
*/
public function displayPerformanceMetrics(string $output): string
{
Expand All @@ -773,6 +813,8 @@ public function displayPerformanceMetrics(string $output): string
*/
protected function tryToRouteIt(?RouteCollectionInterface $routes = null)
{
$this->benchmark->start('routing');

if ($routes === null) {
$routes = Services::routes()->loadRoutes();
}
Expand All @@ -782,9 +824,6 @@ protected function tryToRouteIt(?RouteCollectionInterface $routes = null)

$uri = $this->request->getPath();

$this->benchmark->stop('bootstrap');
$this->benchmark->start('routing');

$this->outputBufferingStart();

$this->controller = $this->router->handle($uri);
Expand Down Expand Up @@ -1051,13 +1090,6 @@ public function spoofRequestMethod()
*/
protected function sendResponse()
{
// Update the performance metrics
$body = $this->response->getBody();
if ($body !== null) {
$output = $this->displayPerformanceMetrics($body);
$this->response->setBody($output);
}

$this->response->send();
}

Expand Down
14 changes: 11 additions & 3 deletions system/Debug/Toolbar/Collectors/Routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,17 @@ public function display(): array
try {
$method = new ReflectionMethod($router->controllerName(), $router->methodName());
} catch (ReflectionException $e) {
// If we're here, the method doesn't exist
// and is likely calculated in _remap.
$method = new ReflectionMethod($router->controllerName(), '_remap');
try {
// If we're here, the method doesn't exist
// and is likely calculated in _remap.
$method = new ReflectionMethod($router->controllerName(), '_remap');
} catch (ReflectionException) {
// If we're here, page cache is returned. The router is not executed.
return [
'matchedRoute' => [],
'routes' => [],
];
}
}
}

Expand Down
90 changes: 89 additions & 1 deletion system/Filters/Filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,94 @@ public function run(string $uri, string $position = 'before')
return $position === 'before' ? $this->request : $this->response;
}

/**
* Runs required filters for the specified position.
*
* @return RequestInterface|ResponseInterface|string|null
*
* @throws FilterException
*/
public function runRequired(string $position = 'before')
{
// Set the toolbar filter to the last position to be executed
if (
in_array('toolbar', $this->config->required['after'], true)
&& ($count = count($this->config->required['after'])) > 1
&& $this->config->required['after'][$count - 1] !== 'toolbar'
) {
array_splice(
$this->config->required['after'],
array_search('toolbar', $this->config->required['after'], true),
1
);
$this->config->required['after'][] = 'toolbar';
}

$filtersClass = [];

foreach ($this->config->required[$position] as $alias) {
if (! array_key_exists($alias, $this->config->aliases)) {
throw FilterException::forNoAlias($alias);
}

if (is_array($this->config->aliases[$alias])) {
$filtersClass[$position] = array_merge($filtersClass[$position], $this->config->aliases[$alias]);
} else {
$filtersClass[$position][] = $this->config->aliases[$alias];
}
}

foreach ($filtersClass[$position] as $className) {
$class = new $className();

if (! $class instanceof FilterInterface) {
throw FilterException::forIncorrectInterface(get_class($class));
}

if ($position === 'before') {
$result = $class->before(
$this->request,
$this->argumentsClass[$className] ?? null
);

if ($result instanceof RequestInterface) {
$this->request = $result;

continue;
}

// If the response object was sent back,
// then send it and quit.
if ($result instanceof ResponseInterface) {
// short circuit - bypass any other filters
return $result;
}
// Ignore an empty result
if (empty($result)) {
continue;
}

return $result;
}

if ($position === 'after') {
$result = $class->after(
$this->request,
$this->response,
$this->argumentsClass[$className] ?? null
);

if ($result instanceof ResponseInterface) {
$this->response = $result;

continue;
}
}
}

return $position === 'before' ? $this->request : $this->response;
}

/**
* Runs through our list of filters provided by the configuration
* object to get them ready for use, including getting uri masks
Expand Down Expand Up @@ -650,7 +738,7 @@ protected function processAliasesToClass(string $position)
private function pathApplies(string $uri, $paths)
{
// empty path matches all
if (empty($paths)) {
if ($paths === '' || $paths === []) {
return true;
}

Expand Down

0 comments on commit d49ab26

Please sign in to comment.