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 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