API Create HTTPMiddleware and standardise middleware for request handling

This commit is contained in:
Damian Mooyman 2017-06-19 18:07:53 +12:00
parent 2a10c2397b
commit bba9791146
8 changed files with 58 additions and 67 deletions

View File

@ -15,5 +15,5 @@ $request = HTTPRequest::createFromEnvironment();
$kernel = new AppKernel(); $kernel = new AppKernel();
$app = new HTTPApplication($kernel); $app = new HTTPApplication($kernel);
$app->addMiddleware(new OutputMiddleware()); $app->addMiddleware(new OutputMiddleware());
$app->addMiddleware(new ErrorControlChainMiddleware($app, $request)); $app->addMiddleware(new ErrorControlChainMiddleware($app));
$app->handle($request); $app->handle($request);

View File

@ -316,7 +316,7 @@ class HTTPRequest implements ArrayAccess
} }
// Initiate an empty session - doesn't initialize an actual PHP session (see HTTPApplication) // Initiate an empty session - doesn't initialize an actual PHP session (see HTTPApplication)
$session = new Session($variables['_SESSION']); $session = new Session(isset($variables['_SESSION']) ? $variables['_SESSION'] : null);
$request->setSession($session); $request->setSession($session);
return $request; return $request;

View File

@ -13,13 +13,4 @@ interface Application
* @return Kernel * @return Kernel
*/ */
public function getKernel(); public function getKernel();
/**
* Invoke the application control chain
*
* @param callable $callback
* @param bool $flush
* @return mixed
*/
public function execute(callable $callback, $flush = false);
} }

View File

@ -12,12 +12,12 @@ use SilverStripe\Control\HTTPResponse;
class HTTPApplication implements Application class HTTPApplication implements Application
{ {
/** /**
* @var callable[] * @var HTTPMiddleware[]
*/ */
protected $middlewares = []; protected $middlewares = [];
/** /**
* @return callable[] * @return HTTPMiddleware[]
*/ */
public function getMiddlewares() public function getMiddlewares()
{ {
@ -25,7 +25,7 @@ class HTTPApplication implements Application
} }
/** /**
* @param callable[] $middlewares * @param HTTPMiddleware[] $middlewares
* @return $this * @return $this
*/ */
public function setMiddlewares($middlewares) public function setMiddlewares($middlewares)
@ -35,10 +35,10 @@ class HTTPApplication implements Application
} }
/** /**
* @param callable $middleware * @param HTTPMiddleware $middleware
* @return $this * @return $this
*/ */
public function addMiddleware($middleware) public function addMiddleware(HTTPMiddleware $middleware)
{ {
$this->middlewares[] = $middleware; $this->middlewares[] = $middleware;
return $this; return $this;
@ -47,19 +47,21 @@ class HTTPApplication implements Application
/** /**
* Call middleware * Call middleware
* *
* @param HTTPRequest $request
* @param callable $last Last config to call * @param callable $last Last config to call
* @return HTTPResponse * @return HTTPResponse
*/ */
protected function callMiddleware($last) protected function callMiddleware(HTTPRequest $request, $last)
{ {
// Reverse middlewares // Reverse middlewares
$next = $last; $next = $last;
/** @var HTTPMiddleware $middleware */
foreach (array_reverse($this->getMiddlewares()) as $middleware) { foreach (array_reverse($this->getMiddlewares()) as $middleware) {
$next = function () use ($middleware, $next) { $next = function ($request) use ($middleware, $next) {
return call_user_func($middleware, $next); return $middleware->process($request, $next);
}; };
} }
return call_user_func($next); return call_user_func($next, $request);
} }
/** /**
@ -93,7 +95,7 @@ class HTTPApplication implements Application
$flush = $request->getVar('flush') || strpos($request->getURL(), 'dev/build') === 0; $flush = $request->getVar('flush') || strpos($request->getURL(), 'dev/build') === 0;
// Ensure boot is invoked // Ensure boot is invoked
return $this->execute(function () use ($request) { return $this->execute($request, function (HTTPRequest $request) {
// Start session and execute // Start session and execute
$request->getSession()->init(); $request->getSession()->init();
return Director::direct($request); return Director::direct($request);
@ -103,17 +105,18 @@ class HTTPApplication implements Application
/** /**
* Safely boot the application and execute the given main action * Safely boot the application and execute the given main action
* *
* @param HTTPRequest $request
* @param callable $callback * @param callable $callback
* @param bool $flush * @param bool $flush
* @return HTTPResponse * @return HTTPResponse
*/ */
public function execute(callable $callback, $flush = false) public function execute(HTTPRequest $request, callable $callback, $flush = false)
{ {
try { try {
return $this->callMiddleware(function () use ($callback, $flush) { return $this->callMiddleware($request, function ($request) use ($callback, $flush) {
// Pre-request boot // Pre-request boot
$this->getKernel()->boot($flush); $this->getKernel()->boot($flush);
return call_user_func($callback); return call_user_func($callback, $request);
}); });
} finally { } finally {
$this->getKernel()->shutdown(); $this->getKernel()->shutdown();

View File

@ -0,0 +1,22 @@
<?php
namespace SilverStripe\Core;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
/**
* HTTP Request middleware
* Based on https://github.com/php-fig/fig-standards/blob/master/proposed/http-middleware/middleware.md#21-psrhttpservermiddlewaremiddlewareinterface
*/
interface HTTPMiddleware
{
/**
* Generate response for the given request
*
* @param HTTPRequest $request
* @param callable $delegate
* @return HTTPResponse
*/
public function process(HTTPRequest $request, callable $delegate);
}

View File

@ -7,52 +7,42 @@ use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse; use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\HTTPResponse_Exception; use SilverStripe\Control\HTTPResponse_Exception;
use SilverStripe\Core\Application; use SilverStripe\Core\Application;
use SilverStripe\Core\HTTPMiddleware;
use SilverStripe\Security\Permission; use SilverStripe\Security\Permission;
use SilverStripe\Security\Security; use SilverStripe\Security\Security;
/** /**
* Decorates application bootstrapping with errorcontrolchain * Decorates application bootstrapping with errorcontrolchain
*/ */
class ErrorControlChainMiddleware class ErrorControlChainMiddleware implements HTTPMiddleware
{ {
/** /**
* @var Application * @var Application
*/ */
protected $application = null; protected $application = null;
/**
* @var HTTPRequest
*/
protected $request = null;
/** /**
* Build error control chain for an application * Build error control chain for an application
* *
* @param Application $application * @param Application $application
* @param HTTPRequest $request
*/ */
public function __construct(Application $application, HTTPRequest $request) public function __construct(Application $application)
{ {
$this->application = $application; $this->application = $application;
$this->request = $request;
} }
/** public function process(HTTPRequest $request, callable $next)
* @param callable $next
* @return HTTPResponse
*/
public function __invoke(callable $next)
{ {
$result = null; $result = null;
// Prepare tokens and execute chain // Prepare tokens and execute chain
$reloadToken = ParameterConfirmationToken::prepare_tokens( $reloadToken = ParameterConfirmationToken::prepare_tokens(
['isTest', 'isDev', 'flush'], ['isTest', 'isDev', 'flush'],
$this->getRequest() $request
); );
$chain = new ErrorControlChain(); $chain = new ErrorControlChain();
$chain $chain
->then(function () use ($chain, $reloadToken, $next, &$result) { ->then(function () use ($request, $chain, $reloadToken, $next, &$result) {
// If no redirection is necessary then we can disable error supression // If no redirection is necessary then we can disable error supression
if (!$reloadToken) { if (!$reloadToken) {
$chain->setSuppression(false); $chain->setSuppression(false);
@ -61,10 +51,10 @@ class ErrorControlChainMiddleware
try { try {
// Check if a token is requesting a redirect // Check if a token is requesting a redirect
if ($reloadToken) { if ($reloadToken) {
$result = $this->safeReloadWithToken($reloadToken); $result = $this->safeReloadWithToken($request, $reloadToken);
} else { } else {
// If no reload necessary, process application // If no reload necessary, process application
$result = call_user_func($next); $result = call_user_func($next, $request);
} }
} catch (HTTPResponse_Exception $exception) { } catch (HTTPResponse_Exception $exception) {
$result = $exception->getResponse(); $result = $exception->getResponse();
@ -84,16 +74,17 @@ class ErrorControlChainMiddleware
* Reload application with the given token, but only if either the user is authenticated, * Reload application with the given token, but only if either the user is authenticated,
* or authentication is impossible. * or authentication is impossible.
* *
* @param HTTPRequest $request
* @param ParameterConfirmationToken $reloadToken * @param ParameterConfirmationToken $reloadToken
* @return HTTPResponse * @return HTTPResponse
*/ */
protected function safeReloadWithToken($reloadToken) protected function safeReloadWithToken(HTTPRequest $request, $reloadToken)
{ {
// Safe reload requires manual boot // Safe reload requires manual boot
$this->getApplication()->getKernel()->boot(false); $this->getApplication()->getKernel()->boot(false);
// Ensure session is started // Ensure session is started
$this->getRequest()->getSession()->init(); $request->getSession()->init();
// Next, check if we're in dev mode, or the database doesn't have any security data, or we are admin // Next, check if we're in dev mode, or the database doesn't have any security data, or we are admin
if (Director::isDev() || !Security::database_is_ready() || Permission::check('ADMIN')) { if (Director::isDev() || !Security::database_is_ready() || Permission::check('ADMIN')) {
@ -102,7 +93,7 @@ class ErrorControlChainMiddleware
// Fail and redirect the user to the login page // Fail and redirect the user to the login page
$loginPage = Director::absoluteURL(Security::config()->get('login_url')); $loginPage = Director::absoluteURL(Security::config()->get('login_url'));
$loginPage .= "?BackURL=" . urlencode($this->getRequest()->getURL()); $loginPage .= "?BackURL=" . urlencode($request->getURL());
$result = new HTTPResponse(); $result = new HTTPResponse();
$result->redirect($loginPage); $result->redirect($loginPage);
return $result; return $result;
@ -125,22 +116,4 @@ class ErrorControlChainMiddleware
$this->application = $application; $this->application = $application;
return $this; return $this;
} }
/**
* @return HTTPRequest
*/
public function getRequest()
{
return $this->request;
}
/**
* @param HTTPRequest $request
* @return $this
*/
public function setRequest(HTTPRequest $request)
{
$this->request = $request;
return $this;
}
} }

View File

@ -2,13 +2,15 @@
namespace SilverStripe\Core\Startup; namespace SilverStripe\Core\Startup;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse; use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\HTTPResponse_Exception; use SilverStripe\Control\HTTPResponse_Exception;
use SilverStripe\Core\HTTPMiddleware;
/** /**
* Emits response to the browser * Emits response to the browser
*/ */
class OutputMiddleware class OutputMiddleware implements HTTPMiddleware
{ {
protected $defaultResponse = null; protected $defaultResponse = null;
@ -24,11 +26,11 @@ class OutputMiddleware
$this->defaultResponse = $defaultResponse; $this->defaultResponse = $defaultResponse;
} }
public function __invoke(callable $next) public function process(HTTPRequest $request, callable $delegate)
{ {
/** @var HTTPResponse $response */ /** @var HTTPResponse $response */
try { try {
$response = call_user_func($next); $response = call_user_func($delegate, $request);
} catch (HTTPResponse_Exception $exception) { } catch (HTTPResponse_Exception $exception) {
$response = $exception->getResponse(); $response = $exception->getResponse();
} }

View File

@ -898,7 +898,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase
$app = new HTTPApplication($kernel); $app = new HTTPApplication($kernel);
// Custom application // Custom application
$app->execute(function () use ($request) { $app->execute($request, function (HTTPRequest $request) {
// Start session and execute // Start session and execute
$request->getSession()->init(); $request->getSession()->init();