lhc 12.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (195) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +37 -0
  3. data/.rubocop.localch.yml +325 -0
  4. data/.rubocop.yml +61 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +13 -0
  7. data/Gemfile.activesupport4 +4 -0
  8. data/Gemfile.activesupport5 +4 -0
  9. data/Gemfile.activesupport6 +4 -0
  10. data/LICENSE +674 -0
  11. data/README.md +984 -0
  12. data/Rakefile +25 -0
  13. data/cider-ci.yml +6 -0
  14. data/cider-ci/bin/bundle +51 -0
  15. data/cider-ci/bin/ruby_install +8 -0
  16. data/cider-ci/bin/ruby_version +25 -0
  17. data/cider-ci/jobs/rspec-activesupport-4.yml +28 -0
  18. data/cider-ci/jobs/rspec-activesupport-5.yml +27 -0
  19. data/cider-ci/jobs/rspec-activesupport-6.yml +28 -0
  20. data/cider-ci/jobs/rubocop.yml +18 -0
  21. data/cider-ci/task_components/bundle.yml +22 -0
  22. data/cider-ci/task_components/rspec.yml +36 -0
  23. data/cider-ci/task_components/rubocop.yml +29 -0
  24. data/cider-ci/task_components/ruby.yml +15 -0
  25. data/friday.yml +3 -0
  26. data/lhc.gemspec +39 -0
  27. data/lib/core_ext/hash/deep_transform_values.rb +48 -0
  28. data/lib/lhc.rb +136 -0
  29. data/lib/lhc/concerns/lhc/basic_methods_concern.rb +42 -0
  30. data/lib/lhc/concerns/lhc/configuration_concern.rb +20 -0
  31. data/lib/lhc/concerns/lhc/fix_invalid_encoding_concern.rb +42 -0
  32. data/lib/lhc/concerns/lhc/formats_concern.rb +25 -0
  33. data/lib/lhc/concerns/lhc/request/user_agent_concern.rb +25 -0
  34. data/lib/lhc/config.rb +47 -0
  35. data/lib/lhc/endpoint.rb +119 -0
  36. data/lib/lhc/error.rb +80 -0
  37. data/lib/lhc/errors/client_error.rb +73 -0
  38. data/lib/lhc/errors/parser_error.rb +4 -0
  39. data/lib/lhc/errors/server_error.rb +28 -0
  40. data/lib/lhc/errors/timeout.rb +4 -0
  41. data/lib/lhc/errors/unknown_error.rb +4 -0
  42. data/lib/lhc/format.rb +18 -0
  43. data/lib/lhc/formats.rb +8 -0
  44. data/lib/lhc/formats/form.rb +45 -0
  45. data/lib/lhc/formats/json.rb +55 -0
  46. data/lib/lhc/formats/multipart.rb +45 -0
  47. data/lib/lhc/formats/plain.rb +42 -0
  48. data/lib/lhc/interceptor.rb +32 -0
  49. data/lib/lhc/interceptors.rb +26 -0
  50. data/lib/lhc/interceptors/auth.rb +98 -0
  51. data/lib/lhc/interceptors/caching.rb +127 -0
  52. data/lib/lhc/interceptors/default_timeout.rb +16 -0
  53. data/lib/lhc/interceptors/logging.rb +37 -0
  54. data/lib/lhc/interceptors/monitoring.rb +63 -0
  55. data/lib/lhc/interceptors/prometheus.rb +51 -0
  56. data/lib/lhc/interceptors/retry.rb +41 -0
  57. data/lib/lhc/interceptors/rollbar.rb +36 -0
  58. data/lib/lhc/interceptors/throttle.rb +81 -0
  59. data/lib/lhc/interceptors/zipkin.rb +110 -0
  60. data/lib/lhc/railtie.rb +10 -0
  61. data/lib/lhc/request.rb +157 -0
  62. data/lib/lhc/response.rb +60 -0
  63. data/lib/lhc/response/data.rb +28 -0
  64. data/lib/lhc/response/data/base.rb +22 -0
  65. data/lib/lhc/response/data/collection.rb +16 -0
  66. data/lib/lhc/response/data/item.rb +29 -0
  67. data/lib/lhc/rspec.rb +12 -0
  68. data/lib/lhc/test/cache_helper.rb +3 -0
  69. data/lib/lhc/version.rb +5 -0
  70. data/script/ci/build.sh +19 -0
  71. data/spec/basic_methods/delete_spec.rb +34 -0
  72. data/spec/basic_methods/get_spec.rb +49 -0
  73. data/spec/basic_methods/post_spec.rb +42 -0
  74. data/spec/basic_methods/put_spec.rb +48 -0
  75. data/spec/basic_methods/request_spec.rb +19 -0
  76. data/spec/basic_methods/request_without_rails_spec.rb +29 -0
  77. data/spec/config/endpoints_spec.rb +63 -0
  78. data/spec/config/placeholders_spec.rb +32 -0
  79. data/spec/core_ext/hash/deep_transform_values_spec.rb +24 -0
  80. data/spec/dummy/README.rdoc +28 -0
  81. data/spec/dummy/Rakefile +8 -0
  82. data/spec/dummy/app/assets/config/manifest.js +3 -0
  83. data/spec/dummy/app/assets/images/.keep +0 -0
  84. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  85. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  86. data/spec/dummy/app/controllers/application_controller.rb +7 -0
  87. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  88. data/spec/dummy/app/helpers/application_helper.rb +4 -0
  89. data/spec/dummy/app/mailers/.keep +0 -0
  90. data/spec/dummy/app/models/.keep +0 -0
  91. data/spec/dummy/app/models/concerns/.keep +0 -0
  92. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  93. data/spec/dummy/bin/bundle +5 -0
  94. data/spec/dummy/bin/rails +6 -0
  95. data/spec/dummy/bin/rake +6 -0
  96. data/spec/dummy/config.ru +6 -0
  97. data/spec/dummy/config/application.rb +16 -0
  98. data/spec/dummy/config/boot.rb +7 -0
  99. data/spec/dummy/config/environment.rb +7 -0
  100. data/spec/dummy/config/environments/development.rb +36 -0
  101. data/spec/dummy/config/environments/production.rb +77 -0
  102. data/spec/dummy/config/environments/test.rb +41 -0
  103. data/spec/dummy/config/initializers/assets.rb +10 -0
  104. data/spec/dummy/config/initializers/backtrace_silencers.rb +9 -0
  105. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  106. data/spec/dummy/config/initializers/filter_parameter_logging.rb +6 -0
  107. data/spec/dummy/config/initializers/inflections.rb +18 -0
  108. data/spec/dummy/config/initializers/mime_types.rb +6 -0
  109. data/spec/dummy/config/initializers/session_store.rb +5 -0
  110. data/spec/dummy/config/initializers/wrap_parameters.rb +11 -0
  111. data/spec/dummy/config/locales/en.yml +23 -0
  112. data/spec/dummy/config/routes.rb +58 -0
  113. data/spec/dummy/config/secrets.yml +22 -0
  114. data/spec/dummy/lib/assets/.keep +0 -0
  115. data/spec/dummy/log/.keep +0 -0
  116. data/spec/dummy/public/404.html +67 -0
  117. data/spec/dummy/public/422.html +67 -0
  118. data/spec/dummy/public/500.html +66 -0
  119. data/spec/dummy/public/favicon.ico +0 -0
  120. data/spec/endpoint/compile_spec.rb +35 -0
  121. data/spec/endpoint/match_spec.rb +41 -0
  122. data/spec/endpoint/placeholders_spec.rb +30 -0
  123. data/spec/endpoint/remove_interpolated_params_spec.rb +17 -0
  124. data/spec/endpoint/values_as_params_spec.rb +31 -0
  125. data/spec/error/dup_spec.rb +12 -0
  126. data/spec/error/find_spec.rb +57 -0
  127. data/spec/error/response_spec.rb +17 -0
  128. data/spec/error/timeout_spec.rb +14 -0
  129. data/spec/error/to_s_spec.rb +80 -0
  130. data/spec/formats/form_spec.rb +27 -0
  131. data/spec/formats/json_spec.rb +66 -0
  132. data/spec/formats/multipart_spec.rb +26 -0
  133. data/spec/formats/plain_spec.rb +29 -0
  134. data/spec/interceptors/after_request_spec.rb +20 -0
  135. data/spec/interceptors/after_response_spec.rb +39 -0
  136. data/spec/interceptors/auth/basic_auth_spec.rb +17 -0
  137. data/spec/interceptors/auth/bearer_spec.rb +19 -0
  138. data/spec/interceptors/auth/reauthentication_configuration_spec.rb +61 -0
  139. data/spec/interceptors/auth/reauthentication_spec.rb +44 -0
  140. data/spec/interceptors/before_request_spec.rb +21 -0
  141. data/spec/interceptors/before_response_spec.rb +20 -0
  142. data/spec/interceptors/caching/hydra_spec.rb +26 -0
  143. data/spec/interceptors/caching/main_spec.rb +73 -0
  144. data/spec/interceptors/caching/methods_spec.rb +42 -0
  145. data/spec/interceptors/caching/options_spec.rb +89 -0
  146. data/spec/interceptors/caching/parameters_spec.rb +24 -0
  147. data/spec/interceptors/caching/response_status_spec.rb +29 -0
  148. data/spec/interceptors/caching/to_cache_spec.rb +16 -0
  149. data/spec/interceptors/default_interceptors_spec.rb +15 -0
  150. data/spec/interceptors/default_timeout/main_spec.rb +34 -0
  151. data/spec/interceptors/define_spec.rb +29 -0
  152. data/spec/interceptors/dup_spec.rb +19 -0
  153. data/spec/interceptors/logging/main_spec.rb +37 -0
  154. data/spec/interceptors/monitoring/main_spec.rb +97 -0
  155. data/spec/interceptors/prometheus_spec.rb +54 -0
  156. data/spec/interceptors/response_competition_spec.rb +41 -0
  157. data/spec/interceptors/retry/main_spec.rb +73 -0
  158. data/spec/interceptors/return_response_spec.rb +38 -0
  159. data/spec/interceptors/rollbar/invalid_encoding_spec.rb +43 -0
  160. data/spec/interceptors/rollbar/main_spec.rb +57 -0
  161. data/spec/interceptors/throttle/main_spec.rb +106 -0
  162. data/spec/interceptors/throttle/reset_track_spec.rb +53 -0
  163. data/spec/interceptors/zipkin/distributed_tracing_spec.rb +135 -0
  164. data/spec/rails_helper.rb +6 -0
  165. data/spec/request/body_spec.rb +39 -0
  166. data/spec/request/encoding_spec.rb +37 -0
  167. data/spec/request/error_handling_spec.rb +88 -0
  168. data/spec/request/headers_spec.rb +12 -0
  169. data/spec/request/ignore_errors_spec.rb +73 -0
  170. data/spec/request/option_dup_spec.rb +13 -0
  171. data/spec/request/parallel_requests_spec.rb +59 -0
  172. data/spec/request/params_encoding_spec.rb +26 -0
  173. data/spec/request/request_without_rails_spec.rb +15 -0
  174. data/spec/request/url_patterns_spec.rb +54 -0
  175. data/spec/request/user_agent_spec.rb +26 -0
  176. data/spec/request/user_agent_without_rails_spec.rb +27 -0
  177. data/spec/response/body_spec.rb +16 -0
  178. data/spec/response/code_spec.rb +16 -0
  179. data/spec/response/data_accessor_spec.rb +29 -0
  180. data/spec/response/data_spec.rb +61 -0
  181. data/spec/response/effective_url_spec.rb +16 -0
  182. data/spec/response/headers_spec.rb +18 -0
  183. data/spec/response/options_spec.rb +18 -0
  184. data/spec/response/success_spec.rb +13 -0
  185. data/spec/response/time_spec.rb +21 -0
  186. data/spec/spec_helper.rb +8 -0
  187. data/spec/support/fixtures/json/feedback.json +11 -0
  188. data/spec/support/fixtures/json/feedbacks.json +164 -0
  189. data/spec/support/fixtures/json/localina_content_ad.json +23 -0
  190. data/spec/support/load_json.rb +5 -0
  191. data/spec/support/reset_config.rb +7 -0
  192. data/spec/support/zipkin_mock.rb +113 -0
  193. data/spec/timeouts/no_signal_spec.rb +13 -0
  194. data/spec/timeouts/timings_spec.rb +55 -0
  195. metadata +534 -0
@@ -0,0 +1,984 @@
1
+ LHC is an extended/advanced HTTP client. Implementing basic http-communication enhancements like interceptors, exception handling, format handling, accessing response data, configuring endpoints and placeholders and fully compatible, RFC-compliant URL-template support.
2
+
3
+ LHC uses [typhoeus](https://github.com/typhoeus/typhoeus) for low level http communication.
4
+
5
+ See [LHS](https://github.com/local-ch/LHS), if you are searching for something more **high level** that can query webservices easily and provides an ActiveRecord like interface.
6
+
7
+ ## Quick start guide
8
+
9
+ ```ruby
10
+ gem install lhc
11
+ ```
12
+
13
+ or add it to your Gemfile:
14
+
15
+ ```ruby
16
+ gem 'lhc'
17
+ ```
18
+
19
+ use it like:
20
+
21
+ ```ruby
22
+ response = LHC.get('http://datastore/v2/feedbacks')
23
+ response.data.items[0]
24
+ response.data.items[0].recommended
25
+ response.body
26
+ response.headers
27
+ ```
28
+
29
+ ## Table of contents
30
+ * [Quick start guide](#quick-start-guide)
31
+ * [Basic methods](#basic-methods)
32
+ * [Request](#request)
33
+ * [Formats](#formats)
34
+ * [Default format](#default-format)
35
+ * [Unformatted requests](#unformatted-requests)
36
+ * [Upload with LHC](#upload-with-lhc)
37
+ * [Parallel requests](#parallel-requests)
38
+ * [Follow redirects](#follow-redirects)
39
+ * [Transfer data through the request body](#transfer-data-through-the-request-body)
40
+ * [Request parameters](#request-parameters)
41
+ * [Array Parameter Encoding](#array-parameter-encoding)
42
+ * [Request URL encoding](#request-url-encoding)
43
+ * [Request URL-Templates](#request-url-templates)
44
+ * [Request timeout](#request-timeout)
45
+ * [Request Agent](#request-agent)
46
+ * [Response](#response)
47
+ * [Accessing response data](#accessing-response-data)
48
+ * [Exceptions](#exceptions)
49
+ * [Custom error handling (rescue)](#custom-error-handling-rescue)
50
+ * [Ignore certain errors](#ignore-certain-errors)
51
+ * [Configuration](#configuration)
52
+ * [Configuring endpoints](#configuring-endpoints)
53
+ * [Configuring placeholders](#configuring-placeholders)
54
+ * [Interceptors](#interceptors)
55
+ * [Quick start: Configure/Enable Interceptors](#quick-start-configureenable-interceptors)
56
+ * [Interceptors on local request level](#interceptors-on-local-request-level)
57
+ * [Core Interceptors](#core-interceptors)
58
+ * [Authentication Interceptor](#authentication-interceptor)
59
+ * [Bearer Authentication](#bearer-authentication)
60
+ * [Basic Authentication](#basic-authentication)
61
+ * [Reauthenticate](#reauthenticate)
62
+ * [Bearer Authentication with client access token](#bearer-authentication-with-client-access-token)
63
+ * [Caching Interceptor](#caching-interceptor)
64
+ * [Options](#options)
65
+ * [Default Timeout Interceptor](#default-timeout-interceptor)
66
+ * [Overwrite defaults](#overwrite-defaults)
67
+ * [Logging Interceptor](#logging-interceptor)
68
+ * [Installation](#installation)
69
+ * [What and how it logs](#what-and-how-it-logs)
70
+ * [Configure](#configure)
71
+ * [Monitoring Interceptor](#monitoring-interceptor)
72
+ * [Installation](#installation-1)
73
+ * [Environment](#environment)
74
+ * [What it tracks](#what-it-tracks)
75
+ * [Configure](#configure-1)
76
+ * [Prometheus Interceptor](#prometheus-interceptor)
77
+ * [Retry Interceptor](#retry-interceptor)
78
+ * [Limit the amount of retries while making the request](#limit-the-amount-of-retries-while-making-the-request)
79
+ * [Change the default maximum of retries of the retry interceptor](#change-the-default-maximum-of-retries-of-the-retry-interceptor)
80
+ * [Retry all requests](#retry-all-requests)
81
+ * [Do not retry certain response codes](#do-not-retry-certain-response-codes)
82
+ * [Rollbar Interceptor](#rollbar-interceptor)
83
+ * [Forward additional parameters](#forward-additional-parameters)
84
+ * [Throttle](#throttle)
85
+ * [Zipkin](#zipkin)
86
+ * [Create an interceptor from scratch](#create-an-interceptor-from-scratch)
87
+ * [Interceptor callbacks](#interceptor-callbacks)
88
+ * [Interceptor request/response](#interceptor-requestresponse)
89
+ * [Provide a response replacement through an interceptor](#provide-a-response-replacement-through-an-interceptor)
90
+ * [Testing](#testing)
91
+ * [License](#license)
92
+
93
+
94
+
95
+ ## Basic methods
96
+
97
+ Available are `get`, `post`, `put` & `delete`.
98
+
99
+ Other methods are available using `LHC.request(options)`.
100
+
101
+ ## Request
102
+
103
+ The request class handles the http request, implements the interceptor pattern, loads configured endpoints, generates urls from url-templates and raises [exceptions](#exceptions) for any response code that is not indicating success (2xx).
104
+
105
+ ```ruby
106
+ response = LHC.request(url: 'http://local.ch', method: :options)
107
+
108
+ response.request.response #<LHC::Response> the associated response.
109
+
110
+ response.request.options #<Hash> the options used for creating the request.
111
+
112
+ response.request.params # access request params
113
+
114
+ response.request.headers # access request headers
115
+
116
+ response.request.url #<String> URL that is used for doing the request
117
+
118
+ response.request.method #<Symbol> provides the used http-method
119
+ ```
120
+
121
+ ### Formats
122
+
123
+ You can use any of the basic methods in combination with a format like `json`:
124
+
125
+ ```ruby
126
+ LHC.json.get(options)
127
+ ```
128
+
129
+ Currently supported formats: `json`, `multipart`, `plain` (for no formatting)
130
+
131
+ If formats are used, headers for `Content-Type` and `Accept` are enforced by LHC, but also http bodies are translated by LHC, so you can pass bodies as ruby objects:
132
+
133
+ ```ruby
134
+ LHC.json.post('http://slack', body: { text: 'Hi there' })
135
+ # Content-Type: application/json
136
+ # Accept: application/json
137
+ # Translates body to "{\"text\":\"Hi there\"}" before sending
138
+ ```
139
+
140
+ #### Default format
141
+
142
+ If you use LHC's basic methods `LHC.get`, `LHC.post` etc. without any explicit format, `JSON` will be chosen as the default format.
143
+
144
+ #### Unformatted requests
145
+
146
+ In case you need to send requests without LHC formatting headers or the body, use `plain`:
147
+
148
+ ```ruby
149
+ LHC.plain.post('http://endpoint', body: { weird: 'format%s2xX' })
150
+ ```
151
+
152
+ ##### Upload with LHC
153
+
154
+ If you want to upload data with LHC, it's recommended to use the `multipart` format:
155
+
156
+ ```ruby
157
+ response = LHC.multipart.post('http://upload', body: { file })
158
+ response.headers['Location']
159
+ # Content-Type: multipart/form-data
160
+ # Leaves body unformatted
161
+ ```
162
+
163
+ ### Parallel requests
164
+
165
+ If you pass an array of requests to `LHC.request`, it will perform those requests in parallel.
166
+ You will get back an array of LHC::Response objects in the same order of the passed requests.
167
+
168
+ ```ruby
169
+ options = []
170
+ options << { url: 'http://datastore/v2/feedbacks' }
171
+ options << { url: 'http://datastore/v2/content-ads/123/feedbacks' }
172
+ responses = LHC.request(options)
173
+ ```
174
+
175
+ ```ruby
176
+ LHC.request([request1, request2, request3])
177
+ # returns [response1, response2, response3]
178
+ ```
179
+
180
+ ### Follow redirects
181
+
182
+ ```ruby
183
+ LHC.get('http://local.ch', followlocation: true)
184
+ ```
185
+
186
+ ### Transfer data through the request body
187
+
188
+ Data that is transfered using the HTTP request body is transfered using the selected format, or the default `json`, so you need to provide it as a ruby object.
189
+
190
+ Also consider setting the http header for content-type or use one of the provided [formats](#formats), like `LHC.json`.
191
+
192
+ ```ruby
193
+ LHC.post('http://datastore/v2/feedbacks',
194
+ body: feedback,
195
+ headers: { 'Content-Type' => 'application/json' }
196
+ )
197
+ ```
198
+
199
+ ### Request parameters
200
+
201
+ When using LHC, try to pass params via `params` option. It's not recommended to build a url and attach the parameters yourself:
202
+
203
+ DO
204
+ ```ruby
205
+ LHC.get('http://local.ch', params: { q: 'Restaurant' })
206
+ ```
207
+
208
+ DON'T
209
+ ```ruby
210
+ LHC.get('http://local.ch?q=Restaurant')
211
+ ```
212
+
213
+ #### Array Parameter Encoding
214
+
215
+ LHC can encode array parameters in URLs in two ways. The default is `:rack` which generates URL parameters compatible with Rack and Rails.
216
+
217
+ ```ruby
218
+ LHC.get('http://local.ch', params: { q: [1, 2] })
219
+ # http://local.ch?q[]=1&q[]=2
220
+ ```
221
+
222
+ Some Java-based apps expect their arrays in the `:multi` format:
223
+
224
+ ```ruby
225
+ LHC.get('http://local.ch', params: { q: [1, 2] }, params_encoding: :multi)
226
+ # http://local.ch?q=1&q=2
227
+ ```
228
+
229
+ ### Request URL encoding
230
+
231
+ LHC, by default, encodes urls:
232
+
233
+ ```ruby
234
+ LHC.get('http://local.ch?q=some space')
235
+ # http://local.ch?q=some%20space
236
+
237
+ LHC.get('http://local.ch', params: { q: 'some space' })
238
+ # http://local.ch?q=some%20space
239
+ ```
240
+
241
+ which can be disabled:
242
+
243
+ ```ruby
244
+ LHC.get('http://local.ch?q=some space', url_encoding: false)
245
+ # http://local.ch?q=some space
246
+ ```
247
+
248
+ ### Request URL-Templates
249
+
250
+ Instead of using concrete urls you can also use url-templates that contain placeholders.
251
+ This is especially handy for configuring an endpoint once and generate the url from the params when doing the request.
252
+ Since version `7.0` url templates follow the [RFC 6750](https://tools.ietf.org/html/rfc6570).
253
+
254
+ ```ruby
255
+ LHC.get('http://datastore/v2/feedbacks/{id}', params:{ id: 123 })
256
+ # GET http://datastore/v2/feedbacks/123
257
+ ```
258
+
259
+ You can also use URL templates, when [configuring endpoints](#configuring-endpoints):
260
+ ```ruby
261
+ LHC.configure do |c|
262
+ c.endpoint(:find_feedback, 'http://datastore/v2/feedbacks/{id}')
263
+ end
264
+
265
+ LHC.get(:find_feedback, params:{ id: 123 }) # GET http://datastore/v2/feedbacks/123
266
+ ```
267
+
268
+ If you miss to provide a parameter that is part of the url-template, it will raise an exception.
269
+
270
+ ### Request timeout
271
+
272
+ Working and configuring timeouts is important, to ensure your app stays alive when services you depend on start to get really slow...
273
+
274
+ LHC forwards two timeout options directly to typhoeus:
275
+
276
+ `timeout` (in seconds) - The maximum time in seconds that you allow the libcurl transfer operation to take. Normally, name lookups can take a considerable time and limiting operations to less than a few seconds risk aborting perfectly normal operations. This option may cause libcurl to use the SIGALRM signal to timeout system calls.
277
+ `connecttimeout` (in seconds) - It should contain the maximum time in seconds that you allow the connection phase to the server to take. This only limits the connection phase, it has no impact once it has connected. Set to zero to switch to the default built-in connection timeout - 300 seconds.
278
+
279
+ ```ruby
280
+ LHC.get('http://local.ch', timeout: 5, connecttimeout: 1)
281
+ ```
282
+
283
+ LHC provides a [timeout interceptor](#default-timeout-interceptor) that lets you apply default timeout values to all the requests that you are performig in your application.
284
+
285
+ ### Request Agent
286
+
287
+ LHC identifies itself towards outher services, using the `User-Agent` header.
288
+
289
+ ```
290
+ User-Agent LHC (9.4.2) [https://github.com/local-ch/lhc]
291
+ ```
292
+
293
+ If LHC is used in an Rails Application context, also the application name is added to the `User-Agent` header.
294
+
295
+ ```
296
+ User-Agent LHC (9.4.2; MyRailsApplicationName) [https://github.com/local-ch/lhc]
297
+ ```
298
+
299
+ ## Response
300
+
301
+ ```ruby
302
+ response.request #<LHC::Request> the associated request.
303
+
304
+ response.data #<OpenStruct> in case response body contains parsable JSON.
305
+ response.data.something.nested
306
+
307
+ response.body #<String>
308
+
309
+ response.code #<Fixnum>
310
+
311
+ response.headers #<Hash>
312
+
313
+ response.time #<Fixnum> Provides response time in ms.
314
+
315
+ response.timeout? #true|false
316
+ ```
317
+
318
+ ### Accessing response data
319
+
320
+ The response data can be access with dot-notation and square-bracket notation. You can convert response data to open structs or json (if the response format is json).
321
+
322
+ ```ruby
323
+ response = LHC.request(url: 'http://datastore/entry/1')
324
+ response.data.as_open_struct #<OpenStruct name='local.ch'>
325
+ response.data.as_json # { name: 'local.ch' }
326
+ response.data.name # 'local.ch'
327
+ response.data[:name] # 'local.ch'
328
+ ```
329
+
330
+ You can also access response data directly through the response object (with square bracket notation only):
331
+
332
+ ```ruby
333
+ LHC.json.get(url: 'http://datastore/entry/1')[:name]
334
+ ```
335
+
336
+ ## Exceptions
337
+
338
+ Anything but a response code indicating success (2xx) raises an exception.
339
+
340
+ ```ruby
341
+
342
+ LHC.get('localhost') # UnknownError: 0
343
+ LHC.get('http://localhost:3000') # LHC::Timeout: 0
344
+
345
+ ```
346
+
347
+ You can access the response object that was causing the error.
348
+
349
+ ```ruby
350
+ LHC.get('local.ch')
351
+ rescue => e
352
+ e.response #<LHC:Response>
353
+ e.response.code # 403
354
+ e.response.timeout? # false
355
+ Rails.logger.error e
356
+ # LHC::UnknownError: get http://local.cac
357
+ # Params: {:url=>"http://local.cac", :method=>:get}
358
+ # Response Code: 0
359
+ # <Response Body>
360
+ ```
361
+
362
+ All errors that are raise by LHC inherit from `LHC::Error`.
363
+ They are divided into `LHC::ClientError`, `LHC::ServerError`, `LHC::Timeout` and `LHC::UnkownError` and mapped according to the following status code.
364
+
365
+ ```ruby
366
+ 400 => LHC::BadRequest
367
+ 401 => LHC::Unauthorized
368
+ 402 => LHC::PaymentRequired
369
+ 403 => LHC::Forbidden
370
+ 403 => LHC::Forbidden
371
+ 404 => LHC::NotFound
372
+ 405 => LHC::MethodNotAllowed
373
+ 406 => LHC::NotAcceptable
374
+ 407 => LHC::ProxyAuthenticationRequired
375
+ 408 => LHC::RequestTimeout
376
+ 409 => LHC::Conflict
377
+ 410 => LHC::Gone
378
+ 411 => LHC::LengthRequired
379
+ 412 => LHC::PreconditionFailed
380
+ 413 => LHC::RequestEntityTooLarge
381
+ 414 => LHC::RequestUriToLong
382
+ 415 => LHC::UnsupportedMediaType
383
+ 416 => LHC::RequestedRangeNotSatisfiable
384
+ 417 => LHC::ExpectationFailed
385
+ 422 => LHC::UnprocessableEntity
386
+ 423 => LHC::Locked
387
+ 424 => LHC::FailedDependency
388
+ 426 => LHC::UpgradeRequired
389
+
390
+ 500 => LHC::InternalServerError
391
+ 501 => LHC::NotImplemented
392
+ 502 => LHC::BadGateway
393
+ 503 => LHC::ServiceUnavailable
394
+ 504 => LHC::GatewayTimeout
395
+ 505 => LHC::HttpVersionNotSupported
396
+ 507 => LHC::InsufficientStorage
397
+ 510 => LHC::NotExtended
398
+
399
+ timeout? => LHC::Timeout
400
+
401
+ anything_else => LHC::UnknownError
402
+ ```
403
+
404
+ ### Custom error handling (rescue)
405
+
406
+ You can provide custom error handlers to handle errors happening during the request.
407
+
408
+ If a error handler is provided nothing is raised.
409
+
410
+ If your error handler returns anything else but `nil` it replaces the response body.
411
+
412
+ ```ruby
413
+ handler = ->(response){ do_something_with_response; return {name: 'unknown'} }
414
+ response = LHC.get('http://something', rescue: handler)
415
+ response.data.name # 'unknown'
416
+ ```
417
+
418
+ ### Ignore certain errors
419
+
420
+ As it's discouraged to rescue errors and then don't handle them (ruby styleguide)[https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions],
421
+ but you often want to continue working with `nil`, LHC provides the `ignore` option.
422
+
423
+ Errors listed in this option will not be raised and will leave the `response.body` and `response.data` to stay `nil`.
424
+
425
+ You can either pass the LHC error class you want to be ignored or an array of LHC error classes.
426
+
427
+ ```ruby
428
+ response = LHC.get('http://something', ignore: LHC::NotFound)
429
+
430
+ response.body # nil
431
+ response.data # nil
432
+ response.error_ignored? # true
433
+ response.request.error_ignored? # true
434
+ ```
435
+
436
+ ## Configuration
437
+
438
+ If you want to configure LHC, do it on initialization (like in a Rails initializer, `environment.rb` or `application.rb`), otherwise you could run into the problem that certain configurations can only be set once.
439
+
440
+ You can use `LHC.configure` to prevent the initialization problem.
441
+ Take care that you only use `LHC.configure` once, because it is actually reseting previously made configurations and applies the new once.
442
+
443
+ ```ruby
444
+
445
+ LHC.configure do |c|
446
+ c.placeholder :datastore, 'http://datastore'
447
+ c.endpoint :feedbacks, '{+datastore}/feedbacks', params: { has_reviews: true }
448
+ c.interceptors = [CachingInterceptor, MonitorInterceptor, TrackingIdInterceptor]
449
+ end
450
+
451
+ ```
452
+
453
+ ### Configuring endpoints
454
+
455
+ You can configure endpoints, for later use, by giving them a name, a url and some parameters (optional).
456
+
457
+ ```ruby
458
+ LHC.configure do |c|
459
+ c.endpoint(:feedbacks, 'http://datastore/v2/feedbacks', params: { has_reviews: true })
460
+ c.endpoint(:find_feedback, 'http://datastore/v2/feedbacks/{id}')
461
+ end
462
+
463
+ LHC.get(:feedbacks) # GET http://datastore/v2/feedbacks
464
+ LHC.get(:find_feedback, params:{ id: 123 }) # GET http://datastore/v2/feedbacks/123
465
+ ```
466
+
467
+ Explicit request options override configured options.
468
+
469
+ ```ruby
470
+ LHC.get(:feedbacks, params: { has_reviews: false }) # Overrides configured params
471
+ ```
472
+
473
+ ### Configuring placeholders
474
+
475
+ You can configure global placeholders, that are used when generating urls from url-templates.
476
+
477
+ ```ruby
478
+ LHC.configure do |c|
479
+ c.placeholder(:datastore, 'http://datastore')
480
+ c.endpoint(:feedbacks, '{+datastore}/feedbacks', { params: { has_reviews: true } })
481
+ end
482
+
483
+ LHC.get(:feedbacks) # http://datastore/v2/feedbacks
484
+ ```
485
+
486
+ ## Interceptors
487
+
488
+ To monitor and manipulate the HTTP communication done with LHC, you can define interceptors that follow the (Inteceptor Pattern)[https://en.wikipedia.org/wiki/Interceptor_pattern].
489
+ There are some interceptors that are part of LHC already, so called [Core Interceptors](#core-interceptors), that cover some basic usecases.
490
+
491
+ ### Quick start: Configure/Enable Interceptors
492
+
493
+ ```ruby
494
+ LHC.configure do |c|
495
+ c.interceptors = [LHC::Auth, LHC::Caching, LHC::DefaultTimeout, LHC::Logging, LHC::Monitoring, LHC::Prometheus, LHC::Retry, LHC::Rollbar, LHC::Zipkin]
496
+ end
497
+ ```
498
+
499
+ You can only set the list of global interceptors once and you can not alter it after you set it.
500
+
501
+ ### Interceptors on local request level
502
+
503
+ You can override the global list of interceptors on local request level:
504
+
505
+ ```ruby
506
+ interceptors = LHC.config.interceptors
507
+ interceptors -= [LHC::Caching] # remove caching
508
+ interceptors += [LHC::Retry] # add retry
509
+ LHC.request({url: 'http://local.ch', retry: 2, interceptors: interceptors})
510
+
511
+ LHC.request({url: 'http://local.ch', interceptors: []}) # no interceptor for this request at all
512
+ ```
513
+
514
+ ### Core Interceptors
515
+
516
+ #### Authentication Interceptor
517
+
518
+ Add the auth interceptor to your basic set of LHC interceptors.
519
+
520
+ ```ruby
521
+ LHC.configure do |c|
522
+ c.interceptors = [LHC::Auth]
523
+ end
524
+ ```
525
+
526
+ ##### Bearer Authentication
527
+
528
+ ```ruby
529
+ LHC.get('http://local.ch', auth: { bearer: -> { access_token } })
530
+ ```
531
+
532
+ Adds the following header to the request:
533
+ ```
534
+ 'Authorization': 'Bearer 123456'
535
+ ```
536
+
537
+ Assuming the method `access_token` responds on runtime of the request with `123456`.
538
+
539
+ ##### Basic Authentication
540
+
541
+ ```ruby
542
+ LHC.get('http://local.ch', auth: { basic: { username: 'steve', password: 'can' } })
543
+ ```
544
+
545
+ Adds the following header to the request:
546
+ ```
547
+ 'Authorization': 'Basic c3RldmU6Y2Fu'
548
+ ```
549
+
550
+ Which is the base64 encoded credentials "username:password".
551
+
552
+ ##### Reauthenticate
553
+
554
+ The current implementation can only offer reauthenticate for _client access tokens_. For this to work the following has to be given:
555
+
556
+ * You have configured and implemented `LHC::Auth.refresh_client_token = -> { TokenRefreshUtil.client_access_token(true) }` which when called will force a refresh of the token and return the new value. It is also expected that this implementation will handle invalidating caches if necessary.
557
+ * Your interceptors contain `LHC::Auth` and `LHC::Retry`, whereas `LHC::Retry` comes _after_ `LHC::Auth` in the chain.
558
+
559
+ ##### Bearer Authentication with client access token
560
+
561
+ Reauthentication will be initiated if:
562
+
563
+ * setup is correct
564
+ * `response.success?` is false and an `LHC::Unauthorized` was observed
565
+ * reauthentication wasn't already attempted once
566
+
567
+ If this is the case, this happens:
568
+
569
+ * refresh the client token, by calling `refresh_client_token`
570
+ * the authentication header will be updated with the new token
571
+ * `LHC::Retry` will be triggered by adding `retry: { max: 1 }` to the request options
572
+
573
+ #### Caching Interceptor
574
+
575
+ Add the cache interceptor to your basic set of LHC interceptors.
576
+
577
+ ```ruby
578
+ LHC.configure do |c|
579
+ c.interceptors = [LHC::Caching]
580
+ end
581
+ ```
582
+
583
+ You can configure your own cache (default Rails.cache) and logger (default Rails.logger):
584
+
585
+ ```ruby
586
+ LHC::Caching.cache = ActiveSupport::Cache::MemoryStore.new
587
+ LHC::Caching.logger = Logger.new(STDOUT)
588
+ ```
589
+
590
+ Caching is not enabled by default, although you added it to your basic set of interceptors.
591
+ If you want to have requests served/stored and stored in/from cache, you have to enable it by request.
592
+
593
+ ```ruby
594
+ LHC.get('http://local.ch', cache: true)
595
+ ```
596
+
597
+ You can also enable caching when configuring an endpoint in LHS.
598
+
599
+ ```ruby
600
+ class Feedbacks < LHS::Service
601
+ endpoint '{+datastore}/v2/feedbacks', cache: true
602
+ end
603
+ ```
604
+
605
+ Only GET requests are cached by default. If you want to cache any other request method, just configure it:
606
+
607
+ ```ruby
608
+ LHC.get('http://local.ch', cache: { methods: [:get] })
609
+ ```
610
+
611
+ Responses served from cache are marked as served from cache:
612
+
613
+ ```ruby
614
+ response = LHC.get('http://local.ch', cache: true)
615
+ response.from_cache? # true
616
+ ```
617
+
618
+ ##### Options
619
+
620
+ ```ruby
621
+ LHC.get('http://local.ch', cache: { key: 'key' expires_in: 1.day, race_condition_ttl: 15.seconds, use: ActiveSupport::Cache::MemoryStore.new })
622
+ ```
623
+
624
+ `expires_in` - lets the cache expires every X seconds.
625
+
626
+ `key` - Set the key that is used for caching by using the option. Every key is prefixed with `LHC_CACHE(v1): `.
627
+
628
+ `race_condition_ttl` - very useful in situations where a cache entry is used very frequently and is under heavy load.
629
+ If a cache expires and due to heavy load several different processes will try to read data natively and then they all will try to write to cache.
630
+ To avoid that case the first process to find an expired cache entry will bump the cache expiration time by the value set in `cache_race_condition_ttl`.
631
+
632
+ `use` - Set an explicit cache to be used for this request. If this option is missing `LHC::Caching.cache` is used.
633
+
634
+ #### Default Timeout Interceptor
635
+
636
+ Applies default timeout values to all requests made in an application, that uses LHC.
637
+
638
+ ```ruby
639
+ LHC.configure do |c|
640
+ c.interceptors = [LHC::DefaultTimeout]
641
+ end
642
+ ```
643
+
644
+ `timeout` default: 15 seconds
645
+ `connecttimeout` default: 2 seconds
646
+
647
+ ##### Overwrite defaults
648
+
649
+ ```ruby
650
+ LHC::DefaultTimeout.timeout = 5 # seconds
651
+ LHC::DefaultTimeout.connecttimeout = 3 # seconds
652
+ ```
653
+
654
+ #### Logging Interceptor
655
+
656
+ The logging interceptor logs all requests done with LHC to the Rails logs.
657
+
658
+ ##### Installation
659
+
660
+ ```ruby
661
+ LHC.configure do |c|
662
+ c.interceptors = [LHC::Logging]
663
+ end
664
+
665
+ LHC::Logging.logger = Rails.logger
666
+ ```
667
+
668
+ ##### What and how it logs
669
+
670
+ The logging Interceptor logs basic information about the request and the response:
671
+
672
+ ```ruby
673
+ LHC.get('http://local.ch')
674
+ # Before LHC request<70128730317500> GET http://local.ch at 2018-05-23T07:53:19+02:00 Params={} Headers={\"User-Agent\"=>\"Typhoeus - https://github.com/typhoeus/typhoeus\", \"Expect\"=>\"\"}
675
+ # After LHC response for request<70128730317500>: GET http://local.ch at 2018-05-23T07:53:28+02:00 Time=0ms URL=http://local.ch:80/
676
+ ```
677
+
678
+ ##### Configure
679
+
680
+ You can configure the logger beeing used by the logging interceptor:
681
+
682
+ ```ruby
683
+ LHC::Logging.logger = Another::Logger
684
+ ```
685
+
686
+ #### Monitoring Interceptor
687
+
688
+ The monitoring interceptor reports all requests done with LHC to a given StatsD instance.
689
+
690
+ ##### Installation
691
+
692
+ ```ruby
693
+ LHC.configure do |c|
694
+ c.interceptors = [LHC::Monitoring]
695
+ end
696
+ ```
697
+
698
+ You also have to configure statsd in order to have the monitoring interceptor report.
699
+
700
+ ```ruby
701
+ LHC::Monitoring.statsd = <your-instance-of-statsd>
702
+ ```
703
+
704
+ ##### Environment
705
+
706
+ By default, the monitoring interceptor uses Rails.env to determine the environment. In case you want to configure that, use:
707
+
708
+ ```ruby
709
+ LHC::Monitoring.env = ENV['DEPLOYMENT_TYPE'] || Rails.env
710
+ ```
711
+
712
+ ##### What it tracks
713
+
714
+ It tracks request attempts with `before_request` and `after_request` (counts).
715
+
716
+ In case your workers/processes are getting killed due limited time constraints,
717
+ you are able to detect deltas with relying on "before_request", and "after_request" counts:
718
+
719
+ ```ruby
720
+ "lhc.<app_name>.<env>.<host>.<http_method>.before_request", 1
721
+ "lhc.<app_name>.<env>.<host>.<http_method>.after_request", 1
722
+ ```
723
+
724
+ In case of a successful response it reports the response code with a count and the response time with a gauge value.
725
+
726
+ ```ruby
727
+ LHC.get('http://local.ch')
728
+
729
+ "lhc.<app_name>.<env>.<host>.<http_method>.count", 1
730
+ "lhc.<app_name>.<env>.<host>.<http_method>.200", 1
731
+ "lhc.<app_name>.<env>.<host>.<http_method>.time", 43
732
+ ```
733
+
734
+ Timeouts are also reported:
735
+
736
+ ```ruby
737
+ "lhc.<app_name>.<env>.<host>.<http_method>.timeout", 1
738
+ ```
739
+
740
+ All the dots in the host are getting replaced with underscore, because dot is the default separator in graphite.
741
+
742
+ ##### Configure
743
+
744
+ It is possible to set the key for Monitoring Interceptor on per request basis:
745
+
746
+ ```ruby
747
+ LHC.get('http://local.ch', monitoring_key: 'local_website')
748
+
749
+ "local_website.count", 1
750
+ "local_website.200", 1
751
+ "local_website.time", 43
752
+ ```
753
+
754
+ If you use this approach you need to add all namespaces (app, environment etc.) to the key on your own.
755
+
756
+
757
+ #### Prometheus Interceptor
758
+
759
+ Logs basic request/response information to prometheus.
760
+
761
+ ```ruby
762
+ require 'prometheus/client'
763
+
764
+ LHC.configure do |c|
765
+ c.interceptors = [LHC::Prometheus]
766
+ end
767
+
768
+ LHC::Prometheus.client = Prometheus::Client
769
+ LHC::Prometheus.namespace = 'web_location_app'
770
+ ```
771
+
772
+ ```ruby
773
+ LHC.get('http://local.ch')
774
+ ```
775
+
776
+ - Creates a prometheus counter that receives additional meta information for: `:code`, `:success` and `:timeout`.
777
+
778
+ - Creates a prometheus histogram for response times in milliseconds.
779
+
780
+
781
+ #### Retry Interceptor
782
+
783
+ If you enable the retry interceptor, you can have LHC retry requests for you:
784
+
785
+ ```ruby
786
+ LHC.configure do |c|
787
+ c.interceptors = [LHC::Retry]
788
+ end
789
+
790
+ response = LHC.get('http://local.ch', retry: true)
791
+ ```
792
+
793
+ It will try to retry the request up to 3 times (default) internally, before it passes the last response back, or raises an error for the last response.
794
+
795
+ Consider, that all other interceptors will run for every single retry.
796
+
797
+ ##### Limit the amount of retries while making the request
798
+
799
+ ```ruby
800
+ LHC.get('http://local.ch', retry: { max: 1 })
801
+ ```
802
+
803
+ ##### Change the default maximum of retries of the retry interceptor
804
+
805
+ ```ruby
806
+ LHC::Retry.max = 3
807
+ ```
808
+
809
+ ##### Retry all requests
810
+
811
+ If you want to retry all requests made from your application, you just need to configure it globally:
812
+
813
+ ```ruby
814
+ LHC::Retry.all = true
815
+ configuration.interceptors = [LHC::Retry]
816
+ ```
817
+
818
+ ##### Do not retry certain response codes
819
+
820
+ If you do not want to retry based on certain response codes, use retry in combination with explicit `ignore`:
821
+
822
+ ```ruby
823
+ LHC.get('http://local.ch', ignore: LHC::NotFound, retry: { max: 1 })
824
+ ```
825
+
826
+ Or if you use `LHC::Retry.all`:
827
+
828
+ ```ruby
829
+ LHC.get('http://local.ch', ignore: LHC::NotFound)
830
+ ```
831
+
832
+ #### Rollbar Interceptor
833
+
834
+ Forward errors to rollbar when exceptions occur during http requests.
835
+
836
+ ```ruby
837
+ LHC.configure do |c|
838
+ c.interceptors = [LHC::Rollbar]
839
+ end
840
+ ```
841
+
842
+ ```ruby
843
+ LHC.get('http://local.ch')
844
+ ```
845
+
846
+ If it raises, it forwards the request and response object to rollbar, which contain all necessary data.
847
+
848
+ ##### Forward additional parameters
849
+
850
+ ```ruby
851
+ LHC.get('http://local.ch', rollbar: { tracking_key: 'this particular request' })
852
+ ```
853
+
854
+ #### Throttle
855
+
856
+ The throttle interceptor allows you to raise an exception if a predefined quota of a provider request limit is reached in advance.
857
+
858
+ ```ruby
859
+ LHC.configure do |c|
860
+ c.interceptors = [LHC::Throttle]
861
+ end
862
+ ```
863
+ ```ruby
864
+ options = {
865
+ throttle: {
866
+ track: true, # enables tracking of current limit/remaining requests of rate-limiting
867
+ break: '80%', # quota in percent after which errors are raised. Percentage symbol is optional, values will be converted to integer (e.g. '23.5' will become 23)
868
+ provider: 'local.ch', # name of the provider under which throttling tracking is aggregated,
869
+ limit: { header: 'Rate-Limit-Limit' }, # either a hard-coded integer, or a hash pointing at the response header containing the limit value
870
+ remaining: { header: 'Rate-Limit-Remaining' }, # a hash pointing at the response header containing the current amount of remaining requests
871
+ expires: { header: 'Rate-Limit-Reset' } # a hash pointing at the response header containing the timestamp when the quota will reset
872
+ }
873
+ }
874
+
875
+ LHC.get('http://local.ch', options)
876
+ # { headers: { 'Rate-Limit-Limit' => 100, 'Rate-Limit-Remaining' => 19 } }
877
+
878
+ LHC.get('http://local.ch', options)
879
+ # raises LHC::Throttle::OutOfQuota: Reached predefined quota for local.ch
880
+ ```
881
+
882
+ #### Zipkin
883
+
884
+ ** Zipkin 0.33 breaks our current implementation of the Zipkin interceptor **
885
+
886
+ Zipkin is a distributed tracing system. It helps gather timing data needed to troubleshoot latency problems in microservice architectures [Zipkin Distributed Tracing](https://zipkin.io/).
887
+
888
+ Add the zipkin interceptor to your basic set of LHC interceptors.
889
+
890
+ ```ruby
891
+ LHC.configure do |c|
892
+ c.interceptors = [LHC::Zipkin]
893
+ end
894
+ ```
895
+
896
+ The following configuration needs to happen in the application that wants to run this interceptor:
897
+
898
+ 1. Add `gem 'zipkin-tracer', '< 0.33.0'` to your Gemfile.
899
+ 2. Add the necessary Rack middleware and configuration
900
+
901
+ ```ruby
902
+ config.middleware.use ZipkinTracer::RackHandler, {
903
+ service_name: 'service-name', # name your service will be known as in zipkin
904
+ service_port: 80, # the port information that is sent along the trace
905
+ json_api_host: 'http://zipkin-collector', # the zipkin endpoint
906
+ sample_rate: 1 # sample rate, where 1 = 100% of all requests, and 0.1 is 10% of all requests
907
+ }
908
+ ```
909
+
910
+ ### Create an interceptor from scratch
911
+
912
+ ```ruby
913
+ class TrackingIdInterceptor < LHC::Interceptor
914
+
915
+ def before_request
916
+ request.params[:tid] = 123
917
+ end
918
+ end
919
+ ```
920
+
921
+ ```ruby
922
+ LHC.configure do |c|
923
+ c.interceptors = [TrackingIdInterceptor]
924
+ end
925
+ ```
926
+
927
+ #### Interceptor callbacks
928
+
929
+ `before_raw_request` is called before the raw typhoeus request is prepared/created.
930
+
931
+ `before_request` is called when the request is prepared and about to be executed.
932
+
933
+ `after_request` is called after request was started.
934
+
935
+ `before_response` is called when response started to arrive.
936
+
937
+ `after_response` is called after the response arrived completely.
938
+
939
+
940
+ #### Interceptor request/response
941
+
942
+ Every interceptor can directly access their instance [request](#request) or [response](#response).
943
+
944
+ #### Provide a response replacement through an interceptor
945
+
946
+ Inside an interceptor, you are able to provide a response, rather then doing a real request.
947
+ This is useful for implementing e.g. caching.
948
+
949
+ ```ruby
950
+ class LHC::Cache < LHC::Interceptor
951
+
952
+ def before_request(request)
953
+ cached_response = Rails.cache.fetch(request.url)
954
+ return LHC::Response.new(cached_response) if cached_response
955
+ end
956
+ end
957
+ ```
958
+
959
+ Take care that having more than one interceptor trying to return a response will cause an exception.
960
+ You can access the request.response to identify if a response was already provided by another interceptor.
961
+
962
+ ```ruby
963
+ class RemoteCacheInterceptor < LHC::Interceptor
964
+
965
+ def before_request(request)
966
+ return unless request.response.nil?
967
+ return LHC::Response.new(remote_cache)
968
+ end
969
+ end
970
+ ```
971
+
972
+ ## Testing
973
+
974
+ When writing tests for your application when using LHC, please make sure you require the lhc rspec test helper:
975
+
976
+ ```ruby
977
+ # spec/spec_helper.rb
978
+
979
+ require 'lhc/rspec'
980
+ ```
981
+
982
+ ## License
983
+
984
+ [GNU General Public License Version 3.](https://www.gnu.org/licenses/gpl-3.0.en.html)