|
13 | 13 | [com.netflix.hystrix.exception HystrixBadRequestException] |
14 | 14 | [org.slf4j MDC])) |
15 | 15 |
|
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 | | - |
28 | 16 | (defn default-fallback [req resp] |
29 | 17 | (if (:status resp) |
30 | 18 | resp |
31 | 19 | {:status 503})) |
32 | 20 |
|
| 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 | + |
33 | 27 | (defn ^:private handle-exception |
34 | 28 | [f req] |
35 | 29 | (let [^Exception raw-response (try (f) (catch Exception e e)) |
|
40 | 34 | ((http/wrap-exceptions (constantly resp)) req)) |
41 | 35 | resp)) |
42 | 36 |
|
| 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 | + |
43 | 55 | (defn ^:private group-key [s] |
44 | 56 | (HystrixCommandGroupKey$Factory/asKey (name s))) |
45 | 57 |
|
|
49 | 61 | (defn ^:private configurator |
50 | 62 | "Create a configurator that can configure the hystrix according to the |
51 | 63 | 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 |
59 | 73 | command-configurator (doto (HystrixCommandProperties/Setter) |
60 | 74 | (.withExecutionIsolationThreadTimeoutInMilliseconds timeout) |
61 | 75 | (.withCircuitBreakerRequestVolumeThreshold request-volume) |
|
64 | 78 | (.withMetricsRollingPercentileEnabled false)) |
65 | 79 | thread-pool-configurator (doto (HystrixThreadPoolProperties/Setter) |
66 | 80 | (.withCoreSize threads) |
67 | | - (.withMaxQueueSize (:hystrix/queue-size config 5)) |
68 | | - (.withQueueSizeRejectionThreshold (:hystrix/queue-size config 5)))] |
| 81 | + (.withMaxQueueSize queue-size) |
| 82 | + (.withQueueSizeRejectionThreshold queue-size))] |
69 | 83 | (doto (HystrixCommand$Setter/withGroupKey (group-key group)) |
70 | | - (.andCommandKey (command-key (:hystrix/command-key config :default))) |
| 84 | + (.andCommandKey (command-key command)) |
71 | 85 | (.andCommandPropertiesDefaults command-configurator) |
72 | 86 | (.andThreadPoolPropertiesDefaults thread-pool-configurator)))) |
73 | 87 |
|
|
85 | 99 | (fn [req resp] |
86 | 100 | (contains? status-codes (:status resp))))) |
87 | 101 |
|
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))))) |
126 | 144 |
|
127 | 145 | (defn add-hook |
128 | 146 | "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." |
130 | 162 | [] |
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)) |
0 commit comments