Add content-type header to file-response

418 views Asked by At

I have the following endpoint handler (using clj/ring), and it works fine, but it doesn't include a Content Type header in the response, which might be a reason why the returned video does not play in iOS/Safari.

(def stream-partial-media
  {:summary "Stream partial media referenced by file-key"
   :parameters {:path {:file-key uuid?}}
   :handler (fn [{{{:keys [file-key]} :path} :parameters}]
              (let [file-key-res (file-keys/READ-UNEXPIRED file-key)]
                (if (nil? file-key-res)
                  {:status 404
                   :body {:message "file-key not found"}}
                  (let [user-res (users/READ (:user-id file-key-res))]
                    (if (or (:dev env) (:prod env))
                      (log-ut/log-media-access {:file-id (str (:file-id file-key-res))
                                                :username (:username user-res)}))
                    (file-response (utils/file-id-to-path (:file-id file-key-res)))))))})

I want to make the endpoint dynamically return a Content Type, based on the extension of the requested file (mp4/mp3/etc.). I found that ring has a built in wrap-content-type function that uses the file's extension to add a Content Type header, but I do not know where to implement it. I tried putting it in a few different places, but still have no Content Type header.

I would like to know either how to correct implement this wrapper, or how to check the file extension and manually add a header on that basis: mp4=>video/mp4, mp3=>audio/mp3.

1

There are 1 answers

0
user2609980 On BEST ANSWER

From the request you have to get the filename, from which you can get the extension. Then based on the extension you can add the "Content-Disposition" to the headers map. Note that Ring request is just a map and the response as well. Something like this might work:

(require '[ring.util.response :refer [header]])

(defn extension [s]
  (second (re-find #"\.([a-zA-Z0-9]+)$" s)))

(extension "foo.mp3")
;; => "mp3"

(extension "foo.mp4")
;; => "mp4"

(extension "foo")
;; => nil

(def stream-partial-media
  {:summary "Stream partial media referenced by file-key"
   :parameters {:path {:file-key uuid?}}
   :handler (fn [{{{:keys [file-key]} :path} :parameters :as request}]
              (let [file-key-res (file-keys/READ-UNEXPIRED file-key)]
                (if (nil? file-key-res)
                  {:status 404
                   :body {:message "file-key not found"}}
                  (let [user-res (users/READ (:user-id file-key-res))]
                    (when (or (:dev env) (:prod env))
                      (log-ut/log-media-access {:file-id (str (:file-id file-key-res))
                                                :username (:username user-res)}))
                    (-> (file-response (utils/file-id-to-path (:file-id file-key-res)))
                        (header "Content-Type"
                                (case (extension (:filename request))
                                  :mp4 "video/mp4"
                                  :mp3 "audio/mp3")))))))})

Above Ring's (header "Content-Type" <value>) just adds {:header {"Content-Type" <value>} to the response. The assumption is the :filename key is available in the request.

I would also add some extra check to see if the filename is correct, and otherwise return an error.