diff --git a/core/core.services.yml b/core/core.services.yml
index ea1a56b..8fe29d2 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -596,7 +596,7 @@ services:
     public: false
   controller_resolver:
     class: Drupal\Core\Controller\ControllerResolver
-    arguments: ['@class_resolver']
+    arguments: ['@psr7.http_message_factory', '@class_resolver']
   class_resolver:
     class: Drupal\Core\DependencyInjection\ClassResolver
     calls:
diff --git a/core/lib/Drupal/Core/Controller/ControllerResolver.php b/core/lib/Drupal/Core/Controller/ControllerResolver.php
index bf58aff..a8f2b3c 100644
--- a/core/lib/Drupal/Core/Controller/ControllerResolver.php
+++ b/core/lib/Drupal/Core/Controller/ControllerResolver.php
@@ -7,11 +7,13 @@
 
 namespace Drupal\Core\Controller;
 
+use Drupal\Core\DependencyInjection\ClassResolverInterface;
 use Drupal\Core\Routing\RouteMatch;
-use Psr\Log\LoggerInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpKernel\Controller\ControllerResolver as BaseControllerResolver;
-use Drupal\Core\DependencyInjection\ClassResolverInterface;
 
 /**
  * ControllerResolver to enhance controllers beyond Symfony's basic handling.
@@ -38,12 +40,23 @@ class ControllerResolver extends BaseControllerResolver implements ControllerRes
   protected $classResolver;
 
   /**
+   * The PSR-7 converter.
+   *
+   * @var \Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface
+   */
+  protected $httpMessageFactory;
+
+  /**
    * Constructs a new ControllerResolver.
    *
+   * @param \Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface $http_message_factory
+   *   The PSR-7 converter.
+   *
    * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
    *   The class resolver.
    */
-  public function __construct(ClassResolverInterface $class_resolver) {
+  public function __construct(HttpMessageFactoryInterface $http_message_factory, ClassResolverInterface $class_resolver) {
+    $this->httpMessageFactory = $http_message_factory;
     $this->classResolver = $class_resolver;
   }
 
@@ -94,10 +107,10 @@ public function getController(Request $request) {
    *   A PHP callable.
    *
    * @throws \LogicException
-   *   If the controller cannot be parsed
+   *   If the controller cannot be parsed.
    *
    * @throws \InvalidArgumentException
-   *   If the controller class does not exist
+   *   If the controller class does not exist.
    */
   protected function createController($controller) {
     // Controller in the service:method notation.
@@ -135,7 +148,10 @@ protected function doGetArguments(Request $request, $controller, array $paramete
       elseif ($param->getClass() && $param->getClass()->isInstance($request)) {
         $arguments[] = $request;
       }
-      elseif ($param->getClass() && ($param->getClass()->name == 'Drupal\Core\Routing\RouteMatchInterface' || is_subclass_of($param->getClass()->name, 'Drupal\Core\Routing\RouteMatchInterface'))) {
+      elseif ($param->getClass() && $param->getClass()->name === ServerRequestInterface::class) {
+        $arguments[] = $this->httpMessageFactory->createRequest($request);
+      }
+      elseif ($param->getClass() && ($param->getClass()->name == RouteMatchInterface::class || is_subclass_of($param->getClass()->name, RouteMatchInterface::class))) {
         $arguments[] = RouteMatch::createFromRequest($request);
       }
       elseif ($param->isDefaultValueAvailable()) {
diff --git a/core/lib/Drupal/Core/Menu/menu.api.php b/core/lib/Drupal/Core/Menu/menu.api.php
index 8550fc5..5c34cf3 100644
--- a/core/lib/Drupal/Core/Menu/menu.api.php
+++ b/core/lib/Drupal/Core/Menu/menu.api.php
@@ -122,6 +122,25 @@
  * For example, the placeholder '{myvar}' in a route will become the $myvar
  * parameter to the method.
  *
+ * Additionally, if a parameter is typed to one of the following special classes
+ * the system will pass
+ *
+ * Additionally, there are three special parameters that a controller method
+ * may implement to cause the system to pass those values as well.
+ *
+ * - \Symfony\Component\HttpFoundation\Request: The raw Symfony request object.
+ *   It is generally only useful if the controller needs access to the query
+ *   parameters of the request. By convention, this parameter is usually named
+ *   $request.
+ * - \Psr\Http\Message\ServerRequestInterface: The raw request, represented
+ *   using the PSR-7 ServerRequest format. This object is derived as necessary
+ *   from the Symfony request, so if either will suffice the Symfony request
+ *   will be slightly more performant. By convention this parameter is usually
+ *   named $request.
+ * - \Drupal\Core\Routing\RouteMatchInterface: The "route match" data from
+ *   this request. This object contains various standard data derived from
+ *   the request and routing process. Consult the interface for details.
+ *
  * Most controllers will need to display some information stored in the Drupal
  * database, which will involve using one or more Drupal services (see the
  * @link container Services and container topic @endlink). In order to properly
diff --git a/core/tests/Drupal/Tests/Core/Controller/ControllerResolverTest.php b/core/tests/Drupal/Tests/Core/Controller/ControllerResolverTest.php
index d015a31..dc8c8d1 100644
--- a/core/tests/Drupal/Tests/Core/Controller/ControllerResolverTest.php
+++ b/core/tests/Drupal/Tests/Core/Controller/ControllerResolverTest.php
@@ -19,6 +19,8 @@
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
 use Symfony\Component\HttpFoundation\Request;
+use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
+use Psr\Http\Message\ServerRequestInterface;
 
 /**
  * @coversDefaultClass \Drupal\Core\Controller\ControllerResolver
@@ -41,6 +43,13 @@ class ControllerResolverTest extends UnitTestCase {
   protected $container;
 
   /**
+   * The PSR-7 converter.
+   *
+   * @var \Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface
+   */
+  protected $httpMessageFactory;
+
+  /**
    * {@inheritdoc}
    */
   protected function setUp() {
@@ -49,7 +58,8 @@ protected function setUp() {
     $this->container = new ContainerBuilder();
     $class_resolver = new ClassResolver();
     $class_resolver->setContainer($this->container);
-    $this->controllerResolver = new ControllerResolver($class_resolver);
+    $this->httpMessageFactory = new DiactorosFactory();
+    $this->controllerResolver = new ControllerResolver($this->httpMessageFactory, $class_resolver);
   }
 
   /**
@@ -61,7 +71,7 @@ protected function setUp() {
    * @see \Drupal\Core\Controller\ControllerResolver::doGetArguments()
    */
   public function testGetArguments() {
-    $controller = function(EntityInterface $entity, $user, RouteMatchInterface $route_match) {
+    $controller = function(EntityInterface $entity, $user, RouteMatchInterface $route_match, ServerRequestInterface $psr_7) {
     };
     $mock_entity = $this->getMockBuilder('Drupal\Core\Entity\Entity')
       ->disableOriginalConstructor()
@@ -71,12 +81,13 @@ public function testGetArguments() {
       'entity' => $mock_entity,
       'user' => $mock_account,
       '_raw_variables' => new ParameterBag(array('entity' => 1, 'user' => 1)),
-    ));
+    ), array(), array(), array('HTTP_HOST' => 'drupal.org'));
     $arguments = $this->controllerResolver->getArguments($request, $controller);
 
     $this->assertEquals($mock_entity, $arguments[0]);
     $this->assertEquals($mock_account, $arguments[1]);
     $this->assertEquals(RouteMatch::createFromRequest($request), $arguments[2], 'Ensure that the route match object is passed along as well');
+    $this->assertInstanceOf(ServerRequestInterface::class, $arguments[3], 'Ensure that the PSR-7 object is passed along as well');
   }
 
   /**
@@ -219,6 +230,20 @@ public function testGetArgumentsWithRouteMatchAndRequest() {
     $this->assertEquals([RouteMatch::createFromRequest($request), $request], $arguments);
   }
 
+  /**
+   * Tests getArguments with a route match and a PSR-7 request.
+   *
+   * @covers ::getArguments
+   * @covers ::doGetArguments
+   */
+  public function testGetArgumentsWithRouteMatchAndPsr7Request() {
+    $request = Request::create('/test');
+    $mock_controller = new MockControllerPsr7();
+    $arguments = $this->controllerResolver->getArguments($request, [$mock_controller, 'getControllerWithRequestAndRouteMatch']);
+    $this->assertEquals(RouteMatch::createFromRequest($request), $arguments[0], 'Ensure that the route match object is passed along as well');
+    $this->assertInstanceOf('Psr\Http\Message\ServerRequestInterface', $arguments[1], 'Ensure that the PSR-7 object is passed along as well');
+  }
+
 }
 
 class MockController {
@@ -231,6 +256,17 @@ public function getControllerWithRequestAndRouteMatch(RouteMatchInterface $route
   }
 
 }
+class MockControllerPsr7 {
+  public function getResult() {
+    return ['#markup' => 'This is a regular controller'];
+  }
+
+  public function getControllerWithRequestAndRouteMatch(RouteMatchInterface $route_match, ServerRequestInterface $request) {
+    return ['#markup' => 'this is another example controller'];
+  }
+
+}
+
 class MockContainerInjection implements ContainerInjectionInterface {
   protected $result;
   public function __construct($result) {
