[TASK] Directly wire Application and RequestHandler 42/55642/11
authorBenjamin Franzke <bfr@qbus.de>
Thu, 8 Feb 2018 23:40:18 +0000 (00:40 +0100)
committerSusanne Moog <susanne.moog@typo3.org>
Mon, 12 Feb 2018 20:45:43 +0000 (21:45 +0100)
The frontend and backend Application and RequestHandler classes
are tightly coupled since the frontend eID request handler was
moved into a middleware and the backend ajax request handler
was merged with the regular request handler. (1:1 relationship)

There is no (longer) need to resolve the request handler in
Bootstap. For the install application we are still using two
request handlers but will dispatch them from within the
application now.

That means the call chain is now:
 Application -> RequestHandler
instead of:
 Application -> Bootstrap -> RequestHandler

That allows us to deprecate all HTTP related code in Bootstrap (with
a separate commit) and instead implement that in an HTTP specific
ApplicationTrait.

This patch introduces a legacy RequestHandler dispatcher middleware
which ensures that registering custom request handlers using
Bootstrap::registerAdditionalRequestHandler() still works for the
frontend and backend.
(although it is marked @internal, there is interest to not just
drop this)

Change-Id: Id80158bb3d078719f6508ae950b2f32f018e1ac4
Releases: master
Resolves: #83864
Reviewed-on: https://review.typo3.org/55642
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
typo3/sysext/backend/Classes/Http/Application.php
typo3/sysext/backend/Configuration/RequestMiddlewares.php
typo3/sysext/core/Classes/Console/CommandApplication.php
typo3/sysext/core/Classes/Core/Bootstrap.php
typo3/sysext/core/Classes/Http/AbstractApplication.php [new file with mode: 0644]
typo3/sysext/core/Classes/Middleware/LegacyRequestHandlerDispatcher.php [new file with mode: 0644]
typo3/sysext/frontend/Classes/Http/Application.php
typo3/sysext/frontend/Configuration/RequestMiddlewares.php
typo3/sysext/install/Classes/Http/Application.php

index 935fd60..710bbc3 100644 (file)
@@ -1,4 +1,5 @@
 <?php
+declare(strict_types = 1);
 namespace TYPO3\CMS\Backend\Http;
 
 /*
@@ -13,15 +14,25 @@ namespace TYPO3\CMS\Backend\Http;
  *
  * The TYPO3 project - inspiring people to share!
  */
-use TYPO3\CMS\Core\Core\ApplicationInterface;
 use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Http\AbstractApplication;
 
 /**
  * Entry point for the TYPO3 Backend (HTTP requests)
  */
-class Application implements ApplicationInterface
+class Application extends AbstractApplication
 {
     /**
+     * @var string
+     */
+    protected $requestHandler = RequestHandler::class;
+
+    /**
+     * @var string
+     */
+    protected $middlewareStack = 'backend';
+
+    /**
      * @var Bootstrap
      */
     protected $bootstrap;
@@ -34,14 +45,6 @@ class Application implements ApplicationInterface
     protected $entryPointLevel = 1;
 
     /**
-     * All available request handlers that can handle backend requests (non-CLI)
-     * @var array
-     */
-    protected $availableRequestHandlers = [
-        \TYPO3\CMS\Backend\Http\RequestHandler::class
-    ];
-
-    /**
      * Constructor setting up legacy constant and register available Request Handlers
      *
      * @param \Composer\Autoload\ClassLoader $classLoader an instance of the class loader
@@ -60,30 +63,10 @@ class Application implements ApplicationInterface
             $this->bootstrap->redirectToInstallTool($this->entryPointLevel);
         }
 
-        foreach ($this->availableRequestHandlers as $requestHandler) {
-            $this->bootstrap->registerRequestHandlerImplementation($requestHandler);
-        }
-
         $this->bootstrap->configure();
     }
 
     /**
-     * Set up the application and shut it down afterwards
-     *
-     * @param callable $execute
-     */
-    public function run(callable $execute = null)
-    {
-        $this->bootstrap->handleRequest(\TYPO3\CMS\Core\Http\ServerRequestFactory::fromGlobals(), 'backend');
-
-        if ($execute !== null) {
-            call_user_func($execute);
-        }
-
-        $this->bootstrap->shutdown();
-    }
-
-    /**
      * Define constants and variables
      */
     protected function defineLegacyConstants()
index cfa2346..401cd94 100644 (file)
  */
 return [
     'backend' => [
+        'typo3/cms-core/legacy-request-handler-dispatcher' => [
+            'target' => \TYPO3\CMS\Core\Middleware\LegacyRequestHandlerDispatcher::class,
+        ],
         'typo3/cms-backend/locked-backend' => [
             'target' => \TYPO3\CMS\Backend\Middleware\LockedBackendGuard::class,
+            'after' => [
+                'typo3/cms-core/legacy-request-handler-dispatcher'
+            ],
         ],
         'typo3/cms-backend/https-redirector' => [
             'target' => \TYPO3\CMS\Backend\Middleware\ForcedHttpsBackendRedirector::class,
index 8cf5e99..22ee47e 100644 (file)
@@ -16,6 +16,7 @@ namespace TYPO3\CMS\Core\Console;
 use Symfony\Component\Console\Input\ArgvInput;
 use TYPO3\CMS\Core\Core\ApplicationInterface;
 use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Entry point for the TYPO3 Command Line for Commands
@@ -36,14 +37,6 @@ class CommandApplication implements ApplicationInterface
     protected $entryPointLevel = 4;
 
     /**
-     * All available request handlers that can deal with a CLI Request
-     * @var array
-     */
-    protected $availableRequestHandlers = [
-        \TYPO3\CMS\Core\Console\CommandRequestHandler::class,
-    ];
-
-    /**
      * Constructor setting up legacy constants and register available Request Handlers
      *
      * @param \Composer\Autoload\ClassLoader $classLoader an instance of the class loader
@@ -57,10 +50,6 @@ class CommandApplication implements ApplicationInterface
             ->setRequestType(TYPO3_REQUESTTYPE_CLI)
             ->baseSetup($this->entryPointLevel);
 
-        foreach ($this->availableRequestHandlers as $requestHandler) {
-            $this->bootstrap->registerRequestHandlerImplementation($requestHandler);
-        }
-
         $this->bootstrap->configure();
     }
 
@@ -71,13 +60,12 @@ class CommandApplication implements ApplicationInterface
      */
     public function run(callable $execute = null)
     {
-        $this->bootstrap->handleRequest(new ArgvInput());
+        $handler = GeneralUtility::makeInstance(CommandRequestHandler::class, $this->bootstrap);
+        $handler->handleRequest(new ArgvInput());
 
         if ($execute !== null) {
             call_user_func($execute);
         }
-
-        $this->bootstrap->shutdown();
     }
 
     /**
index ddad790..f470a46 100644 (file)
@@ -16,10 +16,6 @@ namespace TYPO3\CMS\Core\Core;
 
 use Doctrine\Common\Annotations\AnnotationReader;
 use Doctrine\Common\Annotations\AnnotationRegistry;
-use Psr\Http\Message\ServerRequestInterface;
-use Psr\Http\Server\RequestHandlerInterface;
-use TYPO3\CMS\Core\Http\MiddlewareDispatcher;
-use TYPO3\CMS\Core\Http\MiddlewareStackResolver;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
@@ -308,7 +304,7 @@ class Bootstrap
      * @throws \TYPO3\CMS\Core\Exception
      * @internal This is not a public API method, do not use in own extensions
      */
-    protected function resolveRequestHandler($request)
+    public function resolveRequestHandler($request)
     {
         $suitableRequestHandlers = [];
         foreach ($this->availableRequestHandlers as $requestHandlerClassName) {
@@ -334,32 +330,17 @@ class Bootstrap
      * through the request handlers depending on Frontend, Backend, CLI etc.
      *
      * @param \Psr\Http\Message\RequestInterface|\Symfony\Component\Console\Input\InputInterface $request
-     * @param string $middlewareStackName the name of the middleware, usually "frontend" or "backend" for TYPO3 applications
      * @return Bootstrap
      * @throws \TYPO3\CMS\Core\Exception
      * @internal This is not a public API method, do not use in own extensions
      */
-    public function handleRequest($request, string $middlewareStackName = null)
+    public function handleRequest($request)
     {
         // Resolve request handler that were registered based on the Application
         $requestHandler = $this->resolveRequestHandler($request);
 
-        // The application requested a middleware stack, and the request handler is PSR-15 capable.
-        // Fill the middleware dispatcher; enqueue the request handler as kernel for the middleware stack.
-        if ($request instanceof ServerRequestInterface && $requestHandler instanceof RequestHandlerInterface && $middlewareStackName !== null) {
-            $resolver = new MiddlewareStackResolver(
-                $this->getEarlyInstance(\TYPO3\CMS\Core\Package\PackageManager::class),
-                GeneralUtility::makeInstance(\TYPO3\CMS\Core\Service\DependencyOrderingService::class),
-                $this->getEarlyInstance(\TYPO3\CMS\Core\Cache\CacheManager::class)->getCache('cache_core')
-            );
-            $middlewares = $resolver->resolve($middlewareStackName);
-
-            $dispatcher = new MiddlewareDispatcher($requestHandler, $middlewares);
-            $this->response = $dispatcher->handle($request);
-        } else {
-            // Execute the command which returns a Response object or NULL
-            $this->response = $requestHandler->handleRequest($request);
-        }
+        // Execute the command which returns a Response object or NULL
+        $this->response = $requestHandler->handleRequest($request);
 
         return $this;
     }
diff --git a/typo3/sysext/core/Classes/Http/AbstractApplication.php b/typo3/sysext/core/Classes/Http/AbstractApplication.php
new file mode 100644 (file)
index 0000000..d51514f
--- /dev/null
@@ -0,0 +1,110 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Http;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use TYPO3\CMS\Core\Core\ApplicationInterface;
+use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * @internal
+ */
+class AbstractApplication implements ApplicationInterface
+{
+    /**
+     * @var string
+     */
+    protected $requestHandler = '';
+
+    /**
+     * @var string
+     */
+    protected $middlewareStack = '';
+
+    /**
+     * @param RequestHandlerInterface $requestHandler
+     * @return MiddlewareDispatcher
+     */
+    protected function createMiddlewareDispatcher(RequestHandlerInterface $requestHandler): MiddlewareDispatcher
+    {
+        $resolver = new MiddlewareStackResolver(
+            GeneralUtility::makeInstance(\TYPO3\CMS\Core\Package\PackageManager::class),
+            GeneralUtility::makeInstance(\TYPO3\CMS\Core\Service\DependencyOrderingService::class),
+            GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class)->getCache('cache_core')
+        );
+        $middlewares = $resolver->resolve($this->middlewareStack);
+
+        return new MiddlewareDispatcher($requestHandler, $middlewares);
+    }
+
+    /**
+     * Outputs content
+     *
+     * @param ResponseInterface $response
+     */
+    protected function sendResponse(ResponseInterface $response)
+    {
+        if ($response instanceof \TYPO3\CMS\Core\Http\NullResponse) {
+            return;
+        }
+
+        if (!headers_sent()) {
+            // If the response code was not changed by legacy code (still is 200)
+            // then allow the PSR-7 response object to explicitly set it.
+            // Otherwise let legacy code take precedence.
+            // This code path can be deprecated once we expose the response object to third party code
+            if (http_response_code() === 200) {
+                header('HTTP/' . $response->getProtocolVersion() . ' ' . $response->getStatusCode() . ' ' . $response->getReasonPhrase());
+            }
+
+            foreach ($response->getHeaders() as $name => $values) {
+                header($name . ': ' . implode(', ', $values));
+            }
+        }
+        echo $response->getBody()->__toString();
+    }
+
+    /**
+     * @param ServerRequestInterface $request
+     * @return ResponseInterface
+     */
+    protected function handle(ServerRequestInterface $request): ResponseInterface
+    {
+        $requestHandler = GeneralUtility::makeInstance($this->requestHandler, Bootstrap::getInstance());
+        $dispatcher = $this->createMiddlewareDispatcher($requestHandler);
+
+        return $dispatcher->handle($request);
+    }
+
+    /**
+     * Set up the application and shut it down afterwards
+     *
+     * @param callable $execute
+     */
+    public function run(callable $execute = null)
+    {
+        $response = $this->handle(\TYPO3\CMS\Core\Http\ServerRequestFactory::fromGlobals());
+
+        if ($execute !== null) {
+            call_user_func($execute);
+        }
+
+        $this->sendResponse($response);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Middleware/LegacyRequestHandlerDispatcher.php b/typo3/sysext/core/Classes/Middleware/LegacyRequestHandlerDispatcher.php
new file mode 100644 (file)
index 0000000..60840e4
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Middleware;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Exception;
+use TYPO3\CMS\Core\Http\NullResponse;
+
+/**
+ * Dispatch legacy request handlers; although
+ * Bootstrap::registerRequestHandlerImplementation() was always
+ * marked as internal quite some extensions use that method
+ * to register custom request handlers.
+ *
+ * @internal
+ */
+class LegacyRequestHandlerDispatcher implements MiddlewareInterface
+{
+    protected $bootstrap;
+
+    public function __construct()
+    {
+        $this->bootstrap = Bootstrap::getInstance();
+    }
+
+    /**
+     * @param ServerRequestInterface $request
+     * @param RequestHandlerInterface $handler
+     * @return ResponseInterface
+     */
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        $requestHandler = null;
+
+        try {
+            $requestHandler = $this->bootstrap->resolveRequestHandler($request);
+        } catch (Exception $e) {
+            // 'No suitable request handler found.'
+            if ($e->getCode() !== 1225418233) {
+                throw $e;
+            }
+        }
+
+        if ($requestHandler !== null) {
+            // @todo: E_USER_DEPRECATED
+            return $requestHandler->handleRequest($request) ?? new NullResponse();
+        }
+
+        return $handler->handle($request);
+    }
+}
index 33b1def..53fcbf7 100644 (file)
@@ -1,4 +1,5 @@
 <?php
+declare(strict_types = 1);
 namespace TYPO3\CMS\Frontend\Http;
 
 /*
@@ -14,15 +15,25 @@ namespace TYPO3\CMS\Frontend\Http;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Core\Core\ApplicationInterface;
 use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Http\AbstractApplication;
 
 /**
  * Entry point for the TYPO3 Frontend
  */
-class Application implements ApplicationInterface
+class Application extends AbstractApplication
 {
     /**
+     * @var string
+     */
+    protected $requestHandler = RequestHandler::class;
+
+    /**
+     * @var string
+     */
+    protected $middlewareStack = 'frontend';
+
+    /**
      * @var Bootstrap
      */
     protected $bootstrap;
@@ -35,14 +46,6 @@ class Application implements ApplicationInterface
     protected $entryPointLevel = 0;
 
     /**
-     * All available request handlers that can deal with a Frontend Request
-     * @var array
-     */
-    protected $availableRequestHandlers = [
-        \TYPO3\CMS\Frontend\Http\RequestHandler::class,
-    ];
-
-    /**
      * Constructor setting up legacy constant and register available Request Handlers
      *
      * @param \Composer\Autoload\ClassLoader $classLoader an instance of the class loader
@@ -61,30 +64,10 @@ class Application implements ApplicationInterface
             $this->bootstrap->redirectToInstallTool($this->entryPointLevel);
         }
 
-        foreach ($this->availableRequestHandlers as $requestHandler) {
-            $this->bootstrap->registerRequestHandlerImplementation($requestHandler);
-        }
-
         $this->bootstrap->configure();
     }
 
     /**
-     * Starting point
-     *
-     * @param callable $execute
-     */
-    public function run(callable $execute = null)
-    {
-        $this->bootstrap->handleRequest(\TYPO3\CMS\Core\Http\ServerRequestFactory::fromGlobals(), 'frontend');
-
-        if ($execute !== null) {
-            call_user_func($execute);
-        }
-
-        $this->bootstrap->shutdown();
-    }
-
-    /**
      * Define constants and variables
      */
     protected function defineLegacyConstants()
index 5d09c58..1808342 100644 (file)
  */
 return [
     'frontend' => [
+        'typo3/cms-core/legacy-request-handler-dispatcher' => [
+            'target' => \TYPO3\CMS\Core\Middleware\LegacyRequestHandlerDispatcher::class,
+        ],
         'typo3/cms-frontend/timetracker' => [
             'target' => \TYPO3\CMS\Frontend\Middleware\TimeTrackerInitialization::class,
+            'after' => [
+                'typo3/cms-core/legacy-request-handler-dispatcher'
+            ],
         ],
         'typo3/cms-frontend/preprocessing' => [
             'target' => \TYPO3\CMS\Frontend\Middleware\PreprocessRequestHook::class,
index 8057689..5648d2e 100644 (file)
@@ -13,14 +13,18 @@ namespace TYPO3\CMS\Install\Http;
  *
  * The TYPO3 project - inspiring people to share!
  */
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Core\Cache\Backend\NullBackend;
-use TYPO3\CMS\Core\Core\ApplicationInterface;
 use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Http\AbstractApplication;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Entry point for the TYPO3 Install Tool
  */
-class Application implements ApplicationInterface
+class Application extends AbstractApplication
 {
     /**
      * @var Bootstrap
@@ -56,10 +60,6 @@ class Application implements ApplicationInterface
             ->setRequestType(TYPO3_REQUESTTYPE_INSTALL)
             ->baseSetup($this->entryPointLevel);
 
-        foreach ($this->availableRequestHandlers as $requestHandler) {
-            $this->bootstrap->registerRequestHandlerImplementation($requestHandler);
-        }
-
         $this->bootstrap
             ->startOutputBuffering()
             ->loadConfigurationAndInitialize(false, \TYPO3\CMS\Core\Package\FailsafePackageManager::class);
@@ -68,21 +68,19 @@ class Application implements ApplicationInterface
     }
 
     /**
-     * Set up the application and shut it down afterwards
-     * Failsafe minimal setup mode for the install tool
-     * Does not call "run()" therefore
-     *
-     * @param callable $execute
+     * @param ServerRequestInterface $request
+     * @return ResponseInterface
      */
-    public function run(callable $execute = null)
+    protected function handle(ServerRequestInterface $request): ResponseInterface
     {
-        $this->bootstrap->handleRequest(\TYPO3\CMS\Core\Http\ServerRequestFactory::fromGlobals());
-
-        if ($execute !== null) {
-            call_user_func($execute);
+        foreach ($this->availableRequestHandlers as $requestHandler) {
+            $handler = GeneralUtility::makeInstance($requestHandler, $this->bootstrap);
+            if ($handler->canHandleRequest($request)) {
+                return $handler->handleRequest($request);
+            }
         }
 
-        $this->bootstrap->shutdown();
+        throw new \TYPO3\CMS\Core\Exception('No suitable request handler found.', 1518448686);
     }
 
     /**