subscribe
(last updated: )

The problems with embedding in REST today and how it might be solved with HTTP/2

Please note: Recently browsers deprecate HTTP/2 Push from the Web Platform, making some of the ideas in this article no longer relevant. See Push is Dead for some more information.

When looking at REST, the underlying theory, and various interpretations and even HTTP, you’ll find that REST is about singular resources and transferring state of those resources.

There’s very little supporting information about the concept of a collection of resources. Almost every REST API out there will have a need for them, but there doesn’t seem to be a clear definition for them, or an accepted way to technically handle them.

Fielding’s dissertation on REST says:

The key abstraction of information in REST is a resource. Any information that can be named can be a resource: a document or image, a temporal service (e.g. “today’s weather in Los Angeles”), a collection of other resources, a non-virtual object (e.g. a person), and so on. In other words, any concept that might be the target of an author’s hypertext reference must fit within the definition of a resource. A resource is a conceptual mapping to a set of entities, not the entity that corresponds to the mapping at any particular point in time.

This paragraph is the only place where the word ‘collection’ appears, and it basically says that it’s just another resource. This is good, because it tells us that collections are not a separate concept from other resources and should abide by the same constraints.

However, REST gives us an architecture, we still have to impose our own further contstraints and meaning on top of it. So how do we treat a collection of resources?

Well, when you look at any of the common hypermedia formats, you’ll find that each of them has a slightly different idea about it. However, each of these formats tends to have something in common: they treat items in a collection as a sort of ‘sub-resource’ inside a ‘resource’.

What is a collection, really?

Before we go into some examples, I think it’s worthwhile to discuss this. A collection is a set of resources. Resources may ‘belong’ to a collection in some cases, but resources may also appear in multiple collections.

For example, if my REST API represents blog posts, the same article may appear in a collection that represents all posts from 2017, and all posts written by me.

You might think of a collection as a database table, but I kind of think that that’s not the right analogy. I think a collection is more like a directory on a modern filesystem. The filesystem directory only contains links to files and their position on the filesystem (inode). You could say that a directory has references to files, but you can’t really say that a directory IS a collection of those files.

Files can appear in multiple directories using hardlinks too.

So knowning that, we can translate this to REST terms. A collection is a set of hyperlinks to other resources.

If you’re using HAL, you can express this using:

{
  "_links" : {
     "self" : { "href" : "/articles/byyear/2017" },
     "item" : [
        { "href" : "/articles/1" },
        { "href" : "/articles/2" },
        { "href" : "/articles/3" }
     ]
  }
}

There you have it. In HAL the top-level property names in the _links object are link relations, and the item link relation was standardized by Mike Amundsen as RFC6573.

This is great, but unpractical

The problem with the linked approach is that very often, a REST client will want to receive the details of every item in a collection. In the last example this would mean an extra HTTP GET request for each of the items in the collection.

This is not acceptable for many real-world REST services out there, so we need a solution. HAL (and virtually every other hypermedia format) solves this by embedding the resources in the collection.

To demonstrate, here is the same collection again.

{
  "_links" : {
     "self" : { "href" : "/articles/byyear/2017" }
  },
  "_embedded" : {
     "item" : [
        { 
            "_links" : {
              "self" : { "href" : "/articles/1" }
            },

            "title" : "..",
            "pubDate" : "..",
            "content" : ".."
        },
        { 
            "_links" : {
              "self" : { "href" : "/articles/2" }
            },

            "title" : "..",
            "pubDate" : "..",
            "content" : ".."
        },
        { 
            "_links" : {
              "self" : { "href" : "/articles/3" }
            },

            "title" : "..",
            "pubDate" : "..",
            "content" : ".."
        }
     ]

  }
}

Two things happened here. Each individual item in the collection now appears in _embedded. They have their own sets of links, including a self link, referring to the uri of the resource we just embedded. We also removed the items from _links.

The way we look at _embedded is that things that appear in _embedded are:

  • Link relations, equal to items appearing in _links.
  • The data you would receive, if you did a GET request on the target of the link.

This is important, because a good HAL-based REST client should ideally consider things appearing in _embedded and _links as the exact same thing. The only difference is that because an item appears in _embedded, it’s no longer needed to perform that GET request. The client should cache it.

If a client is built around this core concept, another benefit is that the client becomes adaptable to changes to the server. You might for example see over time that a HAL client very often might follow a certain link and almost always will want the data for it.

An example

Here’s a real-world example from our API. Our API has a document on the root of the API that the client uses to discover all the other resources. It contains a link to a resource that has information about the current user:

{
  "_links" : {
     "self" : { "href" : "/" },
     "current-user" : { "href" : "/user/1356" },
     "support" : { "href": "mailto:support@example.org" }
  }
}

We noticed that all clients always request the current users’ information after logging in. It always follows up this initial GET to a GET to whatever the current-user relation points at.

Knowing this, we can change our API to simply assume this and pre-emptively send that resource over:

{
  "_links" : {
     "self" : { "href" : "/" },
     "support" : { "href": "mailto:support@example.org" }
  },
  "_embedded" : {
     "current-user" : {
        "_links" : {
            "self" : { "href" : "/user/1356" }
        },
        "firstName" : "...",
        "lastName" : "...",
        "email" : "..."
     }
  }
}

A good HAL client would need 0 changes, and simply adapt to this new situation and skip the second GET request.

A few problems with this

One of the biggest advantages and promises that REST typically gives us, is by using the functionality HTTP, we get all the benefits from HTTP. The usual example of this is being able to use the rich caching features from HTTP.

However, HTTP caches will not be aware of embedded resources. In the last example, the cache doesn’t know that /user/1356 was embedded and cached, and it does not know it can skip a future GET request to that resource.

Also, if we did a real cached GET request, but later on we issue a PUT on that same resource, a HTTP client ‘knows’ that since a PUT was issued, the local cache is no longer valid.

So typically, a HAL client that wants to be adaptable to this needs it’s own cache. What we’ll ideally want to do, is something like this (in pseudo-code)

var api = new API('/'); // Referring to the root.
var currentUserResource = await api.follow('current-user');
console.log(await currentUserResource.get());

I hope the source makes some sense, but the general idea is that once we ‘follow’ the current-user link and get its representation, we only need the extra GET request, if it wasn’t embedded. This should be a seamless experience.

To implement this in browsers today, it means that the API client will need to:

  • Understand _embedded
  • Take items from _embedded, treat them the same as links
  • Store them into a some kind of local cache.
  • Use that local cache when the user wants to do a GET request.
  • Invalidate that local cache when PUT, DELETE or another non-safe HTTP request is issued on that same resource.

So while we can still use HTTP semantics, we now have two caching layers which may conflict.

Fetch: A future solution to this problem

The Fetch API is the future of doing HTTP requests in browsers. It’s a much nicer api than XMLHTTPRequest, but also has another really cool feature: Once it lands it gives us direct access to the browsers’ HTTP cache.

The specific thing to look for is Cache.put(). This API should allow us to directly add things to the browser cache. A HAL client in this case could parse out everything that appears in _embedded and directly add it to this cache.

A future GET request will then simply directly be taken from this cache, and the GET request is avoided. This is huge.

What’s interesting about this API is that it does not just take a URL and the thing you want to store, you actually store a HTTP request and a HTTP response.

This is important, because a HTTP cache is not just URL-based. A single GET request to a single url might have different responses based on HTTP request headers like:

  • Accept
  • Accept-Language
  • Authorization

Or even other custom headers. A HTTP server can indicate to a client how the client should store responses in the cache based on the Vary header. For example, if a response to a GET request has the Vary: X-Foo header, the http client knows that depending on the value of the X-Foo header in the HTTP Request (Not the response, this is important!) the server might emit a different response.

What’s also important to know, is that the HTTP client cache will store and expire the cache for any given resource based on instructions the server gives in headers such as Cache-Control and Expires.

In HAL we have none of that information available in _embedded, so we sort of need to make these values up, and that’s not great.

A suggested proposal to improve HAL

Conceptually I feel that HAL’s _embedded is not strictly for specifying collections and/or sub-resources. It’s core feature is really means to prepopulate the HTTP cache to avoid future GET requests.

Knowing that, I believe _embedded is not the best format to achieve this goal. We’re missing crucial information to do this as correct as possible.

Here’s a format I would like to see instead:

{
  "_links" : {
     "self" : { "href" : "/" },
     "current-user" : { "href" : "/user/1356" },
     "support" : { "href": "mailto:support@example.org" }
  },
  "_push" : [
    {
      "request" : {
        "method" : "GET",
        "uri" : "/user/1356",
        "headers" : {
          "accept" : "application/hal+json"
        }
      },
      "response" : {
        "headers" : {
            "vary": "Accept",
            "cache-control" : "private; max-age=3600; no-revalidate",
            "content-type" : "application/hal+json",
            "etag" : "\"foo-bar\""
        },
        "body" : {
          "_links" : {
            "self" : { "href" : "/user/1356" }
          },
          "firstName" : "...",
          "lastName" : "...",
          "email" : "..."
        }
      }

  ]
}

Changed from _embedded are:

  • I added HTTP request and response headers and the request method.
  • I’m no longer indexing things in _embedded by their relation type, it’s just an array of requests and response pairs.
  • I’m no longer removing items from _links. The link just stays there.
  • _push is not nested. It only appears in the top and it’s really just a ‘transport-level’ feature instead of a part of the data-structure.

The drawback? If you’re not interesed in developing an an advanced HAL client/server, and just want to build a ‘dumb’ parser of collections, there’s more data and a higher cognitive load.

However, even if this is a better way to push resources to a client, it’s only really needed for HTTP/1.1. HTTP/2 makes this obsolete.

HTTP/2

Those aware of what’s going on with HTTP/2 might recognize _push. HTTP/2 has a very similar feature.

HTTP/2 actually introduces a protocol-level push and it works in the exact same way. HTTP/2 Push can be used to preemptively populate the browser cache if the server knows the client will likely want to do certain GET requests in the future.

A HTTP/2 push message always contains BOTH the HTTP response, but also the HTTP Request that the browser would send if they did have to do the GET request.

And now there’s a new feature under development that makes this especially cool: HTTP/2 cache digest.

HTTP/2 cache digests is an extension to the HTTP/2 protocol that will allow a client to send a small summary of the cache a browser has for the server.

This is really the missing key, because the biggest thing we were missing with HTTP/2 push is that the server doesn’t know in advance which resources the client already knows. This results in unneeded pushes.

This is identical to a HAL server always adding resources to _embedded. The server doesn’t know if the client cared for them, or if it already had an up-to-date copy.

It would be easy for a HTTP/2 HAL client to add a HTTP header to GET requests such X-Please-Push: current-user to automatically push request/response pairs for a current-user relationship and only if the client didn’t already have an up-to-date copy of it.

For this reason I think that my _push proposal doesn’t make all that much sense. It’s only really useful to provide a HTTP/2-like push feature to HTTP/1.1. It’s a stopgap.

And since we no longer really need _embedded, it might make sense to also ditch _links and just move the information to the HTTP Link header, which also means all this REST linking logic is no longer restricted to JSON or XML-based formats.

Conclusion

In HAL and REST we currently have an awkward and poor fit for embedding resources. The only reason we need this in the first place, is because clients doing many GET requests is expensive. Technical limitations of HTTP.

This causes problems because these resources are not easily cached and simply don’t fit well within the HTTP design. We need to create layers on top of HTTP just to work around this.

HTTP/2 and cache digests might offer a great solution, in that we can completely avoid embedding resources and preemptively send resources that the client will probably want and doesn’t already have an up-to-date copy for.

This will make REST much more practical and alleviate some of the largest isues people have with it today. I think it eventually makes formats like HAL completely obsolete, because we’re moving the hard stuff to the protocol layer.

Web mentions

Comments

  • Asmir Mustafic

    Nice idea "push on demand" :)

  • Saquib Rizwan

    Great concept, But it is very much complicated.

  • develCuy

    Only thinking on REST for browsers? how about REST on JS server-side (node.js)?

    • Evert

      Evert

      Everything I talked about applies to the server-side too. Let me know if you have specific questions about stuff, I might be able to give you some pointers. To do the HTTP/2 stuff you'll need a HTTP/2 client in node.js, which I believe exists (but I haven't used yet).