diff --git a/.htaccess b/.htaccess
index a69bdd4..725897e 100644
--- a/.htaccess
+++ b/.htaccess
@@ -109,7 +109,7 @@ DirectoryIndex index.php index.html index.htm
   RewriteCond %{REQUEST_FILENAME} !-f
   RewriteCond %{REQUEST_FILENAME} !-d
   RewriteCond %{REQUEST_URI} !=/favicon.ico
-  RewriteRule ^ index.php [L]
+  RewriteRule ^(.*)$ index.php [L]
 
   # Rules to correctly serve gzip compressed CSS and JS files.
   # Requires both mod_rewrite and mod_headers to be enabled.
diff --git a/core/includes/batch.inc b/core/includes/batch.inc
index 83ddd30..0b07d8e 100644
--- a/core/includes/batch.inc
+++ b/core/includes/batch.inc
@@ -14,6 +14,8 @@
  * @see batch_get()
  */
 
+use \Symfony\Component\HttpFoundation\JsonResponse;
+
 /**
  * Loads a batch from the database.
  *
@@ -77,7 +79,7 @@ function _batch_page() {
 
     case 'do':
       // JavaScript-based progress page callback.
-      _batch_do();
+      $output = _batch_do();
       break;
 
     case 'do_nojs':
@@ -160,7 +162,7 @@ function _batch_do() {
   // Perform actual processing.
   list($percentage, $message) = _batch_process();
 
-  drupal_json_output(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message));
+  return new JsonResponse(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message));
 }
 
 /**
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 12b1d80..d76ca4c 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -3,6 +3,7 @@
 use Drupal\Core\Database\Database;
 use Symfony\Component\ClassLoader\UniversalClassLoader;
 use Symfony\Component\ClassLoader\ApcUniversalClassLoader;
+use Symfony\Component\HttpFoundation\Request;
 
 /**
  * @file
@@ -1511,6 +1512,27 @@ function request_uri() {
 }
 
 /**
+ * Returns the current global reuqest object.
+ *
+ * @todo Replace this function with a proper dependency injection container.
+ *
+ * @staticvar Request $request
+ * @param Request $new_request
+ *   The new request object to store. If you are not index.php, you probably
+ *   should not be using this parameter.
+ * @return Request
+ *   The current request object.
+ */
+function request(Request $new_request = NULL) {
+  static $request;
+
+  if ($new_request) {
+    $request = $new_request;
+  }
+  return $request;
+}
+
+/**
  * Logs an exception.
  *
  * This is a wrapper function for watchdog() which automatically decodes an
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 5ff6167..cd21512 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -1,5 +1,7 @@
 <?php
 
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Drupal\Core\Database\Database;
 
 /**
@@ -711,7 +713,11 @@ function drupal_site_offline() {
  * bubble up to menu_execute_active_handler() should call drupal_not_found().
  */
 function drupal_not_found() {
-  drupal_deliver_page(MENU_NOT_FOUND);
+
+  throw new NotFoundHttpException();
+
+  // @todo Remove this line.
+  //drupal_deliver_page(MENU_NOT_FOUND);
 }
 
 /**
@@ -724,7 +730,11 @@ function drupal_not_found() {
  * drupal_access_denied().
  */
 function drupal_access_denied() {
-  drupal_deliver_page(MENU_ACCESS_DENIED);
+
+  throw new AccessDeniedHttpException();
+
+  // @todo Remove this line.
+  //drupal_deliver_page(MENU_ACCESS_DENIED);
 }
 
 /**
@@ -2246,31 +2256,20 @@ function url($path = NULL, array $options = array()) {
   $base = $options['absolute'] ? $options['base_url'] . '/' : base_path();
   $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];
 
-  // With Clean URLs.
-  if (!empty($GLOBALS['conf']['clean_url'])) {
-    $path = drupal_encode_path($prefix . $path);
-    if ($options['query']) {
-      return $base . $path . '?' . drupal_http_build_query($options['query']) . $options['fragment'];
-    }
-    else {
-      return $base . $path . $options['fragment'];
-    }
+  // If Clean URLs are not enabled, we need to prefix the script name onto
+  // the link.
+  // @todo: Make this dynamic based on the request object without using a global
+  // request object.
+  if (empty($GLOBALS['conf']['clean_url'])) {
+    $base .= 'index.php/';
+  }
+
+  $path = drupal_encode_path($prefix . $path);
+  if ($options['query']) {
+    return $base . $path . '?' . drupal_http_build_query($options['query']) . $options['fragment'];
   }
-  // Without Clean URLs.
   else {
-    $path = $prefix . $path;
-    $query = array();
-    if (!empty($path)) {
-      $query['q'] = $path;
-    }
-    if ($options['query']) {
-      // We do not use array_merge() here to prevent overriding $path via query
-      // parameters.
-      $query += $options['query'];
-    }
-    $query = $query ? ('?' . drupal_http_build_query($query)) : '';
-    $script = isset($options['script']) ? $options['script'] : '';
-    return $base . $script . $query . $options['fragment'];
+    return $base . $path . $options['fragment'];
   }
 }
 
@@ -5243,7 +5242,7 @@ function _drupal_bootstrap_full() {
  *
  * @see drupal_page_header()
  */
-function drupal_page_set_cache() {
+function drupal_page_set_cache($response_body) {
   global $base_root;
 
   if (drupal_page_is_cacheable()) {
@@ -5251,7 +5250,7 @@ function drupal_page_set_cache() {
       'cid' => $base_root . request_uri(),
       'data' => array(
         'path' => $_GET['q'],
-        'body' => ob_get_clean(),
+        'body' => $response_body,
         'title' => drupal_get_title(),
         'headers' => array(),
       ),
diff --git a/core/includes/file.inc b/core/includes/file.inc
index eaff634..fa6670d 100644
--- a/core/includes/file.inc
+++ b/core/includes/file.inc
@@ -5,6 +5,9 @@
  * API for handling file uploads and server file management.
  */
 
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\HttpFoundation\StreamedResponse;
 use Drupal\Core\StreamWrapper\LocalStream;
 
 /**
@@ -2046,18 +2049,27 @@ function file_download() {
       $function = $module . '_file_download';
       $result = $function($uri);
       if ($result == -1) {
-        return drupal_access_denied();
+        throw new AccessDeniedHttpException();
       }
       if (isset($result) && is_array($result)) {
         $headers = array_merge($headers, $result);
       }
     }
     if (count($headers)) {
-      file_transfer($uri, $headers);
+      return new StreamedResponse(function() use ($uri) {
+        $scheme = file_uri_scheme($uri);
+        // Transfer file in 1024 byte chunks to save memory usage.
+        if ($scheme && file_stream_wrapper_valid_scheme($scheme) && $fd = fopen($uri, 'rb')) {
+          while (!feof($fd)) {
+            print fread($fd, 1024);
+          }
+          fclose($fd);
+        }
+      }, 200, $headers);
     }
-    return drupal_access_denied();
+    throw new AccessDeniedHttpException();
   }
-  return drupal_not_found();
+  throw new NotFoundHttpException();
 }
 
 
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 37b3281..e7bf3f8 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -3,6 +3,9 @@
 use Drupal\Core\Database\Database;
 use Drupal\Core\Database\Install\TaskException;
 
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
 /**
  * @file
  * API functions for installing Drupal.
@@ -261,6 +264,14 @@ function install_begin_request(&$install_state) {
 
   drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
 
+  // A request object from the HTTPFoundation to tell us about the request.
+  $request = Request::createFromGlobals();
+
+  // Set the global $request object.  This is a temporary measure to
+  // keep legacy utility functions working.  It should be moved to a dependency
+  // injection container at some point.
+  request($request);
+
   // This must go after drupal_bootstrap(), which unsets globals!
   global $conf;
 
@@ -488,6 +499,15 @@ function install_run_task($task, &$install_state) {
     elseif ($current_batch == $function) {
       include_once DRUPAL_ROOT . '/core/includes/batch.inc';
       $output = _batch_page();
+      // Because Batch API now returns a JSON response for intermediary steps,
+      // but the installer doesn't handle Response objects yet, we will just
+      // send the output here and emulate the old model.
+      // @todo: Replace this when we refactor the installer to use a
+      // Request/Response workflow.
+      if ($output instanceof Response) {
+        $output->send();
+        $output = NULL;
+      }
       // The task is complete when we try to access the batch page and receive
       // FALSE in return, since this means we are at a URL where we are no
       // longer requesting a batch ID.
diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index fecd158..9ccac08 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -3807,7 +3807,7 @@ function _menu_site_is_offline($check_only = FALSE) {
       // Ensure that the maintenance mode message is displayed only once
       // (allowing for page redirects) and specifically suppress its display on
       // the maintenance mode settings page.
-      if (!$check_only && $_GET['q'] != 'admin/config/development/maintenance') {
+      if (!$check_only && request()->attributes->get('system_path') != '/admin/config/development/maintenance') {
         if (user_access('administer site configuration')) {
           drupal_set_message(t('Operating in maintenance mode. <a href="@url">Go online.</a>', array('@url' => url('admin/config/development/maintenance'))), 'status', FALSE);
         }
diff --git a/core/lib/Drupal/Core/ContentNegotiation.php b/core/lib/Drupal/Core/ContentNegotiation.php
new file mode 100644
index 0000000..e19d950
--- /dev/null
+++ b/core/lib/Drupal/Core/ContentNegotiation.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\ContentNegotiation.
+ */
+
+namespace Drupal\Core;
+
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * This class is a central library for content type negotiation.
+ *
+ * @todo Replace this class with a real content negotiation library based on
+ * mod_negotiation.  Development of that is a work in progress.
+ */
+class ContentNegotiation {
+
+  /**
+   * Returns the normalized type of a given request.
+   *
+   * The normalized type is a short, lowercase version of the format, such as
+   * "html" or "json" or "atom".
+   *
+   * @param Request $request
+   *   The request object from which to extract the content type.
+   */
+  public function getContentType(Request $request) {
+    $acceptable_content_types = $request->getAcceptableContentTypes();
+
+    // AJAX iframe uploads need special handling, because they contain a json
+    // response wrapped in <textarea>.
+    if ($request->get('ajax_iframe_upload', FALSE)) {
+      return 'iframeupload';
+    }
+
+    // AJAX calls need to be run through ajax rendering functions
+    elseif ($request->isXmlHttpRequest()) {
+      return 'ajax';
+    }
+
+    // JSON requests can be responded to using JsonResponse().
+    elseif (in_array('application/json', $acceptable_content_types)) {
+      return 'json';
+    }
+
+    // Do HTML last so that it always wins for */* formats.
+    elseif(in_array('text/html', $acceptable_content_types) || in_array('*/*', $acceptable_content_types)) {
+      return 'html';
+    }
+  }
+
+}
+
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
new file mode 100644
index 0000000..26ff906
--- /dev/null
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @file
+ *
+ * Definition of Drupal\Core\DrupalKernel.
+ */
+
+namespace Drupal\Core;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\RequestContext;
+use Symfony\Component\HttpKernel\HttpKernel;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
+use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
+use Symfony\Component\HttpKernel\EventListener\RouterListener;
+use Symfony\Component\HttpKernel\EventListener\ExceptionListener;
+use Drupal\Core\EventSubscriber\ViewSubscriber;
+use Drupal\Core\EventSubscriber\AccessSubscriber;
+use Drupal\Core\EventSubscriber\PathSubscriber;
+use Drupal\Core\EventSubscriber\LegacyControllerSubscriber;
+use Drupal\Core\EventSubscriber\MaintenanceModeSubscriber;
+use Drupal\Core\EventSubscriber\RequestCloseSubscriber;
+
+use Exception;
+
+/**
+ * The DrupalKernel class is the core of Drupal itself.
+ */
+class DrupalKernel extends HttpKernel {
+
+    /**
+     * The event dispatcher used by this kernel.
+     *
+     * @var EventDispatcherInterface
+     */
+    protected $dispatcher;
+
+    /**
+     * The controller resolver that will extract the controller from a Request.
+     *
+     * @var ControllerResolverInterface
+     */
+    protected $resolver;
+
+
+    /**
+     * Constructor
+     *
+     * @param EventDispatcherInterface $dispatcher
+     *   An EventDispatcherInterface instance
+     * @param ControllerResolverInterface $resolver
+     *   A ControllerResolverInterface instance
+     */
+   public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver) {
+      parent::__construct($dispatcher, $resolver);
+      $this->dispatcher = $dispatcher;
+      $this->resolver = $resolver;
+
+      $context = new RequestContext();
+      $this->matcher = new UrlMatcher($context);
+      $this->dispatcher->addSubscriber(new RouterListener($this->matcher));
+
+      $negotiation = new ContentNegotiation();
+
+      // @todo Make this extensible rather than just hard coding some.
+      // @todo Add a subscriber to handle other things, too, like our Ajax
+      // replacement system.
+      $this->dispatcher->addSubscriber(new ViewSubscriber($negotiation));
+      $this->dispatcher->addSubscriber(new AccessSubscriber());
+      $this->dispatcher->addSubscriber(new MaintenanceModeSubscriber());
+      $this->dispatcher->addSubscriber(new PathSubscriber());
+      $this->dispatcher->addSubscriber(new LegacyControllerSubscriber());
+      $this->dispatcher->addSubscriber(new RequestCloseSubscriber());
+
+      // Some other form of error occured that wasn't handled by another kernel
+      // listener.  That could mean that it's a method/mime-type/error
+      // combination that is not accounted for, or some other type of error.
+      // Either way, treat it as a server-level error and return an HTTP 500.
+      // By default, this will be an HTML-type response because that's a decent
+      // best guess if we don't know otherwise.
+      $this->dispatcher->addSubscriber(new ExceptionListener(array(new ExceptionController($this, $negotiation), 'execute')));
+    }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php
new file mode 100644
index 0000000..04eac0a
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Drupal\Core\EventSubscriber;
+
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * @file
+ *
+ * Definition of Drupal\Core\EventSubscriber\AccessSubscriber
+ */
+
+/**
+ * Access subscriber for controller requests.
+ */
+class AccessSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Verifies that the current user can access the requested path.
+   *
+   * @todo This is a total hack to keep our current access system working. It
+   * should be replaced with something robust and injected at some point.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onKernelRequestAccessCheck(GetResponseEvent $event) {
+
+    $router_item = $event->getRequest()->attributes->get('drupal_menu_item');
+
+    if (isset($router_item['access']) && !$router_item['access']) {
+      throw new AccessDeniedHttpException();
+    }
+  }
+
+  /**
+   * 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('onKernelRequestAccessCheck', 30);
+
+    return $events;
+  }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php
new file mode 100644
index 0000000..cc4da15
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Drupal\Core\EventSubscriber;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * @file
+ *
+ * Definition of Drupal\Core\EventSubscriber\LegacyControllerSubscriber
+ */
+
+/**
+ * Access subscriber for controller requests.
+ */
+class LegacyControllerSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Wraps legacy controllers in a closure to handle old-style arguments.
+   *
+   * This is a backward compatibility layer only.  This is a rather ugly way
+   * to piggyback Drupal's existing menu router items onto the Symfony model,
+   * but it works for now.  If we did not do this, any menu router item with
+   * a variable number of arguments would fail to work.  This bypasses Symfony's
+   * controller argument handling entirely and lets the old-style approach work.
+   *
+   * @todo Convert Drupal to use the IETF-draft-RFC style {placeholders}. That
+   * will allow us to use the native Symfony conversion, including out-of-order
+   * argument mapping, name-based mapping, and with another listener
+   * auto-conversion of parameters to full objects.  That may necessitate not
+   * using func_get_args()-based controllers.  That is likely for the best,
+   * as those are quite hard to document anyway.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onKernelControllerLegacy(FilterControllerEvent $event) {
+    $router_item = $event->getRequest()->attributes->get('drupal_menu_item');
+    $controller = $event->getController();
+
+    // This BC logic applies only to functions.  Otherwise, skip it.
+    if (is_string($controller) && function_exists($controller)) {
+      $new_controller = function() use ($router_item) {
+        return call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
+      };
+      $event->setController($new_controller);
+    }
+  }
+
+  /**
+   * Registers the methods in this class that should be listeners.
+   *
+   * @return array
+   *   An array of event listener definitions.
+   */
+  static function getSubscribedEvents() {
+    $events[KernelEvents::CONTROLLER][] = array('onKernelControllerLegacy', 30);
+
+    return $events;
+  }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php
new file mode 100644
index 0000000..2befe92
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Drupal\Core\EventSubscriber;
+
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * @file
+ * Definition of Drupal\Core\EventSubscriber\MaintenanceModeSubscriber.
+ */
+
+/**
+ * Maintenance mode subscriber for controller requests.
+ */
+class MaintenanceModeSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Response with the maintenance page when the site is offline.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onKernelRequestMaintenanceModeCheck(GetResponseEvent $event) {
+    // Check if the site is offline.
+    $status = _menu_site_is_offline() ? MENU_SITE_OFFLINE : MENU_SITE_ONLINE;
+
+    // Allow other modules to change the site status but not the path because
+    // that would not change the global variable. hook_url_inbound_alter() can
+    // be used to change the path. Code later will not use the $read_only_path
+    // variable.
+    $read_only_path = !empty($path) ? $path : $event->getRequest()->attributes->get('system_path');
+    drupal_alter('menu_site_status', $status, $read_only_path);
+
+    // Only continue if the site is online.
+    if ($status != MENU_SITE_ONLINE) {
+      // Deliver the 503 page.
+      drupal_maintenance_theme();
+      drupal_set_title(t('Site under maintenance'));
+      $content = theme('maintenance_page', array('content' => filter_xss_admin(variable_get('maintenance_mode_message', t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))))));
+      $response = new Response('Service unavailable', 503);
+      $response->setContent($content);
+      $event->setResponse($response);
+    }
+  }
+
+  /**
+   * 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('onKernelRequestMaintenanceModeCheck', 40);
+    return $events;
+  }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php
new file mode 100644
index 0000000..dc5059b
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\Core\EventSubscriber;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * @file
+ *
+ * Definition of Drupal\Core\EventSubscriber\AccessSubscriber
+ */
+
+/**
+ * Access subscriber for controller requests.
+ */
+class PathSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Resolve the system path.
+   *
+   * @todo The path system should be objectified to remove the function calls
+   * in this method.
+   *
+   * @todo We're writing back to $_GET['q'] for temporary BC. All instances of
+   * $_GET['q'] should be removed and then this code eliminated.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onKernelRequestPathResolve(GetResponseEvent $event) {
+
+    $request = $event->getRequest();
+
+    $path = ltrim($request->getPathInfo(), '/');
+
+    if (empty($path)) {
+      // @todo Temporary hack. Fix when configuration is injectable.
+      $path = variable_get('site_frontpage', 'user');
+    }
+    $system_path = drupal_get_normal_path($path);
+
+    $request->attributes->set('system_path', $system_path);
+
+    // @todo Remove this line.
+    // Drupal uses $_GET['q'] directly in over 100 places at present,
+    // including writing back to it at times. Those are all critical bugs,
+    // even by Drupal 7 standards, but as many of the places that it does so
+    // are slated to be rewritten anyway we will save time and include this
+    // temporary hack. Removal of this line is a critical, Drupal-release
+    // blocking bug.
+    $_GET['q'] = $system_path;
+  }
+
+  /**
+   * 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('onKernelRequestPathResolve', 100);
+
+    return $events;
+  }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php
new file mode 100644
index 0000000..91f768b
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace Drupal\Core\EventSubscriber;
+
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event\PostResponseEvent;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+
+/**
+ * @file
+ *
+ * Definition of Drupal\Core\EventSubscriber\RequestCloseSubscriber;
+ */
+
+/**
+ * Subscriber for all responses.
+ */
+class RequestCloseSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Performs end of request tasks.
+   *
+   * @todo The body of this function has just been copied almost verbatim from
+   * drupal_page_footer(), with the exception of now passing the response
+   * content to drupal_page_set_cache(). There's probably a lot in here that
+   * needs to get removed/changed.
+   *
+   * @param PostResponseEvent $event
+   *   The Event to process.
+   */
+  public function onTerminate(PostResponseEvent $event) {
+
+    global $user;
+    module_invoke_all('exit');
+
+    // Commit the user session, if needed.
+    drupal_session_commit();
+    $response = $event->getResponse();
+    $config = config('system.performance');
+
+    if ($config->get('cache') && ($cache = drupal_page_set_cache($response->getContent()))) {
+      drupal_serve_page_from_cache($cache);
+    }
+    else {
+      ob_flush();
+    }
+
+    _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE);
+    drupal_cache_system_paths();
+    module_implements_write_cache();
+    system_run_automated_cron();
+  }
+
+  /**
+   * Registers the methods in this class that should be listeners.
+   *
+   * @return array
+   *   An array of event listener definitions.
+   */
+  static function getSubscribedEvents() {
+    $events[KernelEvents::TERMINATE][] = array('onTerminate');
+
+    return $events;
+  }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
new file mode 100644
index 0000000..647b641
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
@@ -0,0 +1,131 @@
+<?php
+
+namespace Drupal\Core\EventSubscriber;
+
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+use Drupal\Core\ContentNegotiation;
+
+/**
+ * @file
+ *
+ * Definition of Drupal\Core\EventSubscriber\ViewSubscriber;
+ */
+
+/**
+ * Main subscriber for VIEW HTTP responses.
+ */
+class ViewSubscriber implements EventSubscriberInterface {
+
+  protected $negotiation;
+
+  public function __construct(ContentNegotiation $negotiation) {
+    $this->negotiation = $negotiation;
+  }
+
+  /**
+   * Processes a successful controller into an HTTP 200 response.
+   *
+   * Some controllers may not return a response object but simply the body of
+   * one.  The VIEW event is called in that case, to allow us to mutate that
+   * body into a Response object.  In particular we assume that the return
+   * from an JSON-type response is a JSON string, so just wrap it into a
+   * Response object.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onView(GetResponseEvent $event) {
+
+    $request = $event->getRequest();
+
+    $method = 'on' . $this->negotiation->getContentType($request);
+
+    if (method_exists($this, $method)) {
+      $event->setResponse($this->$method($event));
+    }
+    else {
+      $event->setResponse(new Response('Unsupported Media Type', 415));
+    }
+  }
+
+  public function onJson(GetResponseEvent $event) {
+    $page_callback_result = $event->getControllerResult();
+
+    //print_r($page_callback_result);
+
+    $response = new JsonResponse();
+    $response->setContent($page_callback_result);
+
+    return $response;
+  }
+
+  public function onAjax(GetResponseEvent $event) {
+    $page_callback_result = $event->getControllerResult();
+
+    // Construct the response content from the page callback result.
+    $commands = ajax_prepare_response($page_callback_result);
+    $json = ajax_render($commands);
+
+    // Build the actual response object.
+    $response = new JsonResponse();
+    $response->setContent($json);
+
+    return $response;
+  }
+
+  public function onIframeUpload(GetResponseEvent $event) {
+    $page_callback_result = $event->getControllerResult();
+
+    // Construct the response content from the page callback result.
+    $commands = ajax_prepare_response($page_callback_result);
+    $json = ajax_render($commands);
+
+    // Browser IFRAMEs expect HTML. Browser extensions, such as Linkification
+    // and Skype's Browser Highlighter, convert URLs, phone numbers, etc. into
+    // links. This corrupts the JSON response. Protect the integrity of the
+    // JSON data by making it the value of a textarea.
+    // @see http://malsup.com/jquery/form/#file-upload
+    // @see http://drupal.org/node/1009382
+    $html = '<textarea>' . $json . '</textarea>';
+
+    return new Response($html);
+  }
+
+  /**
+   * Processes a successful controller into an HTTP 200 response.
+   *
+   * Some controllers may not return a response object but simply the body of
+   * one.  The VIEW event is called in that case, to allow us to mutate that
+   * body into a Response object.  In particular we assume that the return
+   * from an HTML-type response is a render array from a legacy page callback
+   * and render it.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onHtml(GetResponseEvent $event) {
+    $page_callback_result = $event->getControllerResult();
+    return new Response(drupal_render_page($page_callback_result));
+  }
+
+  /**
+   * Registers the methods in this class that should be listeners.
+   *
+   * @return array
+   *   An array of event listener definitions.
+   */
+  static function getSubscribedEvents() {
+    $events[KernelEvents::VIEW][] = array('onView');
+
+    return $events;
+  }
+}
diff --git a/core/lib/Drupal/Core/ExceptionController.php b/core/lib/Drupal/Core/ExceptionController.php
new file mode 100644
index 0000000..3e92e83
--- /dev/null
+++ b/core/lib/Drupal/Core/ExceptionController.php
@@ -0,0 +1,211 @@
+<?php
+
+/**
+ * @file
+ *
+ * Definition of Drupal\Core\ExceptionController.
+ */
+
+namespace Drupal\Core;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\JsonResponse;
+
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\HttpKernel\Controller\ControllerResolver;
+use Symfony\Component\HttpKernel\Exception\FlattenException;
+
+use Exception;
+
+/**
+ * This controller handles HTTP errors generated by the routing system.
+ */
+class ExceptionController {
+
+  /**
+   * The kernel that spawned this controller.
+   *
+   * We will use this to fire subrequests as needed.
+   *
+   * @var HttpKernelInterface
+   */
+  protected $kernel;
+
+  /**
+   * The
+   *
+   * @var ContentNegotiation
+   */
+  protected $negotiation;
+
+  /**
+   * Constructor
+   *
+   * @param HttpKernelInterface $kernel
+   *   The kernel that spawned this controller, so that it can be reused
+   *   for subrequests.
+   * @param ContentNegotiation $negotiation
+   *   The content negotiation library to use to determine the correct response
+   *   format.
+   */
+  public function __construct(HttpKernelInterface $kernel, ContentNegotiation $negotiation) {
+    $this->kernel = $kernel;
+    $this->negotiation = $negotiation;
+  }
+
+  /**
+   * Handles an exception on a request.
+   *
+   * @param FlattenException $exception
+   *   The flattened exception.
+   * @param Request $request
+   *   The request that generated the exception.
+   * @return \Symfony\Component\HttpFoundation\Response
+   *   A response object to be sent to the server.
+   */
+  public function execute(FlattenException $exception, Request $request) {
+
+    $method = 'on' . $exception->getStatusCode() . $this->negotiation->getContentType($request);
+
+    if (method_exists($this, $method)) {
+      return $this->$method($exception, $request);
+    }
+
+    return new Response('A fatal error occurred: ' . $exception->getMessage(), $exception->getStatusCode());
+
+  }
+
+  /**
+   * Processes a MethodNotAllowed exception into an HTTP 405 response.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function on405Html(FlattenException $exception, Request $request) {
+    $event->setResponse(new Response('Method Not Allowed', 405));
+  }
+
+  /**
+   * Processes an AccessDenied exception into an HTTP 403 response.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function on403Html(FlattenException $exception, Request $request) {
+    $system_path = $request->attributes->get('system_path');
+    watchdog('access denied', $system_path, NULL, WATCHDOG_WARNING);
+
+    $path = drupal_get_normal_path(variable_get('site_403', ''));
+    if ($path && $path != $system_path) {
+      // Keep old path for reference, and to allow forms to redirect to it.
+      if (!isset($_GET['destination'])) {
+        $_GET['destination'] = $system_path;
+      }
+
+      $subrequest = Request::create('/' . $path, 'get', array('destination' => $system_path), $request->cookies->all(), array(), $request->server->all());
+
+      $response = $this->kernel->handle($subrequest, DrupalKernel::SUB_REQUEST);
+      $response->setStatusCode(403, 'Access denied');
+    }
+    else {
+      $response = new Response('Access Denied', 403);
+
+      // @todo Replace this block with something cleaner.
+      $return = t('You are not authorized to access this page.');
+      drupal_set_title(t('Access denied'));
+      drupal_set_page_content($return);
+      $page = element_info('page');
+      $content = drupal_render_page($page);
+
+      $response->setContent($content);
+    }
+
+    return $response;
+  }
+
+  /**
+   * Processes a NotFound exception into an HTTP 403 response.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function on404Html(FlattenException $exception, Request $request) {
+    watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
+
+    // Check for and return a fast 404 page if configured.
+    // @todo Inline this rather than using a function.
+    drupal_fast_404();
+
+    $system_path = $request->attributes->get('system_path');
+
+    // Keep old path for reference, and to allow forms to redirect to it.
+    if (!isset($_GET['destination'])) {
+      $_GET['destination'] = $system_path;
+    }
+
+    $path = drupal_get_normal_path(variable_get('site_404', ''));
+    if ($path && $path != $system_path) {
+      // @todo: Um, how do I specify an override URL again? Totally not clear.
+      // Do that and sub-call the kernel rather than using meah().
+      // @todo: The create() method expects a slash-prefixed path, but we
+      // store a normal system path in the site_404 variable.
+      $subrequest = Request::create('/' . $path, 'get', array(), $request->cookies->all(), array(), $request->server->all());
+
+      $response = $this->kernel->handle($subrequest, HttpKernelInterface::SUB_REQUEST);
+      $response->setStatusCode(404, 'Not Found');
+    }
+    else {
+      $response = new Response('Not Found', 404);
+
+      // @todo Replace this block with something cleaner.
+      $return = t('The requested page "@path" could not be found.', array('@path' => $request->getPathInfo()));
+      drupal_set_title(t('Page not found'));
+      drupal_set_page_content($return);
+      $page = element_info('page');
+      $content = drupal_render_page($page);
+
+      $response->setContent($content);
+    }
+
+    return $response;
+  }
+
+  /**
+   * Processes an AccessDenied exception into an HTTP 403 response.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function on403Json(FlattenException $exception, Request $request) {
+    $response = new JsonResponse();
+    $response->setStatusCode(403, 'Access Denied');
+    return $response;
+  }
+
+  /**
+   * Processes a NotFound exception into an HTTP 404 response.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function on404Json(FlattenException $exception, Request $request) {
+    $response = new JsonResponse();
+    $response->setStatusCode(404, 'Not Found');
+    return $response;
+  }
+
+  /**
+   * Processes a MethodNotAllowed exception into an HTTP 405 response.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function on405Json(FlattenException $exception, Request $request) {
+    $response = new JsonResponse();
+    $response->setStatusCode(405, 'Method Not Allowed');
+    return $response;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
index 0cd76e0..1b4abc9 100644
--- a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
+++ b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
@@ -44,7 +44,7 @@ abstract class LocalStream implements StreamWrapperInterface {
 
   /**
    * Gets the path that the wrapper is responsible for.
-   * @TODO: Review this method name in D8 per http://drupal.org/node/701358
+   * @todo: Review this method name in D8 per http://drupal.org/node/701358
    *
    * @return string
    *   String specifying the path.
diff --git a/core/lib/Drupal/Core/Updater/Updater.php b/core/lib/Drupal/Core/Updater/Updater.php
index 2dca5ba..ad2213a 100644
--- a/core/lib/Drupal/Core/Updater/Updater.php
+++ b/core/lib/Drupal/Core/Updater/Updater.php
@@ -213,7 +213,7 @@ class Updater {
       $this->makeWorldReadable($filetransfer, $args['install_dir'] . '/' . $this->name);
 
       // Run the updates.
-      // @TODO: decide if we want to implement this.
+      // @todo: decide if we want to implement this.
       $this->postUpdate();
 
       // For now, just return a list of links of things to do.
@@ -252,7 +252,7 @@ class Updater {
       $this->makeWorldReadable($filetransfer, $args['install_dir'] . '/' . $this->name);
 
       // Potentially enable something?
-      // @TODO: decide if we want to implement this.
+      // @todo: decide if we want to implement this.
       $this->postInstall();
       // For now, just return a list of links of things to do.
       return $this->postInstallTasks();
diff --git a/core/lib/Drupal/Core/UrlMatcher.php b/core/lib/Drupal/Core/UrlMatcher.php
new file mode 100644
index 0000000..585fc45
--- /dev/null
+++ b/core/lib/Drupal/Core/UrlMatcher.php
@@ -0,0 +1,111 @@
+<?php
+
+/**
+ * @file
+ *
+ * Definition of Drupal\Core\UrlMatcher.
+ */
+
+namespace Drupal\Core;
+
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\Matcher\UrlMatcher as SymfonyUrlMatcher;
+use Symfony\Component\Routing\RequestContext;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * UrlMatcher matches URL based on a set of routes.
+ */
+class UrlMatcher extends SymfonyUrlMatcher {
+
+  /**
+   * The request context for this matcher.
+   *
+   * @var RequestContext
+   */
+  protected $context;
+
+  /**
+   * Constructor.
+   *
+   * @param RequestContext  $context
+   *   The request context object.
+   */
+  public function __construct(RequestContext $context) {
+    $this->context = $context;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * @api
+   */
+  public function match($pathinfo) {
+
+    $this->allow = array();
+
+    // Symfony uses a prefixing / but we don't yet.
+    $dpathinfo = ltrim($pathinfo, '/');
+
+    // Do our fancy frontpage logic.
+    if (empty($dpathinfo)) {
+      $dpathinfo = variable_get('site_frontpage', 'user');
+      $pathinfo = '/' . $dpathinfo;
+    }
+
+    if ($router_item = $this->matchDrupalItem($dpathinfo)) {
+      $ret = $this->convertDrupalItem($router_item);
+      // Stash the router item in the attributes while we're transitioning.
+      $ret['drupal_menu_item'] = $router_item;
+
+      // Most legacy controllers (aka page callbacks) are in a separate file,
+      // so we have to include that.
+      if ($router_item['include_file']) {
+        require_once DRUPAL_ROOT . '/' . $router_item['include_file'];
+      }
+
+      return $ret;
+    }
+
+    // This matcher doesn't differentiate by method, so don't bother with those
+    // exceptions.
+    throw new ResourceNotFoundException();
+  }
+
+  /**
+   * Get a drupal menu item.
+   *
+   * @todo Make this return multiple possible candidates for the resolver to
+   * consider.
+   *
+   * @param string $path
+   *   The path being looked up by
+   */
+  protected function matchDrupalItem($path) {
+    // For now we can just proxy our procedural method. At some point this will
+    // become more complicated because we'll need to get back candidates for a
+    // path and them resolve them based on things like method and scheme which
+    // we currently can't do.
+    return menu_get_item($path);
+  }
+
+  protected function convertDrupalItem($router_item) {
+    $route = array(
+      '_controller' => $router_item['page_callback']
+    );
+
+    // Place argument defaults on the route.
+    // @todo: For some reason drush test runs have a serialized page_arguments
+    // but HTTP requests are unserialized. Hack to get around this for now.
+    // This might be because page arguments aren't unserialized in
+    // menu_get_item() when the access is denied.
+    !is_array($router_item['page_arguments']) ? $page_arguments = unserialize($router_item['page_arguments']) : $page_arguments = $router_item['page_arguments'];
+    foreach ($page_arguments as $k => $v) {
+      $route[$k] = $v;
+    }
+    return $route;
+    return new Route($router_item['href'], $route);
+  }
+}
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index a714419..e4dab80 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -275,7 +275,8 @@ function comment_menu() {
     'title' => 'Approve',
     'page callback' => 'comment_approve',
     'page arguments' => array(1),
-    'access arguments' => array('administer comments'),
+    'access callback' => 'comment_approve_access',
+    'access arguments' => array(1),
     'file' => 'comment.pages.inc',
     'weight' => 1,
   );
@@ -2522,3 +2523,23 @@ function comment_file_download_access($field, $entity_type, $entity) {
     return FALSE;
   }
 }
+
+/**
+ * Access callback: Determines if comment approval is accessible.
+ *
+ * @param $cid
+ *   A comment identifier.
+ *
+ * @see comment_approve()
+ * @see comment_menu()
+ */
+function comment_approve_access($cid) {
+  if (!user_access('administer comments')) {
+    return FALSE;
+  }
+  $token = request()->query->get('token');
+  if (!isset($token) || !drupal_valid_token($token, "comment/$cid/approve")) {
+    return FALSE;
+  }
+  return TRUE;
+}
diff --git a/core/modules/comment/comment.pages.inc b/core/modules/comment/comment.pages.inc
index 21fe465..02020ff 100644
--- a/core/modules/comment/comment.pages.inc
+++ b/core/modules/comment/comment.pages.inc
@@ -103,11 +103,9 @@ function comment_reply($node, $pid = NULL) {
  *   A comment identifier.
  *
  * @see comment_menu()
+ * @see comment_approve_access()
  */
 function comment_approve($cid) {
-  if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], "comment/$cid/approve")) {
-    return MENU_ACCESS_DENIED;
-  }
   if ($comment = comment_load($cid)) {
     $comment->status = COMMENT_PUBLISHED;
     comment_save($comment);
@@ -115,5 +113,5 @@ function comment_approve($cid) {
     drupal_set_message(t('Comment approved.'));
     drupal_goto('node/' . $comment->nid);
   }
-  return MENU_NOT_FOUND;
+  drupal_not_found();
 }
diff --git a/core/modules/overlay/overlay.module b/core/modules/overlay/overlay.module
index 144c2ab..862a896 100644
--- a/core/modules/overlay/overlay.module
+++ b/core/modules/overlay/overlay.module
@@ -5,6 +5,8 @@
  * Displays the Drupal administration interface in an overlay.
  */
 
+use Symfony\Component\HttpFoundation\Response;
+
 /**
  * Implements hook_help().
  */
@@ -19,7 +21,7 @@ function overlay_help($path, $arg) {
 }
 
 /**
- * Implements hook_menu().
+ * Implements hook_menu()
  */
 function overlay_menu() {
   $items['overlay-ajax/%'] = array(
@@ -979,5 +981,5 @@ function overlay_trigger_refresh() {
  * @see Drupal.overlay.refreshRegions()
  */
 function overlay_ajax_render_region($region) {
-  print overlay_render_region($region);
+  return new Response(overlay_render_region($region));
 }
diff --git a/core/modules/simpletest/drupal_web_test_case.php b/core/modules/simpletest/drupal_web_test_case.php
index 7163739..59eb239 100644
--- a/core/modules/simpletest/drupal_web_test_case.php
+++ b/core/modules/simpletest/drupal_web_test_case.php
@@ -1837,6 +1837,7 @@ class DrupalWebTestCase extends DrupalTestCase {
    * Retrieve a Drupal path or an absolute path and JSON decode the result.
    */
   protected function drupalGetAJAX($path, array $options = array(), array $headers = array()) {
+    $headers[] = 'X-Requested-With: XMLHttpRequest';
     return drupal_json_decode($this->drupalGet($path, $options, $headers));
   }
 
@@ -2044,6 +2045,7 @@ class DrupalWebTestCase extends DrupalTestCase {
     }
     $content = $this->content;
     $drupal_settings = $this->drupalSettings;
+    $headers[] = 'X-Requested-With: XMLHttpRequest';
 
     // Get the Ajax settings bound to the triggering element.
     if (!isset($ajax_settings)) {
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index aac0c3d..34b3798 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -5,6 +5,8 @@
  * Admin page callbacks for the system module.
  */
 
+use Symfony\Component\HttpFoundation\Response;
+
 /**
  * Menu callback; Provide the administration overview page.
  */
@@ -2357,6 +2359,9 @@ function system_batch_page() {
   if ($output === FALSE) {
     drupal_access_denied();
   }
+  elseif ($output instanceof Response) {
+    return $output;
+  }
   elseif (isset($output)) {
     // Force a page without blocks or messages to
     // display a list of collected messages later.
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index a3594b9..ccaac21 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1983,8 +1983,11 @@ function system_add_module_assets() {
  * Implements hook_custom_theme().
  */
 function system_custom_theme() {
-  if (user_access('view the administration theme') && path_is_admin(current_path())) {
-    return variable_get('admin_theme');
+  if ($request = request()) {
+    $path = ltrim($request->getPathInfo(), '/');
+    if (user_access('view the administration theme') && path_is_admin($path)) {
+      return variable_get('admin_theme');
+    }
   }
 }
 
diff --git a/core/modules/system/tests/file.test b/core/modules/system/tests/file.test
index c5eced1..00bda25 100644
--- a/core/modules/system/tests/file.test
+++ b/core/modules/system/tests/file.test
@@ -2423,7 +2423,7 @@ class FileDownloadTest extends FileTestCase {
 
     $this->checkUrl('public', '', $basename, $base_url . '/' . file_stream_wrapper_get_instance_by_scheme('public')->getDirectoryPath() . '/' . $basename_encoded);
     $this->checkUrl('private', '', $basename, $base_url . '/system/files/' . $basename_encoded);
-    $this->checkUrl('private', '', $basename, $base_url . '/?q=system/files/' . $basename_encoded, '0');
+    $this->checkUrl('private', '', $basename, $base_url . '/index.php/system/files/' . $basename_encoded, '0');
   }
 
   /**
diff --git a/core/modules/taxonomy/taxonomy.test b/core/modules/taxonomy/taxonomy.test
index 65d6805..b3d0080 100644
--- a/core/modules/taxonomy/taxonomy.test
+++ b/core/modules/taxonomy/taxonomy.test
@@ -756,9 +756,8 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase {
     $path = 'taxonomy/autocomplete/taxonomy_';
     $path .= $this->vocabulary->machine_name . '/' . $input;
     // The result order is not guaranteed, so check each term separately.
-    $url = url($path, array('absolute' => TRUE));
-    $result = drupal_http_request($url);
-    $data = drupal_json_decode($result->data);
+    $result = $this->drupalGet($path);
+    $data = drupal_json_decode($result);
     $this->assertEqual($data[$first_term->name], check_plain($first_term->name), 'Autocomplete returned the first matching term');
     $this->assertEqual($data[$second_term->name], check_plain($second_term->name), 'Autocomplete returned the second matching term');
 
diff --git a/index.php b/index.php
index b91fb1e..6ebb47f 100644
--- a/index.php
+++ b/index.php
@@ -11,11 +11,34 @@
  * See COPYRIGHT.txt and LICENSE.txt files in the "core" directory.
  */
 
+use Drupal\Core\DrupalKernel;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\HttpKernel\Controller\ControllerResolver;
+
 /**
  * Root directory of Drupal installation.
  */
 define('DRUPAL_ROOT', getcwd());
-
+// Bootstrap the lowest level of what we need.
 require_once DRUPAL_ROOT . '/core/includes/bootstrap.inc';
+drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
+
+// A request object from the HTTPFoundation to tell us about the request.
+$request = Request::createFromGlobals();
+
+// Set the global $request object.  This is a temporary measure to
+// keep legacy utility functions working.  It should be moved to a dependency
+// injection container at some point.
+request($request);
+
 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
-menu_execute_active_handler();
+
+$dispatcher = new EventDispatcher();
+$resolver = new ControllerResolver();
+
+$kernel = new DrupalKernel($dispatcher, $resolver);
+$response = $kernel->handle($request);
+$response->prepare($request);
+$response->send();
+$kernel->terminate($request, $response);
