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.
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.
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.
(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.