/
web.clj
133 lines (110 loc) · 4.15 KB
/
web.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
(ns duct.middleware.web
(:require [duct.logger :as logger]
[integrant.core :as ig]
[muuntaja.core :as mc]
[muuntaja.middleware :as mm]
[ring.middleware.defaults :refer [wrap-defaults]]
[ring.middleware.stacktrace :refer [wrap-stacktrace]]
[ring.middleware.webjars :refer [wrap-webjars]]
[ring.util.response :as response]
[duct.core.merge :as merge]))
(def ^:private request-log-keys
[:request-method :uri :query-string])
(defn- log-request [logger request]
(logger/log logger :info ::request (select-keys request request-log-keys)))
(defn- log-error [logger ex]
(logger/log logger :error ::handler-error ex))
(defn wrap-log-requests
"Log each request using the supplied logger. The logger must implement the
duct.core.protocols/Logger protocol."
[handler logger]
(fn
([request]
(log-request logger request)
(handler request))
([request respond raise]
(log-request logger request)
(handler request respond raise))))
(defn wrap-log-errors
"Log any exceptions with the supplied logger, then re-throw them."
[handler logger]
(fn
([request]
(try
(handler request)
(catch Throwable ex
(log-error logger ex)
(throw ex))))
([request respond raise]
(try
(handler request respond (fn [ex] (log-error logger ex) (raise ex)))
(catch Throwable ex
(log-error logger ex)
(throw ex))))))
(defn- internal-error [response]
(response/status response 500))
(defn- not-found [response]
(response/status response 404))
(defn wrap-hide-errors
"Middleware that hides any uncaught exceptions behind a 500 'Internal Error'
response generated by an error handler. Intended for use in production when
exception details need to be hidden."
[handler error-handler]
(fn
([request]
(try
(handler request)
(catch Throwable _ (internal-error (error-handler request)))))
([request respond raise]
(try
(handler request respond (fn [_] (respond (internal-error (error-handler request)))))
(catch Throwable _ (respond (internal-error (error-handler request))))))))
(defn wrap-not-found
"Middleware that returns a 404 'Not Found' response from an error handler if
the base handler returns nil."
[handler error-handler]
(fn
([request]
(or (handler request) (not-found (error-handler request))))
([request respond raise]
(handler request #(respond (or % (not-found (error-handler request)))) raise))))
(defn- route-aliases-request [request aliases]
(if-let [alias (aliases (:uri request))]
(assoc request :uri alias)
request))
(defn wrap-route-aliases
"Middleware that takes a map of URI aliases. If the URI of the request matches
a URI in the map's keys, the URI is changed to the value corresponding to that
key."
[handler aliases]
(fn
([request]
(handler (route-aliases-request request aliases)))
([request respond raise]
(handler (route-aliases-request request aliases) respond raise))))
(defmethod ig/prep-key ::log-requests [_ options]
(merge {:logger (ig/ref :duct/logger)} options))
(defmethod ig/prep-key ::log-errors [_ options]
(merge {:logger (ig/ref :duct/logger)} options))
(defmethod ig/init-key ::log-requests [_ {:keys [logger]}]
#(wrap-log-requests % logger))
(defmethod ig/init-key ::log-errors [_ {:keys [logger]}]
#(wrap-log-errors % logger))
(defmethod ig/init-key ::hide-errors [_ {:keys [error-handler]}]
#(wrap-hide-errors % error-handler))
(defmethod ig/init-key ::not-found [_ {:keys [error-handler]}]
#(wrap-not-found % error-handler))
(defmethod ig/init-key ::route-aliases [_ aliases]
#(wrap-route-aliases % aliases))
(defmethod ig/init-key ::defaults [_ defaults]
#(wrap-defaults % defaults))
(defmethod ig/init-key ::webjars [_ {:keys [path] :or {path "/assets"}}]
#(wrap-webjars % path))
(defmethod ig/init-key ::stacktrace [_ options]
#(wrap-stacktrace % options))
(defn- deep-merge [a b]
(if (and (map? a) (map? b))
(merge-with deep-merge a b)
b))
(defmethod ig/init-key ::format [_ options]
#(mm/wrap-format % (deep-merge mc/default-options options)))