Conditional gets in REST

According to the Http specification, any Http GET request should be idempotent and safe. In this context, these principles have the following meaning:

Idempotence: One or more calls with identical requests should return the same resource representation (As long as the resource has not changed on the server between calls).

Safety:  The only action produced by a GET request is to retrieve a resource representation, no side-effects should be evident on the service side after executing the request. An example that violates this principle could be a GET request that also updates some info in the resource.

As long as these principles are applied correctly by a service implementation, the client will have more available mechanisms to cache the resource representation on its side. This has a very positive meaning from a scalability view point, which represents a better use of the network bandwidth and an optimization in the utilization of server resources.

Those caching mechanisms are based on what is commonly called Http conditional gets. An Http conditional get involves the use of two special headers generated by the service, ETag and Last-Modified.

An Etag represents an opaque value that only the server knows how to recreate, it could represent anything, but it is usually a hash representing the resource version (it can be generated hashing the whole representation content or just some parts of it, like the timestamp). On the other hand, the Last-Modified headers represents a datetime that the service can use to determine whether the resource has changed since the last time it was served. The following example illustrates the request/response messages interchanged by the client/service for a service that returns information about customers

First request, the client does not have anything on the cache.

GET http://foo/customers/1

Host foo
Accept */*
Accept-Language en-us,en;q=0.5
Accept-Encoding gzip,deflate
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection close
Referer http://foo/
Pragma no-cache
Cache-Control no-cache

Response:

Connection close
Date Thu, 02 Oct 2008 14:46:57 GMT
Server Microsoft-IIS/6.0
X-Powered-By ASP.NET
X-AspNet-Version 2.0.50727
Content-Encoding gzip
Content-Length 1212
Expires Sat, 01 Nov 2008 14:46:57 GMT
Last-Modified Mon, 29 Sep 2008 15:40:27 GMT
Etag a9331828c517ac5d97f93b3cfdbcc9bc

Content-Type text/xml

The next time the client sends a new request for the same customer, some extra information will be included in the request headers,

GET http://foo/customers/1

Host foo
Accept */*
Accept-Language en-us,en;q=0.5
Accept-Encoding gzip,deflate
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection close
Referer foo
If-Modified-Since Mon, 29 Sep 2008 15:40:27 GMT
If-None-Match a9331828c517ac5d97f93b3cfdbcc9bc

With these two new headers, the service can now determine whether it has to serve the resource representation again or the client can use the cached version. If the resource has not changed according to the values in those headers (If-Modified-Since for Last-Modified and If-None-Match for Etag), the service can return an Http status code "304 Not Modified", which instructs the client to use the cached version.

This approach is widely used nowadays by syndication, RSS or Atom, that usually use the Last-Modified/If-Modified-Since to determine which items they have to send to the client.

Since I am a WCF fan, I will also include here some code snippets to get the value in those headers and return a "304" http status code,

Code to get those values:

if (WebOperationContext.IncomingRequest.Headers[HttpRequestHeader.IfNoneMatch] != null)

{

  // Do something with etag...

}

else if (WebOperationContext.IncomingRequest.Headers[HttpRequestHeader.IfModifiedSince] != null)

{

  var date = DateTime.Parse(context.IncomingRequest.Headers[HttpRequestHeader.IfModifiedSince]);

}

 

Code to set those values:

 

WebOperationContext.OutgoingResponse.LastModified = // sets the last modified;

WebOperationContext.OutgoingResponse.ETag = // sets the Etag

Code to return an Http 304 status code:

WebOperationContext.OutgoingResponse.StatusCode = HttpStatusCode.NotModified;

WebOperationContext.OutgoingResponse.SuppressEntityBody = true;

No Comments