lhc 9.4.0 → 9.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2716d5e639b506ed4d3b10782a61a8c326463b5941e1b33ba9c5759ea51e9566
4
- data.tar.gz: 535d64143094e6b005ae9f0c161fb9d046c610778d3204d4024c4cf3b4d54451
3
+ metadata.gz: 153c47a835c9f4f7e4ac644c18820bc4bb1f87a1f09b5321179fee621bfb42b0
4
+ data.tar.gz: 4995637bddf13e0fb48a97d2bb3c592a1ee1346675d38cc219d1f405d93cd35d
5
5
  SHA512:
6
- metadata.gz: c0471c962b62778ba637ae90ef90671c88510c6f4c9ad5e10024dae82f00266edb1d88fdd10395c6e8bd6dbca2da98c2717ccff09a79d0db05afa09eef6307f5
7
- data.tar.gz: c2ef427484136c827a507d30964c0db9b32174286324db1ba13a6b3c4d180b09b496ea720c9da092c8ecfce2e71dd6dd774b351078957482d3267caae7889a39
6
+ metadata.gz: 1c934cbf9f4de81b821d3ae61ff93f9be65ed7700be82b842d697cf978880c4d368b2e0a26ec0bc302ba608ffb52832702069ccdf77d8218e505cf9967196f74
7
+ data.tar.gz: 83e666f17c733e063bb5d64e082d47d5a830c48d7bb331dfeee827ec56a18335b64db104f270541e1f88dbf17da5fb05f00ed58872cd51664074a079085c3d13
data/README.md CHANGED
@@ -1,78 +1,137 @@
1
1
  LHC
2
2
  ===
3
3
 
4
- LHC uses [typhoeus](https://github.com/typhoeus/typhoeus) for http communication.
4
+ LHC uses [typhoeus](https://github.com/typhoeus/typhoeus) for low level http communication.
5
5
 
6
- See [LHS](https://github.com/local-ch/LHS), if you are searching for something more **high level** that can query webservices easily and provides easy data access.
6
+ 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.
7
7
 
8
- ## Quick Start Guide
8
+ ## Quick start guide
9
+
10
+ ```ruby
11
+ gem install lhc
12
+ ```
13
+
14
+ or add it to your Gemfile:
15
+
16
+ ```ruby
17
+ gem 'lhc'
18
+ ```
19
+
20
+ use it like:
9
21
 
10
22
  ```ruby
11
23
  response = LHC.get('http://datastore/v2/feedbacks')
12
24
  response.data.items[0]
13
25
  response.data.items[0].recommended
14
- response.body # String
15
- response.headers # Hash
26
+ response.body
27
+ response.headers
16
28
  ```
17
29
 
30
+ ## Table of contents
31
+
32
+ * [LHC](#lhc)
33
+ * [Quick start guide](#quick-start-guide)
34
+ * [Basic methods](#basic-methods)
35
+ * [Request](#request)
36
+ * [Formats](#formats)
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
+ * [Response](#response)
46
+ * [Accessing response data](#accessing-response-data)
47
+ * [Exceptions](#exceptions)
48
+ * [Custom error handling](#custom-error-handling)
49
+ * [Ignore certain errors](#ignore-certain-errors)
50
+ * [Configuration](#configuration)
51
+ * [Configuring endpoints](#configuring-endpoints)
52
+ * [Configuring placeholders](#configuring-placeholders)
53
+ * [Interceptors](#interceptors)
54
+ * [Quick start: Configure/Enable Interceptors](#quick-start-configureenable-interceptors)
55
+ * [Interceptors on local request level](#interceptors-on-local-request-level)
56
+ * [Core Interceptors](#core-interceptors)
57
+ * [Authentication Interceptor](#authentication-interceptor)
58
+ * [Bearer Authentication](#bearer-authentication)
59
+ * [Basic Authentication](#basic-authentication)
60
+ * [Reauthenticate](#reauthenticate)
61
+ * [Bearer Authentication with client access token](#bearer-authentication-with-client-access-token)
62
+ * [Caching Interceptor](#caching-interceptor)
63
+ * [Options](#options)
64
+ * [Testing](#testing)
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
+ * [Rollbar Interceptor](#rollbar-interceptor)
81
+ * [Forward additional parameters](#forward-additional-parameters)
82
+ * [Zipkin](#zipkin)
83
+ * [Create an interceptor from scratch](#create-an-interceptor-from-scratch)
84
+ * [Interceptor callbacks](#interceptor-callbacks)
85
+ * [Interceptor request/response](#interceptor-requestresponse)
86
+ * [Provide a response replacement through an interceptor](#provide-a-response-replacement-through-an-interceptor)
87
+ * [License](#license)
88
+
18
89
  ## Basic methods
19
90
 
20
91
  Available are `get`, `post`, `put` & `delete`.
21
92
 
22
93
  Other methods are available using `LHC.request(options)`.
23
94
 
24
- ## Formats: like json etc.
95
+ ## Request
25
96
 
26
- You can use any of the basic methods in combination with a format like `json`:
97
+ 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).
27
98
 
28
99
  ```ruby
29
- LHC.json.get(options)
30
- ```
100
+ response = LHC.request(url: 'http://local.ch', method: :options)
31
101
 
32
- Currently supported formats: `json`
102
+ response.request.response #<LHC::Response> the associated response.
33
103
 
34
- If formats are used, headers for `Content-Type` and `Accept` are set by lhc, but also http bodies are translated by LHC, so you can pass bodies as ruby objects:
104
+ response.request.options #<Hash> the options used for creating the request.
35
105
 
36
- ```ruby
37
- LHC.json.post('http://slack', body: { text: 'Hi there' })
38
- # Content-Type: application/json
39
- # Accept: application/json
40
- # Translates body to "{\"text\":\"Hi there\"}" before sending
41
- ```
106
+ response.request.params # access request params
42
107
 
43
- ## A request from scratch
108
+ response.request.headers # access request headers
44
109
 
45
- ```ruby
46
- response = LHC.request(url: 'http://local.ch', method: :options)
47
- response.headers
110
+ response.request.url #<String> URL that is used for doing the request
48
111
 
49
- response = LHC.request(url: 'http://datastore/v2/feedbacks', method: :get)
50
- response.data
112
+ response.request.method #<Symbol> provides the used http-method
51
113
  ```
52
114
 
53
- [Read more about the request object](docs/request.md)
54
-
55
- → [Read more about the response object](docs/response.md)
56
-
57
- ## Accessing data
115
+ ### Formats
58
116
 
59
- 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).
117
+ You can use any of the basic methods in combination with a format like `json`:
60
118
 
61
119
  ```ruby
62
- response = LHC.request(url: 'http://datastore/entry/1')
63
- response.data.as_open_struct #<OpenStruct name='local.ch'>
64
- response.data.as_json # { name: 'local.ch' }
65
- response.data.name # 'local.ch'
66
- response.data[:name] # 'local.ch'
120
+ LHC.json.get(options)
67
121
  ```
68
122
 
69
- You can also access response data directly through the response object (with square bracket notation only):
123
+ Currently supported formats: `json`
124
+
125
+ If formats are used, headers for `Content-Type` and `Accept` are set by LHC, but also http bodies are translated by LHC, so you can pass bodies as ruby objects:
70
126
 
71
127
  ```ruby
72
- LHC.json.get(url: 'http://datastore/entry/1')[:name]
128
+ LHC.json.post('http://slack', body: { text: 'Hi there' })
129
+ # Content-Type: application/json
130
+ # Accept: application/json
131
+ # Translates body to "{\"text\":\"Hi there\"}" before sending
73
132
  ```
74
133
 
75
- ## Parallel requests
134
+ ### Parallel requests
76
135
 
77
136
  If you pass an array of requests to `LHC.request`, it will perform those requests in parallel.
78
137
  You will get back an array of LHC::Response objects in the same order of the passed requests.
@@ -89,17 +148,17 @@ LHC.request([request1, request2, request3])
89
148
  # returns [response1, response2, response3]
90
149
  ```
91
150
 
92
- ## Follow redirects
151
+ ### Follow redirects
93
152
 
94
153
  ```ruby
95
154
  LHC.get('http://local.ch', followlocation: true)
96
155
  ```
97
156
 
98
- ## Transfer data through the body
157
+ ### Transfer data through the request body
99
158
 
100
159
  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.
101
160
 
102
- Also consider setting the http header for content-type or use one of the provided formats, like `LHC.json`.
161
+ Also consider setting the http header for content-type or use one of the provided [formats](#formats), like `LHC.json`.
103
162
 
104
163
  ```ruby
105
164
  LHC.post('http://datastore/v2/feedbacks',
@@ -108,9 +167,9 @@ Also consider setting the http header for content-type or use one of the provide
108
167
  )
109
168
  ```
110
169
 
111
- ## Parameter
170
+ ### Request parameters
112
171
 
113
- When using LHC, try to pass params via `params` option. It's not recommended to build a url and attach the parameter yourself:
172
+ When using LHC, try to pass params via `params` option. It's not recommended to build a url and attach the parameters yourself:
114
173
 
115
174
  DO
116
175
  ```ruby
@@ -122,7 +181,7 @@ DON'T
122
181
  LHC.get('http://local.ch?q=Restaurant')
123
182
  ```
124
183
 
125
- ### Array Parameter Encoding
184
+ #### Array Parameter Encoding
126
185
 
127
186
  LHC can encode array parameters in URLs in two ways. The default is `:rack` which generates URL parameters compatible with Rack and Rails.
128
187
 
@@ -138,7 +197,7 @@ LHC.get('http://local.ch', params: { q: [1, 2] }, params_encoding: :multi)
138
197
  # http://local.ch?q=1&q=2
139
198
  ```
140
199
 
141
- ## Encoding
200
+ ### Request URL encoding
142
201
 
143
202
  LHC, by default, encodes urls:
144
203
 
@@ -157,21 +216,29 @@ LHC.get('http://local.ch?q=some space', url_encoding: false)
157
216
  # http://local.ch?q=some space
158
217
  ```
159
218
 
160
- ## Configuration
219
+ ### Request URL-Templates
220
+
221
+ Instead of using concrete urls you can also use url-templates that contain placeholders.
222
+ This is especially handy for configuring an endpoint once and generate the url from the params when doing the request.
223
+ Since version `7.0` url templates follow the [RFC 6750](https://tools.ietf.org/html/rfc6570).
161
224
 
162
- You can configure global endpoints, placeholders and interceptors.
225
+ ```ruby
226
+ LHC.get('http://datastore/v2/feedbacks/{id}', params:{ id: 123 })
227
+ # GET http://datastore/v2/feedbacks/123
228
+ ```
163
229
 
230
+ You can also use URL templates, when [configuring endpoints](#configuring-endpoints):
164
231
  ```ruby
165
232
  LHC.configure do |c|
166
- c.placeholder :datastore, 'http://datastore/v2'
167
- c.endpoint :feedbacks, '{+datastore}/feedbacks', params: { has_reviews: true }
168
- c.interceptors = [LHC::Caching]
233
+ c.endpoint(:find_feedback, 'http://datastore/v2/feedbacks/{id}')
169
234
  end
235
+
236
+ LHC.get(:find_feedback, params:{ id: 123 }) # GET http://datastore/v2/feedbacks/123
170
237
  ```
171
238
 
172
- [Read more about configuration](docs/configuration.md)
239
+ If you miss to provide a parameter that is part of the url-template, it will raise an exception.
173
240
 
174
- ## Timeout
241
+ ### Request timeout
175
242
 
176
243
  Working and configuring timeouts is important, to ensure your app stays alive when services you depend on start to get really slow...
177
244
 
@@ -184,35 +251,112 @@ LHC forwards two timeout options directly to typhoeus:
184
251
  LHC.get('http://local.ch', timeout: 5, connecttimeout: 1)
185
252
  ```
186
253
 
187
- LHC provides a [timeout interceptor](docs/interceptors/default_timeout.md) that lets you apply default timeout values to all the requests that you are performig in your application.
254
+ 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.
188
255
 
189
- ## URL-Templates
256
+ ## Response
190
257
 
191
- Instead of using concrete urls you can also use url-templates that contain placeholders.
192
- This is especially handy for configuring an endpoint once and generate the url from the params when doing the request.
193
- Since version `7.0` url templates follow the [RFC 6750](https://tools.ietf.org/html/rfc6570).
258
+ ```ruby
259
+ response.request #<LHC::Request> the associated request.
260
+
261
+ response.data #<OpenStruct> in case response body contains parsable JSON.
262
+ response.data.something.nested
263
+
264
+ response.body #<String>
265
+
266
+ response.code #<Fixnum>
267
+
268
+ response.headers #<Hash>
269
+
270
+ response.time #<Fixnum> Provides response time in ms.
271
+
272
+ response.timeout? #true|false
273
+ ```
274
+
275
+ ### Accessing response data
276
+
277
+ 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).
194
278
 
195
279
  ```ruby
196
- url = 'http://datastore/v2/feedbacks/{id}'
197
- LHC.config.endpoint(:find_feedback, url, options)
198
- LHC.get(:find_feedback, params:{ id: 123 })
199
- # GET http://datastore/v2/feedbacks/123
280
+ response = LHC.request(url: 'http://datastore/entry/1')
281
+ response.data.as_open_struct #<OpenStruct name='local.ch'>
282
+ response.data.as_json # { name: 'local.ch' }
283
+ response.data.name # 'local.ch'
284
+ response.data[:name] # 'local.ch'
200
285
  ```
201
286
 
202
- This also works in place without configuring an endpoint.
287
+ You can also access response data directly through the response object (with square bracket notation only):
203
288
 
204
289
  ```ruby
205
- LHC.get('http://datastore/v2/feedbacks/{id}', params:{ id: 123 })
206
- # GET http://datastore/v2/feedbacks/123
290
+ LHC.json.get(url: 'http://datastore/entry/1')[:name]
207
291
  ```
208
292
 
209
- If you miss to provide a parameter that is part of the url-template, it will raise an exception.
293
+ ## Exceptions
210
294
 
211
- ## Exception handling
295
+ Anything but a response code indicating success (2xx) raises an exception.
212
296
 
213
- Anything but a response code indicating success (2**) throws an exception.
297
+ ```ruby
214
298
 
215
- [Read more about exceptions](docs/exceptions.md)
299
+ LHC.get('localhost') # UnknownError: 0
300
+ LHC.get('http://localhost:3000') # LHC::Timeout: 0
301
+
302
+ ```
303
+
304
+ You can access the response object that was causing the error.
305
+
306
+ ```ruby
307
+ LHC.get('local.ch')
308
+ rescue => e
309
+ e.response #<LHC:Response>
310
+ e.response.code # 403
311
+ e.response.timeout? # false
312
+ Rails.logger.error e
313
+ # LHC::UnknownError: get http://local.cac
314
+ # Params: {:url=>"http://local.cac", :method=>:get}
315
+ # Response Code: 0
316
+ # <Response Body>
317
+ ```
318
+
319
+ All errors that are raise by LHC inherit from `LHC::Error`.
320
+ They are divided into `LHC::ClientError`, `LHC::ServerError`, `LHC::Timeout` and `LHC::UnkownError` and mapped according to the following status code.
321
+
322
+ ```ruby
323
+ 400 => LHC::BadRequest
324
+ 401 => LHC::Unauthorized
325
+ 402 => LHC::PaymentRequired
326
+ 403 => LHC::Forbidden
327
+ 403 => LHC::Forbidden
328
+ 404 => LHC::NotFound
329
+ 405 => LHC::MethodNotAllowed
330
+ 406 => LHC::NotAcceptable
331
+ 407 => LHC::ProxyAuthenticationRequired
332
+ 408 => LHC::RequestTimeout
333
+ 409 => LHC::Conflict
334
+ 410 => LHC::Gone
335
+ 411 => LHC::LengthRequired
336
+ 412 => LHC::PreconditionFailed
337
+ 413 => LHC::RequestEntityTooLarge
338
+ 414 => LHC::RequestUriToLong
339
+ 415 => LHC::UnsupportedMediaType
340
+ 416 => LHC::RequestedRangeNotSatisfiable
341
+ 417 => LHC::ExpectationFailed
342
+ 422 => LHC::UnprocessableEntity
343
+ 423 => LHC::Locked
344
+ 424 => LHC::FailedDependency
345
+ 426 => LHC::UpgradeRequired
346
+
347
+ 500 => LHC::InternalServerError
348
+ 501 => LHC::NotImplemented
349
+ 502 => LHC::BadGateway
350
+ 503 => LHC::ServiceUnavailable
351
+ 504 => LHC::GatewayTimeout
352
+ 505 => LHC::HttpVersionNotSupported
353
+ 507 => LHC::InsufficientStorage
354
+ 510 => LHC::NotExtended
355
+
356
+ timeout? => LHC::Timeout
357
+
358
+ anything_else => LHC::UnknownError
359
+ ```
216
360
 
217
361
  ### Custom error handling
218
362
 
@@ -244,9 +388,438 @@ response.error_ignored? # true
244
388
  response.request.error_ignored? # true
245
389
  ```
246
390
 
391
+ ## Configuration
392
+
393
+ 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.
394
+
395
+ You can use `LHC.configure` to prevent the initialization problem.
396
+ Take care that you only use `LHC.configure` once, because it is actually reseting previously made configurations and applies the new once.
397
+
398
+ ```ruby
399
+
400
+ LHC.configure do |c|
401
+ c.placeholder :datastore, 'http://datastore/v2'
402
+ c.endpoint :feedbacks, '{+datastore}/feedbacks', params: { has_reviews: true }
403
+ c.interceptors = [CachingInterceptor, MonitorInterceptor, TrackingIdInterceptor]
404
+ end
405
+
406
+ ```
407
+
408
+ ### Configuring endpoints
409
+
410
+ You can configure endpoints, for later use, by giving them a name, a url and some parameters (optional).
411
+
412
+ ```ruby
413
+ LHC.configure do |c|
414
+ c.endpoint(:feedbacks, 'http://datastore/v2/feedbacks', params: { has_reviews: true })
415
+ c.endpoint(:find_feedback, 'http://datastore/v2/feedbacks/{id}')
416
+ end
417
+
418
+ LHC.get(:feedbacks) # GET http://datastore/v2/feedbacks
419
+ LHC.get(:find_feedback, params:{ id: 123 }) # GET http://datastore/v2/feedbacks/123
420
+ ```
421
+
422
+ Explicit request options override configured options.
423
+
424
+ ```ruby
425
+ LHC.get(:feedbacks, params: { has_reviews: false }) # Overrides configured params
426
+ ```
427
+
428
+ ### Configuring placeholders
429
+
430
+ You can configure global placeholders, that are used when generating urls from url-templates.
431
+
432
+ ```ruby
433
+ LHC.configure do |c|
434
+ c.placeholder(:datastore, 'http://datastore/v2')
435
+ c.endpoint(:feedbacks, '{+datastore}/feedbacks', { params: { has_reviews: true } })
436
+ end
437
+
438
+ LHC.get(:feedbacks) # http://datastore/v2/feedbacks
439
+ ```
440
+
247
441
  ## Interceptors
248
442
 
249
- To monitor and manipulate the http communication done with LHC, you can define interceptors.
443
+ 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].
444
+ There are some interceptors that are part of LHC already, so called [Core Interceptors](#core-interceptors), that cover some basic usecases.
445
+
446
+ ### Quick start: Configure/Enable Interceptors
447
+
448
+ ```ruby
449
+ LHC.configure do |c|
450
+ c.interceptors = [LHC::Auth, LHC::Caching, LHC::DefaultTimeout, LHC::Logging, LHC::Monitoring, LHC::Prometheus, LHC::Retry, LHC::Rollbar, LHC::Zipkin]
451
+ end
452
+ ```
453
+
454
+ You can only set the list of global interceptors once and you can not alter it after you set it.
455
+
456
+ ### Interceptors on local request level
457
+
458
+ You can override the global list of interceptors on local request level:
459
+
460
+ ```ruby
461
+ interceptors = LHC.config.interceptors
462
+ interceptors -= [LHC::Caching] # remove caching
463
+ interceptors += [LHC::Retry] # add retry
464
+ LHC.request({url: 'http://local.ch', retry: 2, interceptors: interceptors})
465
+
466
+ LHC.request({url: 'http://local.ch', interceptors: []}) # no interceptor for this request at all
467
+ ```
468
+
469
+ ### Core Interceptors
470
+
471
+ #### Authentication Interceptor
472
+
473
+ Add the auth interceptor to your basic set of LHC interceptors.
474
+
475
+ ```ruby
476
+ LHC.configure do |c|
477
+ c.interceptors = [LHC::Auth]
478
+ end
479
+ ```
480
+
481
+ ##### Bearer Authentication
482
+
483
+ ```ruby
484
+ LHC.get('http://local.ch', auth: { bearer: -> { access_token } })
485
+ ```
486
+
487
+ Adds the following header to the request:
488
+ ```
489
+ 'Authorization': 'Bearer 123456'
490
+ ```
491
+
492
+ Assuming the method `access_token` responds on runtime of the request with `123456`.
493
+
494
+ ##### Basic Authentication
495
+
496
+ ```ruby
497
+ LHC.get('http://local.ch', auth: { basic: { username: 'steve', password: 'can' } })
498
+ ```
499
+
500
+ Adds the following header to the request:
501
+ ```
502
+ 'Authorization': 'Basic c3RldmU6Y2Fu'
503
+ ```
504
+
505
+ Which is the base64 encoded credentials "username:password".
506
+
507
+ ##### Reauthenticate
508
+
509
+ The current implementation can only offer reauthenticate for _client access tokens_. For this to work the following has to be given:
510
+
511
+ * 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.
512
+ * Your interceptors contain `LHC::Auth` and `LHC::Retry`, whereas `LHC::Retry` comes _after_ `LHC::Auth` in the chain.
513
+
514
+ ##### Bearer Authentication with client access token
515
+
516
+ Reauthentication will be initiated if:
517
+
518
+ * setup is correct
519
+ * `response.success?` is false and an `LHC::Unauthorized` was observed
520
+ * reauthentication wasn't already attempted once
521
+
522
+ If this is the case, this happens:
523
+
524
+ * refresh the client token, by calling `refresh_client_token`
525
+ * the authentication header will be updated with the new token
526
+ * `LHC::Retry` will be triggered by adding `retry: { max: 1 }` to the request options
527
+
528
+ #### Caching Interceptor
529
+
530
+ Add the cache interceptor to your basic set of LHC interceptors.
531
+
532
+ ```ruby
533
+ LHC.configure do |c|
534
+ c.interceptors = [LHC::Caching]
535
+ end
536
+ ```
537
+
538
+ You can configure your own cache (default Rails.cache) and logger (default Rails.logger):
539
+
540
+ ```ruby
541
+ LHC::Caching.cache = ActiveSupport::Cache::MemoryStore.new
542
+ LHC::Caching.logger = Logger.new(STDOUT)
543
+ ```
544
+
545
+ Caching is not enabled by default, although you added it to your basic set of interceptors.
546
+ If you want to have requests served/stored and stored in/from cache, you have to enable it by request.
547
+
548
+ ```ruby
549
+ LHC.get('http://local.ch', cache: true)
550
+ ```
551
+
552
+ You can also enable caching when configuring an endpoint in LHS.
553
+
554
+ ```ruby
555
+ class Feedbacks < LHS::Service
556
+ endpoint '{+datastore}/v2/feedbacks', cache: true
557
+ end
558
+ ```
559
+
560
+ Only GET requests are cached by default. If you want to cache any other request method, just configure it:
561
+
562
+ ```ruby
563
+ LHC.get('http://local.ch', cache: { methods: [:get] })
564
+ ```
565
+
566
+ Responses served from cache are marked as served from cache:
567
+
568
+ ```ruby
569
+ response = LHC.get('http://local.ch', cache: true)
570
+ response.from_cache? # true
571
+ ```
572
+
573
+ ##### Options
574
+
575
+ ```ruby
576
+ LHC.get('http://local.ch', cache: { key: 'key' expires_in: 1.day, race_condition_ttl: 15.seconds, use: ActiveSupport::Cache::MemoryStore.new })
577
+ ```
578
+
579
+ `expires_in` - lets the cache expires every X seconds.
580
+
581
+ `key` - Set the key that is used for caching by using the option. Every key is prefixed with `LHC_CACHE(v1): `.
582
+
583
+ `race_condition_ttl` - very useful in situations where a cache entry is used very frequently and is under heavy load.
584
+ 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.
585
+ 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`.
586
+
587
+ `use` - Set an explicit cache to be used for this request. If this option is missing `LHC::Caching.cache` is used.
588
+
589
+ ##### Testing
590
+
591
+ Add to your spec_helper.rb:
592
+
593
+ ```ruby
594
+ require 'lhc/test/cache_helper.rb'
595
+ ```
596
+
597
+ This will initialize a MemoryStore cache for LHC::Caching interceptor and resets the cache before every test.
598
+
599
+ #### Default Timeout Interceptor
600
+
601
+ Applies default timeout values to all requests made in an application, that uses LHC.
602
+
603
+ ```ruby
604
+ LHC.configure do |c|
605
+ c.interceptors = [LHC::DefaultTimeout]
606
+ end
607
+ ```
608
+
609
+ `timeout` default: 15 seconds
610
+ `connecttimeout` default: 2 seconds
611
+
612
+ ##### Overwrite defaults
613
+
614
+ ```ruby
615
+ LHC::DefaultTimeout.timeout = 5 # seconds
616
+ LHC::DefaultTimeout.connecttimeout = 3 # seconds
617
+ ```
618
+
619
+ #### Logging Interceptor
620
+
621
+ The logging interceptor logs all requests done with LHC to the Rails logs.
622
+
623
+ ##### Installation
624
+
625
+ ```ruby
626
+ LHC.configure do |c|
627
+ c.interceptors = [LHC::Logging]
628
+ end
629
+
630
+ LHC::Logging.logger = Rails.logger
631
+ ```
632
+
633
+ ##### What and how it logs
634
+
635
+ The logging Interceptor logs basic information about the request and the response:
636
+
637
+ ```ruby
638
+ LHC.get('http://local.ch')
639
+ # 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\"=>\"\"}
640
+ # 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/
641
+ ```
642
+
643
+ ##### Configure
644
+
645
+ You can configure the logger beeing used by the logging interceptor:
646
+
647
+ ```ruby
648
+ LHC::Logging.logger = Another::Logger
649
+ ```
650
+
651
+ #### Monitoring Interceptor
652
+
653
+ The monitoring interceptor reports all requests done with LHC to a given StatsD instance.
654
+
655
+ ##### Installation
656
+
657
+ ```ruby
658
+ LHC.configure do |c|
659
+ c.interceptors = [LHC::Monitoring]
660
+ end
661
+ ```
662
+
663
+ You also have to configure statsd in order to have the monitoring interceptor report.
664
+
665
+ ```ruby
666
+ LHC::Monitoring.statsd = <your-instance-of-statsd>
667
+ ```
668
+
669
+ ##### Environment
670
+
671
+ By default, the monitoring interceptor uses Rails.env to determine the environment. In case you want to configure that, use:
672
+
673
+ ```ruby
674
+ LHC::Monitoring.env = ENV['DEPLOYMENT_TYPE'] || Rails.env
675
+ ```
676
+
677
+ ##### What it tracks
678
+
679
+ It tracks request attempts with `before_request` and `after_request` (counts).
680
+
681
+ In case your workers/processes are getting killed due limited time constraints,
682
+ you are able to detect deltas with relying on "before_request", and "after_request" counts:
683
+
684
+ ```ruby
685
+ "lhc.<app_name>.<env>.<host>.<http_method>.before_request", 1
686
+ "lhc.<app_name>.<env>.<host>.<http_method>.after_request", 1
687
+ ```
688
+
689
+ In case of a successful response it reports the response code with a count and the response time with a gauge value.
690
+
691
+ ```ruby
692
+ LHC.get('http://local.ch')
693
+
694
+ "lhc.<app_name>.<env>.<host>.<http_method>.count", 1
695
+ "lhc.<app_name>.<env>.<host>.<http_method>.200", 1
696
+ "lhc.<app_name>.<env>.<host>.<http_method>.time", 43
697
+ ```
698
+
699
+ Timeouts are also reported:
700
+
701
+ ```ruby
702
+ "lhc.<app_name>.<env>.<host>.<http_method>.timeout", 1
703
+ ```
704
+
705
+ All the dots in the host are getting replaced with underscore, because dot is the default separator in graphite.
706
+
707
+ ##### Configure
708
+
709
+ It is possible to set the key for Monitoring Interceptor on per request basis:
710
+
711
+ ```ruby
712
+ LHC.get('http://local.ch', monitoring_key: 'local_website')
713
+
714
+ "local_website.count", 1
715
+ "local_website.200", 1
716
+ "local_website.time", 43
717
+ ```
718
+
719
+ If you use this approach you need to add all namespaces (app, environment etc.) to the key on your own.
720
+
721
+
722
+ #### Prometheus Interceptor
723
+
724
+ Logs basic request/response information to prometheus.
725
+
726
+ ```ruby
727
+ require 'prometheus/client'
728
+
729
+ LHC.configure do |c|
730
+ c.interceptors = [LHC::Prometheus]
731
+ end
732
+
733
+ LHC::Prometheus.client = Prometheus::Client
734
+ LHC::Prometheus.namespace = 'web_location_app'
735
+ ```
736
+
737
+ ```ruby
738
+ LHC.get('http://local.ch')
739
+ ```
740
+
741
+ - Creates a prometheus counter that receives additional meta information for: `:code`, `:success` and `:timeout`.
742
+
743
+ - Creates a prometheus histogram for response times in milliseconds.
744
+
745
+
746
+ #### Retry Interceptor
747
+
748
+ If you enable the retry interceptor, you can have LHC retry requests for you:
749
+
750
+ ```ruby
751
+ LHC.configure do |c|
752
+ c.interceptors = [LHC::Retry]
753
+ end
754
+
755
+ response = LHC.get('http://local.ch', retry: true)
756
+ ```
757
+
758
+ 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.
759
+
760
+ Consider, that all other interceptors will run for every single retry.
761
+
762
+ ##### Limit the amount of retries while making the request
763
+
764
+ ```ruby
765
+ LHC.get('http://local.ch', retry: { max: 1 })
766
+ ```
767
+
768
+ ##### Change the default maximum of retries of the retry interceptor
769
+
770
+ ```ruby
771
+ LHC::Retry.max = 3
772
+ ```
773
+
774
+ #### Rollbar Interceptor
775
+
776
+ Forward errors to rollbar when exceptions occur during http requests.
777
+
778
+ ```ruby
779
+ LHC.configure do |c|
780
+ c.interceptors = [LHC::Rollbar]
781
+ end
782
+ ```
783
+
784
+ ```ruby
785
+ LHC.get('http://local.ch')
786
+ ```
787
+
788
+ If it raises, it forwards the request and response object to rollbar, which contain all necessary data.
789
+
790
+ ##### Forward additional parameters
791
+
792
+ ```ruby
793
+ LHC.get('http://local.ch', rollbar: { tracking_key: 'this particular request' })
794
+ ```
795
+
796
+ #### Zipkin
797
+
798
+ 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/).
799
+
800
+ Add the zipkin interceptor to your basic set of LHC interceptors.
801
+
802
+ ```ruby
803
+ LHC.configure do |c|
804
+ c.interceptors = [LHC::Zipkin]
805
+ end
806
+ ```
807
+
808
+ The following configuration needs to happen in the application that wants to run this interceptor:
809
+
810
+ 1. Add `gem 'zipkin-tracer'` to your Gemfile.
811
+ 2. Add the necessary Rack middleware and configuration
812
+
813
+ ```ruby
814
+ config.middleware.use ZipkinTracer::RackHandler, {
815
+ service_name: 'service-name', # name your service will be known as in zipkin
816
+ service_port: 80, # the port information that is sent along the trace
817
+ json_api_host: 'http://zipkin-collector', # the zipkin endpoint
818
+ sample_rate: 1 # sample rate, where 1 = 100% of all requests, and 0.1 is 10% of all requests
819
+ }
820
+ ```
821
+
822
+ ### Create an interceptor from scratch
250
823
 
251
824
  ```ruby
252
825
  class TrackingIdInterceptor < LHC::Interceptor
@@ -257,13 +830,56 @@ To monitor and manipulate the http communication done with LHC, you can define i
257
830
  end
258
831
  ```
259
832
 
260
- → [Read more about interceptors](docs/interceptors.md)
833
+ ```ruby
834
+ LHC.configure do |c|
835
+ c.interceptors = [TrackingIdInterceptor]
836
+ end
837
+ ```
261
838
 
262
- ### Core Interceptors
839
+ #### Interceptor callbacks
840
+
841
+ `before_raw_request` is called before the raw typhoeus request is prepared/created.
842
+
843
+ `before_request` is called when the request is prepared and about to be executed.
844
+
845
+ `after_request` is called after request was started.
846
+
847
+ `before_response` is called when response started to arrive.
848
+
849
+ `after_response` is called after the response arrived completely.
850
+
851
+
852
+ #### Interceptor request/response
853
+
854
+ Every interceptor can directly access their instance [request](#request) or [response](#response).
855
+
856
+ #### Provide a response replacement through an interceptor
263
857
 
264
- There are some interceptors that are part of LHC already, that cover some basic usecases:
858
+ Inside an interceptor, you are able to provide a response, rather then doing a real request.
859
+ This is useful for implementing e.g. caching.
265
860
 
266
- [Available Core Interceptors](/docs/interceptors)
861
+ ```ruby
862
+ class LHC::Cache < LHC::Interceptor
863
+
864
+ def before_request(request)
865
+ cached_response = Rails.cache.fetch(request.url)
866
+ return LHC::Response.new(cached_response) if cached_response
867
+ end
868
+ end
869
+ ```
870
+
871
+ Take care that having more than one interceptor trying to return a response will cause an exception.
872
+ You can access the request.response to identify if a response was already provided by another interceptor.
873
+
874
+ ```ruby
875
+ class RemoteCacheInterceptor < LHC::Interceptor
876
+
877
+ def before_request(request)
878
+ return unless request.response.nil?
879
+ return LHC::Response.new(remote_cache)
880
+ end
881
+ end
882
+ ```
267
883
 
268
884
  ## License
269
885