on client-side Cache, Sinatra and Conditional GET’s
July 1st, 2011
After re-reading Google Code - Page Speed - Optimize Caching section, I began implementing it on the Sinatra app part of our eClinic.Pro system. Later, after having it working, I found out that Sinatra has this amazing helper:
def last_modified(time)
return unless time
time = time_for time
response[’Last-Modified’] = time.httpdate
halt 304 if Time.httpdate(env[’HTTP_IF_MODIFIED_SINCE’]).to_i >= time.to_i
rescue ArgumentError
end
Commited the code with a farewell git commit -am “Awesome handmade conditional GET to leverage client caching”, pushed it and refactored it to use the instance method last_modified(timestamp), just to commit it again.
Combine this with a before filter, like:
before do
cache_control :public, :max_age => 31536000
db_version = Admin.first.created_at
last_modified db_version
end
In short, the client (browser) issues a request for some content via a URI to the server. The server receives the request and checks the Request Headers to see if a If-Modified-Since directive is defined. If not, a response['Last-Modified'] header is set to the current time and the execution flow will proceed to the Sinatra app request route to deliver a response [no client local caching scenario]. Yet, this timestamp will be used by the client (browser) cache manager to store it locally, together with the response payload, building a client-side local cache for this resource. Now, if there is another client request, to the same URI, this time a If-Modified-Since Request Header will be sent along the request. The server will catch it and compare to an expiry date, in this case, the
Acording to Google, using both Cache-Control: max-age and Last-Modified headers, or even Etag if necessary, instructs the browser to load previously downloaded resources from local disk rather than over the network. This is client (browser) caching.
Based on the documentation it is redundant to specify both Expires and Cache-Control: max-age, or to specify both Last-Modified and ETag. It makes note to use the Cache control: public directive to enable HTTPS caching for Firefox.
This allows the browser to efficiently update its cached resources by issuing conditional GET requests when the user explicitly reloads the page. Conditional GETs don’t return the full response unless the resource has changed at the server, and thus have lower latency than full GETs.
For this, I set the server to dispatch a 304 Not Modified response to the browser so that is gets the local cached content and presents it as the result of the request, with a:
Request Method: GET
Status Code: 304 Not Modified
This is quite important since I’m requesting
to be fetched, and the result of this is 13 miliseconds response time. Local content. The payload is considerable, about 136KB of JSON response for one of the areas. Adding up that for one of the pages I’m loading 3 payloads of these via jQuery, the main server Merb app is now feeling quite lighter due to this recent decoupling. This means lower CPU utilization, database access, bandwith and time to respond.
Combining this with application server caching, response times get quite improved.


Final Notes:
- Github SinatraRb documentation for (Object) last_modified(time);
- Google Code Optimize Caching page;
- HTTP Headers on Wikipedia;
- Google Chrome is an amazing browser to develop on, using the Developer Tools;
- still got to detect a “typo” on Google Code documentation.