Question

I have a Rails 3.2 application, where the client side polls a resource in the server for updates. This resource is not an asset, but dynamic content.

The implementation strategy i chose is a Conditional Get via the fresh_when directive:

fresh_when(:etag => @etag, :public => false ) #it's a private resource, requires auth etc

So, when the resource is still fresh (request 'If-Not-Modified' header equals the resource current ETag), the server only returns a 304 header.

Started GET "/news/4fe13e74aa5e7d3d70000001"
Processing by NewsController#show as JSON
Parameters: {"id"=>"4fe13e74aa5e7d3d70000001"}
Completed 304 Not Modified

When the resource is no longer fresh, then we have status 200 and the most recent version of the resource in the response body:

Started GET "/news/4fe13e74aa5e7d3d70000001"
Processing by NewsController#show as JSON
Parameters: {"id"=>"4fe13e74aa5e7d3d70000001"}
Rendered news/show.json.rabl (8.1ms)
Completed 200 OK in 14ms

At the development environment this works perfectly. The problem lies in the production environment (Heroku Cedar Stack). In this scenario, the responses are always 200 with the full object representation in the body:

2012-08-03T21:44:33+00:00 heroku[router]: GET blah.com/news/4fa43b428b91cd0001000002 dyno=web.1 queue=0 wait=0ms service=22ms status=200 bytes=2105

2012-08-03T21:44:33+00:00 app[web.1]: Started GET "/news/4fa43b428b91cd0001000002"
2012-08-03T21:44:33+00:00 app[web.1]: cache: [GET /news/4fa43b428b91cd0001000002] miss

2012-08-03T21:44:33+00:00 heroku[nginx]: 187.38.19.138 - - [03/Aug/2012:21:44:33 +0000] "GET /news/4fa43b428b91cd0001000002 HTTP/1.1" 200 665 "http://www.bla.com/news/4fa43b428b91cd0001000002" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.57 Safari/537.1" 

From what it looks, this request isn't reaching the Rails Controller (where the freshness is actually evaluated), but is passing through heroku's router and caching layers.

What i've tried so far:

  • Double checked Rails (3.2.1) and Thin (1.3.1) versions in both environments.
  • Setting 'config.action_controller.perform_caching = false'
  • Removing Rack::Cache middleware (config.middleware.delete Rack::Cache)

The reason i don't want those redundant responses is because it bombs the client side app with unneeded object refreshes, causing a serious performance hit to the end user. When only a 302 header is returned from the server, the client-side javascript just sleeps for a while before polling again.

Thanks

Was it helpful?

Solution

The root of the problem lies in a API misuse. This line:

fresh_when(etag: @etag, public: false)

Behaves as expected in development, but not in production. I thought that the exchange of Etag/If-Not-Modified headers was enough for this kind of conditional get procedure, so i didn't care to add the 'Last-Modified' one.

After reading the HTTP 1.1 RFC which said: "HTTP/1.1 servers SHOULD send Last-Modified whenever feasible." i decided to give it a try:

fresh_when(etag: @etag, public: false, last_modified: @news.updated_at)

And it worked at Heroku! I'm 99% sure that the lack of this header was messing up the request/response path somehow at their stack (given they have other servers at the front of your dyno like Varnish and Nginx)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top