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