network-client 1.1.6 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/network-client.rb +2 -2
- data/lib/network/client.rb +347 -0
- data/lib/network/version.rb +3 -0
- data/network-client.gemspec +2 -2
- metadata +4 -4
- data/lib/network-client/core.rb +0 -239
- data/lib/network-client/version.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 69a4eea5e01f0c07a0728a987d1e8722edf1b228
|
4
|
+
data.tar.gz: b1a9d49d2f9e73c9a88eb3bd3956b2a1ebfe7202
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba909db3bb7de0357673b06bd444b107810bdf1796afd2dbf2e8fc3669c072157dbd0f31af601903c0ec895c55419ae83534e7d56710ce147433a724c0479cef
|
7
|
+
data.tar.gz: cd044bc44966fa7292741f9088a9be74ce2409e4099bfba6ec94fcd3582e755a071e09db9ce838f99966cc87612812896efc1174fa54659b1525b195ba4b3ff0
|
data/Gemfile.lock
CHANGED
data/lib/network-client.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
require_relative "network
|
2
|
-
require_relative "network
|
1
|
+
require_relative "network/client"
|
2
|
+
require_relative "network/version"
|
@@ -0,0 +1,347 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'openssl'
|
3
|
+
require 'json'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
module Network
|
7
|
+
class Client
|
8
|
+
DEFAULT_HEADERS = { 'accept' => 'application/json',
|
9
|
+
'Content-Type' => 'application/json' }.freeze
|
10
|
+
# The success response template.
|
11
|
+
#
|
12
|
+
# Represents the return of rest-like methods holding two values:
|
13
|
+
# HTTP response code, and body <em>(parsed as json if request type is json)</em>.
|
14
|
+
Response = Struct.new(:code, :body)
|
15
|
+
|
16
|
+
# Stamp in front of each log written by client +@logger+.
|
17
|
+
LOG_TAG = '[NETWORK CLIENT]:'.freeze
|
18
|
+
|
19
|
+
attr_reader :username, :password, :default_headers, :logger, :tries, :user_agent,
|
20
|
+
:bearer_token, :auth_token_header
|
21
|
+
|
22
|
+
# Error list for retrying strategy.
|
23
|
+
# Initially contains common errors encountered usually in net calls.
|
24
|
+
attr_accessor :errors_to_recover
|
25
|
+
|
26
|
+
# Error list for stop and propagate strategy.
|
27
|
+
# Takes priority over +@errors_to_recover+.
|
28
|
+
# Do not assign ancestor error classes here that prevent retry for descendant ones.
|
29
|
+
attr_accessor :errors_to_propagate
|
30
|
+
|
31
|
+
##
|
32
|
+
# Construct and prepare client for requests targeting +endpoint+.
|
33
|
+
#
|
34
|
+
# == Parameters:
|
35
|
+
#
|
36
|
+
# [*endpoint*] +string+ Uri for the host with schema and port.
|
37
|
+
# any other segment like paths will be discarded.
|
38
|
+
# [*tries*] +integer+ to specify how many is to repeat failed calls. Default is 2.
|
39
|
+
# [*headers*] +hash+ to contain any common HTTP headers to be set in client calls.
|
40
|
+
# [*username*] +string+ for HTTP basic authentication. Applies on all requests. Default to nil.
|
41
|
+
# [*password*] +string+ for HTTP basic authentication. Applies on all requests. Default to nil.
|
42
|
+
# [*user_agent*] +string+ Specifies the _User-Agent_ header value when making requests.
|
43
|
+
# *User-Agent* header value provided within +headers+ parameter in +initialize+ or on one of
|
44
|
+
# request methods will take precedence over +user_agent+ parameter.
|
45
|
+
#
|
46
|
+
# == Example:
|
47
|
+
# require "network-client"
|
48
|
+
#
|
49
|
+
# github_client = Network::Client.new(endpoint: 'https://api.github.com')
|
50
|
+
# github_client.get '/emojis'
|
51
|
+
#
|
52
|
+
# #=> { "+1": "https://assets-cdn.github.com/images/icons/emoji/unicode/1f44d.png?v7",
|
53
|
+
# "-1": "https://assets-cdn.github.com/images/icons/emoji/unicode/1f44e.png?v7",
|
54
|
+
# ... }
|
55
|
+
#
|
56
|
+
def initialize(endpoint:, tries: 2, headers: {}, username: nil, password: nil,
|
57
|
+
user_agent: 'network-client gem')
|
58
|
+
@uri = URI.parse(endpoint)
|
59
|
+
@tries = tries
|
60
|
+
|
61
|
+
set_http_client
|
62
|
+
set_default_headers(headers)
|
63
|
+
set_basic_auth(username, password)
|
64
|
+
set_logger
|
65
|
+
define_error_strategies
|
66
|
+
set_user_agent(headers['User-Agent'] || user_agent)
|
67
|
+
set_bearer_auth
|
68
|
+
set_custom_token_auth
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Perform a get request on the targeted client +endpoint+.
|
73
|
+
#
|
74
|
+
# == Parameters:
|
75
|
+
# [*path*] +string+ path on client's target host.
|
76
|
+
# [*params*] request parameters to be url encoded. Can be +hash+ or pair of values +array+.
|
77
|
+
# [*headers*] +hash+ set of http request headers.
|
78
|
+
#
|
79
|
+
# == Returns:
|
80
|
+
# http response data contained in +Response+ struct.
|
81
|
+
#
|
82
|
+
def get(path, params: {}, headers: {})
|
83
|
+
request_json :get, path, params, headers
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# Perform a post request on the targeted client +endpoint+.
|
88
|
+
#
|
89
|
+
# == Parameters:
|
90
|
+
# [*path*] +string+ path on client's target host.
|
91
|
+
# [*params*] +hash+ request parameters to json encoded in request body.
|
92
|
+
# [*headers*] +hash+ set of http request headers.
|
93
|
+
#
|
94
|
+
# == Returns:
|
95
|
+
# http response data contained in +Response+ struct.
|
96
|
+
#
|
97
|
+
def post(path, params: {}, headers: {})
|
98
|
+
request_json :post, path, params, headers
|
99
|
+
end
|
100
|
+
|
101
|
+
##
|
102
|
+
# Perform a patch request on the targeted client +endpoint+.
|
103
|
+
#
|
104
|
+
# == Parameters:
|
105
|
+
# [*path*] +string+ path on client's target host.
|
106
|
+
# [*params*] +hash+ request parameters to json encoded in request body.
|
107
|
+
# [*headers*] +hash+ set of http request headers.
|
108
|
+
#
|
109
|
+
# == Returns:
|
110
|
+
# http response data contained in +Response+ struct.
|
111
|
+
#
|
112
|
+
def patch(path, params: {}, headers: {})
|
113
|
+
request_json :patch, path, params, headers
|
114
|
+
end
|
115
|
+
|
116
|
+
##
|
117
|
+
# Perform a put request on the targeted client +endpoint+.
|
118
|
+
#
|
119
|
+
# == Parameters:
|
120
|
+
# [*path*] +string+ path on client's target host.
|
121
|
+
# [*params*] +hash+ request parameters to json encoded in request body.
|
122
|
+
# [*headers*] +hash+ set of http request headers.
|
123
|
+
#
|
124
|
+
# == Returns:
|
125
|
+
# http response data cotained in +Response+ strcut.
|
126
|
+
#
|
127
|
+
def put(path, params: {}, headers: {})
|
128
|
+
request_json :put, path, params, headers
|
129
|
+
end
|
130
|
+
|
131
|
+
##
|
132
|
+
# Perform a delete request on the targeted client +endpoint+.
|
133
|
+
#
|
134
|
+
# == Parameters:
|
135
|
+
# [*path*] +string+ path on client's target host.
|
136
|
+
# [*params*] +hash+ request parameters to json encoded in request body.
|
137
|
+
# [*headers*] +hash+ set of http request headers.
|
138
|
+
#
|
139
|
+
# == Returns:
|
140
|
+
# http response data contained in +Response+ struct.
|
141
|
+
#
|
142
|
+
def delete(path, params: {}, headers: {})
|
143
|
+
request_json :delete, path, params, headers
|
144
|
+
end
|
145
|
+
|
146
|
+
def get_html(path, params: {}, headers: {})
|
147
|
+
raise NotImplementedError
|
148
|
+
end
|
149
|
+
|
150
|
+
def post_form(path, params: {}, headers: {})
|
151
|
+
raise NotImplementedError
|
152
|
+
end
|
153
|
+
|
154
|
+
##
|
155
|
+
# Sets the client logger object.
|
156
|
+
# Execution is yielded to passed +block+ to set, customize, and returning a logger instance.
|
157
|
+
#
|
158
|
+
# == Returns:
|
159
|
+
# +logger+ instance variable.
|
160
|
+
#
|
161
|
+
def set_logger
|
162
|
+
@logger = if block_given?
|
163
|
+
yield
|
164
|
+
elsif defined?(Rails)
|
165
|
+
Rails.logger
|
166
|
+
else
|
167
|
+
logger = Logger.new(STDOUT)
|
168
|
+
logger.level = Logger::DEBUG
|
169
|
+
logger
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def set_basic_auth(username, password)
|
174
|
+
@username = username.nil? ? '' : username
|
175
|
+
@password = password.nil? ? '' : password
|
176
|
+
end
|
177
|
+
|
178
|
+
##
|
179
|
+
# Assigns authentication bearer type token for use in standard HTTP authorization header.
|
180
|
+
#
|
181
|
+
# == Parameters:
|
182
|
+
# [*token*] +string+ bearer token value.
|
183
|
+
#
|
184
|
+
# == Returns:
|
185
|
+
# [@bearer_token] +string+ the newly assigned +@bearer_token+ value.
|
186
|
+
#
|
187
|
+
def set_bearer_auth(token: '')
|
188
|
+
@bearer_token = token
|
189
|
+
end
|
190
|
+
|
191
|
+
##
|
192
|
+
# Assigns custom authentication token for use in standard HTTP authorization header.
|
193
|
+
# This takes precedence over Bearer authentication if both are set.
|
194
|
+
#
|
195
|
+
# == Parameters:
|
196
|
+
# [*header_value*] +string+ full authorization header value. _(e.g. Token token=123)_.
|
197
|
+
#
|
198
|
+
# == Returns:
|
199
|
+
# [@auth_token_header] +string+ the newly assigned +@auth_token_header+ value.
|
200
|
+
#
|
201
|
+
def set_custom_token_auth(header_value: '')
|
202
|
+
@auth_token_header = header_value
|
203
|
+
end
|
204
|
+
|
205
|
+
##
|
206
|
+
# Assigns a new +User-Agent+ header to be sent in any subsequent request.
|
207
|
+
#
|
208
|
+
# == Parameters:
|
209
|
+
# [*new_user_agent*] +string+ the user-agent header value.
|
210
|
+
#
|
211
|
+
# == Returns:
|
212
|
+
# [@user_agent] +string+ the newly assigned +User-Agent+ header value.
|
213
|
+
#
|
214
|
+
def set_user_agent(new_user_agent)
|
215
|
+
@user_agent = @default_headers['User-Agent'] = new_user_agent
|
216
|
+
end
|
217
|
+
|
218
|
+
private
|
219
|
+
|
220
|
+
def set_http_client
|
221
|
+
@http = Net::HTTP.new(@uri.host, @uri.port)
|
222
|
+
@http.use_ssl = @uri.scheme == 'https' ? true : false
|
223
|
+
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
224
|
+
end
|
225
|
+
|
226
|
+
def set_default_headers(headers)
|
227
|
+
@default_headers = DEFAULT_HEADERS.merge(headers)
|
228
|
+
end
|
229
|
+
|
230
|
+
def define_error_strategies
|
231
|
+
@errors_to_recover = [Net::HTTPTooManyRequests,
|
232
|
+
Net::HTTPServerError,
|
233
|
+
Net::ProtocolError,
|
234
|
+
Net::HTTPBadResponse,
|
235
|
+
Net::ReadTimeout,
|
236
|
+
Net::OpenTimeout,
|
237
|
+
Errno::ECONNREFUSED,
|
238
|
+
Errno::ETIMEDOUT,
|
239
|
+
OpenSSL::SSL::SSLError,
|
240
|
+
SocketError]
|
241
|
+
@errors_to_propagate = [Net::HTTPRequestURITooLarge,
|
242
|
+
Net::HTTPMethodNotAllowed]
|
243
|
+
end
|
244
|
+
|
245
|
+
def request_json(http_method, path, params, headers)
|
246
|
+
response = request(http_method, path, params, headers)
|
247
|
+
body = parse_as_json(response.body)
|
248
|
+
Response.new(response.code.to_i, body)
|
249
|
+
end
|
250
|
+
|
251
|
+
def request(http_method, path, params, headers)
|
252
|
+
path = formulate_path(path)
|
253
|
+
path = encode_path_params(path, params) if http_method == :get
|
254
|
+
|
255
|
+
headers = @default_headers.merge(headers)
|
256
|
+
headers = authenticate(headers)
|
257
|
+
|
258
|
+
request = Net::HTTP::const_get(http_method.to_s.capitalize.to_sym).new(path, headers)
|
259
|
+
request.body = params.to_s unless http_method == :get
|
260
|
+
|
261
|
+
basic_auth(request)
|
262
|
+
|
263
|
+
response = http_request(request)
|
264
|
+
|
265
|
+
unless Net::HTTPSuccess === response
|
266
|
+
log "endpoint responded with non-success #{response.code} code.\nResponse: #{response.body}"
|
267
|
+
end
|
268
|
+
|
269
|
+
response
|
270
|
+
end
|
271
|
+
|
272
|
+
def basic_auth(request)
|
273
|
+
request.basic_auth(@username, @password) unless @username.empty? && @password.empty?
|
274
|
+
end
|
275
|
+
|
276
|
+
def authenticate(headers)
|
277
|
+
headers['Authorization'] = "Bearer #{bearer_token}" unless bearer_token.empty?
|
278
|
+
headers['Authorization'] = auth_token_header unless auth_token_header.empty?
|
279
|
+
headers
|
280
|
+
end
|
281
|
+
|
282
|
+
def http_request(request)
|
283
|
+
tries_count ||= @tries
|
284
|
+
finished = ->() { (tries_count -= 1).zero? }
|
285
|
+
|
286
|
+
begin
|
287
|
+
response = @http.request(request)
|
288
|
+
end until !recoverable?(response) || finished.call
|
289
|
+
response
|
290
|
+
|
291
|
+
rescue *@errors_to_propagate => error
|
292
|
+
log "Request Failed. \nReason: #{error.message}"
|
293
|
+
raise
|
294
|
+
|
295
|
+
rescue *@errors_to_recover => error
|
296
|
+
warn_on_retry "#{error.message}"
|
297
|
+
finished.call ? raise : retry
|
298
|
+
end
|
299
|
+
|
300
|
+
def recoverable?(response)
|
301
|
+
if @errors_to_recover.any? { |error_class| response.is_a?(error_class) }
|
302
|
+
warn_on_retry "#{response.class} response type."
|
303
|
+
true
|
304
|
+
else
|
305
|
+
false
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def parse_as_json(response_body)
|
310
|
+
body = response_body
|
311
|
+
body = body.nil? || body.empty? ? body : JSON.parse(body)
|
312
|
+
|
313
|
+
rescue JSON::ParserError => error
|
314
|
+
log "Parsing response body as JSON failed! Returning raw body. \nDetails: \n#{error.message}"
|
315
|
+
body
|
316
|
+
end
|
317
|
+
|
318
|
+
def encode_path_params(path, params)
|
319
|
+
if params.nil? || params.empty?
|
320
|
+
path
|
321
|
+
else
|
322
|
+
params = stringify_keys(params)
|
323
|
+
encoded = URI.encode_www_form(params)
|
324
|
+
[path, encoded].join("?")
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
def formulate_path(path)
|
329
|
+
path = '/' if path.nil? || path.empty?
|
330
|
+
path.strip! if path.respond_to?(:strip)
|
331
|
+
path.prepend('/') unless path.chars.first == '/'
|
332
|
+
path
|
333
|
+
end
|
334
|
+
|
335
|
+
def log(message)
|
336
|
+
@logger.error("\n#{LOG_TAG} #{message}.")
|
337
|
+
end
|
338
|
+
|
339
|
+
def warn_on_retry(message)
|
340
|
+
@logger.warn("\n#{LOG_TAG} #{message} \nRetrying now ..")
|
341
|
+
end
|
342
|
+
|
343
|
+
def stringify_keys(params)
|
344
|
+
params.respond_to?(:keys) ? params.collect { |k, v| [k.to_s, v] }.to_h : params
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
data/network-client.gemspec
CHANGED
@@ -2,12 +2,12 @@
|
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
4
|
|
5
|
-
require 'network
|
5
|
+
require 'network/version'
|
6
6
|
require 'date'
|
7
7
|
|
8
8
|
Gem::Specification.new do |spec|
|
9
9
|
spec.name = "network-client"
|
10
|
-
spec.version =
|
10
|
+
spec.version = Network::VERSION
|
11
11
|
spec.date = Date.today.to_s
|
12
12
|
spec.authors = ["Abdullah Barrak (abarrak)"]
|
13
13
|
spec.email = ["abdullah@abarrak.com"]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: network-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abdullah Barrak (abarrak)
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-07-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -127,8 +127,8 @@ files:
|
|
127
127
|
- README.md
|
128
128
|
- Rakefile
|
129
129
|
- lib/network-client.rb
|
130
|
-
- lib/network
|
131
|
-
- lib/network
|
130
|
+
- lib/network/client.rb
|
131
|
+
- lib/network/version.rb
|
132
132
|
- network-client.gemspec
|
133
133
|
homepage: https://github.com/abarrak/network-client
|
134
134
|
licenses:
|
data/lib/network-client/core.rb
DELETED
@@ -1,239 +0,0 @@
|
|
1
|
-
require 'net/http'
|
2
|
-
require 'json'
|
3
|
-
require 'logger'
|
4
|
-
|
5
|
-
module NetworkClient
|
6
|
-
class Client
|
7
|
-
|
8
|
-
HTTP_VERBS = {
|
9
|
-
:get => Net::HTTP::Get,
|
10
|
-
:post => Net::HTTP::Post,
|
11
|
-
:put => Net::HTTP::Put,
|
12
|
-
:delete => Net::HTTP::Delete
|
13
|
-
}
|
14
|
-
|
15
|
-
DEFAULT_HEADERS = { 'accept' => 'application/json',
|
16
|
-
'Content-Type' => 'application/json' }.freeze
|
17
|
-
|
18
|
-
# The success response template. Represents the return of rest-like methods holding two values:
|
19
|
-
# HTTP response code, and body (parsed as json if request type is json).
|
20
|
-
Response = Struct.new(:code, :body)
|
21
|
-
|
22
|
-
# Stamp in front of each log written by client *@logger*
|
23
|
-
LOG_TAG = '[NETWORK CLIENT]:'.freeze
|
24
|
-
|
25
|
-
attr_reader :username, :password, :default_headers, :logger, :tries
|
26
|
-
|
27
|
-
# Error list for retrying strategy.
|
28
|
-
# Initially contains common errors encountered usually in net calls.
|
29
|
-
attr_accessor :errors_to_recover
|
30
|
-
|
31
|
-
# Error list for stop and propagate strategy.
|
32
|
-
# Takes priority over *:errors_to_recover*.
|
33
|
-
# Do not assign ancestor error classes here that prevent retry for descendant ones.
|
34
|
-
attr_accessor :errors_to_propagate
|
35
|
-
|
36
|
-
##
|
37
|
-
# Construct and prepare client for requests targeting :endpoint.
|
38
|
-
#
|
39
|
-
# === Parameters:
|
40
|
-
#
|
41
|
-
# *endpoint*:
|
42
|
-
# Uri for the host with schema and port. any other segment like paths will be discarded.
|
43
|
-
# *tries*:
|
44
|
-
# Number to specify how many is to repeat failed calls. Default is 2.
|
45
|
-
# *headers*:
|
46
|
-
# Hash to contain any common HTTP headers to be set in client calls.
|
47
|
-
# *username*:
|
48
|
-
# for HTTP basic authentication. Applies on all requests. Default to nil.
|
49
|
-
# *password*:
|
50
|
-
# for HTTP basic authentication. Applies on all requests. Default to nil.
|
51
|
-
#
|
52
|
-
# === Example:
|
53
|
-
# require "network-client"
|
54
|
-
#
|
55
|
-
# github_client = NetworkClient::Client.new(endpoint: 'https://api.github.com')
|
56
|
-
# github_client.get '/emojis'
|
57
|
-
# #=> { "+1": "https://assets-cdn.github.com/images/icons/emoji/unicode/1f44d.png?v7",
|
58
|
-
# "-1": "https://assets-cdn.github.com/images/icons/emoji/unicode/1f44e.png?v7",
|
59
|
-
# "100": "https://assets-cdn.github.com/images/icons/emoji/unicode/1f4af.png?v7",
|
60
|
-
# ... }
|
61
|
-
#
|
62
|
-
def initialize(endpoint:, tries: 2, headers: {}, username: nil, password: nil)
|
63
|
-
@uri = URI.parse(endpoint)
|
64
|
-
@tries = tries
|
65
|
-
|
66
|
-
set_http_client
|
67
|
-
set_default_headers(headers)
|
68
|
-
set_basic_auth(username, password)
|
69
|
-
set_logger
|
70
|
-
define_error_strategies
|
71
|
-
end
|
72
|
-
|
73
|
-
def get(path, params = {}, headers = {})
|
74
|
-
request_json :get, path, params, headers
|
75
|
-
end
|
76
|
-
|
77
|
-
def post(path, params = {}, headers = {})
|
78
|
-
request_json :post, path, params, headers
|
79
|
-
end
|
80
|
-
|
81
|
-
def put(path, params = {}, headers = {})
|
82
|
-
request_json :put, path, params, headers
|
83
|
-
end
|
84
|
-
|
85
|
-
def delete(path, params = {}, headers = {})
|
86
|
-
request_json :delete, path, params, headers
|
87
|
-
end
|
88
|
-
|
89
|
-
def post_form(path, params = {}, headers = {})
|
90
|
-
raise NotImplementedError
|
91
|
-
end
|
92
|
-
|
93
|
-
def put_form(path, params = {}, headers = {})
|
94
|
-
raise NotImplementedError
|
95
|
-
end
|
96
|
-
|
97
|
-
def set_logger
|
98
|
-
@logger = if block_given?
|
99
|
-
yield
|
100
|
-
elsif defined?(Rails)
|
101
|
-
Rails.logger
|
102
|
-
else
|
103
|
-
logger = Logger.new(STDOUT)
|
104
|
-
logger.level = Logger::DEBUG
|
105
|
-
logger
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def set_basic_auth(username, password)
|
110
|
-
@username = username.nil? ? '' : username
|
111
|
-
@password = password.nil? ? '' : password
|
112
|
-
end
|
113
|
-
|
114
|
-
private
|
115
|
-
|
116
|
-
def set_http_client
|
117
|
-
@http = Net::HTTP.new(@uri.host, @uri.port)
|
118
|
-
@http.use_ssl = @uri.scheme == 'https' ? true : false
|
119
|
-
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
120
|
-
end
|
121
|
-
|
122
|
-
def set_default_headers(headers)
|
123
|
-
@default_headers = DEFAULT_HEADERS.merge(headers)
|
124
|
-
end
|
125
|
-
|
126
|
-
def define_error_strategies
|
127
|
-
@errors_to_recover = [Net::HTTPTooManyRequests,
|
128
|
-
Net::HTTPServerError,
|
129
|
-
Net::ProtocolError,
|
130
|
-
Net::HTTPBadResponse,
|
131
|
-
Net::ReadTimeout,
|
132
|
-
Net::OpenTimeout,
|
133
|
-
Errno::ECONNREFUSED,
|
134
|
-
Errno::ETIMEDOUT,
|
135
|
-
OpenSSL::SSL::SSLError,
|
136
|
-
SocketError]
|
137
|
-
@errors_to_propagate = [Net::HTTPRequestURITooLarge,
|
138
|
-
Net::HTTPMethodNotAllowed]
|
139
|
-
end
|
140
|
-
|
141
|
-
def request_json(http_method, path, params, headers)
|
142
|
-
response = request(http_method, path, params, headers)
|
143
|
-
body = parse_as_json(response.body)
|
144
|
-
Response.new(response.code.to_i, body)
|
145
|
-
end
|
146
|
-
|
147
|
-
def request(http_method, path, params, headers)
|
148
|
-
headers = @default_headers.merge(headers)
|
149
|
-
path = formulate_path(path)
|
150
|
-
|
151
|
-
case http_method
|
152
|
-
when :get
|
153
|
-
full_path = encode_path_params(path, params)
|
154
|
-
request = HTTP_VERBS[http_method].new(full_path, headers)
|
155
|
-
else
|
156
|
-
request = HTTP_VERBS[http_method].new(path, headers)
|
157
|
-
request.body = params.to_s
|
158
|
-
end
|
159
|
-
|
160
|
-
basic_auth(request)
|
161
|
-
response = http_request(request)
|
162
|
-
|
163
|
-
unless Net::HTTPSuccess === response
|
164
|
-
log "endpoint responded with non-success #{response.code} code.\nResponse: #{response.body}"
|
165
|
-
end
|
166
|
-
|
167
|
-
response
|
168
|
-
end
|
169
|
-
|
170
|
-
def basic_auth(request)
|
171
|
-
request.basic_auth(@username, @password) unless @username.empty? && @password.empty?
|
172
|
-
end
|
173
|
-
|
174
|
-
def http_request(request)
|
175
|
-
tries_count ||= @tries
|
176
|
-
finished = ->() { (tries_count -= 1).zero? }
|
177
|
-
|
178
|
-
begin
|
179
|
-
response = @http.request(request)
|
180
|
-
end until !recoverable?(response) || finished.call
|
181
|
-
response
|
182
|
-
|
183
|
-
rescue *@errors_to_propagate => error
|
184
|
-
log "Request Failed. \nReason: #{error.message}"
|
185
|
-
raise
|
186
|
-
|
187
|
-
rescue *@errors_to_recover => error
|
188
|
-
warn_on_retry "#{error.message}"
|
189
|
-
finished.call ? raise : retry
|
190
|
-
end
|
191
|
-
|
192
|
-
def recoverable?(response)
|
193
|
-
if @errors_to_recover.any? { |error_class| response.is_a?(error_class) }
|
194
|
-
warn_on_retry "#{response.class} response type."
|
195
|
-
true
|
196
|
-
else
|
197
|
-
false
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
def parse_as_json(response_body)
|
202
|
-
body = response_body
|
203
|
-
body = body.nil? || body.empty? ? body : JSON.parse(body)
|
204
|
-
|
205
|
-
rescue JSON::ParserError => error
|
206
|
-
log "Parsing response body as JSON failed! Returning raw body. \nDetails: \n#{error.message}"
|
207
|
-
body
|
208
|
-
end
|
209
|
-
|
210
|
-
def encode_path_params(path, params)
|
211
|
-
if params.nil? || params.empty?
|
212
|
-
path
|
213
|
-
else
|
214
|
-
params = stringify_keys(params)
|
215
|
-
encoded = URI.encode_www_form(params)
|
216
|
-
[path, encoded].join("?")
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
def formulate_path(path)
|
221
|
-
path = '/' if path.nil? || path.empty?
|
222
|
-
path.strip! if path.respond_to?(:strip)
|
223
|
-
path.prepend('/') unless path.chars.first == '/'
|
224
|
-
path
|
225
|
-
end
|
226
|
-
|
227
|
-
def log(message)
|
228
|
-
@logger.error("\n#{LOG_TAG} #{message}.")
|
229
|
-
end
|
230
|
-
|
231
|
-
def warn_on_retry(message)
|
232
|
-
@logger.warn("\n#{LOG_TAG} #{message} \nRetrying now ..")
|
233
|
-
end
|
234
|
-
|
235
|
-
def stringify_keys(params)
|
236
|
-
params.respond_to?(:keys) ? params.collect { |k, v| [k.to_s, v] }.to_h : params
|
237
|
-
end
|
238
|
-
end
|
239
|
-
end
|