-
Notifications
You must be signed in to change notification settings - Fork 2
/
core.clj
216 lines (191 loc) · 9.64 KB
/
core.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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
(ns salutem.check-fns.http-endpoint.core
"Provides an HTTP endpoint check function for salutem.
Packaged in a separate module, `salutem.check-fns.http-endpoint` versioned
in lock step with `salutem.core`."
(:require
[clj-http.client :as http]
[tick.core :as time]
[cartus.core :as log]
[cartus.null :as cn]
[salutem.core :as salutem])
(:import
[org.apache.http.conn ConnectTimeoutException]
[java.net SocketTimeoutException ConnectException]))
(defn- resolve-if-fn [thing context]
(if (fn? thing) (thing context) thing))
(defn failure-reason
"Determines the failure reason associated with an exception.
This failure reason function, the default used by the check function, uses a
reason of `:threw-exception` for all exceptions other than:
* [org.apache.http.conn.ConnectTimeoutException](https://www.javadoc.io/doc/org.apache.httpcomponents/httpclient/latest/org/apache/http/conn/ConnectTimeoutException.html)
* [java.net.SocketTimeoutException](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/java/sql/SQLTimeoutException.html)
* [java.net.ConnectException](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/java/sql/SQLTimeoutException.html)
when the exception message includes \"Timeout\"
for which the reason is `:timed-out`.
In the case that this default behaviour is insufficient, an alternative
failure reason function can be passed to [[http-endpoint-check-fn]] using the
`:failure-reason-fn` option."
[exception]
(let [exception-class (class exception)
exception-message (ex-message exception)
contains-timeout (re-matches #".*Timeout.*" exception-message)]
(if (or
(isa? exception-class ConnectTimeoutException)
(isa? exception-class SocketTimeoutException)
(and (isa? exception-class ConnectException) contains-timeout))
:timed-out
:threw-exception)))
(defn successful?
"Returns true if the provided response has a successful status, false
otherwise.
This response success function, the default used by the check function,
treats status codes of 200, 201, 202, 203, 204, 205, 206, 207, 300, 301, 302,
303, 304, 307 and 308 as successful.
In the case that this default behaviour is insufficient, an alternative
response success function can be passed to [[http-endpoint-check-fn]] using
the `:successful-response-fn` option."
[{:keys [status]}]
(http/unexceptional-status? status))
(defn http-endpoint-check-fn
"Returns a check function for the HTTP endpoint identified by the provided
URL.
Accepts the following options in the option map:
- `:method`: a keyword representing the method used to check the endpoint
(one of `:get`, `:head`, `:post`, `:put`, `:delete`, `:options`, `:copy`,
`:move` or `:patch`) or a function of context that will return such a
keyword; defaults to `:get`.
- `:body`: an object representing the body sent to the endpoint on check
execution (supporting anything [`clj-http`](https://github.com/dakrone/clj-http)
will accept) or a function of context that will return such an object;
defaults to `nil`.
- `:headers`: a map of headers to be sent to the endpoint on check
execution (as supported by [`clj-http`](https://github.com/dakrone/clj-http))
or a function of context that will return such a map; defaults to
`nil`.
- `:query-params`: a map of query parameters to be sent to the endpoint on
check execution (as supported by [`clj-http`](https://github.com/dakrone/clj-http))
or a function of context that will return such a map; defaults to
`nil`.
- `:opts`: a map of additional query options (as supported by
[`clj-http`](https://github.com/dakrone/clj-http)) or a function of
context that will return such a map; defaults to
`{:throw-exceptions false}` since we want response success to be deduced
by the `:response-result-fn` rather than treating unsuccessful statuses
as exceptions; note that any timeouts passed in this query options map are
ignored and should be set using `:connection-request-timeout`,
`:connection-timeout` and `:socket-timeout`.
- `:connection-request-timeout`: the [[salutem.core/duration]] to wait
when obtaining a connection from the connection manager before
considering the request failed; defaults to 5 seconds.
- `:connection-timeout`: the [[salutem.core/duration]] to wait when
establishing an HTTP connection before considering the request failed;
defaults to 5 seconds.
- `:socket-timeout`: the [[salutem.core/duration]] to wait while streaming
response data since the last data was received before considering the
request failed; defaults to 5 seconds.
- `:successful-response-fn`: a function of context and the response from a
request to the endpoint, returning true if the response was successful,
false otherwise; by default uses [[successful?]].
- `:response-result-fn`: a function, of context and the response from a
request to the endpoint, used to produce a result for the check; by
default, a healthy result is returned if the response is successful
according to `:successful-response-fn`, otherwise an unhealthy result is
returned.
- `:failure-reason-fn`: a function, of context and an exception, to
determine the reason for a failure; by default uses [[failure-reason]].
- `:exception-result-fn`: a function, of context and an exception, used to
produce a result for the check in the case that an exception is thrown;
by default, an unhealthy result is returned including a `:salutem/reason`
entry with the reason derived by `:failure-reason-fn` and a
`:salutem/exception` entry containing the thrown exception.
Additionally, if the URL parameter is instead a function, it will be called
with the context map at check execution time in order to obtain the
endpoint URL.
If the returned check function is invoked with a context map including a
`:logger` key with a
[`cartus.core/Logger`](https://logicblocks.github.io/cartus/cartus.core.html#var-Logger)
value, the check function will emit a number of log events whilst
executing."
([url] (http-endpoint-check-fn url {}))
([url
{:keys [method
body
headers
query-params
opts
connection-request-timeout
connection-timeout
socket-timeout
successful-response-fn
response-result-fn
failure-reason-fn
exception-result-fn]}]
; Defaulting this way keeps argument documentation more readable while
; allowing defaults to be viewed more clearly using 'view source'.
(let [method (or method :get)
connection-request-timeout
(or connection-request-timeout (time/new-duration 5 :seconds))
connection-timeout
(or connection-timeout (time/new-duration 5 :seconds))
socket-timeout
(or socket-timeout (time/new-duration 5 :seconds))
successful-response-fn
(or successful-response-fn
(fn [_ response]
(successful? response)))
response-result-fn
(or response-result-fn
(fn [context response]
(if (successful-response-fn context response)
(salutem/healthy)
(salutem/unhealthy))))
failure-reason-fn
(or failure-reason-fn
(fn [_ exception]
(failure-reason exception)))
exception-result-fn
(or exception-result-fn
(fn [context exception]
(salutem/unhealthy
{:salutem/reason (failure-reason-fn context exception)
:salutem/exception exception})))]
(fn [context result-cb]
(let [logger (get context :logger (cn/logger))]
(try
(let [endpoint-params
{:url (resolve-if-fn url context)
:method (resolve-if-fn method context)
:body (resolve-if-fn body context)
:headers (resolve-if-fn headers context)
:query-params (resolve-if-fn query-params context)}]
(log/info logger :salutem.check-fns.http-endpoint/check.starting
endpoint-params)
(http/request
(merge
endpoint-params
{:throw-exceptions false}
(resolve-if-fn opts context)
{:async? true
:connection-timeout (time/millis connection-timeout)
:socket-timeout (time/millis socket-timeout)
:connection-request-timeout
(time/millis connection-request-timeout)})
(fn [response]
(log/info logger
:salutem.check-fns.http-endpoint/check.successful)
(result-cb
(response-result-fn
context response)))
(fn [exception]
(log/warn logger :salutem.check-fns.http-endpoint/check.failed
{:reason (failure-reason-fn context exception)}
{:exception exception})
(result-cb
(exception-result-fn
context exception)))))
(catch Exception exception
(log/warn logger :salutem.check-fns.http-endpoint/check.failed
{:reason (failure-reason-fn context exception)}
{:exception exception})
(result-cb
(exception-result-fn context exception)))))))))