Fork me on GitHub

Conditional Requests

A key feature of HTTP is the support for conditional requests. This allows the user agent to check the freshness of a cached representation or to prevent update of a resource based on a stale local representation. Liberator provides the client with the necessary information and informs the client if the representation was not modified.

Last-Modified

To enable the conditional request based on the time of the last modification, a resource must provide a function at the key :last-modified. The value returned by this function will be picked up by the default implementation of the decision function :modified-since? and used to check against the request header If-Modified-Since.

(ANY "/timehop" []
       (resource
        :available-media-types ["text/plain"]
        ;; timestamp changes every 10s
        :last-modified (* 10000 (long  (/ (System/currentTimeMillis) 10000)))
        :handle-ok (fn [_] (format "It's now %s" (java.util.Date.)))))

Curl shows that the the resource sends a 304 if the time stamp in the request header relates to a fresh representation.

$ curl -i http://localhost:3000/timehop
HTTP/1.1 200 OK
Date: Thu, 25 Apr 2013 07:27:25 GMT
Vary: Accept
Last-Modified: Thu, 25 Apr 2013 07:27:20 GMT
Content-Type: text/plain;charset=UTF-8
Content-Length: 38
Server: Jetty(7.6.1.v20120215)

It's now Thu Apr 25 09:27:25 CEST 2013

$ curl -i -H 'If-Modified-Since: Thu, 25 Apr 2013 07:27:20 GMT' http://localhost:3000/timehop
HTTP/1.1 304 Not Modified
Date: Thu, 25 Apr 2013 07:27:28 GMT
Last-Modified: Thu, 25 Apr 2013 07:27:20 GMT
Content-Type: text/plain
Server: Jetty(7.6.1.v20120215)

Liberator will handle If-Unmodified-Since as well and supports conditional updates for PUT and POST.

ETag

While If-Modified-Since requires that the clocks of client and server are sufficiently in sync, there is no such need for conditional requests based on ETags. A resource can generate an ETag by providing a function at the key :etag and the required outcome will be calculated by liberator as expected.

Liberator does not support weak ETags. The value generated by :etag will be considered a strong ETag.
(ANY "/changetag" []
       (resource
        :available-media-types ["text/plain"]
        ;; etag changes every 10s
        :etag (let [i (int (mod (/ (System/currentTimeMillis) 10000) 10))]
                (.substring "abcdefhghijklmnopqrst"  i (+ i 10)))
        :handle-ok (format "It's now %s" (java.util.Date.))))

Curl shows that the resource works as expected:

$ curl -i  http://localhost:3000/changetag
HTTP/1.1 200 OK
Date: Thu, 25 Apr 2013 07:44:56 GMT
Vary: Accept
ETag: "ijklmnopqr"
Content-Type: text/plain;charset=UTF-8
Content-Length: 38
Server: Jetty(7.6.1.v20120215)

It's now Thu Apr 25 09:44:56 CEST 2013

$ curl -i -H'If-None-Match: "ijklmnopqr"' http://localhost:3000/changetag
HTTP/1.1 200 OK
Date: Thu, 25 Apr 2013 07:45:01 GMT
Vary: Accept
ETag: "abcdefhghi"
Content-Type: text/plain;charset=UTF-8
Content-Length: 38
Server: Jetty(7.6.1.v20120215)

It's now Thu Apr 25 09:45:01 CEST 2013

$ curl -i -H'If-Match: "ijklmnopqr"' http://localhost:3000/changetag
HTTP/1.1 412 Precondition Failed
Date: Thu, 25 Apr 2013 07:45:04 GMT
ETag: "abcdefhghi"
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 20
Server: Jetty(7.6.1.v20120215)

Precondition failed.

Liberator also supports the lesser known semantic of using * for If-Match and If-None-Match. These are useful for conditional updates of resources.

Continue with Handling POST, PUT and DELETE.