network-client 1.0.9 → 1.1.5

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: 64ff58738532ca1e75915fdf62d59dbffe8fb3b3
4
- data.tar.gz: bf9d4e529a0d104ff1e72dc7a02da3a9c23a5bd2
3
+ metadata.gz: 69bc26f62b6e80de3ad2d7ec172477b1bf8e9977
4
+ data.tar.gz: b5e873f8f2a1824428365ed72bf0c8d995cf03c5
5
5
  SHA512:
6
- metadata.gz: a525076e0a9b9d6a9a2f9252af12a2e32fa7598b6fb8c0999d94b1ba6b65aeab37780157f3d42495cc4c3b005a6d9f13fb39460c9cdcfc15705222fb4fb6273c
7
- data.tar.gz: e90d925d53cf26b219c0ea15c75fed6e71b46bcb8bf05b447a1b09759c166ed141d819af8ae55adfe7fad23257155433310f62a1f151da6ada5adae470788f83
6
+ metadata.gz: 52a46ce916e2a56e75b027684891d3910f0c9b671b99cf3260199a70f3d3cfec513846eb28255521e8dc6fcc38a81d15113cf26ec41a22aa860355b5d9ae2370
7
+ data.tar.gz: c250a7b4eba369c875e55a1df6ae532d86f8909f5d1a5d1922921ba84d6fbd98c81e9a8b7df03dd8f1162acc6acb9c2d6fb78fa3948ef4aba9976cb04ab431d3
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- network-client (1.0.9)
4
+ network-client (1.1.5)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
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](#soon).
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).
@@ -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
- # Return values of rest-like methods is a struct holding two values:
25
- # the response code, and body (parsed as JSON in json request).
26
- RESPONSE = Struct.new(:code, :body)
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
- # = *endpoint*:
31
- # Uri for the host with scheam and port. any other segment like paths will be discarded.
32
- # = *tries*:
33
- # Number to specify how many is to repeat falied calls.
34
- # = *headers*:
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: 1, headers: {})
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 defined?(Rails)
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 = JSON.parse(response.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
- case response
123
- when Net::HTTPSuccess
124
- true
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
- rescue *errors_to_recover_by_retry => error
137
- @logger.warn "[Error]: #{error.message} \nRetry .."
138
- (tries_count -= 1).zero? ? raise : retry
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
- response
193
+ false
141
194
  end
142
195
  end
143
196
 
144
- def errors_to_recover_by_retry
145
- [Errno::ECONNREFUSED, Net::HTTPServiceUnavailable, Net::ProtocolError, Net::ReadTimeout,
146
- Net::OpenTimeout, OpenSSL::SSL::SSLError, SocketError]
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
- def errors_to_recover_by_propogate
150
- # TODO: make configurable set of errors that stop net call without retry.
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.chars.last.nil? ? "#{path}/" : 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
@@ -1,3 +1,3 @@
1
1
  module NetworkClient
2
- VERSION = "1.0.9"
2
+ VERSION = "1.1.5"
3
3
  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.0.9
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-05-28 00:00:00.000000000 Z
11
+ date: 2017-06-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler