Permalink
Fetching contributors…
Cannot retrieve contributors at this time
56 lines (51 sloc) 2.41 KB
(ns ring.middleware.file-info
"Augment Ring File responses."
(:require [ring.util.response :as res])
(:use [ring.util.mime-type :only (ext-mime-type)])
(:import java.io.File
(java.util Date Locale TimeZone)
java.text.SimpleDateFormat))
(defn- guess-mime-type
"Returns a String corresponding to the guessed mime type for the given file,
or application/octet-stream if a type cannot be guessed."
[^File file mime-types]
(or (ext-mime-type (.getPath file) mime-types)
"application/octet-stream"))
(defn ^SimpleDateFormat make-http-format
"Formats or parses dates into HTTP date format (RFC 822/1123)."
[]
;; SimpleDateFormat is not threadsafe, so return a new instance each time
(doto (SimpleDateFormat. "EEE, dd MMM yyyy HH:mm:ss ZZZ" Locale/US)
(.setTimeZone (TimeZone/getTimeZone "UTC"))))
(defn- not-modified-since?
"Has the file been modified since the last request from the client?"
[{headers :headers :as req} last-modified]
(if-let [modified-since (headers "if-modified-since")]
(not (.before (.parse (make-http-format) modified-since)
last-modified))))
(defn wrap-file-info
"Wrap an app such that responses with a file a body will have corresponding
Content-Type, Content-Length, and Last Modified headers added if they can be
determined from the file.
If the request specifies a If-Modified-Since header that matches the last
modification date of the file, a 304 Not Modified response is returned.
If two arguments are given, the second is taken to be a map of file extensions
to content types that will supplement the default, built-in map."
[app & [mime-types]]
(fn [req]
(let [{:keys [headers body] :as response} (app req)]
(if (instance? File body)
(let [file-type (guess-mime-type body mime-types)
file-length (.length ^File body)
lmodified (Date. (.lastModified ^File body))
response (-> response
(res/content-type file-type)
(res/header
"Last-Modified"
(.format (make-http-format) lmodified)))]
(if (not-modified-since? req lmodified)
(-> response (res/status 304)
(res/header "Content-Length" 0)
(assoc :body ""))
(-> response (res/header "Content-Length" file-length))))
response))))