API Round-trip Reduction

Overview

When using a RESTful, normalized API, a client will often need to make multiple round-trips in order to get all of the data it needs in order to display any of it. For example, in its first request, the client might request the list of Messages sent to the user. The server will respond with the IDs of the Messages in the Inbox. Then the client has to make another set of requests to fetch the Messages themselves. The second set of requests could only be performed after the response of the first request was received. Now each Message refers to the User who sent that message, and the client needs to request those Users.

Each round-trip has the overhead of performing another request, which may involve another connection establishment. This could easily be hundreds of milliseconds, multiplied by the number of round-trips.

Here I propose “SARTRA”, the server-assisted round-trip-reducing aggregation format, which allows the client to direct a server to determine the other objects that the client will need, fetch those, aggregate everything, and provide it all to the client in one response.

Example Scenario

Let’s assume that a mobile application wants to show the user the list of Messages in their Inbox. There are three messages, and showing each of these Messages also requires the User who sent the Message, which includes the person’s name.

  • The server and client use JSON.
  • Each object is identified by a globally-unique URI, and the server knows how to fetch an object given (just) its URI.

1st Request

Request: GET /mailbox/Inbox

Response:

 {
"uri" : "/mailbox/Inbox",
"messages" : [
{ "messageUri" : "/message/1" },
{ "messageUri" : "/message/99" },
{ "messageUri" : "/message/123" },
]
}

2nd Request

Now the client will fetch the Messages. The server supports batching, so in one actual request we will request all three messages. One of those requests and responses will look like this:

Request: GET /message/1

Response:

 {
"uri" : "/message/1",
"senderUri" : "/user/1337",
"sendTime" : "Oct 22, 2014 23:16 PM PDT",
"subject" : "Hello World",
"body" : "foo"
}

3rd Request

The client has received the three Messages, collected the SenderUris of each message (and they’re three different Users), and now needs to fetch the three users in a batch request. One of those requests and responses may look like this:

Request: GET /user/1337

Response:

 {
"uri" : "/user/1337",
"firstName" : "Tony",
"photos" : {
"thumbnailUrl" : "http://example.com/photos/1337_thumb.png"
}
}

Over three round-trips we’ve requested 7 resources (1 list, 3 messages, and 3 users).

Proposal

A simple way for the client to tell the server how to find URIs of objects within responses that the client will also need. The server will collect the URIs, fetch those, repeat if nesting was defined, and aggregate all of the objects into one response.

This is build on top of multipart/batch with two additions:

  1. Each batch part may also contain a “sartra” part, which describes where to look in the response for more resource URIs to fetch
  2. The response may contain parts for objects that were not explicitly requested.

The Multipart/Sartra Request

Within the Sartra part, the client uses JSON to describe where in the JSON response to look for URIs to resolve.

 POST /sartra HTTP/1.1
Host: example.org
Content-Type: multipart/sartra;
type="application/http;version=1.1";
sartra-boundary=sartra
batch-boundary=batch
Mime-Version: 1.0

--batch
Content-Type: application/http;version=1.1
Content-Transfer-Encoding: binary
Content-ID: <mailbox-inbox@example.org>

GET /mailbox/Inbox HTTP/1.1
Host: example.org

--sartra
[
{ "label" : "messages",
"path" : "messages[]/messageUri",
"rtr" : [
{ "label" : "senders",
"path" : "senderUri"
}
]
}
]
--batch

The Sartra part contains an array of objects describing what else to fetch:

  • label: a client-specified label, which it can use to indentify why the returned object was included in the response
  • path: describes a path the JSON of the response where URIs to other objects should be found.
    • In this example, this means that the server should find an element named “messages”, which is an array. For each of the objects of that array, collect the URI under the atribute “messageUri”
  • rtr : a nested Sarta part. This means that the server should look in each of the “messages” for more URIs of objects to fetch

Response

After each object is fetched, if a Sartra part was defined for that request, URIs are searched for and collected from the response, and other requests generated. Further URIs may be searched for, collected, and requested until the nesting is satisfied.

All responses (from all levels of nesting) are returned in one (flat) multipart HTTP response.

Thus, a response for the above example would look like:

 HTTP/1.1 200 OK
Date: 2014-10-23 12:06 AM PDT
Server: example.org
Content-Type: multipart/sartra;
type="application/http;type=1.1";
boundary=sartra
Mime-Version: 1.0

--sartra
Content-Type: application/http;version=1.1
Content-Transfer-Encoding: binary
In-Reply-To: <mailbox-inbox@example.org>

HTTP/1.1 200 OK
Server: example.org

{
"uri" : "/mailbox/Inbox",
"messages" : [
{ "messageUri" : "/message/1" },
{ "messageUri" : "/message/99" },
{ "messageUri" : "/message/123" },
]
}

--sartra
Content-Type: application/http;version=1.1
Content-Transfer-Encoding: binary
In-Reply-To: sartra+messages
Sartra-Uri: /message/1

HTTP/1.1 200 OK
Server: example.org

{
"uri" : "/message/1",
"senderUri" : "/user/1337",
"sendTime" : "Oct 22, 2014 23:16 PM PDT",
"subject" : "Hello World",
"body" : "foo"
}

--sartra--
more messages in sartra parts...

--sartra
Content-Type: application/http;version=1.1
Content-Transfer-Encoding: binary
In-Reply-To: sartra+senders
Sartra-Uri: /user/1337

HTTP/1.1 200 OK
Server: example.org

{
"uri" : "/user/1337",
"firstName" : "Tony",
"photos" : {
"thumbnailUrl" : "http://example.com/photos/1337_thumb.png"
}
}

--sartra--
more messages in sartra parts...

--sartra

The client then processes (parses) all responses and caches them (on the “Sartra-Uri” header, which is the URI extracted from other objects and requested. As the client traverses the objects, like Inbox or Messages, and finds it needs more objects, it can find them in its cached.

Performance considerations

Resources may be returned even if the client had already requested and cached the resource previously.