Update yetti and adapt for ring-2.0

This commit is contained in:
Andrey Antukh 2023-11-16 11:02:25 +01:00
parent 7a33817c22
commit bb5a4c0fa5
23 changed files with 407 additions and 440 deletions

View file

@ -21,8 +21,8 @@
java-http-clj/java-http-clj {:mvn/version "0.4.3"} java-http-clj/java-http-clj {:mvn/version "0.4.3"}
funcool/yetti funcool/yetti
{:git/tag "v9.16" {:git/tag "v10.0"
:git/sha "7df3e08" :git/sha "520613f"
:git/url "https://github.com/funcool/yetti.git" :git/url "https://github.com/funcool/yetti.git"
:exclusions [org.slf4j/slf4j-api]} :exclusions [org.slf4j/slf4j-api]}

View file

@ -31,7 +31,7 @@
<Logger name="app.rpc.rlimit" level="info" /> <Logger name="app.rpc.rlimit" level="info" />
<Logger name="app.rpc.climit" level="info" /> <Logger name="app.rpc.climit" level="info" />
<Logger name="app.rpc.mutations.files" level="info" /> <Logger name="app.rpc.mutations.files" level="info" />
<Logger name="app.common.files.migrations" level="debug" /> <Logger name="app.common.files.migrations" level="info" />
<Logger name="app.loggers" level="debug" additivity="false"> <Logger name="app.loggers" level="debug" additivity="false">
<AppenderRef ref="main" level="debug" /> <AppenderRef ref="main" level="debug" />

View file

@ -31,7 +31,7 @@
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[cuerdas.core :as str] [cuerdas.core :as str]
[integrant.core :as ig] [integrant.core :as ig]
[yetti.response :as-alias yrs])) [ring.response :as-alias rres]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HELPERS ;; HELPERS
@ -479,8 +479,8 @@
(defn- redirect-response (defn- redirect-response
[uri] [uri]
{::yrs/status 302 {::rres/status 302
::yrs/headers {"location" (str uri)}}) ::rres/headers {"location" (str uri)}})
(defn- generate-error-redirect (defn- generate-error-redirect
[_ cause] [_ cause]
@ -557,8 +557,8 @@
:props props :props props
:exp (dt/in-future "4h")}) :exp (dt/in-future "4h")})
uri (build-auth-uri cfg state)] uri (build-auth-uri cfg state)]
{::yrs/status 200 {::rres/status 200
::yrs/body {:redirect-uri uri}})) ::rres/body {:redirect-uri uri}}))
(defn- callback-handler (defn- callback-handler
[cfg request] [cfg request]

View file

@ -23,15 +23,14 @@
[app.metrics :as mtx] [app.metrics :as mtx]
[app.rpc :as-alias rpc] [app.rpc :as-alias rpc]
[app.rpc.doc :as-alias rpc.doc] [app.rpc.doc :as-alias rpc.doc]
[app.worker :as wrk]
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[integrant.core :as ig] [integrant.core :as ig]
[promesa.exec :as px] [promesa.exec :as px]
[reitit.core :as r] [reitit.core :as r]
[reitit.middleware :as rr] [reitit.middleware :as rr]
[yetti.adapter :as yt] [ring.request :as rreq]
[yetti.request :as yrq] [ring.response :as-alias rres]
[yetti.response :as-alias yrs])) [yetti.adapter :as yt]))
(declare router-handler) (declare router-handler)
@ -63,8 +62,7 @@
::max-multipart-body-size ::max-multipart-body-size
::router ::router
::handler ::handler
::io-threads ::io-threads]))
::wrk/executor]))
(defmethod ig/init-key ::server (defmethod ig/init-key ::server
[_ {:keys [::handler ::router ::host ::port] :as cfg}] [_ {:keys [::handler ::router ::host ::port] :as cfg}]
@ -75,11 +73,9 @@
:http/max-multipart-body-size (::max-multipart-body-size cfg) :http/max-multipart-body-size (::max-multipart-body-size cfg)
:xnio/io-threads (or (::io-threads cfg) :xnio/io-threads (or (::io-threads cfg)
(max 3 (px/get-available-processors))) (max 3 (px/get-available-processors)))
:xnio/worker-threads (or (::worker-threads cfg) :xnio/dispatch :virtual
(max 6 (px/get-available-processors))) :ring/compat :ring2
:xnio/dispatch true :socket/backlog 4069}
:socket/backlog 4069
:ring/async true}
handler (cond handler (cond
(some? router) (some? router)
@ -102,13 +98,13 @@
(yt/stop! server)) (yt/stop! server))
(defn- not-found-handler (defn- not-found-handler
[_ respond _] [_]
(respond {::yrs/status 404})) {::rres/status 404})
(defn- router-handler (defn- router-handler
[router] [router]
(letfn [(resolve-handler [request] (letfn [(resolve-handler [request]
(if-let [match (r/match-by-path router (yrq/path request))] (if-let [match (r/match-by-path router (rreq/path request))]
(let [params (:path-params match) (let [params (:path-params match)
result (:result match) result (:result match)
handler (or (:handler result) not-found-handler) handler (or (:handler result) not-found-handler)
@ -120,18 +116,15 @@
(let [{:keys [body] :as response} (errors/handle cause request)] (let [{:keys [body] :as response} (errors/handle cause request)]
(cond-> response (cond-> response
(map? body) (map? body)
(-> (update ::yrs/headers assoc "content-type" "application/transit+json") (-> (update ::rres/headers assoc "content-type" "application/transit+json")
(assoc ::yrs/body (t/encode-str body {:type :json-verbose}))))))] (assoc ::rres/body (t/encode-str body {:type :json-verbose}))))))]
(fn [request respond _] (fn [request]
(let [handler (resolve-handler request) (let [handler (resolve-handler request)]
exchange (yrq/exchange request)] (try
(handler (handler)
(fn [response] (catch Throwable cause
(yt/dispatch! exchange (partial respond response))) (on-error cause request)))))))
(fn [cause]
(let [response (on-error cause request)]
(yt/dispatch! exchange (partial respond response)))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HTTP ROUTER ;; HTTP ROUTER
@ -160,8 +153,7 @@
[session/soft-auth cfg] [session/soft-auth cfg]
[actoken/soft-auth cfg] [actoken/soft-auth cfg]
[mw/errors errors/handle] [mw/errors errors/handle]
[mw/restrict-methods] [mw/restrict-methods]]}
[mw/with-dispatch :vthread]]}
(::mtx/routes cfg) (::mtx/routes cfg)
(::assets/routes cfg) (::assets/routes cfg)

View file

@ -11,13 +11,13 @@
[app.db :as db] [app.db :as db]
[app.main :as-alias main] [app.main :as-alias main]
[app.tokens :as tokens] [app.tokens :as tokens]
[yetti.request :as yrq])) [ring.request :as rreq]))
(def header-re #"^Token\s+(.*)") (def header-re #"^Token\s+(.*)")
(defn- get-token (defn- get-token
[request] [request]
(some->> (yrq/get-header request "authorization") (some->> (rreq/get-header request "authorization")
(re-matches header-re) (re-matches header-re)
(second))) (second)))
@ -54,9 +54,8 @@
(l/trace :hint "exception on decoding malformed token" :cause cause) (l/trace :hint "exception on decoding malformed token" :cause cause)
request)))] request)))]
(fn [request respond raise] (fn [request]
(let [request (handle-request request)] (handler (handle-request request)))))
(handler request respond raise)))))
(defn- wrap-authz (defn- wrap-authz
"Authorization middleware, will be executed synchronously on vthread." "Authorization middleware, will be executed synchronously on vthread."

View file

@ -16,7 +16,7 @@
[app.util.time :as dt] [app.util.time :as dt]
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[integrant.core :as ig] [integrant.core :as ig]
[yetti.response :as-alias yrs])) [ring.response :as-alias rres]))
(def ^:private cache-max-age (def ^:private cache-max-age
(dt/duration {:hours 24})) (dt/duration {:hours 24}))
@ -37,8 +37,8 @@
(defn- serve-object-from-s3 (defn- serve-object-from-s3
[{:keys [::sto/storage] :as cfg} obj] [{:keys [::sto/storage] :as cfg} obj]
(let [{:keys [host port] :as url} (sto/get-object-url storage obj {:max-age signature-max-age})] (let [{:keys [host port] :as url} (sto/get-object-url storage obj {:max-age signature-max-age})]
{::yrs/status 307 {::rres/status 307
::yrs/headers {"location" (str url) ::rres/headers {"location" (str url)
"x-host" (cond-> host port (str ":" port)) "x-host" (cond-> host port (str ":" port))
"x-mtype" (-> obj meta :content-type) "x-mtype" (-> obj meta :content-type)
"cache-control" (str "max-age=" (inst-ms cache-max-age))}})) "cache-control" (str "max-age=" (inst-ms cache-max-age))}}))
@ -51,8 +51,8 @@
headers {"x-accel-redirect" (:path purl) headers {"x-accel-redirect" (:path purl)
"content-type" (:content-type mdata) "content-type" (:content-type mdata)
"cache-control" (str "max-age=" (inst-ms cache-max-age))}] "cache-control" (str "max-age=" (inst-ms cache-max-age))}]
{::yrs/status 204 {::rres/status 204
::yrs/headers headers})) ::rres/headers headers}))
(defn- serve-object (defn- serve-object
"Helper function that returns the appropriate response depending on "Helper function that returns the appropriate response depending on
@ -70,7 +70,7 @@
obj (sto/get-object storage id)] obj (sto/get-object storage id)]
(if obj (if obj
(serve-object cfg obj) (serve-object cfg obj)
{::yrs/status 404}))) {::rres/status 404})))
(defn- generic-handler (defn- generic-handler
"A generic handler helper/common code for file-media based handlers." "A generic handler helper/common code for file-media based handlers."
@ -81,7 +81,7 @@
sobj (sto/get-object storage (kf mobj))] sobj (sto/get-object storage (kf mobj))]
(if sobj (if sobj
(serve-object cfg sobj) (serve-object cfg sobj)
{::yrs/status 404}))) {::rres/status 404})))
(defn file-objects-handler (defn file-objects-handler
"Handler that serves storage objects by file media id." "Handler that serves storage objects by file media id."

View file

@ -20,8 +20,8 @@
[integrant.core :as ig] [integrant.core :as ig]
[jsonista.core :as j] [jsonista.core :as j]
[promesa.exec :as px] [promesa.exec :as px]
[yetti.request :as yrq] [ring.request :as rreq]
[yetti.response :as-alias yrs])) [ring.response :as-alias rres]))
(declare parse-json) (declare parse-json)
(declare handle-request) (declare handle-request)
@ -37,9 +37,9 @@
(defmethod ig/init-key ::routes (defmethod ig/init-key ::routes
[_ {:keys [::wrk/executor] :as cfg}] [_ {:keys [::wrk/executor] :as cfg}]
(letfn [(handler [request] (letfn [(handler [request]
(let [data (-> request yrq/body slurp)] (let [data (-> request rreq/body slurp)]
(px/run! executor #(handle-request cfg data))) (px/run! executor #(handle-request cfg data)))
{::yrs/status 200})] {::rres/status 200})]
["/sns" {:handler handler ["/sns" {:handler handler
:allowed-methods #{:post}}])) :allowed-methods #{:post}}]))

View file

@ -32,8 +32,8 @@
[integrant.core :as ig] [integrant.core :as ig]
[markdown.core :as md] [markdown.core :as md]
[markdown.transformers :as mdt] [markdown.transformers :as mdt]
[yetti.request :as yrq] [ring.request :as rreq]
[yetti.response :as yrs])) [ring.response :as rres]))
;; (selmer.parser/cache-off!) ;; (selmer.parser/cache-off!)
@ -43,10 +43,10 @@
(defn index-handler (defn index-handler
[_cfg _request] [_cfg _request]
{::yrs/status 200 {::rres/status 200
::yrs/headers {"content-type" "text/html"} ::rres/headers {"content-type" "text/html"}
::yrs/body (-> (io/resource "app/templates/debug.tmpl") ::rres/body (-> (io/resource "app/templates/debug.tmpl")
(tmpl/render {}))}) (tmpl/render {}))})
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; FILE CHANGES ;; FILE CHANGES
@ -55,17 +55,17 @@
(defn prepare-response (defn prepare-response
[body] [body]
(let [headers {"content-type" "application/transit+json"}] (let [headers {"content-type" "application/transit+json"}]
{::yrs/status 200 {::rres/status 200
::yrs/body body ::rres/body body
::yrs/headers headers})) ::rres/headers headers}))
(defn prepare-download-response (defn prepare-download-response
[body filename] [body filename]
(let [headers {"content-disposition" (str "attachment; filename=" filename) (let [headers {"content-disposition" (str "attachment; filename=" filename)
"content-type" "application/octet-stream"}] "content-type" "application/octet-stream"}]
{::yrs/status 200 {::rres/status 200
::yrs/body body ::rres/body body
::yrs/headers headers})) ::rres/headers headers}))
(def sql:retrieve-range-of-changes (def sql:retrieve-range-of-changes
"select revn, changes from file_change where file_id=? and revn >= ? and revn <= ? order by revn") "select revn, changes from file_change where file_id=? and revn >= ? and revn <= ? order by revn")
@ -107,8 +107,8 @@
(db/update! conn :file (db/update! conn :file
{:data data} {:data data}
{:id file-id}) {:id file-id})
{::yrs/status 201 {::rres/status 201
::yrs/body "OK CREATED"}))) ::rres/body "OK CREATED"})))
:else :else
(prepare-response (blob/decode data)))))) (prepare-response (blob/decode data))))))
@ -137,8 +137,8 @@
{:data data {:data data
:deleted-at nil} :deleted-at nil}
{:id file-id}) {:id file-id})
{::yrs/status 200 {::rres/status 200
::yrs/body "OK UPDATED"}) ::rres/body "OK UPDATED"})
(db/run! pool (fn [{:keys [::db/conn]}] (db/run! pool (fn [{:keys [::db/conn]}]
(create-file conn {:id file-id (create-file conn {:id file-id
@ -148,15 +148,15 @@
(db/update! conn :file (db/update! conn :file
{:data data} {:data data}
{:id file-id}) {:id file-id})
{::yrs/status 201 {::rres/status 201
::yrs/body "OK CREATED"})))) ::rres/body "OK CREATED"}))))
{::yrs/status 500 {::rres/status 500
::yrs/body "ERROR"}))) ::rres/body "ERROR"})))
(defn file-data-handler (defn file-data-handler
[cfg request] [cfg request]
(case (yrq/method request) (case (rreq/method request)
:get (retrieve-file-data cfg request) :get (retrieve-file-data cfg request)
:post (upload-file-data cfg request) :post (upload-file-data cfg request)
(ex/raise :type :http (ex/raise :type :http
@ -238,12 +238,12 @@
1 (render-template-v1 report) 1 (render-template-v1 report)
2 (render-template-v2 report) 2 (render-template-v2 report)
3 (render-template-v3 report))] 3 (render-template-v3 report))]
{::yrs/status 200 {::rres/status 200
::yrs/body result ::rres/body result
::yrs/headers {"content-type" "text/html; charset=utf-8" ::rres/headers {"content-type" "text/html; charset=utf-8"
"x-robots-tag" "noindex"}}) "x-robots-tag" "noindex"}})
{::yrs/status 404 {::rres/status 404
::yrs/body "not found"}))) ::rres/body "not found"})))
(def sql:error-reports (def sql:error-reports
"SELECT id, created_at, "SELECT id, created_at,
@ -256,11 +256,11 @@
[{:keys [::db/pool]} _request] [{:keys [::db/pool]} _request]
(let [items (->> (db/exec! pool [sql:error-reports]) (let [items (->> (db/exec! pool [sql:error-reports])
(map #(update % :created-at dt/format-instant :rfc1123)))] (map #(update % :created-at dt/format-instant :rfc1123)))]
{::yrs/status 200 {::rres/status 200
::yrs/body (-> (io/resource "app/templates/error-list.tmpl") ::rres/body (-> (io/resource "app/templates/error-list.tmpl")
(tmpl/render {:items items})) (tmpl/render {:items items}))
::yrs/headers {"content-type" "text/html; charset=utf-8" ::rres/headers {"content-type" "text/html; charset=utf-8"
"x-robots-tag" "noindex"}})) "x-robots-tag" "noindex"}}))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; EXPORT/IMPORT ;; EXPORT/IMPORT
@ -296,14 +296,14 @@
::binf/profile-id profile-id ::binf/profile-id profile-id
::binf/project-id project-id)) ::binf/project-id project-id))
{::yrs/status 200 {::rres/status 200
::yrs/headers {"content-type" "text/plain"} ::rres/headers {"content-type" "text/plain"}
::yrs/body "OK CLONED"}) ::rres/body "OK CLONED"})
{::yrs/status 200 {::rres/status 200
::yrs/body (io/input-stream path) ::rres/body (io/input-stream path)
::yrs/headers {"content-type" "application/octet-stream" ::rres/headers {"content-type" "application/octet-stream"
"content-disposition" (str "attachmen; filename=" (first file-ids) ".penpot")}})))) "content-disposition" (str "attachmen; filename=" (first file-ids) ".penpot")}}))))
@ -334,9 +334,9 @@
::binf/profile-id profile-id ::binf/profile-id profile-id
::binf/project-id project-id)) ::binf/project-id project-id))
{::yrs/status 200 {::rres/status 200
::yrs/headers {"content-type" "text/plain"} ::rres/headers {"content-type" "text/plain"}
::yrs/body "OK"})) ::rres/body "OK"}))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ACTIONS ;; ACTIONS
@ -363,34 +363,34 @@
(db/update! pool :profile {:is-blocked true} {:id (:id profile)}) (db/update! pool :profile {:is-blocked true} {:id (:id profile)})
(db/delete! pool :http-session {:profile-id (:id profile)}) (db/delete! pool :http-session {:profile-id (:id profile)})
{::yrs/status 200 {::rres/status 200
::yrs/headers {"content-type" "text/plain"} ::rres/headers {"content-type" "text/plain"}
::yrs/body (str/ffmt "PROFILE '%' BLOCKED" (:email profile))}) ::rres/body (str/ffmt "PROFILE '%' BLOCKED" (:email profile))})
(contains? params :unblock) (contains? params :unblock)
(do (do
(db/update! pool :profile {:is-blocked false} {:id (:id profile)}) (db/update! pool :profile {:is-blocked false} {:id (:id profile)})
{::yrs/status 200 {::rres/status 200
::yrs/headers {"content-type" "text/plain"} ::rres/headers {"content-type" "text/plain"}
::yrs/body (str/ffmt "PROFILE '%' UNBLOCKED" (:email profile))}) ::rres/body (str/ffmt "PROFILE '%' UNBLOCKED" (:email profile))})
(contains? params :resend) (contains? params :resend)
(if (:is-blocked profile) (if (:is-blocked profile)
{::yrs/status 200 {::rres/status 200
::yrs/headers {"content-type" "text/plain"} ::rres/headers {"content-type" "text/plain"}
::yrs/body "PROFILE ALREADY BLOCKED"} ::rres/body "PROFILE ALREADY BLOCKED"}
(do (do
(auth/send-email-verification! pool props profile) (auth/send-email-verification! pool props profile)
{::yrs/status 200 {::rres/status 200
::yrs/headers {"content-type" "text/plain"} ::rres/headers {"content-type" "text/plain"}
::yrs/body (str/ffmt "RESENDED FOR '%'" (:email profile))})) ::rres/body (str/ffmt "RESENDED FOR '%'" (:email profile))}))
:else :else
(do (do
(db/update! pool :profile {:is-active true} {:id (:id profile)}) (db/update! pool :profile {:is-active true} {:id (:id profile)})
{::yrs/status 200 {::rres/status 200
::yrs/headers {"content-type" "text/plain"} ::rres/headers {"content-type" "text/plain"}
::yrs/body (str/ffmt "PROFILE '%' ACTIVATED" (:email profile))})))) ::rres/body (str/ffmt "PROFILE '%' ACTIVATED" (:email profile))}))))
(defn- reset-file-data-version (defn- reset-file-data-version
@ -420,9 +420,9 @@
:migrate? false :migrate? false
:inc-revn? false :inc-revn? false
:save? true) :save? true)
{::yrs/status 200 {::rres/status 200
::yrs/headers {"content-type" "text/plain"} ::rres/headers {"content-type" "text/plain"}
::yrs/body "OK"})) ::rres/body "OK"}))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -434,13 +434,13 @@
[{:keys [::db/pool]} _] [{:keys [::db/pool]} _]
(try (try
(db/exec-one! pool ["select count(*) as count from server_prop;"]) (db/exec-one! pool ["select count(*) as count from server_prop;"])
{::yrs/status 200 {::rres/status 200
::yrs/body "OK"} ::rres/body "OK"}
(catch Throwable cause (catch Throwable cause
(l/warn :hint "unable to execute query on health handler" (l/warn :hint "unable to execute query on health handler"
:cause cause) :cause cause)
{::yrs/status 503 {::rres/status 503
::yrs/body "KO"}))) ::rres/body "KO"})))
(defn changelog-handler (defn changelog-handler
[_ _] [_ _]
@ -449,11 +449,11 @@
(md->html [text] (md->html [text]
(md/md-to-html-string text :replacement-transformers (into [transform-emoji] mdt/transformer-vector)))] (md/md-to-html-string text :replacement-transformers (into [transform-emoji] mdt/transformer-vector)))]
(if-let [clog (io/resource "changelog.md")] (if-let [clog (io/resource "changelog.md")]
{::yrs/status 200 {::rres/status 200
::yrs/headers {"content-type" "text/html; charset=utf-8"} ::rres/headers {"content-type" "text/html; charset=utf-8"}
::yrs/body (-> clog slurp md->html)} ::rres/body (-> clog slurp md->html)}
{::yrs/status 404 {::rres/status 404
::yrs/body "NOT FOUND"}))) ::rres/body "NOT FOUND"})))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INIT ;; INIT

View file

@ -16,14 +16,14 @@
[app.http.session :as-alias session] [app.http.session :as-alias session]
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[cuerdas.core :as str] [cuerdas.core :as str]
[yetti.request :as yrq] [ring.request :as rreq]
[yetti.response :as yrs])) [ring.response :as rres]))
(defn- parse-client-ip (defn- parse-client-ip
[request] [request]
(or (some-> (yrq/get-header request "x-forwarded-for") (str/split ",") first) (or (some-> (rreq/get-header request "x-forwarded-for") (str/split ",") first)
(yrq/get-header request "x-real-ip") (rreq/get-header request "x-real-ip")
(yrq/remote-addr request))) (rreq/remote-addr request)))
(defn request->context (defn request->context
"Extracts error report relevant context data from request." "Extracts error report relevant context data from request."
@ -34,10 +34,10 @@
{:request/path (:path request) {:request/path (:path request)
:request/method (:method request) :request/method (:method request)
:request/params (:params request) :request/params (:params request)
:request/user-agent (yrq/get-header request "user-agent") :request/user-agent (rreq/get-header request "user-agent")
:request/ip-addr (parse-client-ip request) :request/ip-addr (parse-client-ip request)
:request/profile-id (:uid claims) :request/profile-id (:uid claims)
:version/frontend (or (yrq/get-header request "x-frontend-version") "unknown") :version/frontend (or (rreq/get-header request "x-frontend-version") "unknown")
:version/backend (:full cf/version)})) :version/backend (:full cf/version)}))
(defmulti handle-error (defmulti handle-error
@ -50,30 +50,30 @@
(defmethod handle-error :authentication (defmethod handle-error :authentication
[err _ _] [err _ _]
{::yrs/status 401 {::rres/status 401
::yrs/body (ex-data err)}) ::rres/body (ex-data err)})
(defmethod handle-error :authorization (defmethod handle-error :authorization
[err _ _] [err _ _]
{::yrs/status 403 {::rres/status 403
::yrs/body (ex-data err)}) ::rres/body (ex-data err)})
(defmethod handle-error :restriction (defmethod handle-error :restriction
[err _ _] [err _ _]
{::yrs/status 400 {::rres/status 400
::yrs/body (ex-data err)}) ::rres/body (ex-data err)})
(defmethod handle-error :rate-limit (defmethod handle-error :rate-limit
[err _ _] [err _ _]
(let [headers (-> err ex-data ::http/headers)] (let [headers (-> err ex-data ::http/headers)]
{::yrs/status 429 {::rres/status 429
::yrs/headers headers})) ::rres/headers headers}))
(defmethod handle-error :concurrency-limit (defmethod handle-error :concurrency-limit
[err _ _] [err _ _]
(let [headers (-> err ex-data ::http/headers)] (let [headers (-> err ex-data ::http/headers)]
{::yrs/status 429 {::rres/status 429
::yrs/headers headers})) ::rres/headers headers}))
(defmethod handle-error :validation (defmethod handle-error :validation
[err request parent-cause] [err request parent-cause]
@ -81,38 +81,38 @@
(cond (cond
(= code :spec-validation) (= code :spec-validation)
(let [explain (ex/explain data)] (let [explain (ex/explain data)]
{::yrs/status 400 {::rres/status 400
::yrs/body (-> data ::rres/body (-> data
(dissoc ::s/problems ::s/value ::s/spec) (dissoc ::s/problems ::s/value ::s/spec)
(cond-> explain (assoc :explain explain)))}) (cond-> explain (assoc :explain explain)))})
(= code :params-validation) (= code :params-validation)
(let [explain (::sm/explain data) (let [explain (::sm/explain data)
explain (sm/humanize-data explain)] explain (sm/humanize-data explain)]
{::yrs/status 400 {::rres/status 400
::yrs/body (-> data ::rres/body (-> data
(dissoc ::sm/explain) (dissoc ::sm/explain)
(assoc :explain explain))}) (assoc :explain explain))})
(= code :data-validation) (= code :data-validation)
(let [explain (::sm/explain data) (let [explain (::sm/explain data)
explain (sm/humanize-data explain)] explain (sm/humanize-data explain)]
{::yrs/status 400 {::rres/status 400
::yrs/body (-> data ::rres/body (-> data
(dissoc ::sm/explain) (dissoc ::sm/explain)
(assoc :explain explain))}) (assoc :explain explain))})
(= code :request-body-too-large) (= code :request-body-too-large)
{::yrs/status 413 ::yrs/body data} {::rres/status 413 ::rres/body data}
(= code :invalid-image) (= code :invalid-image)
(binding [l/*context* (request->context request)] (binding [l/*context* (request->context request)]
(let [cause (or parent-cause err)] (let [cause (or parent-cause err)]
(l/error :hint "unexpected error on processing image" :cause cause) (l/error :hint "unexpected error on processing image" :cause cause)
{::yrs/status 400 ::yrs/body data})) {::rres/status 400 ::rres/body data}))
:else :else
{::yrs/status 400 ::yrs/body data}))) {::rres/status 400 ::rres/body data})))
(defmethod handle-error :assertion (defmethod handle-error :assertion
[error request parent-cause] [error request parent-cause]
@ -123,46 +123,46 @@
(= code :data-validation) (= code :data-validation)
(let [explain (ex/explain data)] (let [explain (ex/explain data)]
(l/error :hint "data assertion error" :cause cause) (l/error :hint "data assertion error" :cause cause)
{::yrs/status 500 {::rres/status 500
::yrs/body {:type :server-error ::rres/body {:type :server-error
:code :assertion :code :assertion
:data (-> data :data (-> data
(dissoc ::sm/explain) (dissoc ::sm/explain)
(cond-> explain (assoc :explain explain)))}}) (cond-> explain (assoc :explain explain)))}})
(= code :spec-validation) (= code :spec-validation)
(let [explain (ex/explain data)] (let [explain (ex/explain data)]
(l/error :hint "spec assertion error" :cause cause) (l/error :hint "spec assertion error" :cause cause)
{::yrs/status 500 {::rres/status 500
::yrs/body {:type :server-error ::rres/body {:type :server-error
:code :assertion :code :assertion
:data (-> data :data (-> data
(dissoc ::s/problems ::s/value ::s/spec) (dissoc ::s/problems ::s/value ::s/spec)
(cond-> explain (assoc :explain explain)))}}) (cond-> explain (assoc :explain explain)))}})
:else :else
(do (do
(l/error :hint "assertion error" :cause cause) (l/error :hint "assertion error" :cause cause)
{::yrs/status 500 {::rres/status 500
::yrs/body {:type :server-error ::rres/body {:type :server-error
:code :assertion :code :assertion
:data data}}))))) :data data}})))))
(defmethod handle-error :not-found (defmethod handle-error :not-found
[err _ _] [err _ _]
{::yrs/status 404 {::rres/status 404
::yrs/body (ex-data err)}) ::rres/body (ex-data err)})
(defmethod handle-error :internal (defmethod handle-error :internal
[error request parent-cause] [error request parent-cause]
(binding [l/*context* (request->context request)] (binding [l/*context* (request->context request)]
(let [cause (or parent-cause error)] (let [cause (or parent-cause error)]
(l/error :hint "internal error" :cause cause) (l/error :hint "internal error" :cause cause)
{::yrs/status 500 {::rres/status 500
::yrs/body {:type :server-error ::rres/body {:type :server-error
:code :unhandled :code :unhandled
:hint (ex-message error) :hint (ex-message error)
:data (ex-data error)}}))) :data (ex-data error)}})))
(defmethod handle-error :default (defmethod handle-error :default
[error request parent-cause] [error request parent-cause]
@ -186,23 +186,23 @@
:cause cause) :cause cause)
(cond (cond
(= state "57014") (= state "57014")
{::yrs/status 504 {::rres/status 504
::yrs/body {:type :server-error ::rres/body {:type :server-error
:code :statement-timeout :code :statement-timeout
:hint (ex-message error)}} :hint (ex-message error)}}
(= state "25P03") (= state "25P03")
{::yrs/status 504 {::rres/status 504
::yrs/body {:type :server-error ::rres/body {:type :server-error
:code :idle-in-transaction-timeout :code :idle-in-transaction-timeout
:hint (ex-message error)}} :hint (ex-message error)}}
:else :else
{::yrs/status 500 {::rres/status 500
::yrs/body {:type :server-error ::rres/body {:type :server-error
:code :unexpected :code :unexpected
:hint (ex-message error) :hint (ex-message error)
:state state}})))) :state state}}))))
(defmethod handle-exception :default (defmethod handle-exception :default
[error request parent-cause] [error request parent-cause]
@ -213,19 +213,19 @@
(nil? edata) (nil? edata)
(binding [l/*context* (request->context request)] (binding [l/*context* (request->context request)]
(l/error :hint "unexpected error" :cause cause) (l/error :hint "unexpected error" :cause cause)
{::yrs/status 500 {::rres/status 500
::yrs/body {:type :server-error ::rres/body {:type :server-error
:code :unexpected :code :unexpected
:hint (ex-message error)}}) :hint (ex-message error)}})
:else :else
(binding [l/*context* (request->context request)] (binding [l/*context* (request->context request)]
(l/error :hint "unhandled error" :cause cause) (l/error :hint "unhandled error" :cause cause)
{::yrs/status 500 {::rres/status 500
::yrs/body {:type :server-error ::rres/body {:type :server-error
:code :unhandled :code :unhandled
:hint (ex-message error) :hint (ex-message error)
:data edata}})))) :data edata}}))))
(defmethod handle-exception java.util.concurrent.CompletionException (defmethod handle-exception java.util.concurrent.CompletionException
[cause request _] [cause request _]

View file

@ -12,13 +12,10 @@
[app.config :as cf] [app.config :as cf]
[app.util.json :as json] [app.util.json :as json]
[cuerdas.core :as str] [cuerdas.core :as str]
[promesa.core :as p] [ring.request :as rreq]
[promesa.exec :as px] [ring.response :as rres]
[promesa.util :as pu]
[yetti.adapter :as yt] [yetti.adapter :as yt]
[yetti.middleware :as ymw] [yetti.middleware :as ymw])
[yetti.request :as yrq]
[yetti.response :as yrs])
(:import (:import
com.fasterxml.jackson.core.JsonParseException com.fasterxml.jackson.core.JsonParseException
com.fasterxml.jackson.core.io.JsonEOFException com.fasterxml.jackson.core.io.JsonEOFException
@ -46,17 +43,17 @@
(defn wrap-parse-request (defn wrap-parse-request
[handler] [handler]
(letfn [(process-request [request] (letfn [(process-request [request]
(let [header (yrq/get-header request "content-type")] (let [header (rreq/get-header request "content-type")]
(cond (cond
(str/starts-with? header "application/transit+json") (str/starts-with? header "application/transit+json")
(with-open [^InputStream is (yrq/body request)] (with-open [^InputStream is (rreq/body request)]
(let [params (t/read! (t/reader is))] (let [params (t/read! (t/reader is))]
(-> request (-> request
(assoc :body-params params) (assoc :body-params params)
(update :params merge params)))) (update :params merge params))))
(str/starts-with? header "application/json") (str/starts-with? header "application/json")
(with-open [^InputStream is (yrq/body request)] (with-open [^InputStream is (rreq/body request)]
(let [params (json/decode is json-mapper)] (let [params (json/decode is json-mapper)]
(-> request (-> request
(assoc :body-params params) (assoc :body-params params)
@ -65,37 +62,36 @@
:else :else
request))) request)))
(handle-error [raise cause] (handle-error [cause]
(cond (cond
(instance? RuntimeException cause) (instance? RuntimeException cause)
(if-let [cause (ex-cause cause)] (if-let [cause (ex-cause cause)]
(handle-error raise cause) (handle-error cause)
(raise cause)) (throw cause))
(instance? RequestTooBigException cause) (instance? RequestTooBigException cause)
(raise (ex/error :type :validation (ex/raise :type :validation
:code :request-body-too-large :code :request-body-too-large
:hint (ex-message cause))) :hint (ex-message cause))
(or (instance? JsonEOFException cause) (or (instance? JsonEOFException cause)
(instance? JsonParseException cause) (instance? JsonParseException cause)
(instance? MismatchedInputException cause)) (instance? MismatchedInputException cause))
(raise (ex/error :type :validation (ex/raise :type :validation
:code :malformed-json :code :malformed-json
:hint (ex-message cause) :hint (ex-message cause)
:cause cause)) :cause cause)
:else :else
(raise cause)))] (throw cause)))]
(fn [request respond raise] (fn [request]
(if (= (yrq/method request) :post) (if (= (rreq/method request) :post)
(let [request (ex/try! (process-request request))] (let [request (ex/try! (process-request request))]
(if (ex/exception? request) (if (ex/exception? request)
(handle-error raise request) (handle-error request)
(handler request respond raise))) (handler request)))
(handler request respond raise))))) (handler request)))))
(def parse-request (def parse-request
{:name ::parse-request {:name ::parse-request
@ -113,7 +109,7 @@
(defn wrap-format-response (defn wrap-format-response
[handler] [handler]
(letfn [(transit-streamable-body [data opts] (letfn [(transit-streamable-body [data opts]
(reify yrs/StreamableResponseBody (reify rres/StreamableResponseBody
(-write-body-to-stream [_ _ output-stream] (-write-body-to-stream [_ _ output-stream]
(try (try
(with-open [^OutputStream bos (buffered-output-stream output-stream buffer-size)] (with-open [^OutputStream bos (buffered-output-stream output-stream buffer-size)]
@ -128,7 +124,7 @@
(.close ^OutputStream output-stream)))))) (.close ^OutputStream output-stream))))))
(json-streamable-body [data] (json-streamable-body [data]
(reify yrs/StreamableResponseBody (reify rres/StreamableResponseBody
(-write-body-to-stream [_ _ output-stream] (-write-body-to-stream [_ _ output-stream]
(try (try
(with-open [^OutputStream bos (buffered-output-stream output-stream buffer-size)] (with-open [^OutputStream bos (buffered-output-stream output-stream buffer-size)]
@ -143,24 +139,24 @@
(.close ^OutputStream output-stream)))))) (.close ^OutputStream output-stream))))))
(format-response-with-json [response _] (format-response-with-json [response _]
(let [body (::yrs/body response)] (let [body (::rres/body response)]
(if (or (boolean? body) (coll? body)) (if (or (boolean? body) (coll? body))
(-> response (-> response
(update ::yrs/headers assoc "content-type" "application/json") (update ::rres/headers assoc "content-type" "application/json")
(assoc ::yrs/body (json-streamable-body body))) (assoc ::rres/body (json-streamable-body body)))
response))) response)))
(format-response-with-transit [response request] (format-response-with-transit [response request]
(let [body (::yrs/body response)] (let [body (::rres/body response)]
(if (or (boolean? body) (coll? body)) (if (or (boolean? body) (coll? body))
(let [qs (yrq/query request) (let [qs (rreq/query request)
opts (if (or (contains? cf/flags :transit-readable-response) opts (if (or (contains? cf/flags :transit-readable-response)
(str/includes? qs "transit_verbose")) (str/includes? qs "transit_verbose"))
{:type :json-verbose} {:type :json-verbose}
{:type :json})] {:type :json})]
(-> response (-> response
(update ::yrs/headers assoc "content-type" "application/transit+json") (update ::rres/headers assoc "content-type" "application/transit+json")
(assoc ::yrs/body (transit-streamable-body body opts)))) (assoc ::rres/body (transit-streamable-body body opts))))
response))) response)))
(format-from-params [{:keys [query-params] :as request}] (format-from-params [{:keys [query-params] :as request}]
@ -169,7 +165,7 @@
(format-response [response request] (format-response [response request]
(let [accept (or (format-from-params request) (let [accept (or (format-from-params request)
(yrq/get-header request "accept"))] (rreq/get-header request "accept"))]
(cond (cond
(or (= accept "application/transit+json") (or (= accept "application/transit+json")
(str/includes? accept "application/transit+json")) (str/includes? accept "application/transit+json"))
@ -186,11 +182,9 @@
(cond-> response (cond-> response
(map? response) (format-response request)))] (map? response) (format-response request)))]
(fn [request respond raise] (fn [request]
(handler request (let [response (handler request)]
(fn [response] (process-response response request)))))
(respond (process-response response request)))
raise))))
(def format-response (def format-response
{:name ::format-response {:name ::format-response
@ -198,12 +192,11 @@
(defn wrap-errors (defn wrap-errors
[handler on-error] [handler on-error]
(fn [request respond raise] (fn [request]
(handler request respond (fn [cause] (try
(try (handler request)
(respond (on-error cause request)) (catch Throwable cause
(catch Throwable cause (on-error cause request)))))
(raise cause)))))))
(def errors (def errors
{:name ::errors {:name ::errors
@ -221,11 +214,11 @@
(defn wrap-cors (defn wrap-cors
[handler] [handler]
(fn [request] (fn [request]
(let [response (if (= (yrq/method request) :options) (let [response (if (= (rreq/method request) :options)
{::yrs/status 200} {::rres/status 200}
(handler request)) (handler request))
origin (yrq/get-header request "origin")] origin (rreq/get-header request "origin")]
(update response ::yrs/headers with-cors-headers origin)))) (update response ::rres/headers with-cors-headers origin))))
(def cors (def cors
{:name ::cors {:name ::cors
@ -239,18 +232,8 @@
(fn [data _] (fn [data _]
(when-let [allowed (:allowed-methods data)] (when-let [allowed (:allowed-methods data)]
(fn [handler] (fn [handler]
(fn [request respond raise] (fn [request]
(let [method (yrq/method request)] (let [method (rreq/method request)]
(if (contains? allowed method) (if (contains? allowed method)
(handler request respond raise) (handler request)
(respond {::yrs/status 405})))))))}) {::rres/status 405}))))))})
(def with-dispatch
{:name ::with-dispatch
:compile
(fn [& _]
(fn [handler executor]
(let [executor (px/resolve-executor executor)]
(fn [request respond raise]
(->> (px/submit! executor (partial handler request))
(p/fnly (pu/handler respond raise)))))))})

View file

@ -20,6 +20,7 @@
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[cuerdas.core :as str] [cuerdas.core :as str]
[integrant.core :as ig] [integrant.core :as ig]
[ring.request :as rreq]
[yetti.request :as yrq])) [yetti.request :as yrq]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -142,7 +143,7 @@
(us/assert! ::us/uuid profile-id) (us/assert! ::us/uuid profile-id)
(fn [request response] (fn [request response]
(let [uagent (yrq/get-header request "user-agent") (let [uagent (rreq/get-header request "user-agent")
params {:profile-id profile-id params {:profile-id profile-id
:user-agent uagent :user-agent uagent
:created-at (dt/now)} :created-at (dt/now)}
@ -209,9 +210,8 @@
(l/trace :hint "exception on decoding malformed token" :cause cause) (l/trace :hint "exception on decoding malformed token" :cause cause)
request)))] request)))]
(fn [request respond raise] (fn [request]
(let [request (handle-request request)] (handler (handle-request request)))))
(handler request respond raise)))))
(defn- wrap-authz (defn- wrap-authz
[handler {:keys [::manager]}] [handler {:keys [::manager]}]

View file

@ -10,7 +10,7 @@
[app.common.exceptions :as ex] [app.common.exceptions :as ex]
[app.common.logging :as l] [app.common.logging :as l]
[app.common.pprint :as pp] [app.common.pprint :as pp]
[app.common.spec :as us] [app.common.schema :as sm]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.db :as db] [app.db :as db]
[app.http.session :as session] [app.http.session :as session]
@ -21,6 +21,7 @@
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[integrant.core :as ig] [integrant.core :as ig]
[promesa.exec.csp :as sp] [promesa.exec.csp :as sp]
[ring.websocket :as rws]
[yetti.websocket :as yws])) [yetti.websocket :as yws]))
(def recv-labels (def recv-labels
@ -277,19 +278,23 @@
:inc 1) :inc 1)
message) message)
(def ^:private schema:params
(s/def ::session-id ::us/uuid) (sm/define
(s/def ::handler-params [:map {:title "params"}
(s/keys :req-un [::session-id])) [:session-id ::sm/uuid]]))
(defn- http-handler (defn- http-handler
[cfg {:keys [params ::session/profile-id] :as request}] [cfg {:keys [params ::session/profile-id] :as request}]
(let [{:keys [session-id]} (us/conform ::handler-params params)] (let [{:keys [session-id]} (sm/conform! schema:params params)]
(cond (cond
(not profile-id) (not profile-id)
(ex/raise :type :authentication (ex/raise :type :authentication
:hint "Authentication required.") :hint "Authentication required.")
;; WORKAROUND: we use the adapter specific predicate for
;; performance reasons; for now, the ring default impl for
;; `upgrade-request?` parses all requests headers before perform
;; any checking.
(not (yws/upgrade-request? request)) (not (yws/upgrade-request? request))
(ex/raise :type :validation (ex/raise :type :validation
:code :websocket-request-expected :code :websocket-request-expected
@ -298,14 +303,13 @@
:else :else
(do (do
(l/trace :hint "websocket request" :profile-id profile-id :session-id session-id) (l/trace :hint "websocket request" :profile-id profile-id :session-id session-id)
(->> (ws/handler {::rws/listener (ws/listener request
::ws/on-rcv-message (partial on-rcv-message cfg) ::ws/on-rcv-message (partial on-rcv-message cfg)
::ws/on-snd-message (partial on-snd-message cfg) ::ws/on-snd-message (partial on-snd-message cfg)
::ws/on-connect (partial on-connect cfg) ::ws/on-connect (partial on-connect cfg)
::ws/handler (partial handle-message cfg) ::ws/handler (partial handle-message cfg)
::profile-id profile-id ::profile-id profile-id
::session-id session-id) ::session-id session-id)}))))
(yws/upgrade request))))))
(defmethod ig/pre-init-spec ::routes [_] (defmethod ig/pre-init-spec ::routes [_]
(s/keys :req [::mbus/msgbus (s/keys :req [::mbus/msgbus
@ -318,5 +322,4 @@
(defmethod ig/init-key ::routes (defmethod ig/init-key ::routes
[_ cfg] [_ cfg]
["/ws/notifications" {:middleware [[session/authz cfg]] ["/ws/notifications" {:middleware [[session/authz cfg]]
:handler (partial http-handler cfg) :handler (partial http-handler cfg)}])
:allowed-methods #{:get}}])

View file

@ -33,7 +33,7 @@
[integrant.core :as ig] [integrant.core :as ig]
[lambdaisland.uri :as u] [lambdaisland.uri :as u]
[promesa.exec :as px] [promesa.exec :as px]
[yetti.request :as yrq])) [ring.request :as rreq]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HELPERS ;; HELPERS
@ -41,9 +41,9 @@
(defn parse-client-ip (defn parse-client-ip
[request] [request]
(or (some-> (yrq/get-header request "x-forwarded-for") (str/split ",") first) (or (some-> (rreq/get-header request "x-forwarded-for") (str/split ",") first)
(yrq/get-header request "x-real-ip") (rreq/get-header request "x-real-ip")
(some-> (yrq/remote-addr request) str))) (some-> (rreq/remote-addr request) str)))
(defn extract-utm-params (defn extract-utm-params
"Extracts additional data from params and namespace them under "Extracts additional data from params and namespace them under

View file

@ -235,7 +235,6 @@
{::http/port (cf/get :http-server-port) {::http/port (cf/get :http-server-port)
::http/host (cf/get :http-server-host) ::http/host (cf/get :http-server-host)
::http/router (ig/ref ::http/router) ::http/router (ig/ref ::http/router)
::wrk/executor (ig/ref ::wrk/executor)
::http/io-threads (cf/get :http-server-io-threads) ::http/io-threads (cf/get :http-server-io-threads)
::http/max-body-size (cf/get :http-server-max-body-size) ::http/max-body-size (cf/get :http-server-max-body-size)
::http/max-multipart-body-size (cf/get :http-server-max-multipart-body-size)} ::http/max-multipart-body-size (cf/get :http-server-max-multipart-body-size)}

View file

@ -34,8 +34,8 @@
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[integrant.core :as ig] [integrant.core :as ig]
[promesa.core :as p] [promesa.core :as p]
[yetti.request :as yrq] [ring.request :as rreq]
[yetti.response :as yrs])) [ring.response :as rres]))
(s/def ::profile-id ::us/uuid) (s/def ::profile-id ::us/uuid)
@ -61,9 +61,9 @@
(if (fn? result) (if (fn? result)
(result request) (result request)
(let [mdata (meta result)] (let [mdata (meta result)]
(-> {::yrs/status (::http/status mdata 200) (-> {::rres/status (::http/status mdata 200)
::yrs/headers (::http/headers mdata {}) ::rres/headers (::http/headers mdata {})
::yrs/body (rph/unwrap result)} ::rres/body (rph/unwrap result)}
(handle-response-transformation request mdata) (handle-response-transformation request mdata)
(handle-before-comple-hook mdata))))) (handle-before-comple-hook mdata)))))
@ -72,7 +72,7 @@
internal async flow into ring async flow." internal async flow into ring async flow."
[methods {:keys [params path-params] :as request}] [methods {:keys [params path-params] :as request}]
(let [type (keyword (:type path-params)) (let [type (keyword (:type path-params))
etag (yrq/get-header request "if-none-match") etag (rreq/get-header request "if-none-match")
profile-id (or (::session/profile-id request) profile-id (or (::session/profile-id request)
(::actoken/profile-id request)) (::actoken/profile-id request))
@ -138,6 +138,8 @@
(f cfg (us/conform spec params))) (f cfg (us/conform spec params)))
f))) f)))
;; TODO: integrate with sm/define
(defn- wrap-params-validation (defn- wrap-params-validation
[_ f mdata] [_ f mdata]
(if-let [schema (::sm/params mdata)] (if-let [schema (::sm/params mdata)]

View file

@ -44,8 +44,8 @@
[cuerdas.core :as str] [cuerdas.core :as str]
[datoteka.io :as io] [datoteka.io :as io]
[promesa.util :as pu] [promesa.util :as pu]
[yetti.adapter :as yt] [ring.response :as rres]
[yetti.response :as yrs]) [yetti.adapter :as yt])
(:import (:import
com.github.luben.zstd.ZstdInputStream com.github.luben.zstd.ZstdInputStream
com.github.luben.zstd.ZstdOutputStream com.github.luben.zstd.ZstdOutputStream
@ -1071,7 +1071,7 @@
::webhooks/event? true} ::webhooks/event? true}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id include-libraries? embed-assets?] :as params}] [{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id include-libraries? embed-assets?] :as params}]
(files/check-read-permissions! pool profile-id file-id) (files/check-read-permissions! pool profile-id file-id)
(let [body (reify yrs/StreamableResponseBody (let [body (reify rres/StreamableResponseBody
(-write-body-to-stream [_ _ output-stream] (-write-body-to-stream [_ _ output-stream]
(-> cfg (-> cfg
(assoc ::file-ids [file-id]) (assoc ::file-ids [file-id])
@ -1080,9 +1080,9 @@
(export! output-stream))))] (export! output-stream))))]
(fn [_] (fn [_]
{::yrs/status 200 {::rres/status 200
::yrs/body body ::rres/body body
::yrs/headers {"content-type" "application/octet-stream"}}))) ::rres/headers {"content-type" "application/octet-stream"}})))
(s/def ::file ::media/upload) (s/def ::file ::media/upload)
(s/def ::import-binfile (s/def ::import-binfile

View file

@ -29,7 +29,7 @@
[app.util.services :as-alias sv] [app.util.services :as-alias sv]
[buddy.core.codecs :as bc] [buddy.core.codecs :as bc]
[buddy.core.hash :as bh] [buddy.core.hash :as bh]
[yetti.response :as yrs])) [ring.response :as-alias rres]))
(def (def
^{:dynamic true ^{:dynamic true
@ -57,7 +57,7 @@
(let [key' (when (or key reuse-key?) (let [key' (when (or key reuse-key?)
(some->> (get-object cfg params) (key-fn params) (fmt-key)))] (some->> (get-object cfg params) (key-fn params) (fmt-key)))]
(if (and (some? key) (= key key')) (if (and (some? key) (= key key'))
(fn [_] {::yrs/status 304}) (fn [_] {::rres/status 304})
(let [result (f cfg params) (let [result (f cfg params)
etag (or (and reuse-key? key') etag (or (and reuse-key? key')
(some-> result meta ::key fmt-key) (some-> result meta ::key fmt-key)

View file

@ -27,7 +27,7 @@
[integrant.core :as ig] [integrant.core :as ig]
[malli.transform :as mt] [malli.transform :as mt]
[pretty-spec.core :as ps] [pretty-spec.core :as ps]
[yetti.response :as yrs])) [ring.response :as-alias rres]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; DOC (human readable) ;; DOC (human readable)
@ -86,11 +86,11 @@
(let [params (:query-params request) (let [params (:query-params request)
pstyle (:type params "js") pstyle (:type params "js")
context (assoc context :param-style pstyle)] context (assoc context :param-style pstyle)]
{::yrs/status 200 {::rres/status 200
::yrs/body (-> (io/resource "app/templates/api-doc.tmpl") ::rres/body (-> (io/resource "app/templates/api-doc.tmpl")
(tmpl/render context))})) (tmpl/render context))}))
(fn [_] (fn [_]
{::yrs/status 404}))) {::rres/status 404})))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; OPENAPI / SWAGGER (v3.1) ;; OPENAPI / SWAGGER (v3.1)
@ -173,12 +173,12 @@
[context] [context]
(if (contains? cf/flags :backend-openapi-doc) (if (contains? cf/flags :backend-openapi-doc)
(fn [_] (fn [_]
{::yrs/status 200 {::rres/status 200
::yrs/headers {"content-type" "application/json; charset=utf-8"} ::rres/headers {"content-type" "application/json; charset=utf-8"}
::yrs/body (json/encode context)}) ::rres/body (json/encode context)})
(fn [_] (fn [_]
{::yrs/status 404}))) {::rres/status 404})))
(defn openapi-handler (defn openapi-handler
[] []
@ -189,12 +189,12 @@
context {:public-uri (cf/get :public-uri) context {:public-uri (cf/get :public-uri)
:swagger-js swagger-js :swagger-js swagger-js
:swagger-css swagger-cs}] :swagger-css swagger-cs}]
{::yrs/status 200 {::rres/status 200
::yrs/headers {"content-type" "text/html"} ::rres/headers {"content-type" "text/html"}
::yrs/body (-> (io/resource "app/templates/openapi.tmpl") ::rres/body (-> (io/resource "app/templates/openapi.tmpl")
(tmpl/render context))})) (tmpl/render context))}))
(fn [_] (fn [_]
{::yrs/status 404}))) {::rres/status 404})))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; MODULE INIT ;; MODULE INIT

View file

@ -11,7 +11,7 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.http :as-alias http] [app.http :as-alias http]
[app.rpc :as-alias rpc] [app.rpc :as-alias rpc]
[yetti.response :as-alias yrs])) [ring.response :as-alias rres]))
;; A utilty wrapper object for wrap service responses that does not ;; A utilty wrapper object for wrap service responses that does not
;; implements the IObj interface that make possible attach metadata to ;; implements the IObj interface that make possible attach metadata to
@ -77,4 +77,4 @@
(fn [_ response] (fn [_ response]
(let [exp (if (integer? max-age) max-age (inst-ms max-age)) (let [exp (if (integer? max-age) max-age (inst-ms max-age))
val (dm/fmt "max-age=%" (int (/ exp 1000.0)))] val (dm/fmt "max-age=%" (int (/ exp 1000.0)))]
(update response ::yrs/headers assoc "cache-control" val))))) (update response ::rres/headers assoc "cache-control" val)))))

View file

@ -15,8 +15,8 @@
[app.util.time :as dt] [app.util.time :as dt]
[promesa.exec :as px] [promesa.exec :as px]
[promesa.exec.csp :as sp] [promesa.exec.csp :as sp]
[yetti.request :as yr] [ring.request :as rreq]
[yetti.util :as yu] [ring.websocket :as rws]
[yetti.websocket :as yws]) [yetti.websocket :as yws])
(:import (:import
java.nio.ByteBuffer)) java.nio.ByteBuffer))
@ -50,7 +50,7 @@
(declare start-io-loop!) (declare start-io-loop!)
(defn handler (defn listener
"A WebSocket upgrade handler factory. Returns a handler that can be "A WebSocket upgrade handler factory. Returns a handler that can be
used to upgrade to websocket connection. This handler implements the used to upgrade to websocket connection. This handler implements the
basic custom protocol on top of websocket connection with all the basic custom protocol on top of websocket connection with all the
@ -61,37 +61,34 @@
It also accepts some options that allows you parametrize the It also accepts some options that allows you parametrize the
protocol behavior. The options map will be used as-as for the protocol behavior. The options map will be used as-as for the
initial data of the `ws` data structure" initial data of the `ws` data structure"
[& {:keys [::on-rcv-message [request & {:keys [::on-rcv-message
::on-snd-message ::on-snd-message
::on-connect ::on-connect
::input-buff-size ::input-buff-size
::output-buff-size ::output-buff-size
::idle-timeout] ::idle-timeout]
:or {input-buff-size 64 :or {input-buff-size 64
output-buff-size 64 output-buff-size 64
idle-timeout 60000 idle-timeout 60000
on-connect identity on-connect identity
on-snd-message identity-3 on-snd-message identity-3
on-rcv-message identity-3} on-rcv-message identity-3}
:as options}] :as options}]
(assert (fn? on-rcv-message) "'on-rcv-message' should be a function") (assert (fn? on-rcv-message) "'on-rcv-message' should be a function")
(assert (fn? on-snd-message) "'on-snd-message' should be a function") (assert (fn? on-snd-message) "'on-snd-message' should be a function")
(assert (fn? on-connect) "'on-connect' should be a function") (assert (fn? on-connect) "'on-connect' should be a function")
(fn [{:keys [::yws/channel] :as request}] (let [input-ch (sp/chan :buf input-buff-size)
(let [input-ch (sp/chan :buf input-buff-size) output-ch (sp/chan :buf output-buff-size)
output-ch (sp/chan :buf output-buff-size) hbeat-ch (sp/chan :buf (sp/sliding-buffer 6))
hbeat-ch (sp/chan :buf (sp/sliding-buffer 6)) close-ch (sp/chan)
close-ch (sp/chan) ip-addr (parse-client-ip request)
uagent (rreq/get-header request "user-agent")
ip-addr (parse-client-ip request) id (uuid/next)
uagent (yr/get-header request "user-agent") state (atom {})
id (uuid/next) beats (atom #{})
state (atom {}) options (-> options
beats (atom #{})
options (-> options
(update ::handler wrap-handler) (update ::handler wrap-handler)
(assoc ::id id) (assoc ::id id)
(assoc ::state state) (assoc ::state state)
@ -101,126 +98,118 @@
(assoc ::heartbeat-ch hbeat-ch) (assoc ::heartbeat-ch hbeat-ch)
(assoc ::output-ch output-ch) (assoc ::output-ch output-ch)
(assoc ::close-ch close-ch) (assoc ::close-ch close-ch)
(assoc ::channel channel)
(assoc ::remote-addr ip-addr) (assoc ::remote-addr ip-addr)
(assoc ::user-agent uagent) (assoc ::user-agent uagent))]
{:on-open
(fn on-open [channel]
(l/trace :fn "on-open" :conn-id id :channel channel)
(let [options (-> options
(assoc ::channel channel)
(on-connect)) (on-connect))
timeout (dt/duration idle-timeout)]
on-ws-open (yws/set-idle-timeout! channel timeout)
(fn [channel] (px/submit! :vthread (partial start-io-loop! options))))
(l/trace :fn "on-ws-open" :conn-id id)
(let [timeout (dt/duration idle-timeout)
name (str "penpot/websocket/io-loop/" id)]
(yws/idle-timeout! channel timeout)
(px/fn->thread (partial start-io-loop! options)
{:name name :virtual true})))
on-ws-terminate :on-close
(fn [_ code reason] (fn on-close [_channel code reason]
(l/trace :fn "on-ws-terminate" (l/info :fn "on-ws-terminate"
:conn-id id :conn-id id
:code code :code code
:reason reason) :reason reason)
(sp/close! close-ch)) (sp/close! close-ch))
on-ws-error :on-error
(fn [_ cause] (fn on-error [_channel cause]
(sp/close! close-ch cause)) (sp/close! close-ch cause))
on-ws-message :on-message
(fn [_ message] (fn on-message [_channel message]
(sp/offer! input-ch message) (when (string? message)
(swap! state assoc ::last-activity-at (dt/now))) (sp/offer! input-ch message)
(swap! state assoc ::last-activity-at (dt/now))))
on-ws-pong :on-pong
(fn [_ buffers] (fn on-pong [_channel data]
;; (l/trace :fn "on-ws-pong" :buffers (pr-str buffers)) (l/trace :fn "on-pong" :data data)
(sp/put! hbeat-ch (yu/copy-many buffers)))] (sp/put! hbeat-ch data))}))
(yws/on-close! channel (fn [_]
(sp/close! close-ch)))
{:on-open on-ws-open
:on-error on-ws-error
:on-close on-ws-terminate
:on-text on-ws-message
:on-pong on-ws-pong})))
(defn- handle-ping! (defn- handle-ping!
[{:keys [::id ::beats ::channel] :as wsp} beat-id] [{:keys [::id ::beats ::channel] :as wsp} beat-id]
(l/trace :hint "ping" :beat beat-id :conn-id id) (l/trace :hint "send ping" :beat beat-id :conn-id id)
(yws/ping! channel (encode-beat beat-id)) (rws/ping channel (encode-beat beat-id))
(let [issued (swap! beats conj (long beat-id))] (let [issued (swap! beats conj (long beat-id))]
(not (>= (count issued) max-missed-heartbeats)))) (not (>= (count issued) max-missed-heartbeats))))
(defn- start-io-loop! (defn- start-io-loop!
[{:keys [::id ::close-ch ::input-ch ::output-ch ::heartbeat-ch ::channel ::handler ::beats ::on-rcv-message ::on-snd-message] :as wsp}] [{:keys [::id ::close-ch ::input-ch ::output-ch ::heartbeat-ch ::channel ::handler ::beats ::on-rcv-message ::on-snd-message] :as wsp}]
(px/thread (try
{:name (str "penpot/websocket/io-loop/" id) (handler wsp {:type :open})
:virtual true} (loop [i 0]
(try (let [ping-ch (sp/timeout-chan heartbeat-interval)
(handler wsp {:type :open}) [msg p] (sp/alts! [close-ch input-ch output-ch heartbeat-ch ping-ch])]
(loop [i 0] (when (rws/open? channel)
(let [ping-ch (sp/timeout-chan heartbeat-interval) (cond
[msg p] (sp/alts! [close-ch input-ch output-ch heartbeat-ch ping-ch])] (identical? p ping-ch)
(when (yws/connected? channel) (if (handle-ping! wsp i)
(cond (recur (inc i))
(identical? p ping-ch) (rws/close channel 8802 "missing to many pings"))
(if (handle-ping! wsp i)
(recur (inc i))
(yws/close! channel 8802 "missing to many pings"))
(or (identical? p close-ch) (nil? msg)) (or (identical? p close-ch) (nil? msg))
(do :nothing) (do :nothing)
(identical? p heartbeat-ch) (identical? p heartbeat-ch)
(let [beat (decode-beat msg)] (let [beat (decode-beat msg)]
;; (l/trace :hint "pong" :beat beat :conn-id id) ;; (l/trace :hint "pong" :beat beat :conn-id id)
(swap! beats disj beat) (swap! beats disj beat)
(recur i)) (recur i))
(identical? p input-ch) (identical? p input-ch)
(let [message (t/decode-str msg) (let [message (t/decode-str msg)
message (on-rcv-message message) message (on-rcv-message message)
{:keys [request-id] :as response} (handler wsp message)] {:keys [request-id] :as response} (handler wsp message)]
(when (map? response) (when (map? response)
(sp/put! output-ch (sp/put! output-ch
(cond-> response (cond-> response
(some? request-id) (some? request-id)
(assoc :request-id request-id)))) (assoc :request-id request-id))))
(recur i)) (recur i))
(identical? p output-ch) (identical? p output-ch)
(let [message (on-snd-message msg) (let [message (on-snd-message msg)
message (t/encode-str message {:type :json-verbose})] message (t/encode-str message {:type :json-verbose})]
;; (l/trace :hint "writing message to output" :message msg) ;; (l/trace :hint "writing message to output" :message msg)
(yws/send! channel message) (rws/send channel message)
(recur i)))))) (recur i))))))
(catch java.nio.channels.ClosedChannelException _) (catch java.nio.channels.ClosedChannelException _)
(catch java.net.SocketException _) (catch java.net.SocketException _)
(catch java.io.IOException _) (catch java.io.IOException _)
(catch InterruptedException _ (catch InterruptedException _cause
(l/debug :hint "websocket thread interrumpted" :conn-id id)) (l/debug :hint "websocket thread interrumpted" :conn-id id))
(catch Throwable cause (catch Throwable cause
(l/error :hint "unhandled exception on websocket thread" (l/error :hint "unhandled exception on websocket thread"
:conn-id id :conn-id id
:cause cause)) :cause cause))
(finally
(finally (try
(handler wsp {:type :close}) (handler wsp {:type :close})
(when (yws/connected? channel) (when (rws/open? channel)
;; NOTE: we need to ignore all exceptions here because ;; NOTE: we need to ignore all exceptions here because
;; there can be a race condition that first returns that ;; there can be a race condition that first returns that
;; channel is connected but on closing, will raise that ;; channel is connected but on closing, will raise that
;; channel is already closed. ;; channel is already closed.
(ex/ignoring (ex/ignoring
(yws/close! channel 8899 "terminated"))) (rws/close channel 8899 "terminated")))
(when-let [on-disconnect (::on-disconnect wsp)] (when-let [on-disconnect (::on-disconnect wsp)]
(on-disconnect)) (on-disconnect))
(l/trace :hint "websocket thread terminated" :conn-id id))))) (catch Throwable cause
(throw cause)))
(l/trace :hint "websocket thread terminated" :conn-id id))))

View file

@ -31,17 +31,17 @@
request (volatile! nil) request (volatile! nil)
handler (#'app.http.access-token/wrap-soft-auth handler (#'app.http.access-token/wrap-soft-auth
(fn [req & _] (vreset! request req)) (fn [req] (vreset! request req))
system)] system)]
(with-mocks [m1 {:target 'app.http.access-token/get-token (with-mocks [m1 {:target 'app.http.access-token/get-token
:return nil}] :return nil}]
(handler {} nil nil) (handler {})
(t/is (= {} @request))) (t/is (= {} @request)))
(with-mocks [m1 {:target 'app.http.access-token/get-token (with-mocks [m1 {:target 'app.http.access-token/get-token
:return (:token token)}] :return (:token token)}]
(handler {} nil nil) (handler {})
(let [token-id (get @request :app.http.access-token/id)] (let [token-id (get @request :app.http.access-token/id)]
(t/is (= token-id (:id token)))))))) (t/is (= token-id (:id token))))))))

View file

@ -25,7 +25,7 @@
(def http-request (def http-request
(reify (reify
yetti.request/Request ring.request/Request
(get-header [_ name] (get-header [_ name]
(case name (case name
"x-forwarded-for" "127.0.0.44")))) "x-forwarded-for" "127.0.0.44"))))

View file

@ -46,6 +46,6 @@
{:keys [error result]} (th/command! (assoc params ::cond/key etag))] {:keys [error result]} (th/command! (assoc params ::cond/key etag))]
(t/is (nil? error)) (t/is (nil? error))
(t/is (fn? result)) (t/is (fn? result))
(t/is (= 304 (-> (result nil) :yetti.response/status)))) (t/is (= 304 (-> (result nil) :ring.response/status))))
)))) ))))