network-client 1.1.6 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 75b9ed5e72145a53a1dc73e09fed8a698cb7f723
4
- data.tar.gz: 0fc103daaef34021254fcfcc00d7ebf624db40df
3
+ metadata.gz: 69a4eea5e01f0c07a0728a987d1e8722edf1b228
4
+ data.tar.gz: b1a9d49d2f9e73c9a88eb3bd3956b2a1ebfe7202
5
5
  SHA512:
6
- metadata.gz: 3d969c67f60e33fd89d143a9939e16c39d27908a5ef3e25ecce35475d5f1be026732b4dd34a1fdec0859856724de0465ba3fff9ad9a0b21b1930d4c53f61ebbe
7
- data.tar.gz: 10e9c5c9b8c12d651288bf50da04f925f526696dba0b9091a14cf0ad0cfe4b4129f531e0d9527f9ae90204c09d75c450e980e84273e044cbfaacdce36af7f254
6
+ metadata.gz: ba909db3bb7de0357673b06bd444b107810bdf1796afd2dbf2e8fc3669c072157dbd0f31af601903c0ec895c55419ae83534e7d56710ce147433a724c0479cef
7
+ data.tar.gz: cd044bc44966fa7292741f9088a9be74ce2409e4099bfba6ec94fcd3582e755a071e09db9ce838f99966cc87612812896efc1174fa54659b1525b195ba4b3ff0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- network-client (1.1.6)
4
+ network-client (2.0.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -1,2 +1,2 @@
1
- require_relative "network-client/core"
2
- require_relative "network-client/version"
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
@@ -0,0 +1,3 @@
1
+ module Network
2
+ VERSION = "2.0.0"
3
+ end
@@ -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-client/version'
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 = NetworkClient::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: 1.1.6
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-06-30 00:00:00.000000000 Z
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-client/core.rb
131
- - lib/network-client/version.rb
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:
@@ -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
@@ -1,3 +0,0 @@
1
- module NetworkClient
2
- VERSION = "1.1.6"
3
- end