Use yada+bidi instead of compojure-api

This commit is contained in:
Marcin Kulik 2017-02-28 11:58:43 +01:00
parent d343d89dea
commit 3424505587
11 changed files with 115 additions and 137 deletions

View File

@ -2,11 +2,7 @@
{:file-store #var asciinema.component.local-file-store/local-file-store
:exp-set #var asciinema.component.mem-expiring-set/mem-expiring-set}
:config
{:app
{:middleware
{:functions {:stacktrace #var ring.middleware.stacktrace/wrap-stacktrace}
:applied ^:replace [:not-found :webjars :ring-defaults :route-aliases :ring-logger :stacktrace]}}
:http
{:http
{:port 4000}
:db
{:uri "jdbc:postgresql://localhost:15432/asciinema_development?user=vagrant"}

View File

@ -4,25 +4,19 @@
:min-lein-version "2.0.0"
:dependencies [[org.clojure/clojure "1.8.0"]
[com.stuartsierra/component "0.3.1"]
[metosin/ring-http-response "0.8.0"]
[clj-time "0.13.0"]
[duct "0.8.2"]
[compojure "1.5.1"]
[metosin/compojure-api "1.1.10"]
[yada "1.2.0"]
[aleph "0.4.1"]
[bidi "2.0.12"]
[prismatic/schema "1.1.3"]
[environ "1.1.0"]
[ring "1.5.0"]
[ring/ring-defaults "0.2.1"]
[ring-jetty-component "0.3.1"]
[ring-webjars "0.1.1"]
[ring-logger-timbre "0.7.5"]
[clj-bugsnag "0.2.9"]
[clj-aws-s3 "0.3.10" :exclusions [joda-time com.fasterxml.jackson.core/jackson-core com.fasterxml.jackson.core/jackson-annotations]]
[aleph "0.4.1"]
[pandect "0.6.1"]
[com.taoensso/carmine "2.15.1"]
[org.slf4j/slf4j-nop "1.7.21"]
[org.webjars/normalize.css "3.0.2"]
[duct/hikaricp-component "0.1.0"]
[org.postgresql/postgresql "9.4.1211"]
[duct/ragtime-component "0.1.4"]]

View File

@ -1,6 +1,5 @@
{:components
{:app #var duct.component.handler/handler-component
:http #var asciinema.component.aleph/aleph-server
{:http #var asciinema.component.yada-listener/yada-listener
:db #var asciinema.component.db/hikaricp
:ragtime #var duct.component.ragtime/ragtime
:file-store #var asciinema.component.s3-file-store/s3-file-store
@ -9,50 +8,11 @@
:endpoints
{:asciicasts #var asciinema.endpoint.asciicasts/asciicasts-endpoint}
:dependencies
{:http [:app]
:app [:asciicasts]
{:http {:app :asciicasts}
:ragtime [:db]
:asciicasts [:db :file-store :exp-set :executor]}
:config
{:app
{:middleware
{:functions
{:hide-errors #var duct.middleware.errors/wrap-hide-errors
:not-found #var duct.middleware.not-found/wrap-not-found
:ring-defaults #var ring.middleware.defaults/wrap-defaults
:route-aliases #var duct.middleware.route-aliases/wrap-route-aliases
:ring-logger #var ring.logger.timbre/wrap-with-logger
:bugsnag #var clj-bugsnag.ring/wrap-bugsnag
:webjars #var ring.middleware.webjars/wrap-webjars}
:applied
[:not-found :webjars :ring-defaults :route-aliases :ring-logger :bugsnag :hide-errors]
:arguments
{:not-found #resource "asciinema/errors/404.html"
:hide-errors #resource "asciinema/errors/500.html"
:bugsnag
{:api-key bugsnag-key
:environment env-name
:version git-sha
:project-ns "asciinema"}
:route-aliases {"/" "/index.html"}
:ring-defaults
{:params {:urlencoded true
:keywordize true
:multipart true
:nested true}
:cookies true
:session {:flash true
:cookie-attrs {:http-only true}}
:security {:anti-forgery true
:xss-protection {:enable? true, :mode :block}
:frame-options :sameorigin
:content-type-options :nosniff}
:static {:resources "asciinema/public"}
:responses {:not-modified-responses true
:absolute-redirects true
:content-types true
:default-charset "utf-8"}}}}}
:http
{:http
{:port http-port}
:db
{:uri db-uri}

View File

@ -5,4 +5,4 @@
(input-stream [this path])
(move-file [this old-path new-path])
(delete-file [this path])
(serve-file [this path opts]))
(serve-file [this ctx path opts]))

View File

@ -1,17 +0,0 @@
(ns asciinema.component.aleph
(:require [com.stuartsierra.component :as component]
[aleph.http :refer [start-server]]))
(defrecord WebServer [port server app]
component/Lifecycle
(start [component]
(let [handler (:handler app)
server (start-server handler {:port port :join? false})]
(assoc component :server server)))
(stop [component]
(when server
(.close server)
component)))
(defn aleph-server [{:keys [port app]}]
(map->WebServer {:port port :app app}))

View File

@ -21,7 +21,7 @@
(.execute executor f)
result)
(catch RejectedExecutionException _
{:status 503 :headers {"Retry-After" "5"} :body "<h1>503</h1>"})))
nil)))
component/Lifecycle
(start [{:keys [threads queue-length] :as component}]

View File

@ -27,11 +27,12 @@
(let [path (str base-path path)]
(io/delete-file path)))
(serve-file [this path {:keys [filename]}]
(let [resp (response/ok (file-store/input-stream this path))]
(serve-file [this ctx path {:keys [filename]}]
(let [path (str base-path path)
response (assoc (:response ctx) :body (io/file path))]
(if filename
(response/header resp "Content-Disposition" (str "attachment; filename=" filename))
resp))))
(update response :headers assoc "content-disposition" (str "attachment; filename=" filename))
response))))
(defn local-file-store [{:keys [path]}]
(->LocalFileStore path))

View File

@ -50,9 +50,12 @@
(let [path (str path-prefix path)]
(s3/delete-object cred bucket path)))
(serve-file [this path opts]
(let [path (str path-prefix path)]
(response/found (generate-presigned-url cred bucket path opts)))))
(serve-file [this ctx path opts]
(let [path (str path-prefix path)
url (generate-presigned-url cred bucket path opts)]
(-> (:response ctx)
(assoc :status 302)
(update :headers assoc "location" url)))))
(defn s3-file-store [{:keys [cred bucket path-prefix]}]
(->S3FileStore cred bucket path-prefix))

View File

@ -0,0 +1,21 @@
(ns asciinema.component.yada-listener
(:require [com.stuartsierra.component :as component]
[yada.yada :as yada]))
(defrecord YadaListener [port server app]
component/Lifecycle
(start [component]
(if server
component
(let [handler (:routes app)
server (yada/listener handler {:port port})]
(assoc component :server server))))
(stop [component]
(if server
(do
((:close server))
(assoc component :server nil))
component)))
(defn yada-listener [{:keys [port app]}]
(map->YadaListener {:port port :app app}))

View File

@ -11,16 +11,15 @@
[clojure.java
[io :as io]
[shell :as shell]]
[clojure.string :as str]
[compojure.api.sweet :refer :all]
[environ.core :refer [env]]
[ring.util.http-response :as response]
[schema.core :as s]))
[schema.core :as s]
[yada.yada :as yada]))
(defn exception-handler [^Exception e data request]
(throw e))
(def Theme (apply s/enum asciicast/themes))
(defn a2png [in-url out-path {:keys [snapshot-at theme scale]}]
(def png-ttl-days 7)
(defn- a2png [in-url out-path {:keys [snapshot-at theme scale]}]
(let [a2png-bin (:a2png-bin env "a2png/a2png.sh")
{:keys [exit] :as result} (shell/sh a2png-bin
"-t" theme
@ -31,53 +30,74 @@
(when-not (zero? exit)
(throw (ex-info "a2png error" result)))))
(def Num (s/if #(str/includes? % ".")
Double
s/Int))
(defn- generate-png [file-store exp-set asciicast png-params png-store-path]
(with-tmp-dir [dir "asciinema-png-"]
(let [json-store-path (asciicast/json-store-path asciicast)
json-local-path (str dir "/asciicast.json")
png-local-path (str dir "/asciicast.png")
expires (-> png-ttl-days t/days t/from-now)]
(with-open [in (fstore/input-stream file-store json-store-path)]
(let [out (io/file json-local-path)]
(io/copy in out)))
(a2png json-local-path png-local-path png-params)
(fstore/put-file file-store (io/file png-local-path) png-store-path)
(exp-set/conj! exp-set png-store-path expires))))
(def Theme (apply s/enum asciicast/themes))
(defn- service-unavailable-response [ctx]
(-> (:response ctx)
(assoc :status 503)
(update :headers assoc "retry-after" "5")))
(def png-ttl-days 7)
(defn- async-response [ctx executor f]
(or (executor/execute executor f)
(service-unavailable-response ctx)))
(defn asciicast-json-resource [db file-store]
(yada/resource {:produces "application/json"
:parameters {:path {:token String}
:query {(s/optional-key :dl) s/Bool}}
:properties (fn [ctx]
(if-let [asciicast (adb/get-asciicast-by-token db (-> ctx :parameters :path :token))]
{:exists? true
::asciicast asciicast}
{:exists? false}))
:response (fn [ctx]
(let [asciicast (-> ctx :properties ::asciicast)
dl (-> ctx :parameters :query :dl)
path (asciicast/json-store-path asciicast)
filename (str "asciicast-" (:id asciicast) ".json")]
(fstore/serve-file file-store ctx path (when dl {:filename filename}))))}))
(defn asciicast-png-resource [db file-store exp-set executor]
(yada/resource {:produces "image/png"
:parameters {:path {:token String}
:query {(s/optional-key :time) s/Num
(s/optional-key :theme) Theme
(s/optional-key :scale) (s/enum "1" "2")}}
:properties (fn [ctx]
(if-let [asciicast (adb/get-asciicast-by-token db (-> ctx :parameters :path :token))]
(let [user (udb/get-user-by-id db (:user_id asciicast))
{:keys [time theme scale]} (-> ctx :parameters :query)
png-params (cond-> (asciicast/png-params asciicast user)
time (assoc :snapshot-at time)
theme (assoc :theme theme)
scale (assoc :scale (Integer/parseInt scale)))]
{:exists? true
:version (asciicast/png-version asciicast png-params)
::asciicast asciicast
::png-params png-params})
{:exists? false}))
:response (fn [ctx]
(let [asciicast (-> ctx :properties ::asciicast)
png-params (-> ctx :properties ::png-params)
png-store-path (asciicast/png-store-path asciicast png-params)]
(if (exp-set/contains? exp-set png-store-path)
(fstore/serve-file file-store ctx png-store-path {})
(async-response ctx executor (fn []
(generate-png file-store exp-set asciicast png-params png-store-path)
(fstore/serve-file file-store ctx png-store-path {}))))))}))
(defn asciicasts-endpoint [{:keys [db file-store exp-set executor]}]
(api
{:exceptions {:handlers {:compojure.api.exception/default exception-handler}}}
(context
"/a" []
(GET "/:token.json" []
:path-params [token :- String]
:query-params [{dl :- s/Bool false}]
(if-let [asciicast (adb/get-asciicast-by-token db token)]
(let [path (asciicast/json-store-path asciicast)
filename (str "asciicast-" (:id asciicast) ".json")]
(fstore/serve-file file-store path (when dl {:filename filename})))
(response/not-found)))
(GET "/:token.png" []
:path-params [token :- String]
:query-params [{time :- Num nil}
{theme :- Theme nil}
{scale :- (s/enum "1" "2") nil}]
(if-let [asciicast (adb/get-asciicast-by-token db token)]
(let [user (udb/get-user-by-id db (:user_id asciicast))
png-params (cond-> (asciicast/png-params asciicast user)
time (assoc :snapshot-at time)
theme (assoc :theme theme)
scale (assoc :scale (Integer/parseInt scale)))
png-store-path (asciicast/png-store-path asciicast png-params)]
(if (exp-set/contains? exp-set png-store-path)
(fstore/serve-file file-store png-store-path {})
(executor/execute executor (fn []
(with-tmp-dir [dir "asciinema-png-"]
(let [json-store-path (asciicast/json-store-path asciicast)
json-local-path (str dir "/asciicast.json")
png-local-path (str dir "/asciicast.png")
expires (-> png-ttl-days t/days t/from-now)]
(with-open [in (fstore/input-stream file-store json-store-path)]
(let [out (io/file json-local-path)]
(io/copy in out)))
(a2png json-local-path png-local-path png-params)
(fstore/put-file file-store (io/file png-local-path) png-store-path)
(exp-set/conj! exp-set png-store-path expires)))
(fstore/serve-file file-store png-store-path {})))))
(response/not-found))))))
["" [["/a/" [[[:token ".json"] (asciicast-json-resource db file-store)]
[[:token ".png"] (asciicast-png-resource db file-store exp-set executor)]]]
[true (yada/as-resource nil)]]])

View File

@ -25,7 +25,7 @@
:theme (theme-name asciicast user)
:scale default-png-scale})
(defn- png-version [asciicast params]
(defn png-version [asciicast params]
(let [attrs (assoc params :id (:id asciicast))]
(->> attrs
(map (fn [[k v]] (str (name k) "=" v)))