diff --git a/core/core.services.yml b/core/core.services.yml
index 631b2a4..81bb939 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -112,6 +112,9 @@ services:
     class: Drupal\Core\PageCache\ResponsePolicy\KillSwitch
     tags:
       - { name: page_cache_response_policy }
+  page_cache_store:
+    class: Drupal\Core\PageCache\CacheStore
+    arguments: ['@cache.render']
   config.manager:
     class: Drupal\Core\Config\ConfigManager
     arguments: ['@entity.manager', '@config.factory', '@config.typed', '@string_translation', '@config.storage', '@event_dispatcher']
@@ -406,7 +409,7 @@ services:
       - { name: http_middleware, priority: 300 }
   http_middleware.page_cache:
     class: Drupal\Core\StackMiddleware\PageCache
-    arguments: ['@kernel']
+    arguments: ['@page_cache_store', '@config.factory', '@page_cache_request_policy', '@page_cache_response_policy']
     tags:
       - { name: http_middleware, priority: 200 }
   http_middleware.kernel_pre_handle:
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 553811f..2e98c6c 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -131,25 +131,18 @@
 const DRUPAL_BOOTSTRAP_KERNEL = 1;
 
 /**
- * Third bootstrap phase: try to serve a cached page.
- *
- * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
- */
-const DRUPAL_BOOTSTRAP_PAGE_CACHE = 2;
-
-/**
  * Fourth bootstrap phase: load code for subsystems and modules.
  *
  * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
  */
-const DRUPAL_BOOTSTRAP_CODE = 3;
+const DRUPAL_BOOTSTRAP_CODE = 2;
 
 /**
  * Final bootstrap phase: initialize language, path, theme, and modules.
  *
  * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
  */
-const DRUPAL_BOOTSTRAP_FULL = 4;
+const DRUPAL_BOOTSTRAP_FULL = 3;
 
 /**
  * Role ID for anonymous users; should match what's in the "role" table.
@@ -374,22 +367,6 @@ function drupal_page_cache_get_cid(Request $request) {
 }
 
 /**
- * Retrieves the current page from the cache.
- *
- * @param \Symfony\Component\HttpFoundation\Request $request
- *   The request for this page.
- *
- * @return \Symfony\Component\HttpFoundation\Response
- *   The response, if the page was found in the cache, NULL otherwise.
- */
-function drupal_page_get_cache(Request $request) {
-  $cache = \Drupal::cache('render')->get(drupal_page_cache_get_cid($request));
-  if ($cache) {
-    return $cache->data;
-  }
-}
-
-/**
  * Sets an HTTP response header for the current page.
  *
  * Note: When sending a Content-Type header, always include a 'charset' type,
@@ -1008,10 +985,6 @@ function drupal_bootstrap($phase = NULL) {
           $kernel->boot();
           break;
 
-        case DRUPAL_BOOTSTRAP_PAGE_CACHE:
-          $kernel->handlePageCache($request);
-          break;
-
         case DRUPAL_BOOTSTRAP_CODE:
         case DRUPAL_BOOTSTRAP_FULL:
           $kernel->prepareLegacyRequest($request);
diff --git a/core/includes/common.inc b/core/includes/common.inc
index fde8b06..2f2be27 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -2278,50 +2278,6 @@ function drupal_clear_js_cache() {
 }
 
 /**
- * Stores the current page in the cache.
- *
- * If page_compression is enabled, a gzipped version of the page is stored in
- * the cache to avoid compressing the output on each request. The cache entry
- * is unzipped in the relatively rare event that the page is requested by a
- * client without gzip support.
- *
- * Page compression requires the PHP zlib extension
- * (http://php.net/manual/ref.zlib.php).
- *
- * @param \Symfony\Component\HttpFoundation\Response $response
- *   The fully populated response.
- * @param \Symfony\Component\HttpFoundation\Request $request
- *   The request for this page.
- *
- * @see drupal_page_header()
- */
-function drupal_page_set_cache(Response $response, Request $request) {
-  // Check if the current page may be compressed.
-  if (\Drupal::config('system.performance')->get('response.gzip') &&
-    !$response->headers->get('Content-Encoding') && extension_loaded('zlib')) {
-
-    $content = $response->getContent();
-    if ($content) {
-      $response->setContent(gzencode($content, 9, FORCE_GZIP));
-      $response->headers->set('Content-Encoding', 'gzip');
-    }
-
-    // When page compression is enabled, ensure that proxy caches will record
-    // and deliver different versions of a page depending on whether the
-    // client supports gzip or not.
-    $response->setVary('Accept-Encoding', FALSE);
-  }
-
-  // Use the actual timestamp from an Expires header, if available.
-  $date = $response->getExpires();
-  $expire = ($date > (new DateTime())) ? $date->getTimestamp() : Cache::PERMANENT;
-
-  $cid = drupal_page_cache_get_cid($request);
-  $tags = HtmlViewSubscriber::convertHeaderToCacheTags($response->headers->get('X-Drupal-Cache-Tags'));
-  \Drupal::cache('render')->set($cid, $response, $expire, $tags);
-}
-
-/**
  * Sets the main page content value for later use.
  *
  * Given the nature of the Drupal page handling, this will be called once with
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
index 8514448..0e704c3 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -450,43 +450,6 @@ public function preHandle(Request $request) {
 
   /**
    * {@inheritdoc}
-   *
-   * @todo Invoke proper request/response/terminate events.
-   */
-  public function handlePageCache(Request $request) {
-    $this->boot();
-    $this->initializeCookieGlobals($request);
-
-    // Check for a cache mode force from settings.php.
-    if (Settings::get('page_cache_without_database')) {
-      $cache_enabled = TRUE;
-    }
-    else {
-      $config = $this->getContainer()->get('config.factory')->get('system.performance');
-      $cache_enabled = $config->get('cache.page.use_internal');
-    }
-
-    $request_policy = \Drupal::service('page_cache_request_policy');
-    if ($cache_enabled && $request_policy->check($request) === RequestPolicyInterface::ALLOW) {
-      // Get the page from the cache.
-      $response = drupal_page_get_cache($request);
-      // If there is a cached page, display it.
-      if ($response) {
-        $response->headers->set('X-Drupal-Cache', 'HIT');
-
-        drupal_serve_page_from_cache($response, $request);
-
-        // We are done.
-        $response->prepare($request);
-        $response->send();
-        exit;
-      }
-    }
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
    */
   public function discoverServiceProviders() {
     $this->serviceYamls = array(
diff --git a/core/lib/Drupal/Core/DrupalKernelInterface.php b/core/lib/Drupal/Core/DrupalKernelInterface.php
index ca7af2b..0db4405 100644
--- a/core/lib/Drupal/Core/DrupalKernelInterface.php
+++ b/core/lib/Drupal/Core/DrupalKernelInterface.php
@@ -86,16 +86,6 @@ public function getSitePath();
   public function updateModules(array $module_list, array $module_filenames = array());
 
   /**
-   * Attempts to serve a page from the cache.
-   *
-   * @param \Symfony\Component\HttpFoundation\Request $request
-   *   The current request.
-   *
-   * @return $this
-   */
-  public function handlePageCache(Request $request);
-
-  /**
    * Prepare the kernel for handling a request without handling the request.
    *
    * @param \Symfony\Component\HttpFoundation\Request $request
diff --git a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
index c5a7be1..4136285 100644
--- a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
@@ -122,17 +122,6 @@ public function onRespond(FilterResponseEvent $event) {
       // header declaring the response as not cacheable.
       $this->setResponseNotCacheable($response, $request);
     }
-
-    // Currently it is not possible to cache some types of responses. Therefore
-    // exclude binary file responses (generated files, e.g. images with image
-    // styles) and streamed responses (files directly read from the disk).
-    // see: https://github.com/symfony/symfony/issues/9128#issuecomment-25088678
-    if ($is_cacheable && $this->config->get('cache.page.use_internal') && !($response instanceof BinaryFileResponse) && !($response instanceof StreamedResponse)) {
-      // Store the response in the internal page cache.
-      drupal_page_set_cache($response, $request);
-      $response->headers->set('X-Drupal-Cache', 'MISS');
-      drupal_serve_page_from_cache($response, $request);
-    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/PageCache/CacheStore.php b/core/lib/Drupal/Core/PageCache/CacheStore.php
new file mode 100644
index 0000000..20bf785
--- /dev/null
+++ b/core/lib/Drupal/Core/PageCache/CacheStore.php
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\PageCache\CacheStore.
+ */
+
+namespace Drupal\Core\PageCache;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\EventSubscriber\HtmlViewSubscriber;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\HttpCache\StoreInterface;
+
+/**
+ * Implements a Symfony HTTP cache store based on the Drupal cache.
+ */
+class CacheStore implements StoreInterface {
+
+  /**
+   * The cache bin.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cache;
+
+  /**
+   * Constructs a new HTTP cache store based on the Drupal cache.
+   *
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
+   *   The cache bin.
+   */
+  public function __construct(CacheBackendInterface $cache) {
+    $this->cache = $cache;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function lookup(Request $request) {
+    $cache = $this->cache->get(drupal_page_cache_get_cid($request));
+    if ($cache) {
+      $response = $cache->data;
+      $response->headers->set('X-Drupal-Cache', 'HIT');
+
+      return $response;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function write(Request $request, Response $response) {
+    // Check if the current page may be compressed.
+    if (\Drupal::config('system.performance')->get('response.gzip') &&
+      !$response->headers->get('Content-Encoding') && extension_loaded('zlib')) {
+
+      $content = $response->getContent();
+      if ($content) {
+        $response->setContent(gzencode($content, 9, FORCE_GZIP));
+        $response->headers->set('Content-Encoding', 'gzip');
+      }
+
+      // When page compression is enabled, ensure that proxy caches will
+      // record and deliver different versions of a page depending on whether
+      // the client supports gzip or not.
+      $response->setVary('Accept-Encoding', FALSE);
+    }
+
+    // Use the actual timestamp from an Expires header, if available.
+    $date = $response->getExpires();
+    $expire = ($date > (new \DateTime())) ? $date->getTimestamp() : Cache::PERMANENT;
+
+    $cid = drupal_page_cache_get_cid($request);
+    $tags = HtmlViewSubscriber::convertHeaderToCacheTags($response->headers->get('X-Drupal-Cache-Tags'));
+    $this->cache->set($cid, $response, $expire, $tags);
+
+    $response->headers->set('X-Drupal-Cache', 'MISS');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function invalidate(Request $request) {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function lock(Request $request) {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function unlock(Request $request) {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isLocked(Request $request) {
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function purge($url) {
+    throw new \BadMethodCallException('Not yet implemented');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function cleanup() {
+  }
+}
diff --git a/core/lib/Drupal/Core/StackMiddleware/PageCache.php b/core/lib/Drupal/Core/StackMiddleware/PageCache.php
index ba59e93..a8c5d05 100644
--- a/core/lib/Drupal/Core/StackMiddleware/PageCache.php
+++ b/core/lib/Drupal/Core/StackMiddleware/PageCache.php
@@ -7,49 +7,113 @@
 
 namespace Drupal\Core\StackMiddleware;
 
+use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\DrupalKernelInterface;
+use Drupal\Core\PageCache\RequestPolicyInterface;
+use Drupal\Core\PageCache\ResponsePolicyInterface;
 use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\HttpCache\Esi;
+use Symfony\Component\HttpKernel\HttpCache\HttpCache;
+use Symfony\Component\HttpKernel\HttpCache\StoreInterface;
 use Symfony\Component\HttpKernel\HttpKernelInterface;
 
 /**
  * Executes the page caching before the main kernel takes over the request.
  */
-class PageCache implements HttpKernelInterface {
+class PageCache extends HttpCache {
 
   /**
-   * The wrapped HTTP kernel.
+   * A config object for the system performance configuration.
    *
-   * @var \Symfony\Component\HttpKernel\HttpKernelInterface
+   * @var \Drupal\Core\Config\Config
    */
-  protected $httpKernel;
+  protected $config;
 
   /**
-   * The main Drupal kernel.
+   * The request policy.
    *
-   * @var \Drupal\Core\DrupalKernelInterface
+   * @var \Drupal\Core\PageCache\RequestPolicyInterface
    */
-  protected $drupalKernel;
+  protected $requestPolicy;
 
   /**
-   * Constructs a ReverseProxyMiddleware object.
+   * The response policy.
    *
-   * @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
-   *   The decorated kernel.
-   * @param \Drupal\Core\DrupalKernelInterface $drupal_kernel
-   *   The main Drupal kernel.
+   * @var \Drupal\Core\PageCache\ResponsePolicyInterface
    */
-  public function __construct(HttpKernelInterface $http_kernel, DrupalKernelInterface $drupal_kernel) {
-    $this->httpKernel = $http_kernel;
-    $this->drupalKernel = $drupal_kernel;
+  protected $responsePolicy;
+
+  /**
+   * Constructs a new page cache middleware.
+   *
+   * @param \Symfony\Component\HttpKernel\HttpKernelInterface $kernel
+   *   An HttpKernelInterface instance.
+   * @param \Symfony\Component\HttpKernel\HttpCache\StoreInterface $store
+   *   A Store instance
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   A config factory for retrieving required config objects.
+   * @param \Drupal\Core\PageCache\RequestPolicyInterface $request_policy
+   *   A policy rule determining the cacheability of a request.
+   * @param \Drupal\Core\PageCache\ResponsePolicyInterface $response_policy
+   *   A policy rule determining the cacheability of a response.
+   * @param \Symfony\Component\HttpKernel\HttpCache\Esi $esi
+   *   (optional) An Esi instance
+   * @param array $options
+   *   (optional) An array of options
+   */
+  public function __construct(HttpKernelInterface $kernel, StoreInterface $store, ConfigFactoryInterface $config_factory, RequestPolicyInterface $request_policy, ResponsePolicyInterface $response_policy, Esi $esi = NULL, array $options = array()) {
+    parent::__construct($kernel, $store, $esi, $options);
+    $this->config = $config_factory->get('system.performance');
+    $this->requestPolicy = $request_policy;
+    $this->responsePolicy = $response_policy;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function lookup(Request $request, $catch = FALSE) {
+    if ($this->config->get('cache.page.use_internal') && $this->requestPolicy->check($request) === RequestPolicyInterface::ALLOW) {
+      $response = parent::lookup($request, $catch);
+      drupal_serve_page_from_cache($response, $request);
+    }
+    else {
+      $response = $this->pass($request, $catch);
+    }
+
+    return $response;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
-    $this->drupalKernel->handlePageCache($request);
+  protected function store(Request $request, Response $response) {
+    // Currently it is not possible to cache some types of responses. Therefore
+    // exclude binary file responses (generated files, e.g. images with image
+    // styles) and streamed responses (files directly read from the disk).
+    // see: https://github.com/symfony/symfony/issues/9128#issuecomment-25088678
+    if ($response instanceof BinaryFileResponse || $response instanceof StreamedResponse) {
+      return;
+    }
+
+    if ($this->responsePolicy->check($response, $request) === ResponsePolicyInterface::DENY) {
+      return;
+    }
+
+    parent::store($request, $response);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function forward(Request $request, $catch = FALSE, Response $entry = NULL) {
+    $response = parent::forward($request, $catch, $entry);
+
+    // Disable the file inclusion / evaluation feature of the parent class.
+    $response->headers->remove('X-Body-Eval');
+    $response->headers->remove('X-Body-File');
 
-    return $this->httpKernel->handle($request, $type, $catch);
+    return $response;
   }
 
 }
diff --git a/core/modules/system/tests/http.php b/core/modules/system/tests/http.php
index 3386979..8c7d556 100644
--- a/core/modules/system/tests/http.php
+++ b/core/modules/system/tests/http.php
@@ -26,7 +26,6 @@
 $request = Request::createFromGlobals();
 $kernel = TestKernel::createFromRequest($request, $autoloader, 'testing', TRUE);
 $response = $kernel
-  ->handlePageCache($request)
   ->handle($request)
     // Handle the response object.
     ->prepare($request)->send();
diff --git a/core/modules/system/tests/https.php b/core/modules/system/tests/https.php
index 00a6b13..702184d 100644
--- a/core/modules/system/tests/https.php
+++ b/core/modules/system/tests/https.php
@@ -28,7 +28,6 @@
 $request = Request::createFromGlobals();
 $kernel = TestKernel::createFromRequest($request, $autoloader, 'testing', TRUE);
 $response = $kernel
-  ->handlePageCache($request)
   ->handle($request)
     // Handle the response object.
     ->prepare($request)->send();
