sequence-sdk 1.0.2 → 1.0.3

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: 94c78d487e125ca4d9436ba7651feeaa4df4e42c
4
- data.tar.gz: 4686db08afa33c144752c475e682e133b3e7dad7
3
+ metadata.gz: 13297573e64f55ffcba16f88365a13ee4540d7c3
4
+ data.tar.gz: e94b5d7ab7f11a8dfe48140fceea4c921d56b2d9
5
5
  SHA512:
6
- metadata.gz: 3b8efcc73ad670aa4df6b2adadc9e6be07043b77e06b0098f918e332dd3df4563e870f31ea561a4bde52e2720d5c3a8904a20cb8db36c30743713dd1caf0c8b6
7
- data.tar.gz: 46da2a5e7ab4800d88c47dc98725da383fa56c54eb8b64a08ed232a50fb750b87277904d90d48a9163c88a34a7ba70a2d80d71389ff7df9d35276ac653e5084b
6
+ metadata.gz: f3231c2b04e1e9a99a8318aac330fdd4ea1eb1bb54ab0510c9cbfa49335454fbfaaa8114d823bc70a799ce89ef6617c9502940f33c48ae4dd5240bd2a2939dea
7
+ data.tar.gz: dad7408ea6e7c10eb8e793faffa29150e95cdaf39a4c651c7566eea2760c46ed0a16e60a545b6993f3e1aab48921dfd03b624a7fa9c96e2511c528c5f0d311d5
@@ -47,7 +47,7 @@ module Sequence
47
47
  # @return [Account]
48
48
  def create(opts)
49
49
  opts = {client_token: SecureRandom.uuid}.merge(opts)
50
- Account.new(client.conn.request('create-account', opts))
50
+ Account.new(client.session.request('create-account', opts))
51
51
  end
52
52
 
53
53
  # Updates an account's tags.
@@ -57,7 +57,7 @@ module Sequence
57
57
  # @option opts [Hash] tags A new set of tags, which will replace the existing tags.
58
58
  # @return [void]
59
59
  def update_tags(opts)
60
- client.conn.request('update-account-tags', opts)
60
+ client.session.request('update-account-tags', opts)
61
61
  end
62
62
 
63
63
  # Executes a query, returning an enumerable over individual accounts.
@@ -72,7 +72,7 @@ module Sequence
72
72
 
73
73
  class Query < Sequence::Query
74
74
  def fetch(query)
75
- client.conn.request('list-accounts', query)
75
+ client.session.request('list-accounts', query)
76
76
  end
77
77
 
78
78
  def translate(raw)
@@ -49,7 +49,7 @@ module Sequence
49
49
  # @return [Asset]
50
50
  def create(opts)
51
51
  opts = {client_token: SecureRandom.uuid}.merge(opts)
52
- Asset.new(client.conn.request('create-asset', opts))
52
+ Asset.new(client.session.request('create-asset', opts))
53
53
  end
54
54
 
55
55
  # Updates an asset's tags.
@@ -59,7 +59,7 @@ module Sequence
59
59
  # @option opts [Hash] tags A new set of tags, which will replace the existing tags.
60
60
  # @return [void]
61
61
  def update_tags(opts)
62
- client.conn.request('update-asset-tags', opts)
62
+ client.session.request('update-asset-tags', opts)
63
63
  end
64
64
 
65
65
  # Executes a query, returning an enumerable over individual assets.
@@ -74,7 +74,7 @@ module Sequence
74
74
 
75
75
  class Query < Sequence::Query
76
76
  def fetch(query)
77
- client.conn.request('list-assets', query)
77
+ client.session.request('list-assets', query)
78
78
  end
79
79
 
80
80
  def translate(raw)
@@ -34,7 +34,7 @@ module Sequence
34
34
 
35
35
  class Query < Sequence::Query
36
36
  def fetch(query)
37
- client.conn.request('list-balances', query)
37
+ client.session.request('list-balances', query)
38
38
  end
39
39
 
40
40
  def translate(raw)
@@ -15,9 +15,13 @@ module Sequence
15
15
  # @option opts [String] access_token The ledger access token.
16
16
  # @return [Query]
17
17
  def initialize(opts = {})
18
- if !opts.key?(:url) || opts[:url].empty?
19
- raise ArgumentError.new('missing :url parameter')
18
+ if !opts.key?(:ledger) || opts[:ledger].empty?
19
+ raise ArgumentError.new('missing :ledger parameter')
20
20
  end
21
+ if !opts.key?(:credential) || opts[:credential].empty?
22
+ raise ArgumentError.new('missing :credential parameter')
23
+ end
24
+
21
25
  @opts = opts
22
26
  end
23
27
 
@@ -25,9 +29,9 @@ module Sequence
25
29
  @opts.dup
26
30
  end
27
31
 
28
- # @return [Connection]
29
- def conn
30
- @conn ||= Connection.new(@opts)
32
+ # @return [Session]
33
+ def session
34
+ @session ||= Session.new(@opts)
31
35
  end
32
36
 
33
37
  # @return [Account::ClientModule]
@@ -74,7 +74,7 @@ module Sequence
74
74
 
75
75
  class Query < Sequence::Query
76
76
  def fetch(query)
77
- client.conn.request('list-contracts', query)
77
+ client.session.request('list-contracts', query)
78
78
  end
79
79
 
80
80
  def translate(raw)
@@ -8,7 +8,7 @@ module Sequence
8
8
  class ClientModule < Sequence::ClientModule
9
9
  # Deletes all data in the ledger.
10
10
  def reset
11
- client.conn.request('/reset', client_token: SecureRandom.uuid)
11
+ client.session.request('/reset', client_token: SecureRandom.uuid)
12
12
  end
13
13
  end
14
14
  end
@@ -11,7 +11,7 @@ module Sequence
11
11
  attr_accessor :response
12
12
 
13
13
  def initialize(response)
14
- super "Response HTTP header field Sequence-Request-ID is unset. There may be network issues. Please check your local network settings."
14
+ super 'Response HTTP header field Chain-Request-ID is unset. There may be network issues. Please check your local network settings.'
15
15
  self.response = response
16
16
  end
17
17
  end
@@ -53,7 +53,7 @@ module Sequence
53
53
  self.temporary = body['temporary']
54
54
 
55
55
  self.response = response
56
- self.request_id = response['Sequence-Request-ID'] if response
56
+ self.request_id = response['Chain-Request-ID'] if response
57
57
 
58
58
  super self.class.format_error_message(code, chain_message, detail, request_id)
59
59
  end
@@ -0,0 +1,111 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'openssl'
4
+ require 'thread'
5
+
6
+ module Sequence
7
+ class HttpWrapper
8
+
9
+ # Parameters to the retry exponential backoff function.
10
+ MAX_RETRIES = 10
11
+ RETRY_BASE_DELAY_MS = 40
12
+ RETRY_MAX_DELAY_MS = 4000
13
+
14
+ NETWORK_ERRORS = [
15
+ InvalidRequestIDError,
16
+ SocketError,
17
+ EOFError,
18
+ IOError,
19
+ Timeout::Error,
20
+ Errno::ECONNABORTED,
21
+ Errno::ECONNRESET,
22
+ Errno::ETIMEDOUT,
23
+ Errno::EHOSTUNREACH,
24
+ Errno::ECONNREFUSED,
25
+ ]
26
+
27
+ attr_accessor :dis_macaroon
28
+
29
+ def initialize(host, macaroon, opts={})
30
+ @mutex = Mutex.new
31
+ @host = URI(host)
32
+ @macaroon = macaroon
33
+ @dis_macaroon = nil
34
+ @opts = opts
35
+ @http = setup_http
36
+ end
37
+
38
+ def post(url, body)
39
+ attempts = 0
40
+ begin
41
+ attempts += 1
42
+ # If this is a retry and not the first attempt, sleep before making the
43
+ # retry request.
44
+ sleep(backoff_delay(attempts)) if attempts > 1
45
+
46
+ @mutex.synchronize do
47
+ req = Net::HTTP::Post.new(url)
48
+ req.body = JSON.dump(body)
49
+ req['Accept'] = 'application/json'
50
+ req['Content-Type'] = 'application/json'
51
+ req['User-Agent'] = 'chain-sdk-ruby/' + Sequence::VERSION
52
+ if @macaroon != nil && @dis_macaroon != nil
53
+ req['Macaroon'] = @macaroon
54
+ req['Discharge-Macaroon'] = @dis_macaroon
55
+ end
56
+ return @http.request(req)
57
+ end
58
+ rescue *NETWORK_ERRORS => e
59
+ raise e if attempts > MAX_RETRIES
60
+ retry
61
+ rescue APIError => e
62
+ raise e if attempts > MAX_RETRIES
63
+ retry if e.retriable?
64
+ raise e
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ MILLIS_TO_SEC = 0.001
71
+
72
+ def backoff_delay(attempt)
73
+ max = RETRY_BASE_DELAY_MS * 2**(attempt-1)
74
+ max = [max, RETRY_MAX_DELAY_MS].min
75
+ millis = rand(max) + 1
76
+ millis * MILLIS_TO_SEC
77
+ end
78
+
79
+ def setup_http
80
+ args = [@host.hostname, @host.port]
81
+
82
+ # Proxy configuration
83
+ if @opts.key?(:proxy_addr)
84
+ args += [@opts[:proxy_addr], @opts[:proxy_port]]
85
+ if @opts.key?(:proxy_user)
86
+ args += [@opts[:proxy_user], @opts[:proxy_pass]]
87
+ end
88
+ end
89
+
90
+ http = Net::HTTP.new(*args)
91
+ http.set_debug_output($stdout) if ENV['DEBUG']
92
+
93
+ # TLS configuration
94
+ http.use_ssl = true
95
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
96
+ [:ca_file, :cert, :key].each do |k|
97
+ next unless @opts.key?(:ssl_params) && @opts[:ssl_params].key?(k)
98
+ http.send("#{k}=", @opts[:ssl_params][k])
99
+ end
100
+
101
+ # Timeout configuration
102
+ [:open_timeout, :read_timeout, :ssl_timeout].each do |k|
103
+ next unless @opts.key?(k)
104
+ http.send "#{k}=", @opts[k]
105
+ end
106
+
107
+ return http
108
+ end
109
+
110
+ end
111
+ end
data/lib/sequence/key.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require_relative './client_module'
2
- require_relative './connection'
2
+ require_relative './session'
3
3
  require_relative './query'
4
4
  require_relative './response_object'
5
5
 
@@ -23,7 +23,7 @@ module Sequence
23
23
  # @option opts [String] alias Unique, user-specified identifier of the key.
24
24
  # @return [Key]
25
25
  def create(opts = {})
26
- Key.new(client.conn.request('create-key', opts))
26
+ Key.new(client.session.request('create-key', opts))
27
27
  end
28
28
 
29
29
  # Executes a query, returning an enumerable over individual keys.
@@ -37,7 +37,7 @@ module Sequence
37
37
 
38
38
  class Query < Sequence::Query
39
39
  def fetch(query)
40
- client.conn.request('list-keys', query)
40
+ client.session.request('list-keys', query)
41
41
  end
42
42
 
43
43
  def translate(obj)
@@ -0,0 +1,95 @@
1
+ require 'json'
2
+
3
+ require_relative './http_wrapper'
4
+ require_relative './errors'
5
+ require_relative './version'
6
+
7
+ module Sequence
8
+ class Session
9
+ def initialize(opts)
10
+ @opts = opts
11
+ @host = @opts[:host] || 'https://api.seq.com'
12
+ @session_host = @opts[:session_host] || 'https://session-api.seq.com'
13
+ @ledger = @opts[:ledger] || raise(ArgumentError, "missing ledger")
14
+ @macaroon = @opts[:credential] || raise(ArgumentError, "missing credential")
15
+
16
+ # Start at 0 to trigger an immediate refresh
17
+ @refresh_at = 0
18
+
19
+ # Expect this to get set in #refresh!
20
+ @team_name = nil
21
+
22
+ # This can be used to avoid making an http request to get a
23
+ # new discharge macaroon.
24
+ @refresh_method = @opts[:refresh_method]
25
+ if @refresh_method
26
+ if !@refresh_method.respond_to?(:call)
27
+ raise(ArgumentError, "refresh_method is not a lambda")
28
+ end
29
+ if @refresh_method.arity != 1
30
+ raise(ArgumentError, "refresh_method must take 1 argument. (the macaroon)")
31
+ end
32
+ end
33
+
34
+ @session_api = HttpWrapper.new(@session_host, nil)
35
+ @ledger_api = HttpWrapper.new(@host, @macaroon, @opts)
36
+ end
37
+
38
+ # Returns a copy of the configuration options
39
+ def opts
40
+ @opts.dup
41
+ end
42
+
43
+ def request(path, body = {})
44
+ request_full_resp(path, body)[:parsed_body]
45
+ end
46
+
47
+ def request_full_resp(path, body={})
48
+ refresh!
49
+
50
+ response = @ledger_api.post(ledger_url(path), body)
51
+ req_id = response['Chain-Request-ID']
52
+ unless req_id.is_a?(String) && req_id.size > 0
53
+ raise InvalidRequestIDError.new(response)
54
+ end
55
+
56
+ status = Integer(response.code)
57
+ parsed_body = nil
58
+ if status != 204 # No Content
59
+ begin
60
+ parsed_body = JSON.parse(response.body)
61
+ rescue JSON::JSONError
62
+ raise JSONError.new(req_id, response)
63
+ end
64
+ end
65
+ if status / 100 != 2
66
+ klass = status == 401 ? UnauthorizedError : APIError
67
+ raise klass.new(parsed_body, response)
68
+ end
69
+
70
+ {parsed_body: parsed_body, response: response}
71
+ end
72
+
73
+ private
74
+
75
+ def ledger_url(path)
76
+ path = path[1..-1] if path.start_with?("/")
77
+ "/#{@team_name}/#{@ledger}/#{path}"
78
+ end
79
+
80
+ def refresh!
81
+ return if @refresh_at > Time.now.to_i
82
+
83
+ result = if @refresh_method
84
+ @refresh_method.call(@macaroon)
85
+ else
86
+ r = @session_api.post('/sessions/validate', {macaroon: @macaroon})
87
+ JSON.parse(r.body)
88
+ end
89
+
90
+ @team_name = result['team_name']
91
+ @refresh_at = Integer(result['refresh_at'])
92
+ @ledger_api.dis_macaroon = result['refresh_token']
93
+ end
94
+ end
95
+ end
@@ -25,7 +25,7 @@ module Sequence
25
25
  # Gets stats from the ledger.
26
26
  # @return [Stats]
27
27
  def get
28
- Stats.new(client.conn.request('stats'))
28
+ Stats.new(client.session.request('stats'))
29
29
  end
30
30
  end
31
31
 
@@ -50,12 +50,12 @@ module Sequence
50
50
  builder = Builder.new(&block)
51
51
  end
52
52
 
53
- tpl = client.conn.request('build-transaction', builder)
54
- tpl = client.conn.request(
53
+ tpl = client.session.request('build-transaction', builder)
54
+ tpl = client.session.request(
55
55
  '/sign-transaction',
56
56
  transaction: tpl,
57
57
  )
58
- Transaction.new(client.conn.request(
58
+ Transaction.new(client.session.request(
59
59
  'submit-transaction',
60
60
  {transaction: tpl}
61
61
  ))
@@ -75,7 +75,7 @@ module Sequence
75
75
 
76
76
  class Query < Sequence::Query
77
77
  def fetch(query)
78
- client.conn.request('list-transactions', query)
78
+ client.session.request('list-transactions', query)
79
79
  end
80
80
 
81
81
  def translate(raw)
@@ -1,3 +1,3 @@
1
1
  module Sequence
2
- VERSION = '1.0.2'
2
+ VERSION = '1.0.3'
3
3
  end
metadata CHANGED
@@ -1,137 +1,137 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequence-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chain Engineering
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-22 00:00:00.000000000 Z
11
+ date: 2017-10-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler-audit
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ~>
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
61
  version: 3.5.0
62
- - - '>='
62
+ - - ">="
63
63
  - !ruby/object:Gem::Version
64
64
  version: 3.5.0
65
65
  type: :development
66
66
  prerelease: false
67
67
  version_requirements: !ruby/object:Gem::Requirement
68
68
  requirements:
69
- - - ~>
69
+ - - "~>"
70
70
  - !ruby/object:Gem::Version
71
71
  version: 3.5.0
72
- - - '>='
72
+ - - ">="
73
73
  - !ruby/object:Gem::Version
74
74
  version: 3.5.0
75
75
  - !ruby/object:Gem::Dependency
76
76
  name: rspec-its
77
77
  requirement: !ruby/object:Gem::Requirement
78
78
  requirements:
79
- - - ~>
79
+ - - "~>"
80
80
  - !ruby/object:Gem::Version
81
81
  version: 1.2.0
82
82
  type: :development
83
83
  prerelease: false
84
84
  version_requirements: !ruby/object:Gem::Requirement
85
85
  requirements:
86
- - - ~>
86
+ - - "~>"
87
87
  - !ruby/object:Gem::Version
88
88
  version: 1.2.0
89
89
  - !ruby/object:Gem::Dependency
90
90
  name: simplecov
91
91
  requirement: !ruby/object:Gem::Requirement
92
92
  requirements:
93
- - - ~>
93
+ - - "~>"
94
94
  - !ruby/object:Gem::Version
95
95
  version: 0.14.1
96
96
  type: :development
97
97
  prerelease: false
98
98
  version_requirements: !ruby/object:Gem::Requirement
99
99
  requirements:
100
- - - ~>
100
+ - - "~>"
101
101
  - !ruby/object:Gem::Version
102
102
  version: 0.14.1
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: webmock
105
105
  requirement: !ruby/object:Gem::Requirement
106
106
  requirements:
107
- - - ~>
107
+ - - "~>"
108
108
  - !ruby/object:Gem::Version
109
109
  version: 2.3.2
110
110
  type: :development
111
111
  prerelease: false
112
112
  version_requirements: !ruby/object:Gem::Requirement
113
113
  requirements:
114
- - - ~>
114
+ - - "~>"
115
115
  - !ruby/object:Gem::Version
116
116
  version: 2.3.2
117
117
  - !ruby/object:Gem::Dependency
118
118
  name: yard
119
119
  requirement: !ruby/object:Gem::Requirement
120
120
  requirements:
121
- - - ~>
121
+ - - "~>"
122
122
  - !ruby/object:Gem::Version
123
123
  version: 0.9.5
124
- - - '>='
124
+ - - ">="
125
125
  - !ruby/object:Gem::Version
126
126
  version: 0.9.5
127
127
  type: :development
128
128
  prerelease: false
129
129
  version_requirements: !ruby/object:Gem::Requirement
130
130
  requirements:
131
- - - ~>
131
+ - - "~>"
132
132
  - !ruby/object:Gem::Version
133
133
  version: 0.9.5
134
- - - '>='
134
+ - - ">="
135
135
  - !ruby/object:Gem::Version
136
136
  version: 0.9.5
137
137
  description: SDK for Sequence
@@ -140,25 +140,26 @@ executables: []
140
140
  extensions: []
141
141
  extra_rdoc_files: []
142
142
  files:
143
- - README.md
144
143
  - LICENSE
144
+ - README.md
145
+ - lib/sequence.rb
145
146
  - lib/sequence/account.rb
146
147
  - lib/sequence/asset.rb
147
148
  - lib/sequence/balance.rb
148
149
  - lib/sequence/client.rb
149
150
  - lib/sequence/client_module.rb
150
- - lib/sequence/connection.rb
151
151
  - lib/sequence/contract.rb
152
152
  - lib/sequence/dev_utils.rb
153
153
  - lib/sequence/errors.rb
154
+ - lib/sequence/http_wrapper.rb
154
155
  - lib/sequence/key.rb
155
156
  - lib/sequence/page.rb
156
157
  - lib/sequence/query.rb
157
158
  - lib/sequence/response_object.rb
159
+ - lib/sequence/session.rb
158
160
  - lib/sequence/stats.rb
159
161
  - lib/sequence/transaction.rb
160
162
  - lib/sequence/version.rb
161
- - lib/sequence.rb
162
163
  homepage: https://dashboard.sequenceledger.com
163
164
  licenses:
164
165
  - Apache-2.0
@@ -169,17 +170,17 @@ require_paths:
169
170
  - lib
170
171
  required_ruby_version: !ruby/object:Gem::Requirement
171
172
  requirements:
172
- - - ~>
173
+ - - "~>"
173
174
  - !ruby/object:Gem::Version
174
175
  version: '2.0'
175
176
  required_rubygems_version: !ruby/object:Gem::Requirement
176
177
  requirements:
177
- - - '>='
178
+ - - ">="
178
179
  - !ruby/object:Gem::Version
179
180
  version: '0'
180
181
  requirements: []
181
182
  rubyforge_project:
182
- rubygems_version: 2.0.14.1
183
+ rubygems_version: 2.6.14
183
184
  signing_key:
184
185
  specification_version: 4
185
186
  summary: SDK for Sequence
@@ -1,164 +0,0 @@
1
- require 'json'
2
- require 'net/http'
3
- require 'net/https'
4
- require 'openssl'
5
- require 'thread'
6
-
7
- require_relative './errors'
8
- require_relative './version'
9
-
10
- module Sequence
11
- class Connection
12
-
13
- # Parameters to the retry exponential backoff function.
14
- MAX_RETRIES = 10
15
- RETRY_BASE_DELAY_MS = 40
16
- RETRY_MAX_DELAY_MS = 4000
17
-
18
- NETWORK_ERRORS = [
19
- InvalidRequestIDError,
20
- SocketError,
21
- EOFError,
22
- IOError,
23
- Timeout::Error,
24
- Errno::ECONNABORTED,
25
- Errno::ECONNRESET,
26
- Errno::ETIMEDOUT,
27
- Errno::EHOSTUNREACH,
28
- Errno::ECONNREFUSED,
29
- ]
30
-
31
- def initialize(opts)
32
- @opts = opts
33
- @url = URI(@opts[:url])
34
- @access_token = @opts[:access_token] || @url.userinfo
35
- @http_mutex = Mutex.new
36
- end
37
-
38
- # Returns a copy of the configuration options
39
- def opts
40
- @opts.dup
41
- end
42
-
43
- def request(path, body = {})
44
- _request_with_retries(path, body)[:body]
45
- end
46
-
47
- def _request_with_retries(path, body)
48
- attempts = 0
49
-
50
- begin
51
- attempts += 1
52
-
53
- # If this is a retry and not the first attempt, sleep before making the
54
- # retry request.
55
- sleep(backoff_delay(attempts)) if attempts > 1
56
-
57
- _single_request(path, body)
58
- rescue *NETWORK_ERRORS => e
59
- raise e if attempts > MAX_RETRIES
60
- retry
61
- rescue APIError => e
62
- raise e if attempts > MAX_RETRIES
63
- retry if e.retriable?
64
- raise e
65
- end
66
- end
67
-
68
- def _single_request(path, body)
69
- @http_mutex.synchronize do
70
- # Timeout configuration
71
- [:open_timeout, :read_timeout, :ssl_timeout].each do |k|
72
- next unless @opts.key?(k)
73
- http.send "#{k}=", @opts[k]
74
- end
75
-
76
- full_path = @url.request_uri.chomp('/')
77
- full_path += (path[0] == '/') ? path : '/' + path
78
-
79
- req = Net::HTTP::Post.new(full_path)
80
- req['Accept'] = 'application/json'
81
- req['Content-Type'] = 'application/json'
82
- req['User-Agent'] = 'chain-sdk-ruby/' + Sequence::VERSION
83
- req.body = JSON.dump(body)
84
-
85
- if @access_token
86
- user, pass = @access_token.split(':')
87
- req.basic_auth(user, pass)
88
- end
89
-
90
- response = http.request(req)
91
-
92
- req_id = response['Chain-Request-ID']
93
- unless req_id.is_a?(String) && req_id.size > 0
94
- raise InvalidRequestIDError.new(response)
95
- end
96
-
97
- status = Integer(response.code)
98
- parsed_body = nil
99
-
100
- if status != 204 # No Content
101
- begin
102
- parsed_body = JSON.parse(response.body)
103
- rescue JSON::JSONError
104
- raise JSONError.new(req_id, response)
105
- end
106
- end
107
-
108
- if status / 100 != 2
109
- klass = status == 401 ? UnauthorizedError : APIError
110
- raise klass.new(parsed_body, response)
111
- end
112
-
113
- {body: parsed_body, response: response}
114
- end
115
- end
116
-
117
- private
118
-
119
- MILLIS_TO_SEC = 0.001
120
-
121
- def backoff_delay(attempt)
122
- max = RETRY_BASE_DELAY_MS * 2**(attempt-1)
123
- max = [max, RETRY_MAX_DELAY_MS].min
124
- millis = rand(max) + 1
125
- millis * MILLIS_TO_SEC
126
- end
127
-
128
- def http
129
- return @http if @http
130
-
131
- args = [@url.host, @url.port]
132
-
133
- # Proxy configuration
134
- if @opts.key?(:proxy_addr)
135
- args += [@opts[:proxy_addr], @opts[:proxy_port]]
136
- if @opts.key?(:proxy_user)
137
- args += [@opts[:proxy_user], @opts[:proxy_pass]]
138
- end
139
- end
140
-
141
- @http = Net::HTTP.new(*args)
142
-
143
- @http.set_debug_output($stdout) if ENV['DEBUG']
144
- if @url.scheme == 'https'
145
- @http.use_ssl = true
146
- @http.verify_mode = OpenSSL::SSL::VERIFY_PEER
147
- if @opts.key?(:ssl_params)
148
- ssl_params = @opts[:ssl_params]
149
- if ssl_params.key?(:ca_file)
150
- @http.ca_file = ssl_params[:ca_file]
151
- end
152
- if ssl_params.key?(:cert)
153
- @http.cert = ssl_params[:cert]
154
- end
155
- if ssl_params.key?(:key)
156
- @http.key = ssl_params[:key]
157
- end
158
- end
159
- end
160
-
161
- @http
162
- end
163
- end
164
- end