nxt_http_client 0.3.4 → 1.0.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/.circleci/config.yml +1 -1
- data/CHANGELOG.md +9 -0
- data/Gemfile.lock +9 -7
- data/README.md +120 -73
- data/lib/nxt_http_client.rb +5 -1
- data/lib/nxt_http_client/client.rb +77 -38
- data/lib/nxt_http_client/client_dsl.rb +46 -24
- data/lib/nxt_http_client/{default_config.rb → config.rb} +1 -1
- data/lib/nxt_http_client/error.rb +9 -3
- data/lib/nxt_http_client/fire_callbacks.rb +66 -0
- data/lib/nxt_http_client/headers.rb +4 -0
- data/lib/nxt_http_client/logger.rb +47 -0
- data/lib/nxt_http_client/response_handler.rb +13 -9
- data/lib/nxt_http_client/undefined.rb +4 -0
- data/lib/nxt_http_client/version.rb +1 -1
- data/nxt_http_client.gemspec +2 -1
- metadata +23 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 851b1b29746c3a64735f9cf29c98378fbe49ada821dbcbc8c69ae28312596d0b
|
4
|
+
data.tar.gz: 3cfcbc1b1a0db863ede586976f83afdbb96b1ca06596bd0d7c9610e04171ca3b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5c2297ee0db6aa262e4becba99f02f26e274b03a1858cc5bb3359a8f9a2fabfd0098cfdf0e5b018e13ecdd3afafa41a25251ee6131b86a41ff5d9346d80b8ea9
|
7
|
+
data.tar.gz: bb3e3ab2e9a0e884f1b4968e88a7dcb5c56b6a4e142c4454b3a6ac83c6504a5a99826ad5e412553d77ed871216e09e60e3a1067ee8e6a1ad79d040bc843e1350
|
data/.circleci/config.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
# v1.0.0 2021-01-10
|
2
|
+
|
3
|
+
### Breaking changes
|
4
|
+
- renamed register_response_handler to _response_handler
|
5
|
+
- replace before_fire and after_fire hooks with proper callbacks before, after and around fire
|
6
|
+
|
7
|
+
### Updated
|
8
|
+
- error now includes more information about request and headers
|
9
|
+
|
1
10
|
# v0.3.4 2021-01-05
|
2
11
|
|
3
12
|
### Updated
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
nxt_http_client (0.
|
4
|
+
nxt_http_client (1.0.0)
|
5
5
|
activesupport (~> 6.0)
|
6
6
|
nxt_registry
|
7
7
|
typhoeus
|
@@ -9,7 +9,7 @@ PATH
|
|
9
9
|
GEM
|
10
10
|
remote: https://rubygems.org/
|
11
11
|
specs:
|
12
|
-
activesupport (6.1.
|
12
|
+
activesupport (6.1.1)
|
13
13
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
14
14
|
i18n (>= 1.6, < 2)
|
15
15
|
minitest (>= 5.1)
|
@@ -24,12 +24,12 @@ GEM
|
|
24
24
|
diff-lcs (1.4.4)
|
25
25
|
ethon (0.12.0)
|
26
26
|
ffi (>= 1.3.0)
|
27
|
-
ffi (1.
|
27
|
+
ffi (1.14.2)
|
28
28
|
hashdiff (1.0.1)
|
29
29
|
i18n (1.8.7)
|
30
30
|
concurrent-ruby (~> 1.0)
|
31
31
|
method_source (1.0.0)
|
32
|
-
minitest (5.14.
|
32
|
+
minitest (5.14.3)
|
33
33
|
nxt_registry (0.3.6)
|
34
34
|
activesupport
|
35
35
|
nxt_vcr_harness (0.1.4)
|
@@ -57,6 +57,7 @@ GEM
|
|
57
57
|
rspec-support (3.10.1)
|
58
58
|
rspec_junit_formatter (0.4.1)
|
59
59
|
rspec-core (>= 2, < 4, != 2.12.0)
|
60
|
+
timecop (0.9.2)
|
60
61
|
typhoeus (1.4.0)
|
61
62
|
ethon (>= 0.9.0)
|
62
63
|
tzinfo (2.0.4)
|
@@ -69,10 +70,10 @@ GEM
|
|
69
70
|
zeitwerk (2.4.2)
|
70
71
|
|
71
72
|
PLATFORMS
|
72
|
-
|
73
|
+
x86_64-darwin-19
|
73
74
|
|
74
75
|
DEPENDENCIES
|
75
|
-
bundler (~>
|
76
|
+
bundler (~> 2.2)
|
76
77
|
nxt_http_client!
|
77
78
|
nxt_vcr_harness
|
78
79
|
pry
|
@@ -80,8 +81,9 @@ DEPENDENCIES
|
|
80
81
|
redis
|
81
82
|
rspec (~> 3.0)
|
82
83
|
rspec_junit_formatter
|
84
|
+
timecop
|
83
85
|
vcr
|
84
86
|
webmock
|
85
87
|
|
86
88
|
BUNDLED WITH
|
87
|
-
|
89
|
+
2.2.4
|
data/README.md
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
# NxtHttpClient
|
2
2
|
|
3
|
-
Build http clients with ease. NxtHttpClient is a simple DSL on top of the
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
Build http clients with ease. NxtHttpClient is a simple DSL on top of the [typhoeus](https://github.com/typhoeus/typhoeus)
|
4
|
+
gem. NxtHttpClient mostly provides you a simple configuration functionality to setup http connections on the class level.
|
5
|
+
Furthermore it's mostly a callback framework that allows you to seamlessly handle your responses. Since it's is just a simple
|
6
|
+
layer on top of [typhoeus](https://github.com/typhoeus/typhoeus) it also allows to access and configure the original
|
7
|
+
`Typhoeus::Request` before making a request.
|
8
|
+
|
7
9
|
|
8
10
|
## Installation
|
9
11
|
|
@@ -23,16 +25,33 @@ Or install it yourself as:
|
|
23
25
|
|
24
26
|
## Usage
|
25
27
|
|
28
|
+
A typical client could look something like this:
|
29
|
+
|
26
30
|
```ruby
|
27
|
-
class
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
31
|
+
class UserFetcher < Client
|
32
|
+
def initialize(id)
|
33
|
+
@url = ".../users/#{id}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def call
|
37
|
+
get(url) do |response_handler|
|
38
|
+
response_handler.on(:success) do |response|
|
39
|
+
JSON(response.body)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
32
43
|
|
33
|
-
|
34
|
-
# your request not to be cached if not used properly
|
44
|
+
private
|
35
45
|
|
46
|
+
attr_reader :url
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
In order to setup a shared configuration you would therefore setup a client base class. The configuration and any
|
51
|
+
response handler or callbacks you setup in your base class are then inherited to your concrete client implementations.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
class Client < NxtHttpClient
|
36
55
|
configure do |config|
|
37
56
|
config.base_url = 'www.example.com'
|
38
57
|
config.request_options.deep_merge!(
|
@@ -43,54 +62,14 @@ class MyClient < NxtHttpClient
|
|
43
62
|
config.x_request_id_proc = -> { ('a'..'z').to_a.shuffle.take(10).join }
|
44
63
|
end
|
45
64
|
|
46
|
-
|
47
|
-
|
48
|
-
Raven.extra_context(error_details: error.to_h) # call error.to_h to inspect request and response
|
49
|
-
raise StandardError, "I can't handle this: #{response.code}"
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
# Will be called before fire so you can reconfigure your handler before fire
|
54
|
-
before_fire do |client, request, handler|
|
55
|
-
handler.on!(200) do |response|
|
56
|
-
# ...
|
57
|
-
end
|
65
|
+
log do |info|
|
66
|
+
Rails.logger.info(info)
|
58
67
|
end
|
59
68
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
after_fire do |client, request, response, result, error|
|
66
|
-
if error
|
67
|
-
raise error
|
68
|
-
else
|
69
|
-
result
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def fetch_details
|
74
|
-
fire('details', method: :get) do |handler|
|
75
|
-
handler.on(:success) do |response|
|
76
|
-
response.body
|
77
|
-
end
|
78
|
-
|
79
|
-
handler.on(404) do |response|
|
80
|
-
raise StandardError, '404'
|
81
|
-
end
|
82
|
-
|
83
|
-
# You can also fuzzy match response codes using the wildcard *
|
84
|
-
handler.on('5**') do |response|
|
85
|
-
raise StandardError, 'This is bad'
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
# there are also convenience methods for all http verbs (get post patch put delete head)
|
91
|
-
def update
|
92
|
-
post(params: { my: 'payload' }) do |handler|
|
93
|
-
# ...
|
69
|
+
response_handler do |handler|
|
70
|
+
handler.on(:error) do |response|
|
71
|
+
Raven.extra_context(error_details: error.to_h)
|
72
|
+
raise StandardError, "I can't handle this: #{response.code}"
|
94
73
|
end
|
95
74
|
end
|
96
75
|
end
|
@@ -98,11 +77,10 @@ end
|
|
98
77
|
|
99
78
|
### HTTP Methods
|
100
79
|
|
101
|
-
|
80
|
+
In order to build a request and execute it NxtHttpClient implements all http standard methods.
|
102
81
|
|
103
82
|
```ruby
|
104
|
-
class
|
105
|
-
|
83
|
+
class Client < NxtHttpClient
|
106
84
|
def initialize(url)
|
107
85
|
@url = url
|
108
86
|
end
|
@@ -131,21 +109,22 @@ class MyClient < NxtHttpClient
|
|
131
109
|
end
|
132
110
|
```
|
133
111
|
|
134
|
-
|
135
112
|
### configure
|
136
113
|
|
137
|
-
Register default request options on the class level. Available options are `request_options` that are passed
|
138
|
-
the underlying Typhoeus Request. Then there is `base_url` and `x_request_id_proc`.
|
114
|
+
Register your default request options on the class level. Available options are `request_options` that are passed
|
115
|
+
directly to the underlying Typhoeus Request. Then there is `base_url` and `x_request_id_proc`.
|
139
116
|
|
140
|
-
###
|
117
|
+
### response_handler
|
141
118
|
|
142
|
-
Register a default response handler for your client class.
|
143
|
-
|
119
|
+
Register a default response handler for your client class. You can reconfigure or overwrite this in subclasses and
|
120
|
+
on the instance level.
|
144
121
|
|
145
122
|
### fire
|
146
123
|
|
147
|
-
|
148
|
-
|
124
|
+
All http methods internally are delegate to `fire('uri', **request_options)`. Since `fire` is a public method you can
|
125
|
+
also use it to fire your requests and use the response handler to register callbacks for specific responses.
|
126
|
+
|
127
|
+
Registered callbacks have a hierarchy by which they are executed. Specific callbacks will come first
|
149
128
|
and more common callbacks will come later in case none of the specific callbacks matched. It this is not what you want you
|
150
129
|
can simply put the logic you need into one common callback that is called in any case. You can also use strings with wildcards
|
151
130
|
to match a group of response by status code. `handler.on('4**') { ... }` basically would match all client errors.
|
@@ -184,15 +163,83 @@ end
|
|
184
163
|
|
185
164
|
### Callbacks around fire
|
186
165
|
|
187
|
-
|
188
|
-
|
189
|
-
|
166
|
+
Next to implementing callbacks for handling responses there are also callbacks around making requests. Note tht you can
|
167
|
+
have as many callbacks as you want. In case you need to reset them because you do not want to inherit them from your
|
168
|
+
parent class (might be a smell when you need to...) you can reset callbacks via `clear_fire_callbacks` on the class level.
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
|
172
|
+
clear_fire_callbacks # Call this to clear callbacks setup in the parent class
|
173
|
+
|
174
|
+
before_fire do |client, request, response_handler|
|
175
|
+
# here you have access to the client, request and response_handler
|
176
|
+
end
|
177
|
+
|
178
|
+
around_fire do |client, request, response_handler, fire|
|
179
|
+
# here you have access to the client, request and response_handler
|
180
|
+
fire.call # You have to call fire here and return the result to the next callback in the chain
|
181
|
+
end
|
182
|
+
|
183
|
+
after_fire do |client, request, response, result, error|
|
184
|
+
result # The result of the last callback in the chain is the result of fire!
|
185
|
+
end
|
186
|
+
```
|
187
|
+
|
190
188
|
|
191
189
|
### NxtHttpClient::Error
|
192
190
|
|
193
191
|
NxtHttpClient also provides an error base class that you might want to use as the base for your client errors.
|
194
192
|
It comes with a nice set of useful methods. You can ask the error for the request and response options since it
|
195
|
-
requires the response for initialization.
|
193
|
+
requires the response for initialization. Furthermore it has a handy `to_h` method that provides you all info about
|
194
|
+
the request and response.
|
195
|
+
|
196
|
+
### Logging
|
197
|
+
|
198
|
+
NxtHttpClient also comes with a log method on the class level that you can pass a proc if you want to log your request.
|
199
|
+
Your proc needs to accept an argument in order to get access to information about the request and response made.
|
200
|
+
|
201
|
+
```ruby
|
202
|
+
log do |info|
|
203
|
+
Rails.logger.info(info)
|
204
|
+
end
|
205
|
+
|
206
|
+
# info is a hash that is implemented as follows:
|
207
|
+
|
208
|
+
{
|
209
|
+
client: client,
|
210
|
+
started_at: started_at,
|
211
|
+
request: request,
|
212
|
+
finished_at: now,
|
213
|
+
elapsed_time_in_milliseconds: finished_at - started_at,
|
214
|
+
response: request.response,
|
215
|
+
http_status: request.response&.code
|
216
|
+
}
|
217
|
+
```
|
218
|
+
|
219
|
+
### Caching
|
220
|
+
|
221
|
+
Typhoeus ships with caching built in. Checkout the [typhoeus](https://github.com/typhoeus/typhoeus) docu to figure out
|
222
|
+
how to set it up. NxtHttpClient builds some functionality on top of this and offer to cache requests within the current
|
223
|
+
thread or globally. You can simply make use of it by providing one of the caching options `:thread` or`:global` as config
|
224
|
+
request option or the actual request options when building the request.
|
225
|
+
|
226
|
+
```ruby
|
227
|
+
class Client < NxtHttpClient::Client
|
228
|
+
configure do |config|
|
229
|
+
config.request_options = { cache: :thread }
|
230
|
+
end
|
231
|
+
|
232
|
+
response_handler do |handler|
|
233
|
+
handler.on(200) do |response|
|
234
|
+
# ...
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def call
|
239
|
+
get('.../url.com', cache: :thread) # configure caching per request level
|
240
|
+
end
|
241
|
+
end
|
242
|
+
```
|
196
243
|
|
197
244
|
## Development
|
198
245
|
|
@@ -202,7 +249,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
202
249
|
|
203
250
|
## Contributing
|
204
251
|
|
205
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
252
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/nxt-insurance/nxt_http_client.
|
206
253
|
|
207
254
|
## License
|
208
255
|
|
data/lib/nxt_http_client.rb
CHANGED
@@ -3,8 +3,12 @@ require 'typhoeus'
|
|
3
3
|
require 'nxt_registry'
|
4
4
|
|
5
5
|
require 'nxt_http_client/version'
|
6
|
+
require 'nxt_http_client/headers'
|
6
7
|
require 'nxt_http_client/response_handler'
|
7
|
-
require 'nxt_http_client/
|
8
|
+
require 'nxt_http_client/config'
|
9
|
+
require 'nxt_http_client/undefined'
|
10
|
+
require 'nxt_http_client/logger'
|
11
|
+
require 'nxt_http_client/fire_callbacks'
|
8
12
|
require 'nxt_http_client/client_dsl'
|
9
13
|
require 'nxt_http_client/client'
|
10
14
|
require 'nxt_http_client/error'
|
@@ -11,42 +11,30 @@ module NxtHttpClient
|
|
11
11
|
Typhoeus::Request.new(url, **opts.symbolize_keys)
|
12
12
|
end
|
13
13
|
|
14
|
-
delegate :before_fire_callback, :after_fire_callback, to: :class
|
15
|
-
|
16
14
|
def fire(url = '', **opts, &block)
|
17
|
-
response_handler = opts
|
18
|
-
response_handler.configure(&block) if block_given?
|
15
|
+
response_handler = build_response_handler(opts[:response_handler], &block)
|
19
16
|
request = build_request(url, **opts.except(:response_handler))
|
20
|
-
before_fire_callback && instance_exec(self, request, response_handler, &before_fire_callback)
|
21
|
-
|
22
|
-
if response_handler.callbacks.resolve('headers')
|
23
|
-
request.on_headers do |response|
|
24
|
-
response_handler.eval_callback(self, 'headers', response)
|
25
|
-
end
|
26
|
-
end
|
27
17
|
|
28
|
-
if response_handler.callbacks.resolve('body')
|
29
|
-
request.on_body do |response|
|
30
|
-
response_handler.eval_callback(self, 'body', response)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
result = nil
|
35
18
|
current_error = nil
|
19
|
+
result = nil
|
20
|
+
|
21
|
+
setup_on_headers_callback(request, response_handler)
|
22
|
+
setup_on_body_callback(request, response_handler)
|
36
23
|
|
37
24
|
request.on_complete do |response|
|
38
25
|
result = callback_or_response(response, response_handler)
|
26
|
+
end
|
27
|
+
|
28
|
+
run_before_fire_callbacks(request, response_handler)
|
29
|
+
|
30
|
+
run_around_fire_callbacks(request, response_handler) do
|
31
|
+
request.run
|
39
32
|
rescue StandardError => error
|
40
33
|
current_error = error
|
41
|
-
ensure
|
42
|
-
if after_fire_callback
|
43
|
-
result = instance_exec(self, request, response, result, current_error, &after_fire_callback)
|
44
|
-
else
|
45
|
-
result || (raise current_error)
|
46
|
-
end
|
47
34
|
end
|
48
35
|
|
49
|
-
request.
|
36
|
+
result = run_after_fire_callbacks(request, request.response, result, current_error)
|
37
|
+
result || (raise current_error if current_error)
|
50
38
|
|
51
39
|
result
|
52
40
|
end
|
@@ -60,7 +48,7 @@ module NxtHttpClient
|
|
60
48
|
private
|
61
49
|
|
62
50
|
def build_url(opts, url)
|
63
|
-
base_url = opts.delete(:base_url) ||
|
51
|
+
base_url = opts.delete(:base_url) || config.base_url
|
64
52
|
url = [base_url, url].reject(&:blank?).join('/')
|
65
53
|
|
66
54
|
url_without_duplicated_hashes(url)
|
@@ -68,11 +56,11 @@ module NxtHttpClient
|
|
68
56
|
end
|
69
57
|
|
70
58
|
def build_headers(opts)
|
71
|
-
opts =
|
59
|
+
opts = config.request_options.with_indifferent_access.deep_merge(opts.with_indifferent_access)
|
72
60
|
opts[:headers] ||= {}
|
73
61
|
|
74
|
-
if
|
75
|
-
opts[:headers][
|
62
|
+
if config.x_request_id_proc
|
63
|
+
opts[:headers][XRequestId] ||= config.x_request_id_proc.call
|
76
64
|
end
|
77
65
|
|
78
66
|
build_cache_header(opts)
|
@@ -83,8 +71,8 @@ module NxtHttpClient
|
|
83
71
|
self.class.response_handler.dup
|
84
72
|
end
|
85
73
|
|
86
|
-
def
|
87
|
-
self.class.
|
74
|
+
def config
|
75
|
+
self.class.config
|
88
76
|
end
|
89
77
|
|
90
78
|
def build_cache_header(opts)
|
@@ -92,13 +80,13 @@ module NxtHttpClient
|
|
92
80
|
strategy = opts.delete(:cache)
|
93
81
|
|
94
82
|
case strategy.to_s
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
83
|
+
when 'thread'
|
84
|
+
cache_key = Thread.current[:nxt_http_client_cache_key] ||= "#{SecureRandom.base58}::#{DateTime.current}"
|
85
|
+
opts[:headers].reverse_merge!(cache_key: cache_key)
|
86
|
+
when 'global'
|
87
|
+
opts[:headers].delete(:cache_key)
|
88
|
+
else
|
89
|
+
raise ArgumentError, "Cache strategy unknown: #{strategy}. Options are #{CACHE_STRATEGIES}"
|
102
90
|
end
|
103
91
|
end
|
104
92
|
end
|
@@ -116,5 +104,56 @@ module NxtHttpClient
|
|
116
104
|
callback = response_handler.callback_for_response(response)
|
117
105
|
callback && instance_exec(response, &callback) || response
|
118
106
|
end
|
107
|
+
|
108
|
+
def build_response_handler(handler, &block)
|
109
|
+
response_handler = handler || dup_handler_from_class || NxtHttpClient::ResponseHandler.new
|
110
|
+
response_handler.configure(&block) if block_given?
|
111
|
+
response_handler
|
112
|
+
end
|
113
|
+
|
114
|
+
def run_before_fire_callbacks(request, response_handler)
|
115
|
+
callbacks.run_before(target: self, request: request, response_handler: response_handler)
|
116
|
+
end
|
117
|
+
|
118
|
+
def run_around_fire_callbacks(request, response_handler, &fire)
|
119
|
+
callbacks.run_around(
|
120
|
+
target: self,
|
121
|
+
request: request,
|
122
|
+
response_handler: response_handler,
|
123
|
+
fire: fire
|
124
|
+
)
|
125
|
+
end
|
126
|
+
|
127
|
+
def run_after_fire_callbacks(request, response, result, current_error)
|
128
|
+
return result unless callbacks.any_after_callbacks?
|
129
|
+
|
130
|
+
callbacks.run_after(
|
131
|
+
target: self,
|
132
|
+
request: request,
|
133
|
+
response: response,
|
134
|
+
result: result,
|
135
|
+
error: current_error
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
def setup_on_headers_callback(request, response_handler)
|
140
|
+
return unless response_handler.callbacks.resolve('headers')
|
141
|
+
|
142
|
+
request.on_headers do |response|
|
143
|
+
response_handler.eval_callback(self, 'headers', response)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def setup_on_body_callback(request, response_handler)
|
148
|
+
return unless response_handler.callbacks.resolve('body')
|
149
|
+
|
150
|
+
request.on_body do |response|
|
151
|
+
response_handler.eval_callback(self, 'body', response)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def callbacks
|
156
|
+
@callbacks ||= self.class.callbacks
|
157
|
+
end
|
119
158
|
end
|
120
159
|
end
|
@@ -1,57 +1,79 @@
|
|
1
1
|
module NxtHttpClient
|
2
2
|
module ClientDsl
|
3
3
|
def configure(opts = {}, &block)
|
4
|
-
opts.each { |k, v|
|
5
|
-
|
6
|
-
|
4
|
+
opts.each { |k, v| config.send(k, v) }
|
5
|
+
config.tap { |d| block.call(d) }
|
6
|
+
config
|
7
7
|
end
|
8
8
|
|
9
|
-
def
|
10
|
-
@
|
9
|
+
def log(&block)
|
10
|
+
@logger ||= block || dup_option_from_ancestor(:@logger)
|
11
|
+
|
12
|
+
return unless @logger.present?
|
13
|
+
logger = @logger
|
14
|
+
|
15
|
+
around_fire do |client, request, response_handler, fire|
|
16
|
+
Logger.new(logger).call(client, request, response_handler, fire)
|
17
|
+
end
|
11
18
|
end
|
12
19
|
|
13
|
-
def
|
14
|
-
|
20
|
+
def clear_fire_callbacks(*kinds)
|
21
|
+
callbacks.clear(*kinds)
|
22
|
+
end
|
23
|
+
|
24
|
+
def before_fire(&block)
|
25
|
+
callbacks.register(:before, block)
|
15
26
|
end
|
16
27
|
|
17
28
|
def after_fire(&block)
|
18
|
-
|
29
|
+
callbacks.register(:after, block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def around_fire(&block)
|
33
|
+
callbacks.register(:around, block)
|
19
34
|
end
|
20
35
|
|
21
|
-
def
|
22
|
-
@
|
36
|
+
def config
|
37
|
+
@config ||= dup_option_from_ancestor(:@config) { Config.new }
|
23
38
|
end
|
24
39
|
|
25
|
-
def
|
26
|
-
@
|
40
|
+
def callbacks
|
41
|
+
@callbacks ||= dup_option_from_ancestor(:@callbacks) { FireCallbacks.new }
|
27
42
|
end
|
28
43
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
44
|
+
def response_handler(handler = Undefined.new, &block)
|
45
|
+
if undefined?(handler)
|
46
|
+
@response_handler ||= dup_option_from_ancestor(:@response_handler) { NxtHttpClient::ResponseHandler.new }
|
47
|
+
else
|
48
|
+
@response_handler = handler
|
49
|
+
end
|
50
|
+
|
32
51
|
@response_handler.configure(&block) if block_given?
|
33
52
|
@response_handler
|
34
53
|
end
|
35
54
|
|
36
|
-
|
37
|
-
|
38
|
-
|
55
|
+
alias_method :response_handler, :response_handler
|
56
|
+
|
57
|
+
private
|
39
58
|
|
40
59
|
def client_ancestors
|
41
60
|
ancestors.select { |ancestor| ancestor <= NxtHttpClient::Client }
|
42
61
|
end
|
43
62
|
|
44
|
-
def
|
45
|
-
client = client_ancestors.find { |c| c.instance_variable_get(
|
46
|
-
|
47
|
-
client.instance_variable_get(instance_variable_name)
|
63
|
+
def option_from_ancestors(name)
|
64
|
+
client = client_ancestors.find { |c| c.instance_variable_get(name) }
|
65
|
+
client && client.instance_variable_get(name)
|
48
66
|
end
|
49
67
|
|
50
|
-
def
|
51
|
-
result =
|
68
|
+
def dup_option_from_ancestor(name)
|
69
|
+
result = option_from_ancestors(name).dup
|
52
70
|
return result unless block_given?
|
53
71
|
|
54
72
|
result || yield
|
55
73
|
end
|
74
|
+
|
75
|
+
def undefined?(value)
|
76
|
+
value.is_a?(Undefined)
|
77
|
+
end
|
56
78
|
end
|
57
79
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module NxtHttpClient
|
2
2
|
CONFIGURABLE_OPTIONS = %i[request_options base_url x_request_id_proc].freeze
|
3
3
|
|
4
|
-
|
4
|
+
Config = Struct.new('Config', *CONFIGURABLE_OPTIONS) do
|
5
5
|
def initialize(request_options: ActiveSupport::HashWithIndifferentAccess.new, base_url: '', x_request_id_proc: nil)
|
6
6
|
self.request_options = request_options
|
7
7
|
self.base_url = base_url
|
@@ -13,7 +13,7 @@ module NxtHttpClient
|
|
13
13
|
alias_method :to_s, :message
|
14
14
|
|
15
15
|
def default_message
|
16
|
-
"
|
16
|
+
"#{self.class.name}::#{response_code}"
|
17
17
|
end
|
18
18
|
|
19
19
|
def to_h
|
@@ -23,12 +23,14 @@ module NxtHttpClient
|
|
23
23
|
response_code: response_code,
|
24
24
|
request_options: request_options,
|
25
25
|
response_headers: response_headers,
|
26
|
-
|
26
|
+
request_headers: request_headers,
|
27
|
+
body: body,
|
28
|
+
x_request_id: x_request_id
|
27
29
|
}
|
28
30
|
end
|
29
31
|
|
30
32
|
def body
|
31
|
-
if response_content_type&.starts_with?(
|
33
|
+
if response_content_type&.starts_with?(ApplicationJson)
|
32
34
|
JSON.parse(response.body)
|
33
35
|
else
|
34
36
|
response.body
|
@@ -49,6 +51,10 @@ module NxtHttpClient
|
|
49
51
|
request.url
|
50
52
|
end
|
51
53
|
|
54
|
+
def x_request_id
|
55
|
+
request_headers[XRequestId]
|
56
|
+
end
|
57
|
+
|
52
58
|
def request_options
|
53
59
|
@request_options ||= (request.original_options || {}).with_indifferent_access
|
54
60
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module NxtHttpClient
|
2
|
+
class FireCallbacks
|
3
|
+
def initialize
|
4
|
+
@registry = build_registry
|
5
|
+
end
|
6
|
+
|
7
|
+
def clear(*kinds)
|
8
|
+
Array(kinds).each { |kind| registry.register!(kind, []) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def register(kind, callback)
|
12
|
+
registry.resolve!(kind) << callback
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(target, kind, *args)
|
16
|
+
registry.resolve!(kind).each do |callback|
|
17
|
+
run_callback(target, callback, *args)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def run_before(target:, request:, response_handler:)
|
22
|
+
registry.resolve!(:before).each do |callback|
|
23
|
+
run_callback(target, callback, *[target, request, response_handler])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def run_after(target:, request:, response:, result:, error:)
|
28
|
+
registry.resolve!(:after).inject(result) do |_, callback|
|
29
|
+
run_callback(target, callback, *[target, request, response, result, error])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def any_after_callbacks?
|
34
|
+
registry.resolve(:after).any?
|
35
|
+
end
|
36
|
+
|
37
|
+
def run_around(target:, request:, response_handler:, fire:)
|
38
|
+
around_callbacks = registry.resolve!(:around)
|
39
|
+
return fire.call unless around_callbacks.any?
|
40
|
+
|
41
|
+
args = *[target, request, response_handler]
|
42
|
+
callback_chain = around_callbacks.reverse.inject(fire) do |previous, callback|
|
43
|
+
-> { target.instance_exec(*args, previous, &callback) }
|
44
|
+
end
|
45
|
+
|
46
|
+
callback_chain.call
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_reader :registry
|
52
|
+
|
53
|
+
def run_callback(target, callback, *args)
|
54
|
+
args = args.take(callback.arity)
|
55
|
+
target.instance_exec(*args, &callback)
|
56
|
+
end
|
57
|
+
|
58
|
+
def build_registry
|
59
|
+
NxtRegistry::Registry.new(:callbacks) do
|
60
|
+
register(:before, [])
|
61
|
+
register(:around, [])
|
62
|
+
register(:after, [])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module NxtHttpClient
|
2
|
+
class Logger
|
3
|
+
def initialize(logger)
|
4
|
+
@logger = logger
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(client, request, _response_handler, fire)
|
8
|
+
started_at = now
|
9
|
+
error = nil
|
10
|
+
result = nil
|
11
|
+
|
12
|
+
options = {
|
13
|
+
client: client,
|
14
|
+
started_at: started_at,
|
15
|
+
request: request
|
16
|
+
}
|
17
|
+
|
18
|
+
begin
|
19
|
+
result = fire.call
|
20
|
+
rescue => e
|
21
|
+
error = e
|
22
|
+
options.merge!(error: e)
|
23
|
+
ensure
|
24
|
+
finished_at = now
|
25
|
+
options.merge!(
|
26
|
+
finished_at: now,
|
27
|
+
elapsed_time_in_milliseconds: finished_at - started_at,
|
28
|
+
response: request.response,
|
29
|
+
http_status: request.response&.code
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
logger.call(options)
|
34
|
+
raise error if error
|
35
|
+
|
36
|
+
result
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
attr_reader :logger
|
42
|
+
|
43
|
+
def now
|
44
|
+
Time.current.to_i * 1000
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -10,8 +10,6 @@ module NxtHttpClient
|
|
10
10
|
attr_accessor :result
|
11
11
|
|
12
12
|
def eval_callback(target, key, response)
|
13
|
-
return unless callbacks.resolve!(key)
|
14
|
-
|
15
13
|
target.instance_exec(response, &callbacks.resolve(key))
|
16
14
|
end
|
17
15
|
|
@@ -20,6 +18,8 @@ module NxtHttpClient
|
|
20
18
|
end
|
21
19
|
|
22
20
|
def register_callback(code, overwrite: false, &block)
|
21
|
+
code = regex_or_code(code)
|
22
|
+
|
23
23
|
if overwrite
|
24
24
|
callbacks.register!(code, block)
|
25
25
|
else
|
@@ -36,14 +36,10 @@ module NxtHttpClient
|
|
36
36
|
|
37
37
|
def callback_for_response(response)
|
38
38
|
key_from_response = response.code.to_s
|
39
|
-
|
40
|
-
|
41
|
-
first_matching_key = callbacks.keys.sort.reverse.find do |key|
|
42
|
-
regex_key = key.gsub('*', '[0-9]{1}')
|
43
|
-
key_from_response =~ /\A#{regex_key}\z/
|
44
|
-
end
|
39
|
+
matching_any_callback = callbacks.resolve('any')
|
40
|
+
return matching_any_callback if matching_any_callback.present?
|
45
41
|
|
46
|
-
|
42
|
+
callbacks.resolve(key_from_response) ||
|
47
43
|
response.success? && callbacks.resolve('success') ||
|
48
44
|
response.timed_out? && callbacks.resolve('timed_out') ||
|
49
45
|
!response.success? && callbacks.resolve('error') ||
|
@@ -60,6 +56,14 @@ module NxtHttpClient
|
|
60
56
|
|
61
57
|
private
|
62
58
|
|
59
|
+
def regex_or_code(key)
|
60
|
+
return key if key.is_a?(Regexp)
|
61
|
+
return key if key.to_s.exclude?('*')
|
62
|
+
|
63
|
+
regex_key = key.to_s.gsub('*', '[0-9]{1}')
|
64
|
+
/\A#{regex_key}\z/
|
65
|
+
end
|
66
|
+
|
63
67
|
def raise_callback_already_registered(code)
|
64
68
|
msg = "Callback already registered for status: #{code}."
|
65
69
|
msg << ' Use bang method to overwrite the callback.'
|
data/nxt_http_client.gemspec
CHANGED
@@ -39,7 +39,7 @@ Gem::Specification.new do |spec|
|
|
39
39
|
spec.add_dependency 'activesupport', '~> 6.0'
|
40
40
|
spec.add_dependency 'nxt_registry'
|
41
41
|
|
42
|
-
spec.add_development_dependency 'bundler', '~>
|
42
|
+
spec.add_development_dependency 'bundler', '~> 2.2'
|
43
43
|
spec.add_development_dependency 'rake', '~> 13.0'
|
44
44
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
45
45
|
spec.add_development_dependency 'pry'
|
@@ -48,4 +48,5 @@ Gem::Specification.new do |spec|
|
|
48
48
|
spec.add_development_dependency 'nxt_vcr_harness'
|
49
49
|
spec.add_development_dependency 'redis'
|
50
50
|
spec.add_development_dependency 'rspec_junit_formatter'
|
51
|
+
spec.add_development_dependency 'timecop'
|
51
52
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nxt_http_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andreas Robecke
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: exe
|
13
13
|
cert_chain: []
|
14
|
-
date: 2021-01-
|
14
|
+
date: 2021-01-10 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: typhoeus
|
@@ -61,14 +61,14 @@ dependencies:
|
|
61
61
|
requirements:
|
62
62
|
- - "~>"
|
63
63
|
- !ruby/object:Gem::Version
|
64
|
-
version: '
|
64
|
+
version: '2.2'
|
65
65
|
type: :development
|
66
66
|
prerelease: false
|
67
67
|
version_requirements: !ruby/object:Gem::Requirement
|
68
68
|
requirements:
|
69
69
|
- - "~>"
|
70
70
|
- !ruby/object:Gem::Version
|
71
|
-
version: '
|
71
|
+
version: '2.2'
|
72
72
|
- !ruby/object:Gem::Dependency
|
73
73
|
name: rake
|
74
74
|
requirement: !ruby/object:Gem::Requirement
|
@@ -181,6 +181,20 @@ dependencies:
|
|
181
181
|
- - ">="
|
182
182
|
- !ruby/object:Gem::Version
|
183
183
|
version: '0'
|
184
|
+
- !ruby/object:Gem::Dependency
|
185
|
+
name: timecop
|
186
|
+
requirement: !ruby/object:Gem::Requirement
|
187
|
+
requirements:
|
188
|
+
- - ">="
|
189
|
+
- !ruby/object:Gem::Version
|
190
|
+
version: '0'
|
191
|
+
type: :development
|
192
|
+
prerelease: false
|
193
|
+
version_requirements: !ruby/object:Gem::Requirement
|
194
|
+
requirements:
|
195
|
+
- - ">="
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
184
198
|
description: NxtHttpClinet allows you to easily create and configure http clients.
|
185
199
|
email:
|
186
200
|
- a.robecke@getsafe.de
|
@@ -203,9 +217,13 @@ files:
|
|
203
217
|
- lib/nxt_http_client.rb
|
204
218
|
- lib/nxt_http_client/client.rb
|
205
219
|
- lib/nxt_http_client/client_dsl.rb
|
206
|
-
- lib/nxt_http_client/
|
220
|
+
- lib/nxt_http_client/config.rb
|
207
221
|
- lib/nxt_http_client/error.rb
|
222
|
+
- lib/nxt_http_client/fire_callbacks.rb
|
223
|
+
- lib/nxt_http_client/headers.rb
|
224
|
+
- lib/nxt_http_client/logger.rb
|
208
225
|
- lib/nxt_http_client/response_handler.rb
|
226
|
+
- lib/nxt_http_client/undefined.rb
|
209
227
|
- lib/nxt_http_client/version.rb
|
210
228
|
- nxt_http_client.gemspec
|
211
229
|
homepage: https://github.com/nxt-insurance
|