Commit fee75b26 authored by Benni Mack's avatar Benni Mack
Browse files

[!!!][TASK] Enable internal subrequests for error pages

With TYPO3 v11, a new feature flag "subrequestPageErrors"
was added to avoid external requests when loading error pages
such as the 404 page. This feature is now always enabled.

Resolves: #98396
Related: #94402
Releases: main
Change-Id: I3fa5963bd1b33ab543a0f767f20282b3fbb9ca85
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/75796


Tested-by: Andreas Fernandez's avatarAndreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Andreas Fernandez's avatarAndreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent 088b745d
......@@ -61,6 +61,7 @@ class Features
*/
protected array $alwaysActiveFeatures = [
// Enabled since v12.0 at any time.
'subrequestPageErrors',
'security.frontend.htmlSanitizeParseFuncDefault',
'runtimeDbQuotingOfTcaConfiguration',
// Enabled since v11.0 at any time.
......
......@@ -20,14 +20,8 @@ namespace TYPO3\CMS\Core\Error\PageErrorHandler;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
use TYPO3\CMS\Core\Configuration\Features;
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
use TYPO3\CMS\Core\Http\HtmlResponse;
use TYPO3\CMS\Core\Http\RequestFactory;
use TYPO3\CMS\Core\Http\Response;
use TYPO3\CMS\Core\Http\Stream;
use TYPO3\CMS\Core\Http\Uri;
use TYPO3\CMS\Core\LinkHandling\LinkService;
use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
......@@ -44,25 +38,13 @@ use TYPO3\CMS\Frontend\Http\Application;
class PageContentErrorHandler implements PageErrorHandlerInterface
{
protected int $statusCode;
protected array $errorHandlerConfiguration;
protected int $pageUid = 0;
protected Application $application;
protected RequestFactory $requestFactory;
protected ResponseFactoryInterface $responseFactory;
protected SiteFinder $siteFinder;
protected LinkService $link;
protected FrontendInterface $cache;
protected bool $useSubrequest;
/**
* PageContentErrorHandler constructor.
* @param int $statusCode
......@@ -80,12 +62,9 @@ class PageContentErrorHandler implements PageErrorHandlerInterface
// @todo Convert this to DI once this class can be injected properly.
$container = GeneralUtility::getContainer();
$this->application = $container->get(Application::class);
$this->requestFactory = $container->get(RequestFactory::class);
$this->responseFactory = $container->get(ResponseFactoryInterface::class);
$this->siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
$this->link = $container->get(LinkService::class);
$this->cache = $container->get(CacheManager::class)->getCache('pages');
$this->useSubrequest = GeneralUtility::makeInstance(Features::class)->isFeatureEnabled('subrequestPageErrors');
}
public function handlePageError(ServerRequestInterface $request, string $message, array $reasons = []): ResponseInterface
......@@ -102,17 +81,9 @@ class PageContentErrorHandler implements PageErrorHandlerInterface
$this->statusCode
);
}
if ($this->useSubrequest) {
// Create a subrequest and do not take any special query parameters into account
$subRequest = $request->withQueryParams([])->withUri(new Uri($resolvedUrl))->withMethod('GET');
$subResponse = $this->stashEnvironment(fn (): ResponseInterface => $this->sendSubRequest($subRequest, $urlParams['pageuid']));
} else {
try {
$subResponse = $this->cachePageRequest($resolvedUrl, $this->pageUid, fn () => $this->sendRawRequest($resolvedUrl));
} catch (\Exception $e) {
throw new \RuntimeException(sprintf('Error handler could not fetch error page "%s", reason: %s', $resolvedUrl, $e->getMessage()), 1544172838, $e);
}
}
// Create a subrequest and do not take any special query parameters into account
$subRequest = $request->withQueryParams([])->withUri(new Uri($resolvedUrl))->withMethod('GET');
$subResponse = $this->stashEnvironment(fn (): ResponseInterface => $this->sendSubRequest($subRequest, $urlParams['pageuid']));
if ($subResponse->getStatusCode() >= 300) {
throw new \RuntimeException(sprintf('Error handler could not fetch error page "%s", status code: %s', $resolvedUrl, $subResponse->getStatusCode()), 1544172839);
......@@ -127,7 +98,7 @@ class PageContentErrorHandler implements PageErrorHandlerInterface
}
/**
* Stash and restore portions of the global environment around a subreqest callable.
* Stash and restore portions of the global environment around a subrequest callable.
*/
protected function stashEnvironment(callable $fetcher): ResponseInterface
{
......@@ -141,54 +112,6 @@ class PageContentErrorHandler implements PageErrorHandlerInterface
return $result;
}
/**
* Caches a subrequest fetch.
*/
protected function cachePageRequest(string $resolvedUrl, int $pageId, callable $fetcher): ResponseInterface
{
$cacheIdentifier = 'errorPage_' . md5($resolvedUrl);
$responseData = $this->cache->get($cacheIdentifier);
if (!is_array($responseData)) {
/** @var ResponseInterface $response */
$response = $fetcher();
$cacheTags = [];
if ($response->getStatusCode() === 200) {
$cacheTags[] = 'errorPage';
if ($pageId > 0) {
// Cache Tag "pageId_" ensures, cache is purged when content of 404 page changes
$cacheTags[] = 'pageId_' . $pageId;
}
$responseData = [
'headers' => $response->getHeaders(),
'body' => $response->getBody()->getContents(),
'reasonPhrase' => $response->getReasonPhrase(),
];
$this->cache->set($cacheIdentifier, $responseData, $cacheTags);
}
} else {
$body = new Stream('php://temp', 'wb+');
$body->write($responseData['body'] ?? '');
$body->rewind();
$response = new Response(
$body,
200,
$responseData['headers'] ?? [],
$responseData['reasonPhrase'] ?? ''
);
}
return $response;
}
/**
* Sends a full HTTP request to the specified URL.
*/
protected function sendRawRequest(string $resolvedUrl): ResponseInterface
{
return $this->requestFactory->request($resolvedUrl, 'GET', $this->getSubRequestOptions());
}
/**
* Sends an in-process subrequest.
*
......@@ -196,7 +119,7 @@ class PageContentErrorHandler implements PageErrorHandlerInterface
*/
protected function sendSubRequest(ServerRequestInterface $request, int $pageId): ResponseInterface
{
$site = $request->getAttribute('site', null);
$site = $request->getAttribute('site');
if (!$site instanceof Site) {
$site = $this->siteFinder->getSiteByPageId($pageId);
$request = $request->withAttribute('site', $site);
......@@ -205,22 +128,6 @@ class PageContentErrorHandler implements PageErrorHandlerInterface
return $this->application->handle($request);
}
/**
* Returns request options for the subrequest
*
* @return array|int[]
*/
protected function getSubRequestOptions(): array
{
$options = [];
if ((int)$GLOBALS['TYPO3_CONF_VARS']['HTTP']['timeout'] === 0) {
$options = [
'timeout' => 30,
];
}
return $options;
}
/**
* Resolve the URL (currently only page and external URL are supported)
*/
......
......@@ -74,7 +74,6 @@ return [
'security.backend.htmlSanitizeRte' => false,
'security.backend.enforceReferrer' => true,
'yamlImportsFollowDeclarationOrder' => false,
'subrequestPageErrors' => false,
],
'createGroup' => '',
'sitename' => 'TYPO3',
......
......@@ -193,9 +193,6 @@ SYS:
type: container
description: 'New features of TYPO3 that are activated on new installations but upgrading installations can still use the old behaviour'
items:
subrequestPageErrors:
type: bool
description: 'If on, error pages in the Frontend (such as 404 pages) will be requested internally via the TYPO3 Frontend instead of using an external HTTP request. It may be enabled on an experimental basis, as there are some cases where stateful information is not correctly reset for the subrequest. Disable this feature if you have trouble with stateful services or when some custom extensions overriding PHPs global variables.'
redirects.hitCount:
type: bool
description: 'If on, and if extension "redirects" is loaded, each performed redirect is counted and last hit time is logged to the database.'
......
......@@ -345,6 +345,7 @@ The following single field configurations have been removed from :php:`$GLOBALS[
The following features are now always enabled:
- `runtimeDbQuotingOfTcaConfiguration`
- `subrequestPageErrors`
The following features have been removed:
......
......@@ -30,14 +30,10 @@ use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\ResponseContent;
*/
class SlugSiteRequestTest extends AbstractTestCase
{
// Force subrequest-based errors ON, because some tests can't work otherwise.
protected array $configurationToUseInTestInstance = [
'SYS' => [
'devIPmask' => '123.123.123.123',
'encryptionKey' => '4408d27a916d51e624b69af3554f516dbab61037a9f7b9fd6f81b4d3bedeccb6',
'features' => [
'subrequestPageErrors' => true,
],
],
'FE' => [
'cacheHash' => [
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment