Skip to content

Commit

Permalink
API Create HTTPMiddleware and standardise middleware for request hand…
Browse files Browse the repository at this point in the history
…ling
  • Loading branch information
Damian Mooyman committed Jun 20, 2017
1 parent 2a10c23 commit bba9791
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 67 deletions.
2 changes: 1 addition & 1 deletion main.php
Expand Up @@ -15,5 +15,5 @@
$kernel = new AppKernel();
$app = new HTTPApplication($kernel);
$app->addMiddleware(new OutputMiddleware());
$app->addMiddleware(new ErrorControlChainMiddleware($app, $request));
$app->addMiddleware(new ErrorControlChainMiddleware($app));
$app->handle($request);
2 changes: 1 addition & 1 deletion src/Control/HTTPRequest.php
Expand Up @@ -316,7 +316,7 @@ public static function createFromVariables(array $variables, $input)
}

// 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);

return $request;
Expand Down
9 changes: 0 additions & 9 deletions src/Core/Application.php
Expand Up @@ -13,13 +13,4 @@ interface Application
* @return Kernel
*/
public function getKernel();

/**
* Invoke the application control chain
*
* @param callable $callback
* @param bool $flush
* @return mixed
*/
public function execute(callable $callback, $flush = false);
}
29 changes: 16 additions & 13 deletions src/Core/HTTPApplication.php
Expand Up @@ -12,20 +12,20 @@
class HTTPApplication implements Application
{
/**
* @var callable[]
* @var HTTPMiddleware[]
*/
protected $middlewares = [];

/**
* @return callable[]
* @return HTTPMiddleware[]
*/
public function getMiddlewares()
{
return $this->middlewares;
}

/**
* @param callable[] $middlewares
* @param HTTPMiddleware[] $middlewares
* @return $this
*/
public function setMiddlewares($middlewares)
Expand All @@ -35,10 +35,10 @@ public function setMiddlewares($middlewares)
}

/**
* @param callable $middleware
* @param HTTPMiddleware $middleware
* @return $this
*/
public function addMiddleware($middleware)
public function addMiddleware(HTTPMiddleware $middleware)
{
$this->middlewares[] = $middleware;
return $this;
Expand All @@ -47,19 +47,21 @@ public function addMiddleware($middleware)
/**
* Call middleware
*
* @param HTTPRequest $request
* @param callable $last Last config to call
* @return HTTPResponse
*/
protected function callMiddleware($last)
protected function callMiddleware(HTTPRequest $request, $last)
{
// Reverse middlewares
$next = $last;
/** @var HTTPMiddleware $middleware */
foreach (array_reverse($this->getMiddlewares()) as $middleware) {
$next = function () use ($middleware, $next) {
return call_user_func($middleware, $next);
$next = function ($request) use ($middleware, $next) {
return $middleware->process($request, $next);
};
}
return call_user_func($next);
return call_user_func($next, $request);
}

/**
Expand Down Expand Up @@ -93,7 +95,7 @@ public function handle(HTTPRequest $request)
$flush = $request->getVar('flush') || strpos($request->getURL(), 'dev/build') === 0;

// Ensure boot is invoked
return $this->execute(function () use ($request) {
return $this->execute($request, function (HTTPRequest $request) {
// Start session and execute
$request->getSession()->init();
return Director::direct($request);
Expand All @@ -103,17 +105,18 @@ public function handle(HTTPRequest $request)
/**
* Safely boot the application and execute the given main action
*
* @param HTTPRequest $request
* @param callable $callback
* @param bool $flush
* @return HTTPResponse
*/
public function execute(callable $callback, $flush = false)
public function execute(HTTPRequest $request, callable $callback, $flush = false)
{
try {
return $this->callMiddleware(function () use ($callback, $flush) {
return $this->callMiddleware($request, function ($request) use ($callback, $flush) {
// Pre-request boot
$this->getKernel()->boot($flush);
return call_user_func($callback);
return call_user_func($callback, $request);
});
} finally {
$this->getKernel()->shutdown();
Expand Down
22 changes: 22 additions & 0 deletions src/Core/HTTPMiddleware.php
@@ -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);
}
51 changes: 12 additions & 39 deletions src/Core/Startup/ErrorControlChainMiddleware.php
Expand Up @@ -7,52 +7,42 @@
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\HTTPResponse_Exception;
use SilverStripe\Core\Application;
use SilverStripe\Core\HTTPMiddleware;
use SilverStripe\Security\Permission;
use SilverStripe\Security\Security;

/**
* Decorates application bootstrapping with errorcontrolchain
*/
class ErrorControlChainMiddleware
class ErrorControlChainMiddleware implements HTTPMiddleware
{
/**
* @var Application
*/
protected $application = null;

/**
* @var HTTPRequest
*/
protected $request = null;

/**
* Build error control chain for an application
*
* @param Application $application
* @param HTTPRequest $request
*/
public function __construct(Application $application, HTTPRequest $request)
public function __construct(Application $application)
{
$this->application = $application;
$this->request = $request;
}

/**
* @param callable $next
* @return HTTPResponse
*/
public function __invoke(callable $next)
public function process(HTTPRequest $request, callable $next)
{
$result = null;

// Prepare tokens and execute chain
$reloadToken = ParameterConfirmationToken::prepare_tokens(
['isTest', 'isDev', 'flush'],
$this->getRequest()
$request
);
$chain = new ErrorControlChain();
$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 (!$reloadToken) {
$chain->setSuppression(false);
Expand All @@ -61,10 +51,10 @@ public function __invoke(callable $next)
try {
// Check if a token is requesting a redirect
if ($reloadToken) {
$result = $this->safeReloadWithToken($reloadToken);
$result = $this->safeReloadWithToken($request, $reloadToken);
} else {
// If no reload necessary, process application
$result = call_user_func($next);
$result = call_user_func($next, $request);
}
} catch (HTTPResponse_Exception $exception) {
$result = $exception->getResponse();
Expand All @@ -84,16 +74,17 @@ public function __invoke(callable $next)
* Reload application with the given token, but only if either the user is authenticated,
* or authentication is impossible.
*
* @param HTTPRequest $request
* @param ParameterConfirmationToken $reloadToken
* @return HTTPResponse
*/
protected function safeReloadWithToken($reloadToken)
protected function safeReloadWithToken(HTTPRequest $request, $reloadToken)
{
// Safe reload requires manual boot
$this->getApplication()->getKernel()->boot(false);

// 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
if (Director::isDev() || !Security::database_is_ready() || Permission::check('ADMIN')) {
Expand All @@ -102,7 +93,7 @@ protected function safeReloadWithToken($reloadToken)

// Fail and redirect the user to the login page
$loginPage = Director::absoluteURL(Security::config()->get('login_url'));
$loginPage .= "?BackURL=" . urlencode($this->getRequest()->getURL());
$loginPage .= "?BackURL=" . urlencode($request->getURL());
$result = new HTTPResponse();
$result->redirect($loginPage);
return $result;
Expand All @@ -125,22 +116,4 @@ public function setApplication(Application $application)
$this->application = $application;
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;
}
}
8 changes: 5 additions & 3 deletions src/Core/Startup/OutputMiddleware.php
Expand Up @@ -2,13 +2,15 @@

namespace SilverStripe\Core\Startup;

use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\HTTPResponse_Exception;
use SilverStripe\Core\HTTPMiddleware;

/**
* Emits response to the browser
*/
class OutputMiddleware
class OutputMiddleware implements HTTPMiddleware
{
protected $defaultResponse = null;

Expand All @@ -24,11 +26,11 @@ public function __construct($defaultResponse = null)
$this->defaultResponse = $defaultResponse;
}

public function __invoke(callable $next)
public function process(HTTPRequest $request, callable $delegate)
{
/** @var HTTPResponse $response */
try {
$response = call_user_func($next);
$response = call_user_func($delegate, $request);
} catch (HTTPResponse_Exception $exception) {
$response = $exception->getResponse();
}
Expand Down
2 changes: 1 addition & 1 deletion src/Dev/SapphireTest.php
Expand Up @@ -898,7 +898,7 @@ public static function start()
$app = new HTTPApplication($kernel);

// Custom application
$app->execute(function () use ($request) {
$app->execute($request, function (HTTPRequest $request) {
// Start session and execute
$request->getSession()->init();

Expand Down

0 comments on commit bba9791

Please sign in to comment.