sequence-sdk 1.0.2 → 1.0.3

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