full ETag support for PUT

2007-08-26 @ 16:38#

i finally got my exyus framework to fully (and properly) support If-Match and If-Unmodified for PUTs. this means i can safely configure a resource to allow PUT to create and update as long as the client passes the proper headers. i must admit it took a bit of thinking and testing before i got it right. lots of recursion into the framework to handle the request made me woozy!

see, the way this works, is that i'm *actually using HTTP programming* to handle the lost update problem. this prevents someone from PUTting a resource over one that's already been changed. you know the drill:

user_a does a GET on /resource/a
user_b does a GET on /resource/a
user_b does a PUT on /resource/a
user_a does a PUT on /resource/a <-- bam! user_b changes were just overwritten!

the proper solution to the above case is to prevent user_a from completing the second update. and that is done using ETags (or Last-Modified). like this:

user_a does a GET on /resource/a <-- server returns etag=a1
user_b does a GET on /resource/a <-- server returns etag=a1
user_b does a PUT on /resource/a <-- user_b includes etag=a1 in the update
   server checks etag from user_b(a1) against server(a1) and sees they are the same
   server completes the update
   server sets the etag for /resource/a = a2
user_a does a PUT on /resource/a <-- user_a includes etag=a1 in the update
   server checks etag from user_a(a1) against server (a2) and sees they are not the same
   server sends error 412 (precondition failed) to user_a and prevents the update

now that's all fine and it works great. but there's more when it comes to the server. see, the server has to look up the etag for /resource/a>, right? how do i do this? turns out i can make a HEAD call to /resource/a to get the data. so my server gets a request from the user, then makes a request to *itself* to get the data to compare and there we go.

but there's one more item in the mix - cache invalidation on updates. yep, when /resource/a is udpated, the server needs to invalidate any references to /resource/a that already exist. and how is that done? you got it - a HEAD request using Cache-Control:no-cache. so, once again, the server calls *itself* to handle the cache updating. all pretty sweet, actually. but still a bit twisty in my head!

so, my PUT pattern now looks like this:

user does PUT on /resource/a with If-Match header
- server does HEAD on /resource/a with If-None-Match = client.If-Match value
- if return=304 (not modified), then proceed with the update
- if return=200 (ok), check the ETag. if it matches client.If-Match, proceed with the update
  (btw - do this for the client.If-Unmodified-Since and server.Last-Modified dates, too!)
- if proceed==true then server does physical update and...
- server does HEAD w/ Cache-Control:no-cache on /resource/a and all related URI to update the cache

there you have it. full support for ETags on PUT. i need to fully test the code update, but it all seems pretty solid. once this is done, i need to update the SqlXmlHandler to do the same things. that should be pretty much the same pattern, tho.

wow - busy weekend!

code