Skip to content

Commit 9561052

Browse files
committed
Merge pull request #9 from mtkp/custom-defaults
Custom defaults
2 parents 6d4cc86 + 3123e86 commit 9561052

File tree

3 files changed

+149
-65
lines changed

3 files changed

+149
-65
lines changed

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,23 @@ Whenever you make an http request, add one or more of the hystrix-clj options to
2424
:hystrix/breaker-request-volume 20
2525
:hystrix/breaker-error-percent 50
2626
:hystrix/breaker-sleep-window-ms 5000
27-
:hystrix/bad-request-pred client-error?}}
27+
:hystrix/bad-request-pred client-error?})
28+
```
29+
30+
Requests without any `:hystrix/...` keys won't use Hystrix. Any values not supplied will be set to their default values as above. Custom default values can also be specified when registering with `add-hook`:
31+
32+
```clj
33+
(clj-http-hystrix.core/add-hook {:hystrix/timeout-ms 2500
34+
:hystrix/queue-size 12})
35+
36+
;; now each request will fallback to these if not supplied
37+
(http/get "http://www.google.com" {:hystrix/command-key :google})
38+
;;=> {:hystrix/command-key :google
39+
;; :hystrix/timeout-ms 2500
40+
;; :hystrix/queue-size 12
41+
;; ... (rest are clj-http-hystrix defaults)
42+
;; }
2843
```
29-
Any values not supplied will be set to their default values as above. Requests with no Hystrix-related keys won't use Hystrix.
3044

3145
## Bad requests
3246

src/clj_http_hystrix/core.clj

Lines changed: 94 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,17 @@
1313
[com.netflix.hystrix.exception HystrixBadRequestException]
1414
[org.slf4j MDC]))
1515

16-
(def ^:private ^:const hystrix-keys
17-
#{:hystrix/fallback-fn
18-
:hystrix/group-key
19-
:hystrix/command-key
20-
:hystrix/threads
21-
:hystrix/queue-size
22-
:hystrix/timeout-ms
23-
:hystrix/breaker-request-volume
24-
:hystrix/breaker-error-percent
25-
:hystrix/breaker-sleep-window-ms
26-
:hystrix/bad-request-pred})
27-
2816
(defn default-fallback [req resp]
2917
(if (:status resp)
3018
resp
3119
{:status 503}))
3220

21+
(defn client-error?
22+
"Returns true when the response has one of the 4xx family of status
23+
codes"
24+
[req resp]
25+
(http/client-error? resp))
26+
3327
(defn ^:private handle-exception
3428
[f req]
3529
(let [^Exception raw-response (try (f) (catch Exception e e))
@@ -40,6 +34,24 @@
4034
((http/wrap-exceptions (constantly resp)) req))
4135
resp))
4236

37+
(def ^:private ^:const hystrix-base-configuration
38+
{:hystrix/command-key :default
39+
:hystrix/fallback-fn default-fallback
40+
:hystrix/group-key :default
41+
:hystrix/threads 10
42+
:hystrix/queue-size 5
43+
:hystrix/timeout-ms 1000
44+
:hystrix/breaker-request-volume 20
45+
:hystrix/breaker-error-percent 50
46+
:hystrix/breaker-sleep-window-ms 5000
47+
:hystrix/bad-request-pred client-error?})
48+
49+
(def ^:private hystrix-keys
50+
(keys hystrix-base-configuration))
51+
52+
(defn ^:private hystrix-defaults [defaults]
53+
(merge hystrix-base-configuration (select-keys defaults hystrix-keys)))
54+
4355
(defn ^:private group-key [s]
4456
(HystrixCommandGroupKey$Factory/asKey (name s)))
4557

@@ -49,13 +61,15 @@
4961
(defn ^:private configurator
5062
"Create a configurator that can configure the hystrix according to the
5163
declarative config (or some sensible defaults)"
52-
[config]
53-
(let [timeout (:hystrix/timeout-ms config 1000)
54-
group (:hystrix/group-key config :default)
55-
threads (:hystrix/threads config 10)
56-
request-volume (:hystrix/breaker-request-volume config 20)
57-
error-percent (:hystrix/breaker-error-percent config 50)
58-
sleep-window (:hystrix/breaker-sleep-window-ms config 5000)
64+
^HystrixCommand$Setter [config]
65+
(let [{group :hystrix/group-key
66+
command :hystrix/command-key
67+
timeout :hystrix/timeout-ms
68+
threads :hystrix/threads
69+
queue-size :hystrix/queue-size
70+
sleep-window :hystrix/breaker-sleep-window-ms
71+
error-percent :hystrix/breaker-error-percent
72+
request-volume :hystrix/breaker-request-volume} config
5973
command-configurator (doto (HystrixCommandProperties/Setter)
6074
(.withExecutionIsolationThreadTimeoutInMilliseconds timeout)
6175
(.withCircuitBreakerRequestVolumeThreshold request-volume)
@@ -64,10 +78,10 @@
6478
(.withMetricsRollingPercentileEnabled false))
6579
thread-pool-configurator (doto (HystrixThreadPoolProperties/Setter)
6680
(.withCoreSize threads)
67-
(.withMaxQueueSize (:hystrix/queue-size config 5))
68-
(.withQueueSizeRejectionThreshold (:hystrix/queue-size config 5)))]
81+
(.withMaxQueueSize queue-size)
82+
(.withQueueSizeRejectionThreshold queue-size))]
6983
(doto (HystrixCommand$Setter/withGroupKey (group-key group))
70-
(.andCommandKey (command-key (:hystrix/command-key config :default)))
84+
(.andCommandKey (command-key command))
7185
(.andCommandPropertiesDefaults command-configurator)
7286
(.andThreadPoolPropertiesDefaults thread-pool-configurator))))
7387

@@ -85,48 +99,65 @@
8599
(fn [req resp]
86100
(contains? status-codes (:status resp)))))
87101

88-
(defn client-error?
89-
"Returns true when the response has one of the 4xx family of status
90-
codes"
91-
[req resp]
92-
(http/client-error? resp))
93-
94-
(defn wrap-hystrix
95-
"Wrap a clj-http client request with hystrix features (but only if a
96-
command-key is present in the options map)."
97-
[f req]
98-
(if (not-empty (select-keys req hystrix-keys))
99-
(let [bad-request-pred (:hystrix/bad-request-pred req client-error?)
100-
fallback (:hystrix/fallback-fn req default-fallback)
101-
wrap-bad-request (fn [resp] (if (bad-request-pred req resp)
102-
(throw (HystrixBadRequestException. "Ignored bad request"
103-
(wrap {:object resp
104-
:message "Bad request pred"
105-
:stack-trace (stack-trace)})))
106-
resp))
107-
wrap-exception-response (fn [resp] ((http/wrap-exceptions (constantly resp)) (assoc req :throw-exceptions true)))
108-
^HystrixCommand$Setter configurator (configurator req)
109-
logging-context (or (MDC/getCopyOfContextMap) {})
110-
command (proxy [HystrixCommand] [configurator]
111-
(getFallback []
112-
(MDC/setContextMap logging-context)
113-
(log-error (:hystrix/command-key req) this)
114-
(let [exception (.getFailedExecutionException ^HystrixCommand this)
115-
response (when exception (get-thrown-object exception))]
116-
(fallback req response)))
117-
(run []
118-
(MDC/setContextMap logging-context)
119-
(-> req
120-
(assoc :throw-exceptions false)
121-
f
122-
wrap-bad-request
123-
wrap-exception-response)))]
124-
(handle-exception #(.execute command) req))
125-
(f req)))
102+
(defn hystrix-wrapper
103+
"Create a function that wraps clj-http client requests with hystrix features
104+
(but only if a hystrix key is present in the options map).
105+
Accepts a possibly empty map of default hystrix values that will be used as
106+
fallback configuration for each hystrix request."
107+
[custom-defaults]
108+
(let [defaults (hystrix-defaults custom-defaults)]
109+
(fn [f req]
110+
(if (not-empty (select-keys req hystrix-keys))
111+
(let [req (merge defaults req)
112+
bad-request-pred (:hystrix/bad-request-pred req)
113+
fallback (:hystrix/fallback-fn req)
114+
wrap-bad-request (fn [resp]
115+
(if (bad-request-pred req resp)
116+
(throw
117+
(HystrixBadRequestException.
118+
"Ignored bad request"
119+
(wrap {:object resp
120+
:message "Bad request pred"
121+
:stack-trace (stack-trace)})))
122+
resp))
123+
wrap-exception-response (fn [resp]
124+
((http/wrap-exceptions (constantly resp))
125+
(assoc req :throw-exceptions true)))
126+
configurator (configurator req)
127+
logging-context (or (MDC/getCopyOfContextMap) {})
128+
command (proxy [HystrixCommand] [configurator]
129+
(getFallback []
130+
(MDC/setContextMap logging-context)
131+
(log-error (:hystrix/command-key req) this)
132+
(let [exception (.getFailedExecutionException ^HystrixCommand this)
133+
response (when exception (get-thrown-object exception))]
134+
(fallback req response)))
135+
(run []
136+
(MDC/setContextMap logging-context)
137+
(-> req
138+
(assoc :throw-exceptions false)
139+
f
140+
wrap-bad-request
141+
wrap-exception-response)))]
142+
(handle-exception #(.execute command) req))
143+
(f req)))))
126144

127145
(defn add-hook
128146
"Activate clj-http-hystrix to wrap all clj-http client requests as
129-
hystrix commands."
147+
hystrix commands.
148+
Provide custom hystrix defaults by providing an optional defaults map:
149+
150+
;; use clj-http-hystrix.core default configuration
151+
(add-hook)
152+
153+
;; 6 second timeout fallback, if not specified in request
154+
(add-hook {:hystrix/timeout-ms 6000})
155+
"
156+
([] (add-hook {}))
157+
([defaults]
158+
(hooke/add-hook #'http/request ::wrap-hystrix (hystrix-wrapper defaults))))
159+
160+
(defn remove-hook
161+
"Deactivate clj-http-hystrix."
130162
[]
131-
(when-not (some-> (meta http/request) :robert.hooke/hooks deref (contains? #'wrap-hystrix))
132-
(hooke/add-hook #'http/request #'wrap-hystrix)))
163+
(hooke/remove-hook #'http/request ::wrap-hystrix))

test/clj_http_hystrix/core_test.clj

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
[clj-http.client :as http]
55
[clojure.java.io :refer [resource]]
66
[clojure.tools.logging :refer [warn error]]
7+
[robert.hooke :as hooke]
78
[rest-cljer.core :refer [rest-driven]]
89
[midje.sweet :refer :all])
910
(:import [java.net SocketTimeoutException]
@@ -171,3 +172,41 @@
171172
(predicate {} {:status 101}) => false
172173
(predicate {} {:status 202}) => false
173174
(predicate {} {:status 299}) => false))
175+
176+
(defn get-hooks []
177+
(some-> http/request meta :robert.hooke/hooks deref))
178+
179+
(fact "add-hook can be safely called more than once"
180+
(count (get-hooks)) => 1
181+
(contains? (get-hooks) :clj-http-hystrix.core/wrap-hystrix) => true
182+
;call add-hook a few more times and ensure only one hook is present
183+
(add-hook), (add-hook)
184+
(count (get-hooks)) => 1
185+
(contains? (get-hooks) :clj-http-hystrix.core/wrap-hystrix) => true)
186+
187+
(fact "remove-hook removes clj-http-hystrix hook"
188+
(count (get-hooks)) => 1
189+
(contains? (get-hooks) :clj-http-hystrix.core/wrap-hystrix) => true
190+
(remove-hook)
191+
(get-hooks) => nil
192+
;can be called more than once
193+
(remove-hook), (remove-hook)
194+
(get-hooks) => nil
195+
;restore hook for additional testing
196+
(add-hook))
197+
198+
(fact "add-hook with user-defaults will override base configuration, but not call configuration"
199+
(rest-driven
200+
[{:method :GET
201+
:url "/"}
202+
{:status 500
203+
:times 3}]
204+
(make-hystrix-call {})
205+
=> (throws clojure.lang.ExceptionInfo "clj-http: status 500")
206+
;set custom default for fallback-fn
207+
(remove-hook)
208+
(add-hook {:hystrix/fallback-fn (constantly "bar")})
209+
(make-hystrix-call {}) => "bar"
210+
(make-hystrix-call {:hystrix/fallback-fn (constantly "baz")}) => "baz")
211+
(remove-hook)
212+
(add-hook))

0 commit comments

Comments
 (0)