API: Deprecate RequestFilter.

NEW: Allow application of HTTPMiddleware to Director.

Director can now use the same HTTPMiddleware objects as the app object.
They can be applied either globally or pre-rule.
This commit is contained in:
Sam Minnee 2017-06-23 11:17:30 +12:00 committed by Damian Mooyman
parent 26b9bf11ed
commit b30f410ea0
3 changed files with 98 additions and 34 deletions

View File

@ -2,6 +2,8 @@
Name: rootroutes
---
SilverStripe\Control\Director:
middlewares:
RequestProcessor: 'SilverStripe\Control\RequestProcessor'
rules:
'': SilverStripe\Control\Controller
---

View File

@ -131,24 +131,12 @@ class Director implements TemplateGlobalProvider
}
}
// Pre-request
$output = RequestProcessor::singleton()->preRequest($request);
if ($output === false) {
return new HTTPResponse(_t(__CLASS__.'.INVALID_REQUEST', 'Invalid request'), 400);
}
// Generate output
$result = static::handleRequest($request);
// Save session data. Note that save() will start/resume the session if required.
$request->getSession()->save();
// Post-request handling
$postRequest = RequestProcessor::singleton()->postRequest($request, $result);
if ($postRequest === false) {
return new HTTPResponse(_t(__CLASS__ . '.REQUEST_ABORTED', 'Request aborted'), 500);
}
// Return
return $result;
}
@ -351,6 +339,14 @@ class Director implements TemplateGlobalProvider
{
$rules = Director::config()->uninherited('rules');
// Get global middlewares
$middlewares = Director::config()->uninherited('middlewares') ?: [];
// Default handler - mo URL rules matched, so return a 404 error.
$handler = function () {
return new HTTPResponse('No URL rule was matched', 404);
};
foreach ($rules as $pattern => $controllerOptions) {
// Normalise route rule
if (is_string($controllerOptions)) {
@ -361,6 +357,18 @@ 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
$arguments = $request->match($pattern, true);
if ($arguments !== false) {
@ -373,28 +381,71 @@ class Director implements TemplateGlobalProvider
$request->shift($controllerOptions['_PopTokeniser']);
}
// Handle redirection
// Handler for redirection
if (isset($arguments['Redirect'])) {
// Redirection
$response = new HTTPResponse();
$response->redirect(static::absoluteURL($arguments['Redirect']));
return $response;
$handler = function () use ($arguments) {
// Redirection
$response = new HTTPResponse();
$response->redirect(static::absoluteURL($arguments['Redirect']));
return $response;
};
break;
}
// Find the controller name
$controller = $arguments['Controller'];
$controllerObj = Injector::inst()->create($controller);
try {
return $controllerObj->handleRequest($request);
} catch (HTTPResponse_Exception $responseException) {
return $responseException->getResponse();
}
// Handler for calling a controller
$handler = function ($request) use ($controllerObj) {
try {
return $controllerObj->handleRequest($request);
} catch (HTTPResponse_Exception $responseException) {
return $responseException->getResponse();
}
};
break;
}
}
// No URL rules matched, so return a 404 error.
return new HTTPResponse('No URL rule was matched', 404);
// Call the handler with the given middlewares
return self::callWithMiddlewares(
$request,
$middlewares,
$handler
);
}
/**
* Call the given request handler with the given middlewares
* Middlewares are specified as Injector service names
*
* @param $request The request to pass to the handler
* @param $middlewareNames The services names of the middlewares to apply
* @param $handler The request handler
*/
protected static function callWithMiddlewares(HTTPRequest $request, array $middlewareNames, callable $handler)
{
$next = $handler;
if ($middlewareNames) {
$middlewares = array_map(
function ($name) {
return Injector::inst()->get($name);
},
$middlewareNames
);
// Reverse middlewares
/** @var HTTPMiddleware $middleware */
foreach (array_reverse($middlewares) as $middleware) {
$next = function ($request) use ($middleware, $next) {
return $middleware->process($request, $next);
};
}
}
return $next($request);
}
/**

View File

@ -3,11 +3,13 @@
namespace SilverStripe\Control;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Dev\Deprecation;
/**
* Represents a request processer that delegates pre and post request handling to nested request filters
* Middleware that provides back-support for the deprecated RequestFilter API.
* You should use HTTPMiddleware directly instead.
*/
class RequestProcessor implements RequestFilter
class RequestProcessor implements HTTPMiddleware
{
use Injectable;
@ -33,25 +35,34 @@ class RequestProcessor implements RequestFilter
$this->filters = $filters;
}
public function preRequest(HTTPRequest $request)
/**
* @inheritdoc
*/
public function process(HTTPRequest $request, callable $delegate)
{
if ($this->filters) {
Deprecation::notice(
'4.0',
'Deprecated RequestFilters are in use. Apply HTTPMiddleware to Director.middlewares instead.'
);
}
foreach ($this->filters as $filter) {
$res = $filter->preRequest($request);
if ($res === false) {
return false;
return new HTTPResponse(_t(__CLASS__.'.INVALID_REQUEST', 'Invalid request'), 400);
}
}
return null;
}
public function postRequest(HTTPRequest $request, HTTPResponse $response)
{
$response = $delegate($request);
foreach ($this->filters as $filter) {
$res = $filter->postRequest($request, $response);
if ($res === false) {
return false;
return new HTTPResponse(_t(__CLASS__ . '.REQUEST_ABORTED', 'Request aborted'), 500);
}
}
return null;
return $response;
}
}