Http-authenticated Rails page caching
posted: March 31st, 2008 · by: Sven
So, Rails has added some great HTTP related features with HTTP authentication, ETagging and, of course, support for REST. These actually greatly help scaling our applications by moving to HTTP what really belongs there.
One similar feature that has been in Rails for ages is page caching which just saves the response to a file and lets the frontend server handle subsequent requests. For example Mephisto is known to be “fast as hell” just because it uses page caching for public, non-authenticated pages.
Unfortunately Rails page caching doesn’t work with authentication: if you have to authenticate your users you simply can’t use it.
For applications that target human users HTTP authentication might be of limited use anyways in many cases because it still opens up that ugly browser authentication dialog box that only die-hard HTTP geeks really like. On the other hand, when it comes to closing down an admin section for a blog, maybe that dialog is still a valid option for certain applications?
Also, these days it seems to be possible to get around that UI non-design with Ajax … so we might even be able to overcome this limitation. (Or maybe it it’s not, like Mislav told me at Euruko. I’d still have to actually try that stuff out. But anyways :)
But even if not, the ability to HTTP authenticate cached pages should be great news at least when it comes to web services that need some authentication but often times don’t need any “personalization” based on user preferences or whatever.
After some recent experiments I really think this kind of stuff is actually possible. And not only that, it’s even very simple to implement it in Mongrel and Rails as a proof of concept (which might not be sufficient for production, but see below).
Request-response cycle for HTTP authenticated page caching
So, let’s have a look at how the request/response cycle would have to look like in order to teach our Rails stack HTTP authenticated page caching.
Usually, with page caching, when a GET request is handled by Rails for the first time Rails saves the response body to a file in the servers document root (usually public/) before it returns it to the frontend server. That’s all about it. For subsequent requests to the same path the frontend server would notice that this file exists and immediately serve it to the client without passing the request to Rails at all.
That means that Rails has no chance to authenticate the user and return a ”401 Unauthorized” header if necessary. Thus the most important tweak for HTTP authenticated page caching is to teach the frontend server to ask Rails whether or not a file may be served to the client with the given credentials (i.e. request headers).
This actually is not a new idea and has been done for other environments before. For example Apache mod_perl allows to do the same and I’m sure there are lots of similar solutions. The basic concept is to configure the frontend server to (instead of using static configuration like an .htaccess file or a more performant DBM database, in case of Apache) ask an arbitrary backend to authenticate the request.
So, for the sake of simplicity the first goal here is to configure Mongrel (as a frontend server for this experiment) to ask the Rails application to authenticate the request when a request to a certain location comes in.
So, lets say we have a realm of private documents and our Rails app handles requests to the private/ path with a PrivateController. Page caching is turned on so Rails saves the response from the index action of this controller to the file public/private.html.
We now want Mongrel to not immediately serve that file but instead ask Rails to authenticate it with the given request headers. If Rails authenticates the request Mongrel should serve the file. If not Mongrel should return a 401 response with no body.
The appropriate HTTP method for “asking” such things to a RESTful service is HEAD (if I by all means understand the HTTP specs correctly, please correct me if I’m wrong). Thus, we’d also need to teach Rails a new bit of knowledge about page caching: that it doesn’t need to re-build an already existing page-cached file when a HEAD request to it comes in.
An experimental implementation
It turns out that these things are pretty easy to implement. Mongrel is just Ruby and Rails is greatly extensible anyways. The Rails handler in Mongrel relies on a CGIWrapper which is tagged “still very alpha” and “ugly but does the job” in the Rdoc comments. But anyways, that doesn’t hold us back from using and extending this stuff.
In order to keep this blog post shorter I’ve uploaded my experimental implementation so can look at the code and I can omit it from the text here. You can install it as a usual Rails plugin and then run Mongrel like this:
mongrel_rails start -S vendor/plugins/authenticated_page_caching/mongrel.rb
This loads the mongrel.rb file as a config script which gets executed while Mongrel is starting up. This file defines the class RailsAuthHandler which implements the behaviour explained above. Also, at the bottom of the file you find the lines:
auth = RailsAuthHandler.new(rails)
auth.location '/private'
uri "/", :handler => auth, :in_front => true
This just registers the RailsAuthHandler instance as a handler and specifies that it needs to be called before other handlers (i.e. before Mongrel’s default Rails handler).
When a request comes in with a PATH_INFO that matches what we have specified as auth.location (of course we could specifiy more that one location) it will intercept things and pass a HEAD request to Rails like discussed above. Depending on Rails’ response the handler will accordingly save the file or pass through 401 and 404 headers.
If a request does not match any of the registered locations the handler won’t do anything but just return and let Mongrel call the next handler which is the default Rails handler.
So we can now set up a controller like this:
class PrivateController < ApplicationController
prepend_before_filter :authenticate
caches_page :index
def index
render :text => "some private content"
end
private
def authenticate
authenticate_or_request_with_http_basic('Private stuff!') do |user, pass|
user == "user" && pass == "pass"
end
end
end
This is basically common ground. We need to use prepend_before_filter instead of before_filter. This way authentication gets called before the serve_page_cached_file filter which already has bee registered by the plugin and teaches Rails to serve an existing page cached file instead of re-rendering and re-saving it.
I’m unsure about the details of the implementation of the serve_page_cached_file filter but for this purpose it does its job.
Show time
Now let’s try this out. We have to make sure that Rails caching is turned on so either start Mongrel in production mode or set config.action_controller.perform_caching to true in config/environments/development.rb. Also, make sure that no public/private.html file exists, yet.
With Mongrel started with our extension we can try things out like follows (I’ve stripped some of the returned lines from the responses for brevity):
$ curl http://localhost:3000/private -I
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="This is private stuff!"
Status: 401 Unauthorized
This is what we’d also get without our handler. The file doesn’t exist, yet, and thus Rails gets called. Because Rails can’t authenticate the request (we haven’t specified any username and password) we receive a 401 response.
(Something I don’t understand is the “Content-Length: 27” header that’s also included in the response. What does this refer to?)
Ok, so let’s try to authenticate:
$ curl http://localhost:3000/private -I --user user:pass
Rails responds:
HTTP/1.1 200 OK
ETag: "47f11a41-15-1ccce4"
Last-Modified: Mon, 31 Mar 2008 17:07:13 GMT
Content-Length: 21
… with the body “some private content\n” which now also has been saved to the file public/private.html:
$ stat -f "%Sm %N" public/private.html
Mar 31 21:24:10 2008 public/private.html
Now, with usual page caching Mongrel would serve the page cached file on subsequent requests even if no credentials have been provided. But with our extension it doesn’t, but instead asks Rails for authentication, which fails. We get the same response as above:
$ curl http://localhost:3000/private -I
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="This is private stuff!"
Status: 401 Unauthorized
Also, even though Rails is called with a HEAD request it doesn’t re-render the index action of the PrivateController. Instead, the ActionController just looks for an existing file public/private.html and uses that to determine the content-length header which will be set accordingly for the response to the HEAD request.
$ stat -f "%Sm %N" public/private.html
Mar 31 21:24:10 2008 public/private.html
… the same as above. Also, for subsequent requests the last-modified header doesn’t change.
Ok, so what?
One question I’ve been asked when discussing this stuff on Euruko was “Great, but how is this different from action caching?”. That’s a great question because different from usual page caching Rails will be called here of course. So where’s the advantage?
The main advantage I see is that with this approach we actually use the HTTP protocol itself to implement the authentication mechanism: we just send a HEAD request to Rails. And a HEAD request is a well-known, specified thing that many applications can understand.
This in some way decouples the cache from Rails a bit more: with this in place we could easily switch to another backend to have our requests authenticated. Maybe just a plain-old .htaccess-style config. Or some LDAP server that both the frontend server and the Rails backend can talk to.
Also, and maybe this is the more important point, it’s no big deal to imagine some simple caching mechanism implemented in the fronend layer. The authenticating response from Rails is just another HTTP response! And HTTP has means to communicate caching metadata build in - so we can easily cache that on the frontend as well.
For example, we could have Mongrel (in this case) store the responses to the authenticating HEAD requests to Memcache. We could either just cache them according to given HTTP headers (say, for a couple of minutes) or we could even have our Rails application expire (sweep) these memcached responses as necessary.
Of course you won’t seriously want to use Mongrel as your only frontend server. That’s right :) I’ve just implemented this proof-of-concept thing as a Mongrel extension because for me this was the simplest thing that could possibly work.
For getting the most out of this approach we’d need an extension for our “real” frontend server like Nginx, Lighttpd, Apache or whatever we use. My last experiences coding C/C++ are like 10 years old so I’m not particulary eager to play with this. (From what I’ve read on the Nginx wiki extensions shouldn’t be too hard to do, though).
On the other hand a Mongrel extension like this should work with a another server in front of it assuming it’s configured accodingly. One could have (e.g.) Nginx serving static files for all locations except for the http-authenticated ones, Mongrel serving http-authenticated static files and Rails creating them. I imagine that this already would allow for a great boost for authenticated files even though Mongrel is not the best choice for serving static files.
Also, with some further tweaking this of course should allow to authenticate page cached files through other, e.g. cookie-based authentication mechanisms, too. (So, yes, you could even think of funky stuff like OpenID authenticated page caching as jokingly suggested by a jumping man at Euruko. ;)
Any ideas?
Although this experimental implementation seems to work quite well there’s still a lot of questions to answer and stuff to work on.
E.g. is my assumption correct or wrong that using a HEAD in this way is the right way to integrate the communication of the frontend server and the backend Rails application? What would be the best way to (e.g.) memcache Rails’ responses in Mongrel? Do you have an idea how to implement this stuff as an Nginx extension (or as an extension for another good frontend server)? Have you got HTTP authentication working with Ajax for all most important browser versions?
I’d greatly appreciate feedback on this. So, let me know what you think :)
Mislav said March 31st, 2008 at 09:54 PM ¶
Nice writeup ;)
Like many others, I’ve set up Nginx to serve cached pages. Those requests never reach my Mongrels. What then?
Sven said March 31st, 2008 at 10:06 PM ¶
Hey, wow, that was quick :))
I’ve never tried that myself, but I’m pretty sure one can explicitely configure certain locations from being served by Nginx, but instead being passed to Mongrel?
Something like:
location /private { … proxy_pass http://mongrel; break; }
jack said January 23rd, 2011 at 10:42 AM ¶
This thing is kinda interesting- are you still working on this? I am using liquid… you know… that sucks.. cheap vps
QQQ said February 7th, 2011 at 06:36 PM ¶
Finally we kissed and the passion scale went sky high and I knew I was onto a good thing - sex was a certainty free porn videos. She never hesitated when I began to fondle her breasts and she willingly exposed them for me mobile porn. They were firm and I suspected a breast enhancement but said nothing - they still felt good and I was enjoying them and gradually working my way further south free porn tube. She was a step ahead of me and before I could completely undress her she moved on me atk hairy and I was suddenly having my pants pulled down and I was enjoying one of he best cock sucking hairy pussy experiences I had ever had. ABB728019394
Otto said March 1st, 2011 at 11:40 PM ¶
In what plugin repository does http_authentication reside? It seems my script/plugin is misbehaving and doesn’t want to let me “discover”, so I’ll have to “source” the repository by hand before I can install the plugin. Aldactone acheter aldactone colesterolo pillole colesterolo.
ddsgd said March 21st, 2011 at 06:28 AM ¶
How to Convert AVCHD films to AVI
URL?http://www.wondershare.com/avchd/convert-avchd-to-avi.html Troubles we may possibly have encountered to take satisfaction in and reveal AVCHD films with friends:
That’s true, the AVCHD structure is not accepted by lots of players, and also this kind of structure cannot be uploaded to internet which prevents film sharing. I’ve searched near to some great offer and uncover an simplest alternative finally. The simplest way can be to convert avchd to avi or other standard formats.
The alternative we are able to consider to solve them: An AVCHD to AVI Converter is needed. Please click to obtain avchd to avi converter, set up and run it. Step one Get AVCHD films from AVCHD Camcorder to PC
Connect the AVCHD Camcorder for the PC using a USB cable. when attached and powered on, the camcorder should seek out the desktop like a brand brand new disk. It is desirable which you duplicate the films for the PC’s hard-drive earlier to converting avchd to avi or editing it. Step 2. fill AVCHD camcorder videos
Click “Add Video” or just drag & squirrel away your AVCHD camcorder films using the document checklist directly, you can very easily include AVCHD camcorder videos.
Load AVCHD camcorder videos Step 3. find AVI as output format
Select AVI as output structure away from your categorized output formats checklist in “Profile” drop-down checklist and specify the output list to preserve your converted records in “Output” drop-down list.
Select Convert AVCHD films to AVI output format Step 4. start to convert avchd to avi
After every little thing is done, strike “Start” key to convert avchd to avi. And all the things can be achieved at quickly demand and higher quality. The conversion demand often is dependent for the genuine film sizing as well as your PC configuration. as well as the output best quality is great.
After converting avchd to avi, now you can upload these AVI films to internet or view them with house windows advertising Player/Movie Maker to reveal them jointly with your friends. The complete method is easy, and you also can possess a try of the AVCHD to AVI Converter to how to convert avchd to avi and reveal your delighted instant easily.
Tips: Why we choose AVI format? As we realize that AVI structure is among the probably the most standard formats, that is broadly accepted by the majority of players. as well as the best quality of AVI film is great. using the sake of reputation and compatibility, AVI is regarded as probably the most advantageous choice. Therefore, you only should convert AVCHD to AVI, after which you can very easily reveal your AVCHD camcorder videos.
Cams said March 30th, 2011 at 07:40 PM ¶
I’m still requiring autentication for each user going on 2 years now. It’s a pain but it is better than nothing.
Okey oyunu said May 12th, 2011 at 03:39 PM ¶
Tüm dünya artik okey oyunu oynuyor. Yillardir bir çok oyun programi olmasina ragmen, içlerinden en güzeli olarak nitelendirebilecegimiz tek bir site göze çarpmaktadir. Diger tüm okey oyunu programlarinin aksine ücretsiz olmasi ve 3 boyutlu olarak hizmet vermesi mükemmel bir gelismedir. Sizlerde www.okey-oyunu.com adresinden bu essiz okey oyununu indirebilirsiniz. Kullanimi çok basit ve Türkçe dil seçenegi ile kolaylikla oyuna baslayabilirsiniz. Ister kendi ülkenizden, isterseniz dünyanin tüm farkli bölgelerinden dilediginiz oyun odalarini seçerek, oyuna hemen baslayabilirsiniz. Okey oyunu oynamak için artik arkadas bile aramaniza gerek kalmadan, bilgisayarinizdan 100 binlerce üye ile online olarak okey oyununu oynamanin zevkine varabilirsiniz.
Hanns said May 13th, 2011 at 01:39 PM ¶
Bookmarked and emailed to a few friends. This was very helpful thanks: class PrivateController < ApplicationController prependbeforefilter :authenticate caches_page :index
def index render :text => “some private content” end
private def authenticate authenticateor_requestwithhttpbasic(‘Private stuff!’) do |user, pass| user == “user” && pass == “pass” end end end
Kind regards Hanns at oil booms
porno said May 23rd, 2011 at 10:37 AM ¶
good comment. thanks you friends.
I’ve surfed the net more than three hours today, however, I haven’t found such useful information. Thanks a lot, it is really useful to me
porno said May 23rd, 2011 at 10:39 AM ¶
good comment. thanks you friends.
I’ve surfed the net more than three hours today, however, I haven’t found such useful information. Thanks a lot, it is really useful to me