Commit 5ef912e9 authored by Dries's avatar Dries

Issue #2303673 by dawehner, damiankloip, effulgentsia, Fabianx: Implement...

Issue #2303673 by dawehner, damiankloip, effulgentsia, Fabianx: Implement stackphp; cleanup handlePageCache() and preHandle()
parent f4d528ca
......@@ -4,7 +4,7 @@
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "6a7a4ba69644c6cf110d03b1a5346e3e",
"hash": "8afb97667c2791fec2fb1fc43853da24",
"packages": [
{
"name": "doctrine/annotations",
......@@ -1413,6 +1413,56 @@
"homepage": "https://github.com/sebastianbergmann/version",
"time": "2014-03-07 15:35:33"
},
{
"name": "stack/builder",
"version": "v1.0.2",
"source": {
"type": "git",
"url": "https://github.com/stackphp/builder.git",
"reference": "b4af43e7b7f3f7fac919ff475b29f7c5dc7b23b7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/stackphp/builder/zipball/b4af43e7b7f3f7fac919ff475b29f7c5dc7b23b7",
"reference": "b4af43e7b7f3f7fac919ff475b29f7c5dc7b23b7",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"symfony/http-foundation": "~2.1",
"symfony/http-kernel": "~2.1"
},
"require-dev": {
"silex/silex": "~1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-0": {
"Stack": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Igor Wiedler",
"email": "igor@wiedler.ch",
"homepage": "http://wiedler.ch/igor/"
}
],
"description": "Builder for stack middlewares based on HttpKernelInterface.",
"keywords": [
"stack"
],
"time": "2014-01-28 19:42:24"
},
{
"name": "symfony-cmf/routing",
"version": "1.2.0",
......
......@@ -370,8 +370,30 @@ services:
class: Drupal\Core\Controller\TitleResolver
arguments: ['@controller_resolver', '@string_translation']
http_kernel:
class: Symfony\Component\HttpKernel\HttpKernel
factory_method: resolve
factory_service: http_kernel_factory
arguments: ['@http_kernel.basic']
http_kernel_factory:
class: Stack\Builder
http_kernel.basic:
class: Symfony\Component\HttpKernel\HttpKernel
arguments: ['@event_dispatcher', '@controller_resolver', '@request_stack']
http_middleware.reverse_proxy:
class: Drupal\Core\StackMiddleware\ReverseProxyMiddleware
arguments: ['@settings']
tags:
- { name: http_middleware, priority: 300 }
http_middleware.page_cache:
class: Drupal\Core\StackMiddleware\PageCache
arguments: ['@kernel']
tags:
- { name: http_middleware, priority: 200 }
http_middleware.kernel_pre_handle:
class: Drupal\Core\StackMiddleware\KernelPreHandle
arguments: ['@kernel']
tags:
- { name: http_middleware, priority: 100 }
language_manager:
class: Drupal\Core\Language\LanguageManager
arguments: ['@language.default']
......@@ -560,11 +582,6 @@ services:
tags:
- { name: event_subscriber }
arguments: ['@resolver_manager.entity']
reverse_proxy_subscriber:
class: Drupal\Core\EventSubscriber\ReverseProxySubscriber
tags:
- { name: event_subscriber }
arguments: ['@settings']
ajax_subscriber:
class: Drupal\Core\EventSubscriber\AjaxSubscriber
tags:
......
......@@ -183,7 +183,7 @@ public function handleException(GetResponseForExceptionEvent $event) {
$active_providers = ($route && $route->getOption('_auth')) ? $route->getOption('_auth') : array($this->defaultProviderId());
// Get the sorted list of active providers for the given route.
$providers = array_intersect($active_providers, array_keys($this->providers));
$providers = array_intersect($active_providers, array_keys($this->getSortedProviders()));
foreach ($providers as $provider_id) {
if ($this->providers[$provider_id]->handleException($event) == TRUE) {
......
......@@ -10,6 +10,7 @@
use Drupal\Core\Cache\CacheContextsPass;
use Drupal\Core\Cache\ListCacheBinsPass;
use Drupal\Core\DependencyInjection\Compiler\BackendCompilerPass;
use Drupal\Core\DependencyInjection\Compiler\StackedKernelPass;
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\Compiler\ModifyServiceDefinitionsPass;
......@@ -50,6 +51,8 @@ public function register(ContainerBuilder $container) {
$container->addCompilerPass(new BackendCompilerPass());
$container->addCompilerPass(new StackedKernelPass());
// Collect tagged handler services as method calls on consumer services.
$container->addCompilerPass(new TaggedHandlersPass());
......
<?php
/**
* @file
* Contains \Drupal\Core\DependencyInjection\Compiler\StackedKernelPass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Provides a compiler pass for stacked HTTP kernels.
*
* @see \Stack\Builder
*/
class StackedKernelPass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
if (!$container->hasDefinition('http_kernel_factory')) {
return;
}
$http_kernel_factory = $container->getDefinition('http_kernel_factory');
$middleware_priorities = array();
$middleware_arguments = array();
foreach ($container->findTaggedServiceIds('http_middleware') as $id => $attributes) {
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$middleware_priorities[$id] = $priority;
$definition = $container->getDefinition($id);
$middleware_arguments[$id] = $definition->getArguments();
array_unshift($middleware_arguments[$id], $definition->getClass());
}
array_multisort($middleware_priorities, SORT_DESC, $middleware_arguments, SORT_DESC);
foreach ($middleware_arguments as $id => $push_arguments) {
$http_kernel_factory->addMethodCall('push', $push_arguments);
}
}
}
......@@ -418,12 +418,9 @@ public function getContainer() {
}
/**
* Helper method that does request related initialization.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
* {@inheritdoc}
*/
protected function preHandle(Request $request) {
public function preHandle(Request $request) {
// Load all enabled modules.
$this->container->get('module_handler')->loadAll();
......@@ -568,7 +565,6 @@ public function terminate(Request $request, Response $response) {
*/
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
$this->boot();
$this->preHandle($request);
return $this->getHttpKernel()->handle($request, $type, $catch);
}
......
......@@ -108,4 +108,12 @@ public function handlePageCache(Request $request);
*/
public function prepareLegacyRequest(Request $request);
/**
* Helper method that does request related initialization.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
*/
public function preHandle(Request $request);
}
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\ReverseProxySubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Site\Settings;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Reverse proxy subscriber for controller requests.
*/
class ReverseProxySubscriber implements EventSubscriberInterface {
/**
* A settings object.
*
* @var \Drupal\Core\Site\Settings
*/
protected $settings;
/**
* Construct the ReverseProxySubscriber.
*
* @param \Drupal\Core\Site\Settings $settings
* The read-only settings object of this request.
*/
public function __construct(Settings $settings) {
$this->settings = $settings;
}
/**
* Passes reverse proxy settings to current request.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The Event to process.
*/
public function onKernelRequestReverseProxyCheck(GetResponseEvent $event) {
$request = $event->getRequest();
if ($this->settings->get('reverse_proxy', 0)) {
$reverse_proxy_header = $this->settings->get('reverse_proxy_header', 'HTTP_X_FORWARDED_FOR');
$request::setTrustedHeaderName($request::HEADER_CLIENT_IP, $reverse_proxy_header);
$reverse_proxy_addresses = $this->settings->get('reverse_proxy_addresses', array());
$request::setTrustedProxies($reverse_proxy_addresses);
}
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/
static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = array('onKernelRequestReverseProxyCheck', 10);
return $events;
}
}
......@@ -22,7 +22,6 @@
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
......@@ -127,10 +126,10 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS
* The theme manager.
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
* The CSRF token generator.
* @param \Symfony\Component\HttpKernel\HttpKernel $http_kernel
* @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
* The HTTP kernel.
*/
public function __construct(FormValidatorInterface $form_validator, FormSubmitterInterface $form_submitter, FormCacheInterface $form_cache, ModuleHandlerInterface $module_handler, EventDispatcherInterface $event_dispatcher, RequestStack $request_stack, ClassResolverInterface $class_resolver, ThemeManagerInterface $theme_manager, CsrfTokenGenerator $csrf_token = NULL, HttpKernel $http_kernel = NULL) {
public function __construct(FormValidatorInterface $form_validator, FormSubmitterInterface $form_submitter, FormCacheInterface $form_cache, ModuleHandlerInterface $module_handler, EventDispatcherInterface $event_dispatcher, RequestStack $request_stack, ClassResolverInterface $class_resolver, ThemeManagerInterface $theme_manager, CsrfTokenGenerator $csrf_token = NULL, HttpKernelInterface $http_kernel = NULL) {
$this->formValidator = $form_validator;
$this->formSubmitter = $form_submitter;
$this->formCache = $form_cache;
......
<?php
/**
* @file
* Contains \Drupal\Core\StackMiddleware\KernelBoot.
*/
namespace Drupal\Core\StackMiddleware;
use Drupal\Core\DrupalKernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Prepares the environment after page caching ran.
*/
class KernelPreHandle implements HttpKernelInterface {
/**
* The wrapped HTTP kernel.
*
* @var \Symfony\Component\HttpKernel\HttpKernelInterface
*/
protected $httpKernel;
/**
* The main Drupal kernel.
*
* @var \Drupal\Core\DrupalKernelInterface
*/
protected $drupalKernel;
/**
* Constructs a new KernelPreHandle instance.
*
* @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
* The wrapped HTTP kernel.
*
* @param \Drupal\Core\DrupalKernelInterface $drupal_kernel
* The main Drupal kernel.
*/
public function __construct(HttpKernelInterface $http_kernel, DrupalKernelInterface $drupal_kernel) {
$this->httpKernel = $http_kernel;
$this->drupalKernel = $drupal_kernel;
}
/**
* {@inheritdoc}
*/
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
$this->drupalKernel->preHandle($request);
return $this->httpKernel->handle($request, $type, $catch);
}
}
<?php
/**
* @file
* Contains \Drupal\Core\StackMiddleware\PageCache.
*/
namespace Drupal\Core\StackMiddleware;
use Drupal\Core\DrupalKernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Executes the page caching before the main kernel takes over the request.
*/
class PageCache implements HttpKernelInterface {
/**
* The wrapped HTTP kernel.
*
* @var \Symfony\Component\HttpKernel\HttpKernelInterface
*/
protected $httpKernel;
/**
* The main Drupal kernel.
*
* @var \Drupal\Core\DrupalKernelInterface
*/
protected $drupalKernel;
/**
* Constructs a ReverseProxyMiddleware object.
*
* @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
* The decorated kernel.
* @param \Drupal\Core\DrupalKernelInterface $drupal_kernel
* The main Drupal kernel.
*/
public function __construct(HttpKernelInterface $http_kernel, DrupalKernelInterface $drupal_kernel) {
$this->httpKernel = $http_kernel;
$this->drupalKernel = $drupal_kernel;
}
/**
* {@inheritdoc}
*/
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
$this->drupalKernel->handlePageCache($request);
return $this->httpKernel->handle($request, $type, $catch);
}
}
<?php
/**
* @file
* Contains \Drupal\Core\StackMiddleware\ReverseProxyMiddleware
*/
namespace Drupal\Core\StackMiddleware;
use Drupal\Core\Site\Settings;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
*
*/
class ReverseProxyMiddleware implements HttpKernelInterface {
/**
* The decorated kernel.
*
* @var \Symfony\Component\HttpKernel\HttpKernelInterface
*/
protected $httpKernel;
/**
* The site settings.
*
* @var \Drupal\Core\Site\Settings
*/
protected $settings;
/**
* Constructs a ReverseProxyMiddleware object.
*
* @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
* The decorated kernel.
* @param \Drupal\Core\Site\Settings $settings
* The site settings.
*/
public function __construct(HttpKernelInterface $http_kernel, Settings $settings) {
$this->httpKernel = $http_kernel;
$this->settings = $settings;
}
/**
* {@inheritdoc}
*/
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
// Initialize proxy settings.
if ($this->settings->get('reverse_proxy', FALSE)) {
$reverse_proxy_header = $this->settings->get('reverse_proxy_header', 'X_FORWARDED_FOR');
$request::setTrustedHeaderName($request::HEADER_CLIENT_IP, $reverse_proxy_header);
$proxies = $this->settings->get('reverse_proxy_addresses', array());
if (count($proxies) > 0) {
$request::setTrustedProxies($proxies);
}
}
return $this->httpKernel->handle($request, $type, $catch);
}
}
<?php
/**
* @file
* Contains \Drupal\system\Tests\HttpKernel\StackKernelIntegrationTest.
*/
namespace Drupal\system\Tests\HttpKernel;
use Drupal\simpletest\KernelTestBase;
use Symfony\Component\HttpFoundation\Request;
/**
* Tests the stacked kernel functionality.
*
* @group Routing.
*/
class StackKernelIntegrationTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('httpkernel_test', 'system');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('system', 'router');
}
/**
* Tests a request.
*/
public function testRequest() {
$request = new Request();
/** @var \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel */
$http_kernel = \Drupal::service('http_kernel');
$http_kernel->handle($request);
$this->assertEqual($request->attributes->get('_hello'), 'world');
$this->assertEqual($request->attributes->get('_previous_optional_argument'), 'test_argument');
}
}
name: 'HttpKernel test'
type: module
description: 'Support module for httpkernel tests.'
package: Testing
version: VERSION
core: 8.x
services:
httpkernel_test.test_middleware:
class: Drupal\httpkernel_test\HttpKernel\TestMiddleware
tags:
- { name: http_middleware }
httpkernel_test.test_middleware2:
class: Drupal\httpkernel_test\HttpKernel\TestMiddleware
arguments: ['test_argument']
tags:
- { name: http_middleware, priority: 20 }
<?php
/**
* @file
* Contains \Drupal\httpkernel_test\HttpKernel\TestMiddleware.
*/
namespace Drupal\httpkernel_test\HttpKernel;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Provides a test middleware.
*/
class TestMiddleware implements HttpKernelInterface {
/**
* The decorated kernel.
*
* @var \Symfony\Component\HttpKernel\HttpKernelInterface
*/
protected $kernel;
/**
* An optional argument.
*
* @var mixed
*/
protected $optionalArgument;
/**
* Constructs a new TestMiddleware object.
*
* @param \Symfony\Component\HttpKernel\HttpKernelInterface $kernel
* The decorated kernel.
* @param mixed $optional_argument
* (optional) An optional argument.
*/
public function __construct(HttpKernelInterface $kernel, $optional_argument = NULL) {
$this->kernel = $kernel;
$this->optionalArgument = $optional_argument;
}
/**
* {@inheritdoc}
*/
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
$request->attributes->set('_hello', 'world');
if ($request->attributes->has('_optional_argument')) {
$request->attributes->set('_previous_optional_argument', $request->attributes->get('_optional_argument'));
}
elseif (isset($this->optionalArgument)) {
$request->attributes->set('_optional_argument', $this->optionalArgument);
}
return $this->kernel->handle($request, $type, $catch);
}
}
......@@ -2,22 +2,34 @@
/**
* @file
* Contains \Drupal\Core\EventSubscriber\ReverseProxySubscriberUnitTest.
* Contains \Drupal\Tests\Core\StackMiddleware\ReverseProxyMiddlewareTest.
*/
namespace Drupal\Tests\Core\EventSubscriber;
namespace Drupal\Tests\Core\StackMiddleware;
use Drupal\Core\EventSubscriber\ReverseProxySubscriber;
use Drupal\Core\Site\Settings;
use Drupal\Core\StackMiddleware\ReverseProxyMiddleware;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\HttpFoundation\Request;
/**
* Unit test the reverse proxy event subscriber.
* Unit test the reverse proxy stack middleware.
*
* @group EventSubscriber
* @group StackMiddleware
*/
class ReverseProxySubscriberUnitTest extends UnitTestCase {
class ReverseProxyMiddlewareTest extends UnitTestCase {
/**
* @var \Symfony\Component\HttpKernel\HttpKernelInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $mockHttpKernel;
/**
* {@inheritdoc}
*/
public function setUp() {
$this->mockHttpKernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface');
}
/**
* Tests that subscriber does not act when reverse proxy is not set.
......@@ -26,37 +38,45 @@ public function testNoProxy() {
$settings = new Settings(array());
$this->assertEquals(0, $settings->get('reverse_proxy'));
$subscriber = new ReverseProxySubscriber($settings);
$middleware = new ReverseProxyMiddleware($this->mockHttpKernel, $settings);
// Mock a request object.
$request = $this->getMock('Symfony\Component\HttpFoundation\Request', array('setTrustedHeaderName', 'setTrustedProxies'));
// setTrustedHeaderName() should never fire.
$request->expects($this->never())
->method('setTrustedHeaderName');
// Mock a response event.
$event = $this->getMockedEvent($request);
// Actually call the check method.
$subscriber->onKernelRequestReverseProxyCheck($event);
$middleware->handle($request);
}
/**
* Tests that subscriber sets trusted headers when reverse proxy is set.
*
* @dataProvider testReverseProxyEnabledProvider
*/
public function testReverseProxyEnabled() {
$cases = array(
public function testReverseProxyEnabled($provided_settings) {
// Enable reverse proxy and add test values.
$settings = new Settings(array('reverse_proxy' => 1) + $provided_settings);
$this->trustedHeadersAreSet($settings);
}
/**
* Data provider for testReverseProxyEnabled.
*/
public function testReverseProxyEnabledProvider() {
return array(
array(
'reverse_proxy_header' => 'HTTP_X_FORWARDED_FOR',
'reverse_proxy_addresses' => array(),
array(
'reverse_proxy_header' => 'HTTP_X_FORWARDED_FOR',
'reverse_proxy_addresses' => array(),
),
),