I'm trying to improve performance for clients fetching pages from my Compojure webserver. We serve up a bunch of static files (JS, CSS) using (compojure.route/resources "/")
, which looks for files on the filesystem, converts them to URLs, and then serves them to Ring as streams. By converting to streams, it seems to lose all file metadata, such as the mod time.
I can wrap the static-resource handler and add an Expires
or Cache-Control: max-age
header, but that prevents the client from sending any request at all. Useful, but these files do change on occasion (when we put out a release).
Ideally I'd like the client to trust its own cached version for, say, an hour, and make a request with an If-Modified-Since
header after that hour has passed. Then we can just return 304 Not Modified
and the client avoids downloading a couple hundred kilos of javascript.
It looks like I can set a Last-Modified
header when serving a response, and that causes the client to qualify subsequent requests with If-Modified-Since
headers. Great, except I'd have to rewrite most of the code in compojure.route/resources
in order to add Last-Modified
- not difficult, but tedious - and invent some more code to recognize and respond to the If-Modified-Since
header. Not a monumental task, but not a simple one either.
Does this already exist somewhere? I couldn't find it, but it seems like a common enough, and large enough, task that someone would have written a library for it by now.
FWIW, I got this to work by using Ring's wrap-file-info middleware; I'm sorta embarrassed that I looked for this in Compojure instead of Ring. However, compojure.route
's files
and resources
handlers both serve up streams instead of Files or URLs, and of course Ring can't figure out metadata from that.
I had to write basically a copy of resources
that returns a File
instead; when wrapped in wrap-file-info
that met my needs. Still wouldn't mind a slightly better solution that doesn't involve copying a chunk of code from Compojure.
Have you considered using the ring-etag-middleware? It uses the last modified date of a file to generate the entity tag. It then keys a 304 on a match to the if-none-match header in the request.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With