chimera_http_client 1.8.0 → 1.9.0
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 +4 -4
- data/README.markdown +67 -3
- data/TODO.markdown +11 -5
- data/lib/chimera_http_client/base.rb +18 -2
- data/lib/chimera_http_client/serializer.rb +23 -0
- data/lib/chimera_http_client/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ca5968573054108171952f955ef7974120a4d4725ed1ca34af29e3d40e831c61
|
|
4
|
+
data.tar.gz: 3f8adfc58275d048326c4cb25c93cc46d637195da9eb042e1c6388d705e3dc54
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b00e1318e81f48ea6897e384062ab4e30b9ca83d1dc4549a22ecf8df9ce1940806c47fd04f8bf2f1109cd58922bee78605622fedc61bc74e5fb15d4be7dc0184
|
|
7
|
+
data.tar.gz: 494531a305d3bb571e98d9da1f6ad69330b64de19d29ed8d4cd64a3218fdc92bc3c2c499f7b88b829f714abc5f291654b09d364dff7577e83d6ff3080b68c6ae
|
data/README.markdown
CHANGED
|
@@ -37,6 +37,8 @@ All information above is given for **Linux**.
|
|
|
37
37
|
|
|
38
38
|
Setting the environment variable `ENV['CHIMERA_HTTP_CLIENT_LOG_REQUESTS']` to `true` (or `'true'`) will provide more detailed error messages for logging and also add additional information to the Error JSON. It is recommended to use this only in development environments.
|
|
39
39
|
|
|
40
|
+
Setting `ENV['CHIMERA_HTTP_CLIENT_DEFAULT_HEADERS']` to a JSON object string will merge those headers into every `Connection`/`Queue` in the process, e.g. `export CHIMERA_HTTP_CLIENT_DEFAULT_HEADERS='{"X-Service-Name":"orders-api"}'`. This is meant for deployment/ops-level defaults (like identifying which service is calling), set once outside application code. See [Custom headers](#custom-headers) below.
|
|
41
|
+
|
|
40
42
|
## Table of Contents
|
|
41
43
|
|
|
42
44
|
<!-- TOC depthFrom:1 depthTo:4 withLinks:1 updateOnSave:0 orderedList:0 -->
|
|
@@ -51,6 +53,8 @@ Setting the environment variable `ENV['CHIMERA_HTTP_CLIENT_LOG_REQUESTS']` to `t
|
|
|
51
53
|
- [Mandatory initialization parameter `base_url`](#mandatory-initialization-parameter-base_url)
|
|
52
54
|
- [Optional initialization parameters](#optional-initialization-parameters)
|
|
53
55
|
- [Custom deserializers](#custom-deserializers)
|
|
56
|
+
- [Custom headers](#custom-headers)
|
|
57
|
+
- [Custom serializer](#custom-serializer)
|
|
54
58
|
- [Monitoring, metrics, instrumentation](#monitoring-metrics-instrumentation)
|
|
55
59
|
- [Request methods](#request-methods)
|
|
56
60
|
- [Mandatory request parameter `endpoint`](#mandatory-request-parameter-endpoint)
|
|
@@ -100,10 +104,12 @@ The optional parameters are:
|
|
|
100
104
|
|
|
101
105
|
* `cache` - an instance of your cache solution, can be overwritten in any request
|
|
102
106
|
* `deserializers` - custom methods to deserialize the response body, below more details
|
|
107
|
+
* `headers` - override/extend the default request headers (`{ "Content-Type" => "application/json" }`), can be overwritten or merged with per-request headers, below more details
|
|
103
108
|
* `logger` - an instance of a logger class that implements `#info`, `#warn` and `#error` methods
|
|
104
109
|
* `monitor` - to collect metrics about requests, the basis for your instrumentation needs
|
|
105
110
|
* `retries` - the number of times a failed idempotent request is retried, can be overwritten in any request, the default is `0` (no retries)
|
|
106
111
|
* `retry_delay` - the base delay in seconds between retries, can be overwritten in any request, the default is `1`
|
|
112
|
+
* `serializer` - override how a Hash/Array request body is turned into a request, can be overwritten in any request, below more details
|
|
107
113
|
* `timeout` - the timeout for all requests, can be overwritten in any request, the default are 3 seconds
|
|
108
114
|
* `user_agent` - if you would like your calls to identify with a specific user agent
|
|
109
115
|
* `verbose` - the default is `false`, set it to true while debugging issues
|
|
@@ -118,7 +124,64 @@ A Deserializer has to be an object on which the method `call` with the parameter
|
|
|
118
124
|
|
|
119
125
|
custom_deserializer.call(body)
|
|
120
126
|
|
|
121
|
-
where `body` is the response body (in the default case a JSON object). The class `Deserializer` contains the default objects that are used. They might help you creating your own.
|
|
127
|
+
where `body` is the response body (in the default case a JSON object). The class `Deserializer` contains the default objects that are used. They might help you creating your own. If the API you connect to does not support JSON, set `headers` (see [Custom headers](#custom-headers) below) once on the `Connection` instead of repeating it on every request - and see [Custom serializer](#custom-serializer) below for the equivalent on the request-body side.
|
|
128
|
+
|
|
129
|
+
##### Custom headers
|
|
130
|
+
|
|
131
|
+
Every request sends `{ "Content-Type" => "application/json" }` plus a `User-Agent` by default. Four layers apply on top of each other (each one merges in, overriding only the keys it sets, so you never have to restate headers you're not changing):
|
|
132
|
+
|
|
133
|
+
1. the built-in default above (also available as `ChimeraHttpClient::Base::DEFAULT_HEADERS`)
|
|
134
|
+
2. `ENV['CHIMERA_HTTP_CLIENT_DEFAULT_HEADERS']`, a JSON object (see [ENV variables](#env-variables) above)
|
|
135
|
+
3. `headers` passed to `Connection.new`/`Queue.new` - the connection's own default
|
|
136
|
+
4. `headers` passed to an individual request - already documented above, unchanged
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
# connecting to a non-JSON API: override just Content-Type, everything else (User-Agent, ...) still applies
|
|
140
|
+
connection = ChimeraHttpClient::Connection.new(base_url: 'http://localhost:3000/v1', headers: { "Content-Type" => "application/xml" })
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
A common use for the connection-level `headers` option is a request/correlation id that should be attached to every call made for one unit of work (a background job, one inbound request being served), without every call site needing to know about it:
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
connection = ChimeraHttpClient::Connection.new(base_url: 'http://localhost:3000/v1', headers: { "X-Request-Id" => job_id })
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
For something that's the same for every request in the whole process instead - e.g. identifying which of your services is calling, in a service-to-service setting - set it once via `ENV['CHIMERA_HTTP_CLIENT_DEFAULT_HEADERS']` at the deployment level rather than in application code:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
export CHIMERA_HTTP_CLIENT_DEFAULT_HEADERS='{"X-Service-Name":"orders-api"}'
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
##### Custom serializer
|
|
156
|
+
|
|
157
|
+
A `Hash` or `Array` `body` is automatically serialized before the request is sent - by default with `body.to_json`, so this now just works:
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
connection.post!('users', body: { name: "Andy" }) # no more body.to_json needed
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
A `String` body (e.g. one you already serialized yourself) is always passed through completely unchanged - so any existing code still doing `body: body.to_json` keeps working exactly as before.
|
|
164
|
+
|
|
165
|
+
To use a different format, pass `serializer` to `Connection.new`/`Queue.new`, or to an individual request. A Serializer has to be an object on which the method `call` with the parameter `body` can be called:
|
|
166
|
+
|
|
167
|
+
custom_serializer.call(body)
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
# talking to an XML API instead
|
|
171
|
+
connection = ChimeraHttpClient::Connection.new(base_url: 'http://localhost:3000/v1', serializer: ->(body) { body.to_xml })
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The serializer normally returns a `String`, but it can also return the `Hash` unconverted - Typhoeus (via Ethon/libcurl) then form-encodes it natively as `application/x-www-form-urlencoded`, or as real multipart if a value looks file-shaped:
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
# posting a plain form instead of JSON
|
|
178
|
+
connection.post(
|
|
179
|
+
'login',
|
|
180
|
+
body: { username: "andy", password: "secret" },
|
|
181
|
+
serializer: ->(body) { body }, # hand the Hash to Typhoeus/Ethon unconverted
|
|
182
|
+
headers: { "Content-Type" => "application/x-www-form-urlencoded" }
|
|
183
|
+
)
|
|
184
|
+
```
|
|
122
185
|
|
|
123
186
|
##### Monitoring, metrics, instrumentation
|
|
124
187
|
|
|
@@ -179,6 +242,7 @@ All request methods expect a mandatory `endpoint` and an optional hash as parame
|
|
|
179
242
|
* `cache` - optionally overwrite the cache store set in `Connection` in any request
|
|
180
243
|
* `retries` - optionally overwrite the number of retries set in `Connection` for this request
|
|
181
244
|
* `retry_delay` - optionally overwrite the retry delay set in `Connection` for this request
|
|
245
|
+
* `serializer` - optionally overwrite the body serializer set in `Connection` for this request
|
|
182
246
|
* `monitoring_context` - pass additional information you want to collect with your instrumentation `monitor`
|
|
183
247
|
|
|
184
248
|
Example:
|
|
@@ -313,7 +377,7 @@ class Users
|
|
|
313
377
|
# CREATE a new user by sending attributes in a JSON body and instantiate the new User
|
|
314
378
|
#
|
|
315
379
|
def create(body:)
|
|
316
|
-
response = connection.post!('users', body: body
|
|
380
|
+
response = connection.post!('users', body: body) # a Hash body is serialized to JSON automatically
|
|
317
381
|
|
|
318
382
|
user = response.parsed_body
|
|
319
383
|
User.new(id: user['id'], name: user['name'], email: user['email'])
|
|
@@ -348,7 +412,7 @@ To create and fetch a user from a remote service with the `Users` wrapper listed
|
|
|
348
412
|
|
|
349
413
|
Usually it does not have to be used directly. It is the class that executes the `Typhoeus::Requests`, raises `Errors` on failing and returns `Response` objects on successful calls.
|
|
350
414
|
|
|
351
|
-
|
|
415
|
+
By the time `Request` receives `body`, it's already in the (serialized) form the endpoint expects: `Connection`/`Queue` auto-serialize a `Hash`/`Array` body to JSON (or via a custom `serializer`, see [Custom serializer](#custom-serializer)) before it ever reaches `Request`. A `String` body is passed through unchanged.
|
|
352
416
|
|
|
353
417
|
## The Response class
|
|
354
418
|
|
data/TODO.markdown
CHANGED
|
@@ -22,6 +22,12 @@ _none known_
|
|
|
22
22
|
* [x] ~~use custom deserializer in #parsed_body instead of default JSON parsing~~
|
|
23
23
|
* [x] ~~add example to README~~
|
|
24
24
|
|
|
25
|
+
### ~~Custom serializer~~
|
|
26
|
+
|
|
27
|
+
* [x] ~~allow to pass custom serializer~~
|
|
28
|
+
* [x] ~~use custom serializer with body instead of default JSON serialization ~~
|
|
29
|
+
* [x] ~~add example to README~~
|
|
30
|
+
|
|
25
31
|
### Queueing / running in parallel
|
|
26
32
|
|
|
27
33
|
* [x] ~~allow to queue multiple requests~~
|
|
@@ -79,12 +85,12 @@ _none known_
|
|
|
79
85
|
* [ ] enable to pass on_headers, on_body, on_complete procs
|
|
80
86
|
* [ ] add example to README
|
|
81
87
|
|
|
82
|
-
### HTTP Headers
|
|
88
|
+
### ~~HTTP Headers~~
|
|
83
89
|
|
|
84
|
-
* [
|
|
85
|
-
* [
|
|
86
|
-
* [
|
|
87
|
-
* [
|
|
90
|
+
* [x] ~~make Connection#default_headers configurable~~
|
|
91
|
+
* [x] ~~allow to set default_headers via ENV vars~~
|
|
92
|
+
* [x] ~~give example how to use default_headers (e.g. request_id)~~
|
|
93
|
+
* [x] ~~explain in README how to benefit from this gem in a given setting~~
|
|
88
94
|
|
|
89
95
|
### ~~Caching~~
|
|
90
96
|
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
module ChimeraHttpClient
|
|
2
2
|
class Base
|
|
3
3
|
USER_AGENT = "ChimeraHttpClient (by mediafinger)".freeze
|
|
4
|
+
DEFAULT_HEADERS = { "Content-Type" => "application/json" }.freeze
|
|
5
|
+
DEFAULT_HEADERS_ENV_VAR = "CHIMERA_HTTP_CLIENT_DEFAULT_HEADERS".freeze
|
|
4
6
|
|
|
5
7
|
def initialize(options = {})
|
|
6
8
|
fail(ChimeraHttpClient::ParameterMissingError, "base_url expected, but not given") if options[:base_url].nil?
|
|
7
9
|
|
|
8
10
|
@base_url = options.fetch(:base_url)
|
|
9
11
|
@deserializer = default_deserializer.merge(options.fetch(:deserializer, {}))
|
|
12
|
+
@serializer = options.fetch(:serializer, ::ChimeraHttpClient::Serializer.json)
|
|
10
13
|
@logger = options[:logger]
|
|
11
14
|
@monitor = options[:monitor]
|
|
12
15
|
@timeout = options[:timeout]
|
|
13
16
|
@cache = options[:cache]
|
|
17
|
+
@headers = DEFAULT_HEADERS.merge(headers_from_env).merge(options[:headers] || {})
|
|
14
18
|
@user_agent = options.fetch(:user_agent, USER_AGENT)
|
|
15
19
|
@verbose = options.fetch(:verbose, false)
|
|
16
20
|
@retries = options[:retries] || 0
|
|
@@ -33,10 +37,13 @@ module ChimeraHttpClient
|
|
|
33
37
|
def extract_body(options)
|
|
34
38
|
body = options.delete(:body)
|
|
35
39
|
body_optional = options.delete(:body_optional)
|
|
40
|
+
serializer = options.delete(:serializer) || @serializer
|
|
36
41
|
|
|
37
42
|
fail(ChimeraHttpClient::ParameterMissingError, "body expected, but not given") if body.nil? && !body_optional
|
|
38
43
|
|
|
39
|
-
body
|
|
44
|
+
return body unless body.is_a?(Hash) || body.is_a?(Array)
|
|
45
|
+
|
|
46
|
+
serializer.call(body)
|
|
40
47
|
end
|
|
41
48
|
|
|
42
49
|
def extract_headers(options, headers)
|
|
@@ -45,7 +52,16 @@ module ChimeraHttpClient
|
|
|
45
52
|
end
|
|
46
53
|
|
|
47
54
|
def default_headers
|
|
48
|
-
|
|
55
|
+
@headers.merge("User-Agent" => @user_agent)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def headers_from_env
|
|
59
|
+
raw = ENV[DEFAULT_HEADERS_ENV_VAR].to_s
|
|
60
|
+
return {} if raw.strip.empty?
|
|
61
|
+
|
|
62
|
+
JSON.parse(raw)
|
|
63
|
+
rescue JSON::ParserError => e
|
|
64
|
+
raise(ChimeraHttpClient::ParameterMissingError, "ENV['#{DEFAULT_HEADERS_ENV_VAR}'] is not valid JSON: #{e.message}")
|
|
49
65
|
end
|
|
50
66
|
|
|
51
67
|
def default_deserializer
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# The default JSON serializer for request bodies.
|
|
2
|
+
#
|
|
3
|
+
# To use a custom serializer, pass it as a param to Connection.new or Queue.new:
|
|
4
|
+
# `serializer: your_serializer`
|
|
5
|
+
# or override it for a single request: `connection.post(endpoint, body: {...}, serializer: your_serializer)`
|
|
6
|
+
#
|
|
7
|
+
# A Serializer has to be an object on which the method `call` with the parameter `body` can be called:
|
|
8
|
+
# `custom_serializer.call(body)`
|
|
9
|
+
# It is only ever invoked for a Hash or Array body - anything else (e.g. an already-serialized
|
|
10
|
+
# String) is passed to the request unchanged. It normally returns a String, but may also return
|
|
11
|
+
# a Hash - Typhoeus/Ethon will then form-encode it natively (application/x-www-form-urlencoded,
|
|
12
|
+
# or multipart if a value looks file-shaped), so an identity serializer (`->(body) { body }`) is
|
|
13
|
+
# a valid way to opt into that instead of JSON.
|
|
14
|
+
#
|
|
15
|
+
module ChimeraHttpClient
|
|
16
|
+
class Serializer
|
|
17
|
+
class << self
|
|
18
|
+
def json
|
|
19
|
+
proc { |body| body.to_json }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: chimera_http_client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andreas Finger
|
|
@@ -292,6 +292,7 @@ files:
|
|
|
292
292
|
- lib/chimera_http_client/queue.rb
|
|
293
293
|
- lib/chimera_http_client/request.rb
|
|
294
294
|
- lib/chimera_http_client/response.rb
|
|
295
|
+
- lib/chimera_http_client/serializer.rb
|
|
295
296
|
- lib/chimera_http_client/version.rb
|
|
296
297
|
homepage: https://github.com/mediafinger/chimera_http_client
|
|
297
298
|
licenses:
|