network-client 1.0.9 → 1.1.5
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/Gemfile.lock +1 -1
- data/README.md +8 -1
- data/lib/network-client/core.rb +107 -44
- data/lib/network-client/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 69bc26f62b6e80de3ad2d7ec172477b1bf8e9977
|
4
|
+
data.tar.gz: b5e873f8f2a1824428365ed72bf0c8d995cf03c5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 52a46ce916e2a56e75b027684891d3910f0c9b671b99cf3260199a70f3d3cfec513846eb28255521e8dc6fcc38a81d15113cf26ec41a22aa860355b5d9ae2370
|
7
|
+
data.tar.gz: c250a7b4eba369c875e55a1df6ae532d86f8909f5d1a5d1922921ba84d6fbd98c81e9a8b7df03dd8f1162acc6acb9c2d6fb78fa3948ef4aba9976cb04ab431d3
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -29,12 +29,19 @@ $ gem install network-client
|
|
29
29
|
## Usage
|
30
30
|
|
31
31
|
|
32
|
-
For more refer to [the API docuemntation](
|
32
|
+
For more details refer to [the API docuemntation](http://www.rubydoc.info/gems/network-client/).
|
33
33
|
|
34
34
|
## Contributing
|
35
35
|
|
36
36
|
Bug reports and pull requests are very much appreciated at [Github](https://github.com/abarrak/network-client).
|
37
37
|
|
38
|
+
- Fork The repository.
|
39
|
+
- Create a branch with a clear name.
|
40
|
+
- Make your changes (Please also add/change test, README if applicable).
|
41
|
+
- Push changes to the created branch
|
42
|
+
- Create an Pull Request
|
43
|
+
- That's it!
|
44
|
+
|
38
45
|
|
39
46
|
## License
|
40
47
|
[MIT](http://opensource.org/licenses/MIT).
|
data/lib/network-client/core.rb
CHANGED
@@ -1,14 +1,8 @@
|
|
1
1
|
require 'net/http'
|
2
2
|
require 'json'
|
3
|
-
require 'securerandom'
|
4
|
-
require 'erb'
|
5
3
|
require 'logger'
|
6
4
|
|
7
|
-
|
8
5
|
module NetworkClient
|
9
|
-
# This class is simple HTTP client that is meant to be initialized configured with a single URI.
|
10
|
-
# Subsequent calls should target endpoints/paths of that URI.
|
11
|
-
#
|
12
6
|
class Client
|
13
7
|
|
14
8
|
HTTP_VERBS = {
|
@@ -21,29 +15,56 @@ module NetworkClient
|
|
21
15
|
DEFAULT_HEADERS = { 'accept' => 'application/json',
|
22
16
|
'Content-Type' => 'application/json' }.freeze
|
23
17
|
|
24
|
-
#
|
25
|
-
#
|
26
|
-
|
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
|
27
35
|
|
28
36
|
# Construct and prepare client for requests targeting :endpoint.
|
29
37
|
#
|
30
|
-
#
|
31
|
-
# Uri for the host with
|
32
|
-
#
|
33
|
-
# Number to specify how many is to repeat
|
34
|
-
#
|
38
|
+
# *endpoint*:
|
39
|
+
# Uri for the host with schema and port. any other segment like paths will be discarded.
|
40
|
+
# *tries*:
|
41
|
+
# Number to specify how many is to repeat failed calls. Default is 2.
|
42
|
+
# *headers*:
|
35
43
|
# Hash to contain any common HTTP headers to be set in client calls.
|
44
|
+
# *username*:
|
45
|
+
# for HTTP basic authentication. Applies on all requests. Default to nil.
|
46
|
+
# *password*:
|
47
|
+
# for HTTP basic authentication. Applies on all requests. Default to nil.
|
36
48
|
#
|
37
|
-
# Example:
|
49
|
+
# ==== Example:
|
50
|
+
# => require "network-client"
|
38
51
|
# =>
|
52
|
+
# => github_client = NetworkClient::Client.new(endpoint: 'https://api.github.com')
|
53
|
+
# => github_client.get '/emojis'
|
54
|
+
# => { "+1": "https://assets-cdn.github.com/images/icons/emoji/unicode/1f44d.png?v7",
|
55
|
+
# => "-1": "https://assets-cdn.github.com/images/icons/emoji/unicode/1f44e.png?v7",
|
56
|
+
# => "100": "https://assets-cdn.github.com/images/icons/emoji/unicode/1f4af.png?v7",
|
57
|
+
# => ... }
|
39
58
|
#
|
40
|
-
def initialize(endpoint:, tries:
|
59
|
+
def initialize(endpoint:, tries: 2, headers: {}, username: nil, password: nil)
|
41
60
|
@uri = URI.parse(endpoint)
|
42
61
|
@tries = tries
|
43
62
|
|
44
63
|
set_http_client
|
45
64
|
set_default_headers(headers)
|
65
|
+
set_basic_auth(username, password)
|
46
66
|
set_logger
|
67
|
+
define_error_strategies
|
47
68
|
end
|
48
69
|
|
49
70
|
def get(path, params = {}, headers = {})
|
@@ -71,10 +92,10 @@ module NetworkClient
|
|
71
92
|
end
|
72
93
|
|
73
94
|
def set_logger
|
74
|
-
@logger = if
|
75
|
-
Rails.logger
|
76
|
-
elsif block_given?
|
95
|
+
@logger = if block_given?
|
77
96
|
yield
|
97
|
+
elsif defined?(Rails)
|
98
|
+
Rails.logger
|
78
99
|
else
|
79
100
|
logger = Logger.new(STDOUT)
|
80
101
|
logger.level = Logger::DEBUG
|
@@ -82,6 +103,11 @@ module NetworkClient
|
|
82
103
|
end
|
83
104
|
end
|
84
105
|
|
106
|
+
def set_basic_auth(username, password)
|
107
|
+
@username = username.nil? ? '' : username
|
108
|
+
@password = password.nil? ? '' : password
|
109
|
+
end
|
110
|
+
|
85
111
|
private
|
86
112
|
|
87
113
|
def set_http_client
|
@@ -94,15 +120,24 @@ module NetworkClient
|
|
94
120
|
@default_headers = DEFAULT_HEADERS.merge(headers)
|
95
121
|
end
|
96
122
|
|
123
|
+
def define_error_strategies
|
124
|
+
@errors_to_recover = [Net::HTTPTooManyRequests,
|
125
|
+
Net::HTTPServerError,
|
126
|
+
Net::ProtocolError,
|
127
|
+
Net::HTTPBadResponse,
|
128
|
+
Net::ReadTimeout,
|
129
|
+
Net::OpenTimeout,
|
130
|
+
Errno::ECONNREFUSED,
|
131
|
+
OpenSSL::SSL::SSLError,
|
132
|
+
SocketError]
|
133
|
+
@errors_to_propagate = [Net::HTTPRequestURITooLarge,
|
134
|
+
Net::HTTPMethodNotAllowed]
|
135
|
+
end
|
136
|
+
|
97
137
|
def request_json(http_method, path, params, headers)
|
98
138
|
response = request(http_method, path, params, headers)
|
99
|
-
body =
|
100
|
-
|
101
|
-
RESPONSE.new(response.code, body)
|
102
|
-
|
103
|
-
rescue JSON::ParserError => error
|
104
|
-
@logger.error "parsing response body as json failed.\n Details: \n #{error.message}"
|
105
|
-
response
|
139
|
+
body = parse_as_json(response.body)
|
140
|
+
Response.new(response.code.to_i, body)
|
106
141
|
end
|
107
142
|
|
108
143
|
def request(http_method, path, params, headers)
|
@@ -118,40 +153,58 @@ module NetworkClient
|
|
118
153
|
request.body = params.to_s
|
119
154
|
end
|
120
155
|
|
156
|
+
basic_auth(request)
|
121
157
|
response = http_request(request)
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
else
|
126
|
-
@logger.error "endpoint responded with a non-success #{response.code} code."
|
158
|
+
|
159
|
+
unless Net::HTTPSuccess === response
|
160
|
+
log "endpoint responded with non-success #{response.code} code.\nResponse: #{response.body}"
|
127
161
|
end
|
128
162
|
|
129
163
|
response
|
130
164
|
end
|
131
165
|
|
166
|
+
def basic_auth(request)
|
167
|
+
request.basic_auth(@username, @password) unless @username.empty? && @password.empty?
|
168
|
+
end
|
169
|
+
|
132
170
|
def http_request(request)
|
171
|
+
tries_count ||= @tries
|
172
|
+
finished = ->() { (tries_count -= 1).zero? }
|
173
|
+
|
133
174
|
begin
|
134
|
-
tries_count ||= @tries
|
135
175
|
response = @http.request(request)
|
136
|
-
|
137
|
-
|
138
|
-
|
176
|
+
end until !recoverable?(response) || finished.call
|
177
|
+
response
|
178
|
+
|
179
|
+
rescue *@errors_to_propagate => error
|
180
|
+
log "Request Failed. \nReason: #{error.message}"
|
181
|
+
raise
|
182
|
+
|
183
|
+
rescue *@errors_to_recover => error
|
184
|
+
warn_on_retry "#{error.message}"
|
185
|
+
finished.call ? raise : retry
|
186
|
+
end
|
187
|
+
|
188
|
+
def recoverable?(response)
|
189
|
+
if @errors_to_recover.any? { |error_class| response.is_a?(error_class) }
|
190
|
+
warn_on_retry "#{response.class} response type."
|
191
|
+
true
|
139
192
|
else
|
140
|
-
|
193
|
+
false
|
141
194
|
end
|
142
195
|
end
|
143
196
|
|
144
|
-
def
|
145
|
-
|
146
|
-
|
147
|
-
end
|
197
|
+
def parse_as_json(response_body)
|
198
|
+
body = response_body
|
199
|
+
body = body.nil? || body.empty? ? body : JSON.parse(body)
|
148
200
|
|
149
|
-
|
150
|
-
|
201
|
+
rescue JSON::ParserError => error
|
202
|
+
log "Parsing response body as JSON failed! Returning raw body. \nDetails: \n#{error.message}"
|
203
|
+
body
|
151
204
|
end
|
152
205
|
|
153
206
|
def encode_path_params(path, params)
|
154
|
-
if params.empty?
|
207
|
+
if params.nil? || params.empty?
|
155
208
|
path
|
156
209
|
else
|
157
210
|
encoded = URI.encode_www_form(params)
|
@@ -160,7 +213,17 @@ module NetworkClient
|
|
160
213
|
end
|
161
214
|
|
162
215
|
def formulate_path(path)
|
163
|
-
path.
|
216
|
+
path = '/' if path.nil? || path.empty?
|
217
|
+
path.prepend('/') unless path.chars.first == '/'
|
218
|
+
path
|
219
|
+
end
|
220
|
+
|
221
|
+
def log(message)
|
222
|
+
@logger.error("\n#{LOG_TAG} #{message}.")
|
223
|
+
end
|
224
|
+
|
225
|
+
def warn_on_retry(message)
|
226
|
+
@logger.warn("\n#{LOG_TAG} #{message} \nRetrying now ..")
|
164
227
|
end
|
165
228
|
end
|
166
229
|
end
|
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: 1.
|
4
|
+
version: 1.1.5
|
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-06-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|