mirror of
https://github.com/penpot/penpot.git
synced 2025-02-02 12:28:54 -05:00
♻️ Refactor error handlers and request/response body encoding/decoding.
This commit is contained in:
parent
22f7c0b020
commit
97220d707b
4 changed files with 169 additions and 95 deletions
|
@ -14,7 +14,6 @@
|
||||||
metosin/reitit-ring {:mvn/version "0.3.9"}
|
metosin/reitit-ring {:mvn/version "0.3.9"}
|
||||||
metosin/reitit-middleware {:mvn/version "0.3.9"}
|
metosin/reitit-middleware {:mvn/version "0.3.9"}
|
||||||
metosin/reitit-spec {:mvn/version "0.3.9"}
|
metosin/reitit-spec {:mvn/version "0.3.9"}
|
||||||
metosin/reitit-dev {:mvn/version "0.3.9"}
|
|
||||||
|
|
||||||
danlentz/clj-uuid {:mvn/version "0.1.9"}
|
danlentz/clj-uuid {:mvn/version "0.1.9"}
|
||||||
org.jsoup/jsoup {:mvn/version "1.12.1"}
|
org.jsoup/jsoup {:mvn/version "1.12.1"}
|
||||||
|
@ -35,6 +34,10 @@
|
||||||
commons-io/commons-io {:mvn/version "2.6"}
|
commons-io/commons-io {:mvn/version "2.6"}
|
||||||
com.draines/postal {:mvn/version "2.0.3"
|
com.draines/postal {:mvn/version "2.0.3"
|
||||||
:exclusions [commons-codec/commons-codec]}
|
:exclusions [commons-codec/commons-codec]}
|
||||||
|
|
||||||
|
;; exception printing
|
||||||
|
io.aviso/pretty {:mvn/version "0.1.37"}
|
||||||
|
|
||||||
hikari-cp/hikari-cp {:mvn/version "2.7.1"}
|
hikari-cp/hikari-cp {:mvn/version "2.7.1"}
|
||||||
mount/mount {:mvn/version "0.1.16"}
|
mount/mount {:mvn/version "0.1.16"}
|
||||||
environ/environ {:mvn/version "1.1.0"}
|
environ/environ {:mvn/version "1.1.0"}
|
||||||
|
@ -44,6 +47,7 @@
|
||||||
{:dev
|
{:dev
|
||||||
{:extra-deps {com.bhauman/rebel-readline {:mvn/version "0.1.4"}
|
{:extra-deps {com.bhauman/rebel-readline {:mvn/version "0.1.4"}
|
||||||
org.clojure/tools.namespace {:mvn/version "0.3.0"}
|
org.clojure/tools.namespace {:mvn/version "0.3.0"}
|
||||||
|
fipp/fipp {:mvn/version "0.6.19"}
|
||||||
clj-http/clj-http {:mvn/version "2.1.0"}
|
clj-http/clj-http {:mvn/version "2.1.0"}
|
||||||
ring/ring-mock {:mvn/version "0.4.0"}
|
ring/ring-mock {:mvn/version "0.4.0"}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,8 @@
|
||||||
|
|
||||||
(ns uxbox.http
|
(ns uxbox.http
|
||||||
(:require [mount.core :refer [defstate]]
|
(:require [mount.core :refer [defstate]]
|
||||||
[muuntaja.core :as m]
|
|
||||||
[ring.adapter.jetty :as jetty]
|
[ring.adapter.jetty :as jetty]
|
||||||
[reitit.ring :as rr]
|
[reitit.ring :as rr]
|
||||||
[reitit.dev.pretty :as pretty]
|
|
||||||
[uxbox.config :as cfg]
|
[uxbox.config :as cfg]
|
||||||
[uxbox.http.middleware :refer [handler
|
[uxbox.http.middleware :refer [handler
|
||||||
middleware
|
middleware
|
||||||
|
@ -25,17 +23,9 @@
|
||||||
[uxbox.api.svg :as api-svg]
|
[uxbox.api.svg :as api-svg]
|
||||||
[uxbox.util.transit :as t]))
|
[uxbox.util.transit :as t]))
|
||||||
|
|
||||||
(def ^:private muuntaja-instance
|
|
||||||
(m/create (update-in m/default-options [:formats "application/transit+json"]
|
|
||||||
merge {:encoder-opts {:handlers t/+write-handlers+}
|
|
||||||
:decoder-opts {:handlers t/+read-handlers+}})))
|
|
||||||
|
|
||||||
(def ^:private router-options
|
(def ^:private router-options
|
||||||
{;;:reitit.middleware/transform dev/print-request-diffs
|
{::rr/default-options-handler options-handler
|
||||||
::rr/default-options-handler options-handler
|
:data {:middleware middleware}})
|
||||||
:exception pretty/exception
|
|
||||||
:data {:muuntaja muuntaja-instance
|
|
||||||
:middleware middleware}})
|
|
||||||
|
|
||||||
(def routes
|
(def routes
|
||||||
[["/media/*" (rr/create-resource-handler {:root "public/media"})]
|
[["/media/*" (rr/create-resource-handler {:root "public/media"})]
|
||||||
|
@ -114,8 +104,8 @@
|
||||||
;; --- State Initialization
|
;; --- State Initialization
|
||||||
(def app
|
(def app
|
||||||
(delay
|
(delay
|
||||||
(-> (rr/router routes router-options)
|
(let [router (rr/router routes router-options)]
|
||||||
(rr/ring-handler (rr/create-default-handler)))))
|
(rr/ring-handler router (rr/create-default-handler)))))
|
||||||
|
|
||||||
(defn- start-server
|
(defn- start-server
|
||||||
[config]
|
[config]
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 20162019 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
|
||||||
(ns uxbox.http.errors
|
(ns uxbox.http.errors
|
||||||
"A errors handling for the http server.")
|
"A errors handling for the http server."
|
||||||
|
(:require [io.aviso.exception :as e]))
|
||||||
|
|
||||||
(defmulti handle-exception #(:type (ex-data %)))
|
(defmulti handle-exception #(:type (ex-data %)))
|
||||||
|
|
||||||
|
@ -15,13 +16,18 @@
|
||||||
{:status 400
|
{:status 400
|
||||||
:body response}))
|
:body response}))
|
||||||
|
|
||||||
|
(defmethod handle-exception :parse
|
||||||
|
[err]
|
||||||
|
{:status 400
|
||||||
|
:body {:type :parse
|
||||||
|
:message (ex-message err)}})
|
||||||
|
|
||||||
(defmethod handle-exception :default
|
(defmethod handle-exception :default
|
||||||
[err]
|
[err]
|
||||||
(let [response (ex-data err)]
|
(e/write-exception err)
|
||||||
{:status 500
|
{:status 500
|
||||||
:body response}))
|
:body {:type :exception
|
||||||
|
:message (ex-message err)}})
|
||||||
;; --- Entry Point
|
|
||||||
|
|
||||||
(defn- handle-data-access-exception
|
(defn- handle-data-access-exception
|
||||||
[err]
|
[err]
|
||||||
|
@ -29,47 +35,19 @@
|
||||||
state (.getSQLState err)
|
state (.getSQLState err)
|
||||||
message (.getMessage err)]
|
message (.getMessage err)]
|
||||||
(case state
|
(case state
|
||||||
"P0002"
|
"P0002" {:status 412 ;; precondition-failed
|
||||||
{:status 412 ;; precondition-failed
|
:body {:message message
|
||||||
:body {:message message
|
:type :occ}}
|
||||||
:payload nil
|
(handle-exception err))))
|
||||||
:type :occ}}
|
|
||||||
|
|
||||||
(do
|
|
||||||
{:status 500
|
|
||||||
:message {:message message
|
|
||||||
:type :unexpected
|
|
||||||
:payload nil}}))))
|
|
||||||
|
|
||||||
(defn- handle-unexpected-exception
|
|
||||||
[err]
|
|
||||||
(let [message (.getMessage err)]
|
|
||||||
{:status 500
|
|
||||||
:body {:message message
|
|
||||||
:type :unexpected
|
|
||||||
:payload nil}}))
|
|
||||||
|
|
||||||
(defn errors-handler
|
(defn errors-handler
|
||||||
[error context]
|
[error context]
|
||||||
(cond
|
(cond
|
||||||
(instance? clojure.lang.ExceptionInfo error)
|
(or (instance? java.util.concurrent.CompletionException error)
|
||||||
(handle-exception error)
|
(instance? java.util.concurrent.ExecutionException error))
|
||||||
|
|
||||||
(instance? java.util.concurrent.CompletionException error)
|
|
||||||
(errors-handler context (.getCause error))
|
|
||||||
|
|
||||||
java.util.concurrent.ExecutionException
|
|
||||||
(errors-handler context (.getCause error))
|
(errors-handler context (.getCause error))
|
||||||
|
|
||||||
(instance? org.jooq.exception.DataAccessException error)
|
(instance? org.jooq.exception.DataAccessException error)
|
||||||
(handle-data-access-exception error)
|
(handle-data-access-exception error)
|
||||||
|
|
||||||
:else
|
:else (handle-exception error)))
|
||||||
(handle-unexpected-exception error)))
|
|
||||||
|
|
||||||
(defn wrap-print-errors
|
|
||||||
[handler error request]
|
|
||||||
(println "\n*********** stack trace ***********")
|
|
||||||
(.printStackTrace error)
|
|
||||||
(println "\n********* end stack trace *********")
|
|
||||||
(handler error request))
|
|
||||||
|
|
|
@ -5,24 +5,33 @@
|
||||||
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
|
||||||
(ns uxbox.http.middleware
|
(ns uxbox.http.middleware
|
||||||
(:require [promesa.core :as p]
|
(:require
|
||||||
[cuerdas.core :as str]
|
[clojure.spec.alpha :as s]
|
||||||
[struct.core :as st]
|
[cuerdas.core :as str]
|
||||||
[reitit.ring :as rr]
|
[promesa.core :as p]
|
||||||
[reitit.ring.middleware.multipart :as multipart]
|
[reitit.ring :as rr]
|
||||||
[reitit.ring.middleware.muuntaja :as muuntaja]
|
[reitit.ring.middleware.exception :as exception]
|
||||||
[reitit.ring.middleware.parameters :as parameters]
|
[reitit.ring.middleware.multipart :as multipart]
|
||||||
[reitit.ring.middleware.exception :as exception]
|
[reitit.ring.middleware.parameters :as parameters]
|
||||||
[ring.middleware.session :refer [wrap-session]]
|
[ring.middleware.multipart-params :refer [wrap-multipart-params]]
|
||||||
[ring.middleware.session.cookie :refer [cookie-store]]
|
[ring.middleware.session :refer [wrap-session]]
|
||||||
[ring.middleware.multipart-params :refer [wrap-multipart-params]]
|
[ring.middleware.session.cookie :refer [cookie-store]]
|
||||||
[uxbox.config :as cfg]
|
[struct.core :as st]
|
||||||
[uxbox.http.etag :refer [wrap-etag]]
|
[uxbox.config :as cfg]
|
||||||
[uxbox.http.cors :refer [wrap-cors]]
|
[uxbox.http.cors :refer [wrap-cors]]
|
||||||
[uxbox.http.errors :as errors]
|
[uxbox.http.errors :as errors]
|
||||||
[uxbox.http.response :as rsp]
|
[uxbox.http.etag :refer [wrap-etag]]
|
||||||
[uxbox.util.data :refer [normalize-attrs]]
|
[uxbox.http.response :as rsp]
|
||||||
[uxbox.util.exceptions :as ex]))
|
[uxbox.util.data :refer [normalize-attrs]]
|
||||||
|
[uxbox.util.exceptions :as ex]
|
||||||
|
[uxbox.util.spec :as us]
|
||||||
|
[uxbox.util.transit :as t]))
|
||||||
|
|
||||||
|
(extend-protocol ring.core.protocols/StreamableResponseBody
|
||||||
|
(Class/forName "[B")
|
||||||
|
(write-body-to-stream [body _ ^java.io.OutputStream output-stream]
|
||||||
|
(with-open [out output-stream]
|
||||||
|
(.write out ^bytes body))))
|
||||||
|
|
||||||
(defn- transform-handler
|
(defn- transform-handler
|
||||||
[handler]
|
[handler]
|
||||||
|
@ -37,6 +46,9 @@
|
||||||
(catch Exception e
|
(catch Exception e
|
||||||
(raise e)))))
|
(raise e)))))
|
||||||
|
|
||||||
|
;; The middleware that transform string keys to keywords and perform
|
||||||
|
;; usability transformations.
|
||||||
|
|
||||||
(def ^:private normalize-params-middleware
|
(def ^:private normalize-params-middleware
|
||||||
{:name ::normalize-params-middleware
|
{:name ::normalize-params-middleware
|
||||||
:wrap (fn [handler]
|
:wrap (fn [handler]
|
||||||
|
@ -89,18 +101,61 @@
|
||||||
(assoc-in req [:parameters (:key spec)] result))))
|
(assoc-in req [:parameters (:key spec)] result))))
|
||||||
request parameters))
|
request parameters))
|
||||||
|
|
||||||
|
(compile-struct [route opts parameters]
|
||||||
|
(let [parameters (prepare parameters)]
|
||||||
|
(fn [handler]
|
||||||
|
(fn
|
||||||
|
([request]
|
||||||
|
(handler (validate request parameters)))
|
||||||
|
([request respond raise]
|
||||||
|
(try
|
||||||
|
(handler (validate request parameters false) respond raise)
|
||||||
|
(catch Exception e
|
||||||
|
(raise e))))))))
|
||||||
|
|
||||||
|
(prepare-spec [parameters]
|
||||||
|
(reduce-kv (fn [acc key s]
|
||||||
|
(let [rk (case key
|
||||||
|
:path :path-params
|
||||||
|
:query :query-params
|
||||||
|
:body :body-params
|
||||||
|
:multipart :multipart-params
|
||||||
|
(throw (ex-info "Not supported key on :parameters" {})))]
|
||||||
|
(assoc acc rk {:key key
|
||||||
|
:fn #(us/conform s %)})))
|
||||||
|
{}
|
||||||
|
parameters))
|
||||||
|
|
||||||
|
(validate-spec [request parameters]
|
||||||
|
(reduce-kv
|
||||||
|
(fn [req key spec]
|
||||||
|
(let [[result errors] ((:fn spec) (get req key))]
|
||||||
|
(if errors
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :parameters
|
||||||
|
:context {:problems (vec (::s/problems errors))
|
||||||
|
:spec (::s/spec errors)
|
||||||
|
:value (::s/value errors)})
|
||||||
|
(assoc-in req [:parameters (:key spec)] result))))
|
||||||
|
request parameters))
|
||||||
|
|
||||||
|
(compile-spec [route opts parameters]
|
||||||
|
(let [parameters (prepare-spec parameters)]
|
||||||
|
(fn [handler]
|
||||||
|
(fn
|
||||||
|
([request]
|
||||||
|
(handler (validate-spec request parameters)))
|
||||||
|
([request respond raise]
|
||||||
|
(try
|
||||||
|
(handler (validate-spec request parameters) respond raise)
|
||||||
|
(catch Exception e
|
||||||
|
(raise e))))))))
|
||||||
|
|
||||||
(compile [route opts]
|
(compile [route opts]
|
||||||
(when-let [parameters (:parameters route)]
|
(when-let [parameters (:parameters route)]
|
||||||
(let [parameters (prepare parameters)]
|
(if (= :spec (:validation route))
|
||||||
(fn [handler]
|
(compile-spec route opts parameters)
|
||||||
(fn
|
(compile-struct route opts parameters))))]
|
||||||
([request]
|
|
||||||
(handler (validate request parameters)))
|
|
||||||
([request respond raise]
|
|
||||||
(try
|
|
||||||
(handler (validate request parameters false) respond raise)
|
|
||||||
(catch Exception e
|
|
||||||
(raise e)))))))))]
|
|
||||||
{:name ::parameters-validation-middleware
|
{:name ::parameters-validation-middleware
|
||||||
:compile compile}))
|
:compile compile}))
|
||||||
|
|
||||||
|
@ -135,8 +190,8 @@
|
||||||
(def ^:private exception-middleware
|
(def ^:private exception-middleware
|
||||||
(exception/create-exception-middleware
|
(exception/create-exception-middleware
|
||||||
(assoc exception/default-handlers
|
(assoc exception/default-handlers
|
||||||
::exception/default errors/errors-handler
|
:muuntaja/decode errors/errors-handler
|
||||||
::exception/wrap errors/wrap-print-errors)))
|
::exception/default errors/errors-handler)))
|
||||||
|
|
||||||
(def authorization-middleware
|
(def authorization-middleware
|
||||||
{:name ::authorization-middleware
|
{:name ::authorization-middleware
|
||||||
|
@ -151,6 +206,49 @@
|
||||||
(handler (assoc request :identity identity :user identity) respond raise)
|
(handler (assoc request :identity identity :user identity) respond raise)
|
||||||
(respond (rsp/forbidden nil))))))})
|
(respond (rsp/forbidden nil))))))})
|
||||||
|
|
||||||
|
(def format-response-middleware
|
||||||
|
(letfn [(process-response [{:keys [body] :as rsp}]
|
||||||
|
(if body
|
||||||
|
(let [body (t/encode body {:type :json-verbose})]
|
||||||
|
(-> rsp
|
||||||
|
(assoc :body body)
|
||||||
|
(update :headers assoc "content-type" "application/transit+json")))
|
||||||
|
rsp))]
|
||||||
|
{:name ::format-response-middleware
|
||||||
|
:wrap (fn [handler]
|
||||||
|
(fn
|
||||||
|
([request]
|
||||||
|
(process-response (handler request)))
|
||||||
|
([request respond raise]
|
||||||
|
(handler request (fn [res] (respond (process-response res))) raise))))}))
|
||||||
|
|
||||||
|
(def parse-request-middleware
|
||||||
|
(letfn [(get-content-type [request]
|
||||||
|
(or (:content-type request)
|
||||||
|
(get (:headers request) "content-type")))
|
||||||
|
(process-request [request]
|
||||||
|
(let [ctype (get-content-type request)]
|
||||||
|
(if (= "application/transit+json" ctype)
|
||||||
|
(try
|
||||||
|
(assoc request :body-params (t/decode (:body request)))
|
||||||
|
(catch Exception e
|
||||||
|
(ex/raise :type :parse
|
||||||
|
:message "Unable to parse transit from request body."
|
||||||
|
:cause e)))
|
||||||
|
request)))]
|
||||||
|
|
||||||
|
{:name ::parse-request-middleware
|
||||||
|
:wrap (fn [handler]
|
||||||
|
(fn
|
||||||
|
([request]
|
||||||
|
(handler (process-request request)))
|
||||||
|
([request respond raise]
|
||||||
|
(try
|
||||||
|
(let [request (process-request request)]
|
||||||
|
(handler request respond raise))
|
||||||
|
(catch Exception e
|
||||||
|
(raise e))))))}))
|
||||||
|
|
||||||
(def middleware
|
(def middleware
|
||||||
[cors-middleware
|
[cors-middleware
|
||||||
session-middleware
|
session-middleware
|
||||||
|
@ -159,18 +257,22 @@
|
||||||
etag-middleware
|
etag-middleware
|
||||||
|
|
||||||
parameters/parameters-middleware
|
parameters/parameters-middleware
|
||||||
muuntaja/format-negotiate-middleware
|
|
||||||
;; encoding response body
|
|
||||||
muuntaja/format-response-middleware
|
|
||||||
;; exception handling
|
|
||||||
exception-middleware
|
|
||||||
;; decoding request body
|
|
||||||
muuntaja/format-request-middleware
|
|
||||||
|
|
||||||
;; multipart
|
;; Format the body into transit
|
||||||
|
format-response-middleware
|
||||||
|
|
||||||
|
;; main exception handling
|
||||||
|
exception-middleware
|
||||||
|
|
||||||
|
;; parse transit format from request body
|
||||||
|
parse-request-middleware
|
||||||
|
|
||||||
|
;; multipart parsing
|
||||||
multipart-params-middleware
|
multipart-params-middleware
|
||||||
|
|
||||||
;; parameters normalization
|
;; parameters normalization
|
||||||
normalize-params-middleware
|
normalize-params-middleware
|
||||||
|
|
||||||
;; parameters validation
|
;; parameters validation
|
||||||
parameters-validation-middleware])
|
parameters-validation-middleware])
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue