Skip to content

Instantly share code, notes, and snippets.

@mccraigmccraig
Created August 20, 2018 13:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mccraigmccraig/87f4bef2cd2192306beeab72bf323895 to your computer and use it in GitHub Desktop.
Save mccraigmccraig/87f4bef2cd2192306beeab72bf323895 to your computer and use it in GitHub Desktop.
(ns er-api.service.resources.tempfile
(:require
;; [taoensso.timbre :refer [debug info warn error]]
[clojure.tools.logging :refer :all]
[schema.core :as s]
[clojure.java.io :as io]
[cheshire.core :as json]
[manifold.stream :as stream]
[byte-streams :as bs]
[yada.yada :refer [resource]]
[yada.request-body :as req-body]
[yada.multipart :as mp]
[bidi.ring :as bidi]
[aleph.http :as http]
[ring.swagger.json-schema :as rjs]
[er-api.service.resources.util :as util])
(:import [java.io File]
[ring.swagger.json_schema]))
(defrecord TempfilePartial [part fieldname filename content-type f out]
mp/Partial
(continue [this piece]
(info "continue TempfilePartial"
{:fieldname fieldname
:filename filename
:content-type content-type
:tempfile f
:piece piece})
(.write out (:bytes piece))
this)
(complete [this state piece]
(when piece
(info "complete TempfilePartial"
{:fieldname fieldname
:filename filename
:content-type content-type
:tempfile f
:piece piece})
(.write out (:bytes piece)))
(.close out)
(update state
:parts
(fnil conj [])
(-> part
(assoc :tempfile-partial this)))))
(defmethod rjs/convert-class TempfilePartial [_ _] {:type "string"})
(defn create-tempfile-partial
[{:keys [headers bytes body-offset] :as piece}]
(let [cd (get headers "content-disposition")
[_ fieldname] (some->> cd (re-find #"name=\"([^\"]+)\""))
[_ filename] (some->> cd (re-find #"filename=\"([^\"]+)\""))
[_ pre suff] (some->> filename (re-find #"(.+)\.(.*)"))
pre (cond
(and pre (<= 3 (count pre))) pre
pre (str pre "___")
:default pre)
ct (get headers "content-type")
f (File/createTempFile (or pre "file") (when suff (str "." suff)))
out (io/output-stream f)]
(info "create-tempfile-partial"
{:fieldname fieldname
:filename filename
:content-type ct
:tempfile f
:piece piece})
(.write out
bytes
body-offset
(- (alength bytes) body-offset))
(->TempfilePartial (-> piece
(dissoc :bytes)
(assoc :type :part))
fieldname
filename
ct
f
out)))
(defn single-piece-tempfile-partial
[state {:keys [] :as part}]
(let [{:keys [fieldname filename content-type f] :as tp} (create-tempfile-partial part)]
(info "single-piece TempfilePartial"
{:fieldname fieldname
:filename filename
:content-type content-type
:tempfile f
:part part})
(mp/complete tp state nil)))
(defrecord TempfilePartConsumer []
mp/PartConsumer
(consume-part [_ state part]
(info "consume-part" part)
(single-piece-tempfile-partial state part))
(start-partial [_ piece]
(info "start-partial" piece)
(create-tempfile-partial piece)))
(def tempfile-part-coercion-matchers
{String
(fn [part]
(cond
(instance? yada.multipart.DefaultPart part)
(let [offset (get part :body-offset 0)]
(String. (:bytes part)
offset
(- (count (:bytes part)) offset)))
(and
(:tempfile-partial part)
(instance? er_api.service.resources.tempfile.TempfilePartial
(:tempfile-partial part)))
(let [tfp (:tempfile-partial part)]
(slurp (:f tfp)))
:else
(throw (ex-info "can't coerce part to String" {:part part}))))
er_api.service.resources.tempfile.TempfilePartial
:tempfile-partial})
(defn tempfile-resource
[]
(resource
(util/apply-access-controls
{:post {:consumes #{"multipart/form-data"}
:part-consumer (->TempfilePartConsumer)
:produces #{"application/json"}
:response (fn [ctx] (json/generate-string :ok))}})))
(defn make-handler
[app]
(bidi/make-handler ["/" (tempfile-resource)]))
(defn start-server
[port]
(http/start-server (make-handler {})
{:port port
:raw-stream? true}))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment