diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php
index 48684e5..d8f6960 100644
--- a/core/lib/Drupal/Core/CoreBundle.php
+++ b/core/lib/Drupal/Core/CoreBundle.php
@@ -93,6 +93,8 @@ public function build(ContainerBuilder $container) {
       ->addTag('nested_matcher', array('method' => 'setInitialMatcher'));
     $container->register('http_method_matcher', 'Drupal\Core\Routing\HttpMethodMatcher')
       ->addTag('nested_matcher', array('method' => 'addPartialMatcher'));
+    $container->register('mime_type_matcher', 'Drupal\Core\Routing\MimeTypeMatcher')
+      ->addTag('nested_matcher', array('method' => 'addPartialMatcher'));
     $container->register('first_entry_final_matcher', 'Drupal\Core\Routing\FirstEntryFinalMatcher')
       ->addTag('nested_matcher', array('method' => 'setFinalMatcher'));
 
diff --git a/core/lib/Drupal/Core/Routing/MimeTypeMatcher.php b/core/lib/Drupal/Core/Routing/MimeTypeMatcher.php
new file mode 100644
index 0000000..76c446e
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/MimeTypeMatcher.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Routing\MimeTypeMatcher.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\HttpException;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * This class filters routes based on the media type in HTTP Accept headers.
+ */
+class MimeTypeMatcher extends PartialMatcher {
+
+  /**
+   * Matches a request against multiple routes.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   A Request object against which to match.
+   *
+   * @return \Symfony\Component\Routing\RouteCollection
+   *   A RouteCollection of matched routes.
+   */
+  public function matchRequestPartial(Request $request) {
+
+    // Generates a list of Symfony formats matching the acceptable MIME types.
+    // @todo replace by proper content negotiation library.
+    $acceptable_mime_types = $request->getAcceptableContentTypes();
+    $acceptable_formats = array_map(array($request, 'getFormat'), $acceptable_mime_types);
+
+    $collection = new RouteCollection();
+
+    foreach ($this->routes->all() as $name => $route) {
+      // _format could be a |-delimited list of supported formats.
+      $supported_formats = array_filter(explode('|', $route->getRequirement('_format')));
+      // The route partially matches if it doesn't care about format, if it
+      // explicitly allows any format, or if one of its allowed formats is
+      // in the request's list of acceptable formats.
+      if (empty($supported_formats) || in_array('*/*', $acceptable_mime_types) || array_intersect($acceptable_formats, $supported_formats)) {
+        $collection->add($name, $route);
+      }
+    }
+
+    if (!count($collection)) {
+      // @todo update with UnsupportedMediaTypeException when available
+      // @see http://drupal.org/node/1831074
+      throw new HttpException(415);
+    }
+
+    return $collection;
+  }
+
+}
diff --git a/core/modules/jsonld/lib/Drupal/jsonld/EventSubscriber/JsonldSubscriber.php b/core/modules/jsonld/lib/Drupal/jsonld/EventSubscriber/JsonldSubscriber.php
new file mode 100644
index 0000000..f764bea
--- /dev/null
+++ b/core/modules/jsonld/lib/Drupal/jsonld/EventSubscriber/JsonldSubscriber.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\jsonld\EventSubscriber\JsonldSubscriber.
+ */
+
+namespace Drupal\jsonld\EventSubscriber;
+
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Subscribes to the kernel request event to add JSON-LD media formats.
+ */
+class JsonldSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Registers JSON-LD formats with the Request class.
+   *
+   * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+   *   The event to process.
+   */
+  public function onKernelRequest(GetResponseEvent $event) {
+    $event->getRequest()->setFormat('drupal_jsonld', 'application/vnd.drupal.ld+json');
+    $event->getRequest()->setFormat('jsonld', 'application/ld+json');
+  }
+
+  /**
+   * 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('onKernelRequest', 40);
+    return $events;
+  }
+
+}
diff --git a/core/modules/jsonld/lib/Drupal/jsonld/JsonldBundle.php b/core/modules/jsonld/lib/Drupal/jsonld/JsonldBundle.php
index f656e9b..1d3d44e 100644
--- a/core/modules/jsonld/lib/Drupal/jsonld/JsonldBundle.php
+++ b/core/modules/jsonld/lib/Drupal/jsonld/JsonldBundle.php
@@ -26,5 +26,8 @@ public function build(ContainerBuilder $container) {
     $container->register('serializer.encoder.jsonld', 'Drupal\jsonld\JsonldEncoder')->addTag('encoder', array('priority' => $priority));
     $container->register('serializer.normalizer.drupal_jsonld', 'Drupal\jsonld\DrupalJsonldNormalizer')->addTag('normalizer', array('priority' => $priority));
     $container->register('serializer.encoder.drupal_jsonld', 'Drupal\jsonld\DrupalJsonldEncoder')->addTag('encoder', array('priority' => $priority));
+
+    $container->register('jsonld.subscriber', 'Drupal\jsonld\EventSubscriber\JsonldSubscriber')
+      ->addTag('event_subscriber');
   }
 }
diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/ResourceBase.php b/core/modules/rest/lib/Drupal/rest/Plugin/ResourceBase.php
index 962af71..c036d31 100644
--- a/core/modules/rest/lib/Drupal/rest/Plugin/ResourceBase.php
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/ResourceBase.php
@@ -55,23 +55,43 @@ public function routes() {
     $collection = new RouteCollection();
 
     $methods = $this->requestMethods();
+    $prefix = strtr($this->plugin_id, ':', '/');
+    $name = strtr($this->plugin_id, ':', '.');
     foreach ($methods as $method) {
       // Only expose routes where the HTTP request method exists on the plugin.
       if (method_exists($this, strtolower($method))) {
-        $prefix = strtr($this->plugin_id, ':', '/');
-        $route = new Route("/$prefix/{id}", array(
-          '_controller' => 'Drupal\rest\RequestHandler::handle',
-          // @todo Once http://drupal.org/node/1793520 is committed we will have
-          // route object avaialble in the controller so 'plugin' property
-          // should be changed to '_plugin'.
-          // @see RequestHandler::handle().
-          'plugin' => $this->plugin_id,
-        ), array(
-          // The HTTP method is a requirement for this route.
-          '_method' => $method,
-        ));
+        // Restrict GET and HEAD requests to the media type specified in the
+        // HTTP Accept headers.
+        if ($method == 'GET' || $method == 'HEAD') {
+          $route = new Route("/$prefix/{id}", array(
+            '_controller' => 'Drupal\rest\RequestHandler::handle',
+            // @todo Once http://drupal.org/node/1793520 is committed we will have
+            // route object avaialble in the controller so 'plugin' property
+            // should be changed to '_plugin'.
+            // @see RequestHandler::handle().
+            'plugin' => $this->plugin_id,
+          ), array(
+            // The HTTP method is a requirement for this route.
+            '_method' => $method,
+            // The media type format is a requirement, too.
+            // @todo Replace hard coded format here with available formats.
+            '_format' => 'drupal_jsonld',
+          ));
+        }
+        else {
+          $route = new Route("/$prefix/{id}", array(
+            '_controller' => 'Drupal\rest\RequestHandler::handle',
+            // @todo Once http://drupal.org/node/1793520 is committed we will have
+            // route object avaialble in the controller so 'plugin' property
+            // should be changed to '_plugin'.
+            // @see RequestHandler::handle().
+            'plugin' => $this->plugin_id,
+          ), array(
+            // The HTTP method is a requirement for this route.
+            '_method' => $method,
+          ));
+        }
 
-        $name = strtr($this->plugin_id, ':', '.');
         $collection->add("$name.$method", $route);
       }
     }
diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/DBLogResource.php b/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/DBLogResource.php
index afc8d50..20223d7 100644
--- a/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/DBLogResource.php
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/DBLogResource.php
@@ -7,17 +7,19 @@
 
 namespace Drupal\rest\Plugin\rest\resource;
 
-use Drupal\rest\Plugin\ResourceBase;
 use Drupal\Core\Annotation\Plugin;
-use Symfony\Component\HttpFoundation\Response;
+use Drupal\Core\Annotation\Translation;
+use Drupal\rest\Plugin\ResourceBase;
+use Drupal\rest\ResourceResponse;
+
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
 /**
  * Provides a resource for database watchdog log entries.
  *
  * @Plugin(
- *  id = "dblog",
- *  label = "Watchdog database log"
+ *   id = "dblog",
+ *   label = @Translation("Watchdog database log")
  * )
  */
 class DBLogResource extends ResourceBase {
@@ -38,23 +40,24 @@ public function routes() {
    *
    * Returns a watchdog log entry for the specified ID.
    *
-   * @return \Symfony\Component\HttpFoundation\Response
-   *   The response object.
+   * @return \Drupal\rest\ResourceResponse
+   *   The response containing the log entry.
    *
    * @throws \Symfony\Component\HttpKernel\Exception\HttpException
    */
   public function get($id = NULL) {
     if ($id) {
-      $result = db_select('watchdog', 'w')
-        ->condition('wid', $id)
-        ->fields('w')
-        ->execute()
-        ->fetchAll();
-      if (empty($result)) {
-        throw new NotFoundHttpException('Not Found');
+      $result = db_query("SELECT * FROM {watchdog} WHERE wid = :wid", array(':wid' => $id))
+        ->fetchObject();
+      if (!empty($result)) {
+        // Serialization is done here, so we indicate with NULL that there is no
+        // subsequent serialization necessary.
+        $response = new ResourceResponse(NULL, 200, array('Content-Type' => 'application/vnd.drupal.ld+json'));
+        // @todo remove hard coded format here.
+        $response->setContent(drupal_json_encode($result));
+        return $response;
       }
-      // @todo remove hard coded format here.
-      return new Response(drupal_json_encode($result[0]), 200, array('Content-Type' => 'application/json'));
     }
+    throw new NotFoundHttpException('Not Found');
   }
 }
diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php b/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php
index 3524389..689fd84 100644
--- a/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php
@@ -8,9 +8,10 @@
 namespace Drupal\rest\Plugin\rest\resource;
 
 use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\rest\Plugin\ResourceBase;
-use Symfony\Component\HttpFoundation\Response;
+use Drupal\rest\ResourceResponse;
 use Symfony\Component\HttpKernel\Exception\HttpException;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
@@ -18,21 +19,41 @@
  * Represents entities as resources.
  *
  * @Plugin(
- *  id = "entity",
- *  label = "Entity",
- *  derivative = "Drupal\rest\Plugin\Derivative\EntityDerivative"
+ *   id = "entity",
+ *   label = @Translation("Entity"),
+ *   derivative = "Drupal\rest\Plugin\Derivative\EntityDerivative"
  * )
  */
 class EntityResource extends ResourceBase {
 
   /**
+   * Responds to entity GET requests.
+   *
+   * @param mixed $id
+   *   The entity ID.
+   *
+   * @return \Drupal\rest\ResourceResponse
+   *   The response containing the loaded entity.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
+   */
+  public function get($id) {
+    $definition = $this->getDefinition();
+    $entity = entity_load($definition['entity_type'], $id);
+    if ($entity) {
+      return new ResourceResponse($entity);
+    }
+    throw new NotFoundHttpException(t('Entity with ID @id not found', array('@id' => $id)));
+  }
+
+  /**
    * Responds to entity DELETE requests.
    *
    * @param mixed $id
    *   The entity ID.
    *
-   * @return \Symfony\Component\HttpFoundation\Response
-   *   The response object.
+   * @return \Drupal\rest\ResourceResponse
+   *   The HTTP response object.
    *
    * @throws \Symfony\Component\HttpKernel\Exception\HttpException
    */
@@ -43,7 +64,7 @@ public function delete($id) {
       try {
         $entity->delete();
         // Delete responses have an empty body.
-        return new Response('', 204);
+        return new ResourceResponse(NULL, 204);
       }
       catch (EntityStorageException $e) {
         throw new HttpException(500, 'Internal Server Error', $e);
diff --git a/core/modules/rest/lib/Drupal/rest/RequestHandler.php b/core/modules/rest/lib/Drupal/rest/RequestHandler.php
index 6bea36a..a374d14 100644
--- a/core/modules/rest/lib/Drupal/rest/RequestHandler.php
+++ b/core/modules/rest/lib/Drupal/rest/RequestHandler.php
@@ -38,12 +38,26 @@ public function handle($plugin, Request $request, $id = NULL) {
       $resource = $this->container
         ->get('plugin.manager.rest')
         ->getInstance(array('id' => $plugin));
+      $received = $request->getContent();
+      // @todo De-serialization should happen here if the request is supposed
+      // to carry incoming data.
       try {
-        return $resource->{$method}($id);
+        $response = $resource->{$method}($id, $received);
       }
       catch (HttpException $e) {
         return new Response($e->getMessage(), $e->getStatusCode(), $e->getHeaders());
       }
+      $data = $response->getResponseData();
+      if ($data != NULL) {
+        // Serialize the response data.
+        $serializer = $this->container->get('serializer');
+        // @todo Replace the format here with something we get from the HTTP
+        //   Accept headers. See http://drupal.org/node/1833440
+        $output = $serializer->serialize($data, 'drupal_jsonld');
+        $response->setContent($output);
+        $response->headers->set('Content-Type', 'application/vnd.drupal.ld+json');
+      }
+      return $response;
     }
     return new Response('Access Denied', 403);
   }
diff --git a/core/modules/rest/lib/Drupal/rest/ResourceResponse.php b/core/modules/rest/lib/Drupal/rest/ResourceResponse.php
new file mode 100644
index 0000000..a93f25e
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/ResourceResponse.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\ResourceResponse.
+ */
+
+namespace Drupal\rest;
+
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Contains data for serialization before sending the response.
+ */
+class ResourceResponse extends Response {
+
+  /**
+   * Response data that should be serialized.
+   *
+   * @var mixed
+   */
+  protected $responseData;
+
+  /**
+   * Constructor for ResourceResponse objects.
+   *
+   * @param mixed $data
+   *   Response data that should be serialized.
+   * @param int $status
+   *   The response status code.
+   * @param array $headers
+   *   An array of response headers.
+   */
+  public function __construct($data = NULL, $status = 200, $headers = array()) {
+    $this->responseData = $data;
+    parent::__construct('', $status, $headers);
+  }
+
+  /**
+   * Returns response data that should be serialized.
+   *
+   * @return mixed
+   *   Response data that should be serialized.
+   */
+  public function getResponseData() {
+    return $this->responseData;
+  }
+}
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/DBLogTest.php b/core/modules/rest/lib/Drupal/rest/Tests/DBLogTest.php
index a18d064..baf4f4d 100644
--- a/core/modules/rest/lib/Drupal/rest/Tests/DBLogTest.php
+++ b/core/modules/rest/lib/Drupal/rest/Tests/DBLogTest.php
@@ -19,7 +19,7 @@ class DBLogTest extends RESTTestBase {
    *
    * @var array
    */
-  public static $modules = array('rest', 'dblog');
+  public static $modules = array('jsonld', 'rest', 'dblog');
 
   public static function getInfo() {
     return array(
@@ -32,17 +32,7 @@ public static function getInfo() {
   public function setUp() {
     parent::setUp();
     // Enable web API for the watchdog resource.
-    $config = config('rest');
-    $config->set('resources', array(
-      'dblog' => 'dblog',
-    ));
-    $config->save();
-
-    // Rebuild routing cache, so that the web API paths are available.
-    drupal_container()->get('router.builder')->rebuild();
-    // Reset the Simpletest permission cache, so that the new resource
-    // permissions get picked up.
-    drupal_static_reset('checkPermissions');
+    $this->enableService('dblog');
   }
 
   /**
@@ -60,15 +50,16 @@ public function testWatchdog() {
     $account = $this->drupalCreateUser(array('restful get dblog'));
     $this->drupalLogin($account);
 
-    $response = $this->httpRequest("dblog/$id", 'GET');
+    $response = $this->httpRequest("dblog/$id", 'GET', NULL, 'application/vnd.drupal.ld+json');
     $this->assertResponse(200);
+    $this->assertHeader('Content-Type', 'application/vnd.drupal.ld+json');
     $log = drupal_json_decode($response);
     $this->assertEqual($log['wid'], $id, 'Log ID is correct.');
     $this->assertEqual($log['type'], 'rest_test', 'Type of log message is correct.');
     $this->assertEqual($log['message'], 'Test message', 'Log message text is correct.');
 
     // Request an unknown log entry.
-    $response = $this->httpRequest("dblog/9999", 'GET');
+    $response = $this->httpRequest("dblog/9999", 'GET', NULL, 'application/vnd.drupal.ld+json');
     $this->assertResponse(404);
     $this->assertEqual($response, 'Not Found', 'Response message is correct.');
   }
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php b/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php
index 187c386..4c039c2 100644
--- a/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php
+++ b/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php
@@ -34,18 +34,7 @@ public static function getInfo() {
    */
   public function testDelete() {
     foreach (entity_get_info() as $entity_type => $info) {
-      // Enable web API for this entity type.
-      $config = config('rest');
-      $config->set('resources', array(
-        'entity:' . $entity_type => 'entity:' . $entity_type,
-      ));
-      $config->save();
-
-      // Rebuild routing cache, so that the web API paths are available.
-      drupal_container()->get('router.builder')->rebuild();
-      // Reset the Simpletest permission cache, so that the new resource
-      // permissions get picked up.
-      drupal_static_reset('checkPermissions');
+      $this->enableService('entity:' . $entity_type);
       // Create a user account that has the required permissions to delete
       // resources via the web API.
       $account = $this->drupalCreateUser(array('restful delete entity:' . $entity_type));
@@ -81,6 +70,7 @@ public function testDelete() {
       $this->assertNotIdentical(FALSE, entity_load($entity_type, $entity->id(), TRUE), 'The ' . $entity_type . ' entity is still in the database.');
     }
     // Try to delete a resource which is not web API enabled.
+    $this->enableService(FALSE);
     $account = $this->drupalCreateUser();
     // Reset cURL here because it is confused from our previously used cURL
     // options.
@@ -88,32 +78,7 @@ public function testDelete() {
     $this->drupalLogin($account);
     $this->httpRequest('entity/user/' . $account->id(), 'DELETE');
     $user = entity_load('user', $account->id(), TRUE);
-    $this->assertEqual($account->id(), $user->id());
+    $this->assertEqual($account->id(), $user->id(), 'User still exists in the database.');
     $this->assertResponse(404);
   }
-
-  /**
-   * Creates entity objects based on their types.
-   *
-   * Required properties differ from entity type to entity type, so we keep a
-   * minimum mapping here.
-   *
-   * @param string $entity_type
-   *   The type of the entity that should be created..
-   *
-   * @return \Drupal\Core\Entity\EntityInterface
-   *   The new entity object.
-   */
-  protected function entityCreate($entity_type) {
-    switch ($entity_type) {
-      case 'entity_test':
-        return entity_create('entity_test', array('name' => 'test', 'user_id' => 1));
-      case 'node':
-        return entity_create('node', array('title' => $this->randomString()));
-      case 'user':
-        return entity_create('user', array('name' => $this->randomName()));
-      default:
-        return entity_create($entity_type, array());
-    }
-  }
 }
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
index 3bc8756..162638c 100644
--- a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
+++ b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
@@ -15,6 +15,13 @@
 abstract class RESTTestBase extends WebTestBase {
 
   /**
+   * Stores HTTP response headers from the last HTTP request.
+   *
+   * @var array
+   */
+  protected $responseHeaders;
+
+  /**
    * Helper function to issue a HTTP request with simpletest's cURL.
    *
    * @param string $url
@@ -31,36 +38,137 @@ protected function httpRequest($url, $method, $body = NULL, $format = 'applicati
       case 'GET':
         // Set query if there are additional GET parameters.
         $options = isset($body) ? array('absolute' => TRUE, 'query' => $body) : array('absolute' => TRUE);
-        return $this->curlExec(array(
+        $curl_options = array(
           CURLOPT_HTTPGET => TRUE,
           CURLOPT_URL => url($url, $options),
-          CURLOPT_NOBODY => FALSE)
+          CURLOPT_NOBODY => FALSE,
+          CURLOPT_HTTPHEADER => array('Accept: ' . $format),
         );
+        break;
+
       case 'POST':
-        return $this->curlExec(array(
+        $curl_options = array(
           CURLOPT_HTTPGET => FALSE,
           CURLOPT_POST => TRUE,
           CURLOPT_POSTFIELDS => $body,
           CURLOPT_URL => url($url, array('absolute' => TRUE)),
           CURLOPT_NOBODY => FALSE,
           CURLOPT_HTTPHEADER => array('Content-Type: ' . $format),
-        ));
+        );
+        break;
+
       case 'PUT':
-        return $this->curlExec(array(
+        $curl_options = array(
           CURLOPT_HTTPGET => FALSE,
           CURLOPT_CUSTOMREQUEST => 'PUT',
           CURLOPT_POSTFIELDS => $body,
           CURLOPT_URL => url($url, array('absolute' => TRUE)),
           CURLOPT_NOBODY => FALSE,
           CURLOPT_HTTPHEADER => array('Content-Type: ' . $format),
-        ));
+        );
+        break;
+
       case 'DELETE':
-        return $this->curlExec(array(
+        $curl_options = array(
           CURLOPT_HTTPGET => FALSE,
           CURLOPT_CUSTOMREQUEST => 'DELETE',
           CURLOPT_URL => url($url, array('absolute' => TRUE)),
           CURLOPT_NOBODY => FALSE,
-        ));
+        );
+        break;
+    }
+    // Include all HTTP headers in the response.
+    $curl_options[CURLOPT_HEADER] = TRUE;
+
+    $response = $this->curlExec($curl_options);
+
+    list($header, $body) = explode("\r\n\r\n", $response, 2);
+    $header_lines = explode("\r\n", $header);
+    foreach ($header_lines as $line) {
+      $parts = explode(':', $line, 2);
+      $this->responseHeaders[$parts[0]] = isset($parts[1]) ? trim($parts[1]) : '';
+    }
+
+    $this->verbose($method . ' request to: ' . $url .
+      '<hr />Code: ' . curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) .
+      '<hr />Response headers: ' . $header .
+      '<hr />Response body: ' . $body);
+
+    return $body;
+  }
+
+  /**
+   * Creates entity objects based on their types.
+   *
+   * Required properties differ from entity type to entity type, so we keep a
+   * minimum mapping here.
+   *
+   * @param string $entity_type
+   *   The type of the entity that should be created..
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   *   The new entity object.
+   */
+  protected function entityCreate($entity_type) {
+    switch ($entity_type) {
+      case 'entity_test':
+        return entity_create('entity_test', array('name' => $this->randomName(), 'user_id' => 1));
+      case 'node':
+        return entity_create('node', array('title' => $this->randomString()));
+      case 'user':
+        return entity_create('user', array('name' => $this->randomName()));
+      default:
+        return entity_create($entity_type, array());
     }
   }
+
+  /**
+   * Enables the web service interface for a specific entity type.
+   *
+   * @param string|FALSE $resource_type
+   *   The resource type that should get web API enabled or FALSE to disable all
+   *   resource types.
+   */
+  protected function enableService($resource_type) {
+    // Enable web API for this entity type.
+    $config = config('rest');
+    if ($resource_type) {
+      $config->set('resources', array(
+        $resource_type => $resource_type,
+      ));
+    }
+    else {
+      $config->set('resources', array());
+    }
+    $config->save();
+
+    // Rebuild routing cache, so that the web API paths are available.
+    drupal_container()->get('router.builder')->rebuild();
+    // Reset the Simpletest permission cache, so that the new resource
+    // permissions get picked up.
+    drupal_static_reset('checkPermissions');
+  }
+
+  /**
+   * Check if a HTTP response header exists and has the expected value.
+   *
+   * @param string $header
+   *   The header key, example: Content-Type
+   * @param string $value
+   *   The header value.
+   * @param string $message
+   *   (optional) A message to display with the assertion.
+   * @param string $group
+   *   (optional) The group this message is in, which is displayed in a column
+   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
+   *   translate this string. Defaults to 'Other'; most tests do not override
+   *   this default.
+   *
+   * @return bool
+   *   TRUE if the assertion succeeded, FALSE otherwise.
+   */
+  protected function assertHeader($header, $value, $message = '', $group = 'Browser') {
+    $match = isset($this->responseHeaders[$header]) && $this->responseHeaders[$header] == $value;
+    return $this->assertTrue($match, $message ? $message : 'HTTP response header ' . $header . ' with value ' . $value . ' found.', $group);
+  }
 }
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php b/core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php
new file mode 100644
index 0000000..5dfd36d
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\test\ReadTest.
+ */
+
+namespace Drupal\rest\Tests;
+
+use Drupal\rest\Tests\RESTTestBase;
+
+/**
+ * Tests resource read operations on test entities, nodes and users.
+ */
+class ReadTest extends RESTTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('jsonld', 'rest', 'entity_test');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Read resource',
+      'description' => 'Tests the retrieval of resources.',
+      'group' => 'REST',
+    );
+  }
+
+  /**
+   * Tests several valid and invalid read requests on all entity types.
+   */
+  public function testRead() {
+    // @todo once EntityNG is implemented for other entity types use the full
+    // entity_get_info() for all entity types here.
+    $entity_test_info = entity_get_info('entity_test');
+    $entity_info = array('entity_test' => $entity_test_info);
+    foreach ($entity_info as $entity_type => $info) {
+      $this->enableService('entity:' . $entity_type);
+      // Create a user account that has the required permissions to delete
+      // resources via the web API.
+      $account = $this->drupalCreateUser(array('restful get entity:' . $entity_type));
+      // Reset cURL here because it is confused from our previously used cURL
+      // options.
+      unset($this->curlHandle);
+      $this->drupalLogin($account);
+
+      // Create an entity programmatically.
+      $entity = $this->entityCreate($entity_type);
+      $entity->save();
+      // Read it over the web API.
+      $response = $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'GET', NULL, 'application/vnd.drupal.ld+json');
+      $this->assertResponse('200', 'HTTP response code is correct.');
+      $this->assertHeader('Content-Type', 'application/vnd.drupal.ld+json');
+      $data = drupal_json_decode($response);
+      // Only assert one example property here, other properties should be
+      // checked in serialization tests.
+      $this->assertEqual($data['uuid'][LANGUAGE_DEFAULT][0]['value'], $entity->uuid(), 'Entity UUID is correct');
+
+      // Try to read the entity with an unsupported media format.
+      $response = $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'GET', NULL, 'application/wrongformat');
+      $this->assertResponse(415);
+
+      // Try to read an entity that does not exist.
+      $response = $this->httpRequest('entity/' . $entity_type . '/9999', 'GET', NULL, 'application/vnd.drupal.ld+json');
+      $this->assertResponse(404);
+      $this->assertEqual($response, 'Entity with ID 9999 not found', 'Response message is correct.');
+
+      // Try to read an entity without proper permissions.
+      $this->drupalLogout();
+      $response = $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'GET', NULL, 'application/vnd.drupal.ld+json');
+      $this->assertResponse(403);
+      $this->assertNull(drupal_json_decode($response), 'No valid JSON found.');
+    }
+    // Try to read a resource which is not web API enabled.
+    $account = $this->drupalCreateUser();
+    // Reset cURL here because it is confused from our previously used cURL
+    // options.
+    unset($this->curlHandle);
+    $this->drupalLogin($account);
+    $response = $this->httpRequest('entity/user/' . $account->id(), 'GET', NULL, 'application/vnd.drupal.ld+json');
+    $this->assertResponse(404);
+    $this->assertNull(drupal_json_decode($response), 'No valid JSON found.');
+  }
+}
diff --git a/core/modules/rest/rest.info b/core/modules/rest/rest.info
index 56c650c..c0bf4bd 100644
--- a/core/modules/rest/rest.info
+++ b/core/modules/rest/rest.info
@@ -3,4 +3,6 @@ description = Exposes entities and other resources as RESTful web API
 package = Core
 version = VERSION
 core = 8.x
+; @todo Remove this dependency once hard coding to JSON-LD is gone.
+dependencies[] = jsonld
 configure = admin/config/services/rest
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/MimeTypeMatcherTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/MimeTypeMatcherTest.php
new file mode 100644
index 0000000..7e6c497
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/MimeTypeMatcherTest.php
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\system\Tests\Routing\MimeTypeMatcherTest.
+ */
+
+namespace Drupal\system\Tests\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\HttpException;
+
+use Drupal\simpletest\UnitTestBase;
+use Drupal\Core\Routing\MimeTypeMatcher;
+use Drupal\Core\Routing\NestedMatcher;
+use Drupal\Core\Routing\FirstEntryFinalMatcher;
+
+/**
+ * Basic tests for the MimeTypeMatcher class.
+ */
+class MimeTypeMatcherTest extends UnitTestBase {
+
+  /**
+   * A collection of shared fixture data for tests.
+   *
+   * @var RoutingFixtures
+   */
+  protected $fixtures;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Partial matcher MIME types tests',
+      'description' => 'Confirm that the mime types partial matcher is functioning properly.',
+      'group' => 'Routing',
+    );
+  }
+
+  function __construct($test_id = NULL) {
+    parent::__construct($test_id);
+
+    $this->fixtures = new RoutingFixtures();
+  }
+
+  /**
+   * Confirms that the MimeType matcher matches properly.
+   */
+  public function testFilterRoutes() {
+
+    $matcher = new MimeTypeMatcher();
+    $matcher->setCollection($this->fixtures->sampleRouteCollection());
+
+    // Tests basic JSON request.
+    $request = Request::create('path/two', 'GET');
+    $request->headers->set('Accept', 'application/json, text/xml;q=0.9');
+    $routes = $matcher->matchRequestPartial($request);
+    $this->assertEqual(count($routes->all()), 4, 'The correct number of routes was found.');
+    $this->assertNotNull($routes->get('route_c'), 'The json route was found.');
+    $this->assertNull($routes->get('route_e'), 'The html route was not found.');
+
+    // Tests JSON request with alternative JSON MIME type Accept header.
+    $request = Request::create('path/two', 'GET');
+    $request->headers->set('Accept', 'application/x-json, text/xml;q=0.9');
+    $routes = $matcher->matchRequestPartial($request);
+    $this->assertEqual(count($routes->all()), 4, 'The correct number of routes was found.');
+    $this->assertNotNull($routes->get('route_c'), 'The json route was found.');
+    $this->assertNull($routes->get('route_e'), 'The html route was not found.');
+
+    // Tests basic HTML request.
+    $request = Request::create('path/two', 'GET');
+    $request->headers->set('Accept', 'text/html, text/xml;q=0.9');
+    $routes = $matcher->matchRequestPartial($request);
+    $this->assertEqual(count($routes->all()), 4, 'The correct number of routes was found.');
+    $this->assertNull($routes->get('route_c'), 'The json route was not found.');
+    $this->assertNotNull($routes->get('route_e'), 'The html route was found.');
+  }
+
+  /**
+   * Confirms we can nest multiple partial matchers.
+   */
+  public function testNestedMatcher() {
+
+    $matcher = new NestedMatcher();
+
+    $matcher->setInitialMatcher(new MockPathMatcher($this->fixtures->sampleRouteCollection()));
+    $matcher->addPartialMatcher(new MimeTypeMatcher());
+    $matcher->setFinalMatcher(new FirstEntryFinalMatcher());
+
+    $request = Request::create('/path/two', 'GET');
+    $request->headers->set('Accept', 'text/html, text/xml;q=0.9');
+
+    $attributes = $matcher->matchRequest($request);
+    $this->assertEqual($attributes['_route'], 'route_e', 'The correct matching route was found.');
+  }
+
+  /**
+   * Confirms that the MimeTypeMatcher matcher throws an exception for no-route.
+   */
+  public function testNoRouteFound() {
+    $matcher = new MimeTypeMatcher();
+
+    // Remove the sample routes that would match any method.
+    $routes = $this->fixtures->sampleRouteCollection();
+    $routes->remove('route_a');
+    $routes->remove('route_b');
+    $routes->remove('route_c');
+    $routes->remove('route_d');
+
+    $matcher->setCollection($routes);
+
+    try {
+      $request = Request::create('path/two', 'GET');
+      $request->headers->set('Accept', 'application/json, text/xml;q=0.9');
+      $routes = $matcher->matchRequestPartial($request);
+      $this->fail(t('No exception was thrown.'));
+    }
+    catch (HttpException $e) {
+      $this->assertEqual($e->getStatusCode(), 415, 'The correct status code exception was thrown.');
+    }
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/RoutingFixtures.php b/core/modules/system/lib/Drupal/system/Tests/Routing/RoutingFixtures.php
index f243ff1..db001c2 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Routing/RoutingFixtures.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/RoutingFixtures.php
@@ -61,6 +61,7 @@ public function sampleRouteCollection() {
 
     $route = new Route('path/two');
     $route->setRequirement('_method', 'GET');
+    $route->setRequirement('_format', 'json');
     $collection->add('route_c', $route);
 
     $route = new Route('path/three');
@@ -68,6 +69,7 @@ public function sampleRouteCollection() {
 
     $route = new Route('path/two');
     $route->setRequirement('_method', 'GET|HEAD');
+    $route->setRequirement('_format', 'html');
     $collection->add('route_e', $route);
 
     return $collection;
