Correct Content-Type of static assets in Ring apps

If you’re thinking of serving assets from a Clojure Ring app, then you should be aware of an interesting quirk of one of the core pieces of Ring middleware: wrap-file-info. This middleware is used to automatically detect the file type based on extension and inject the corresponding Content-Type header into the HTTP response.

Now, when developing with it enabled, you might run into an interesting and somewhat hard situation to debug. In development mode the files will be served with the correct MIME type. In production, when the app is packaged together into a single uberjar, IF your resources were being served from within the resources/ folder, then wrap-file-info will fail to correctly identify the file type. 

Why? In development ring will be working with real files on disk, in a .jar situation you simply don’t get file extensions. See the following thread where Weavejester clarifies the situation.

Solution? Use wrap-content-type middleware instead. It only cares about the extension specified in the URL and should work correctly in most of the basic cases. I imagine you could have Ring fetch resources outside of the .jar itself, in which case the problem above would not reproduce. I believe you could get the best of both worlds by moving your assets outside of the uberjar and by using wrap-content-type regardless.

As a sidenote, cURL seems to infer MIME type based off of the URL, a behavior that’s inconsistent with the current versions of Firefox and Chrome. If you try to debug the issue above with cURL, you will be deeply puzzled. cURL will consistently correctly guess the MIME type, while the browser pointing at the file will print out a console message saying it’s getting a resource with text/plain, instead a different Content-Type value. The browser still correctly tries to use the resource, as they’re wrapped in a <script> or <link> tag with a specified content type. What makes this a tad extra frustrating is that Firefox allows you to generate cURL requests from the debug panel, and the requests are actually inconsistent with the browser that output that command.

If someone knows how to turn off Content-Type inference in cURL, do let me know 🙂

First satisfied school of the year

Front Row Classroom has been out for less than a week and already the visionaries at FCPS are experiencing the power of adaptive math practice and teacher analytics to improve teacher effectiveness and quality of education.

Very satisfying to see the hard work paying off when students across the country have a much better time with math.

Link here.

Back to work now!

First satisfied school of the year

Computer Networks on Coursera

Can’t recommend this course highly enough if you’re a web dev of are doing work beyond one device.

It’s a very bittersweet course, as I continuously run into concepts that make me go:“I really should already know this like the back of my hand”, but oh well, it’s never too late.

David Wetherall is a very pleasant instructor and does a superb job.

I have to say I have a much better understanding of networking internals now and this is a good ramp for diving into meatier material such as http://www.amazon.com/TCP-Illustrated-Volume-Addison-Wesley-Professional/dp/0321336313

Computer Networks on Coursera

nREPL

Want to be able to connect to your clojure Ring-based web-app as it’s running and read/edit its code in real time? No problemo.

Add the clojure.tools.nrepl dependency, and defonce the server somewhere in your handler with a port of your choice. Now you can just ssh into the web app’s box and use your repl of choice (I use Leiningen for simplicity) to play around with things. Really convenient for debugging, since with Clojure you almost never attach a traditional step-through debugger to running applications.

For bonus points you can use cemerick’s Drawbridge to enable logging into your app through the existing HTTP(S) routes. No need to expose additional ports and figure out how to secure that properly (since default nREPL is passwordless, as far as I can tell. That will only work until the first nmap). What’s neat is that you can in fact keep using the same auth mechanisms you already have in place, except this time for administrative REPL access.

nREPL