API: Director::handleRequest() is no longer static - use a Director service

NEW: Add HTMLMiddlewareAware trait to HTTPApplication, Director, and RequestHandler
NEW: Allow service specs to be passed to Director rules.

This refactor of the controller middlewares takes a service definition
approach rather than a static-method-and-config approach that Director
historically had.

The use of a trait for middleware means that the Middlewares array
property can be defined on RequestHandler, Director, and HTTPApplication
objects in the same way.
This commit is contained in:
Sam Minnee 2017-06-25 18:03:03 +12:00 committed by Damian Mooyman
parent e92c63c545
commit 69fe166897
6 changed files with 104 additions and 90 deletions

View File

@ -1,16 +1,16 @@
--- ---
Name: requestprocessors Name: requestprocessors
--- ---
SilverStripe\Core\Injector\Injector:
SilverStripe\Control\Director: SilverStripe\Control\Director:
middlewares: properties:
Middlewares:
TrustedProxyMiddleware: '%$SilverStripe\Control\TrustedProxyMiddleware' TrustedProxyMiddleware: '%$SilverStripe\Control\TrustedProxyMiddleware'
AllowedHostsMiddleware: '%$SilverStripe\Control\AllowedHostsMiddleware' AllowedHostsMiddleware: '%$SilverStripe\Control\AllowedHostsMiddleware'
SessionMiddleware: 'SilverStripe\Control\SessionMiddleware' SessionMiddleware: '%$SilverStripe\Control\SessionMiddleware'
RequestProcessor: 'SilverStripe\Control\RequestProcessor' RequestProcessor: '%$SilverStripe\Control\RequestProcessor'
FlushMiddleware: '%$SilverStripe\Control\FlushMiddleware' FlushMiddleware: '%$SilverStripe\Control\FlushMiddleware'
SilverStripe\Core\Injector\Injector:
SilverStripe\Control\AllowedHostsMiddleware: SilverStripe\Control\AllowedHostsMiddleware:
properties: properties:
AllowedHosts: "`SS_ALLOWED_HOSTS`" AllowedHosts: "`SS_ALLOWED_HOSTS`"

View File

@ -19,10 +19,11 @@ SilverStripe\Core\Injector\Injector:
--- ---
Name: coresecurity Name: coresecurity
--- ---
SilverStripe\Control\Director:
middlewares:
- %$SilverStripe\Security\AuthenticationMiddleware
SilverStripe\Core\Injector\Injector: SilverStripe\Core\Injector\Injector:
SilverStripe\Control\Director:
properties:
Middlewares:
- %$SilverStripe\Security\AuthenticationMiddleware
SilverStripe\Security\AuthenticationMiddleware: SilverStripe\Security\AuthenticationMiddleware:
properties: properties:
AuthenticationHandler: %$SilverStripe\Security\AuthenticationHandler AuthenticationHandler: %$SilverStripe\Security\AuthenticationHandler

View File

@ -29,6 +29,7 @@ use SilverStripe\View\TemplateGlobalProvider;
class Director implements TemplateGlobalProvider class Director implements TemplateGlobalProvider
{ {
use Configurable; use Configurable;
use HTTPMiddlewareAware;
/** /**
* Specifies this url is relative to the base. * Specifies this url is relative to the base.
@ -132,7 +133,7 @@ class Director implements TemplateGlobalProvider
) { ) {
return static::mockRequest( return static::mockRequest(
function (HTTPRequest $request) { function (HTTPRequest $request) {
return static::handleRequest($request); return Injector::inst()->get(Director::class)->handleRequest($request);
}, },
$url, $url,
$postVars, $postVars,
@ -302,15 +303,12 @@ class Director implements TemplateGlobalProvider
* @return HTTPResponse * @return HTTPResponse
* @throws HTTPResponse_Exception * @throws HTTPResponse_Exception
*/ */
public static function handleRequest(HTTPRequest $request) public function handleRequest(HTTPRequest $request)
{ {
Injector::inst()->registerService($request, HTTPRequest::class); Injector::inst()->registerService($request, HTTPRequest::class);
$rules = Director::config()->uninherited('rules'); $rules = Director::config()->uninherited('rules');
// Get global middlewares
$middlewares = Director::config()->uninherited('middlewares') ?: [];
// Default handler - mo URL rules matched, so return a 404 error. // Default handler - mo URL rules matched, so return a 404 error.
$handler = function () { $handler = function () {
return new HTTPResponse('No URL rule was matched', 404); return new HTTPResponse('No URL rule was matched', 404);
@ -326,18 +324,6 @@ class Director implements TemplateGlobalProvider
} }
} }
// Add controller-specific middlewares
if (isset($controllerOptions['Middlewares'])) {
// Force to array
if (!is_array($controllerOptions['Middlewares'])) {
$controllerOptions['Middlewares'] = [$controllerOptions['Middlewares']];
}
$middlewares = array_merge($middlewares, $controllerOptions['Middlewares']);
}
// Remove null middlewares (may be included due to limitatons of config yml)
$middlewares = array_filter($middlewares);
// Match pattern // Match pattern
$arguments = $request->match($pattern, true); $arguments = $request->match($pattern, true);
if ($arguments !== false) { if ($arguments !== false) {
@ -363,12 +349,25 @@ class Director implements TemplateGlobalProvider
// Find the controller name // Find the controller name
$controller = $arguments['Controller']; $controller = $arguments['Controller'];
$controllerObj = Injector::inst()->create($controller);
// String = service name
if (is_string($controller)) {
$controllerObj = Injector::inst()->get($controller);
// Array = service spec
} elseif (is_array($controller)) {
$controllerObj = Injector::inst()->createFromSpec($controller);
} else {
throw new \LogicException("Invalid Controller value '$controller'");
}
// Handler for calling a controller // Handler for calling a controller
$handler = function ($request) use ($controllerObj) { $handler = function ($request) use ($controllerObj) {
try { try {
// Apply the controller's middleware. We do this outside of handleRequest so that
// subclasses of handleRequest will be called after the middlware processing
return $controllerObj->callMiddleware($request, function ($request) use ($controllerObj) {
return $controllerObj->handleRequest($request); return $controllerObj->handleRequest($request);
});
} catch (HTTPResponse_Exception $responseException) { } catch (HTTPResponse_Exception $responseException) {
return $responseException->getResponse(); return $responseException->getResponse();
} }
@ -377,12 +376,8 @@ class Director implements TemplateGlobalProvider
} }
} }
// Call the handler with the given middlewares // Call the handler with the configured middlewares
$response = self::callWithMiddlewares( $response = $this->callMiddleware($request, $handler);
$request,
$middlewares,
$handler
);
// Note that if a different request was previously registered, this will now be lost // Note that if a different request was previously registered, this will now be lost
// In these cases it's better to use Kernel::nest() prior to kicking off a nested request // In these cases it's better to use Kernel::nest() prior to kicking off a nested request

View File

@ -3,6 +3,7 @@
namespace SilverStripe\Control; namespace SilverStripe\Control;
use SilverStripe\Core\Application; use SilverStripe\Core\Application;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Control\HTTPMiddleware; use SilverStripe\Control\HTTPMiddleware;
use SilverStripe\Core\Kernel; use SilverStripe\Core\Kernel;
@ -11,10 +12,8 @@ use SilverStripe\Core\Kernel;
*/ */
class HTTPApplication implements Application class HTTPApplication implements Application
{ {
/**
* @var HTTPMiddleware[] use HTTPMiddlewareAware;
*/
protected $middlewares = [];
/** /**
* @var Kernel * @var Kernel
@ -26,54 +25,6 @@ class HTTPApplication implements Application
$this->kernel = $kernel; $this->kernel = $kernel;
} }
/**
* @return HTTPMiddleware[]
*/
public function getMiddlewares()
{
return $this->middlewares;
}
/**
* @param HTTPMiddleware[] $middlewares
* @return $this
*/
public function setMiddlewares($middlewares)
{
$this->middlewares = $middlewares;
return $this;
}
/**
* @param HTTPMiddleware $middleware
* @return $this
*/
public function addMiddleware(HTTPMiddleware $middleware)
{
$this->middlewares[] = $middleware;
return $this;
}
/**
* Call middleware
*
* @param HTTPRequest $request
* @param callable $last Last config to call
* @return HTTPResponse
*/
protected function callMiddleware(HTTPRequest $request, $last)
{
// Reverse middlewares
$next = $last;
/** @var HTTPMiddleware $middleware */
foreach (array_reverse($this->getMiddlewares()) as $middleware) {
$next = function ($request) use ($middleware, $next) {
return $middleware->process($request, $next);
};
}
return call_user_func($next, $request);
}
/** /**
* Get the kernel for this application * Get the kernel for this application
* *
@ -96,7 +47,7 @@ class HTTPApplication implements Application
// Ensure boot is invoked // Ensure boot is invoked
return $this->execute($request, function (HTTPRequest $request) { return $this->execute($request, function (HTTPRequest $request) {
return Director::handleRequest($request); return Injector::inst()->get(Director::class)->handleRequest($request);
}, $flush); }, $flush);
} }

View File

@ -0,0 +1,64 @@
<?php
namespace SilverStripe\Control;
/**
* Adds middleware support to an object.
* Provides a Middlewares property and a callMiddleware() callback
*/
trait HTTPMiddlewareAware
{
/**
* @var HTTPMiddleware[]
*/
protected $middlewares = [];
/**
* @return HTTPMiddleware[]
*/
public function getMiddlewares()
{
return $this->middlewares;
}
/**
* @param HTTPMiddleware[] $middlewares
* @return $this
*/
public function setMiddlewares($middlewares)
{
// Allow nulls in the middlewares array to deal with limitations of yml config
$this->middlewares = array_filter((array)$middlewares);
return $this;
}
/**
* @param HTTPMiddleware $middleware
* @return $this
*/
public function addMiddleware(HTTPMiddleware $middleware)
{
$this->middlewares[] = $middleware;
return $this;
}
/**
* Call middleware
*
* @param $request The request to pass to the middlewares and callback
* @param $last The callback to call after all middlewares
* @return HTTPResponse
*/
public function callMiddleware(HTTPRequest $request, callable $last)
{
// Reverse middlewares
$next = $last;
/** @var HTTPMiddleware $middleware */
foreach (array_reverse($this->getMiddlewares()) as $middleware) {
$next = function ($request) use ($middleware, $next) {
return $middleware->process($request, $next);
};
}
return $next($request);
}
}

View File

@ -46,6 +46,9 @@ use BadMethodCallException;
*/ */
class RequestHandler extends ViewableData class RequestHandler extends ViewableData
{ {
use HTTPMiddlewareAware;
/** /**
* Optional url_segment for this request handler * Optional url_segment for this request handler
* *