aliyunsdkcore 0.0.7 → 0.0.8

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
  SHA256:
3
- metadata.gz: 75c66f7b9d52d1d4018fcf7a7c1ab5e30642b7ad7136f119209e1703d8b529a9
4
- data.tar.gz: ebe5ab5cc9d7be04ecdb1b14985d90054041943cf68825b97a046e0aca4e0402
3
+ metadata.gz: 32c52b1b01cf9802096c014e087477c1082bd5bd541e132444b35f69036e7572
4
+ data.tar.gz: aea3c6a0f3701aa6c54752143216d98926666e790e73bcff75487cf83e8bdd39
5
5
  SHA512:
6
- metadata.gz: cdf0d90f0e936a2fd34ece59b8699bc0ab628f09daf9fd75af29ab8db8b52ec6d018dc09f834326132db54665eaeece67cb9de60485f427dcf5316dd076d5c80
7
- data.tar.gz: 7f54b1754aa69036f777802c6b10e4de55b682858f59987699561c687c9d34f3a9a8aee35735bd513b794529b5a6f598af400d2409505ec7222bfd23e7853ed2
6
+ metadata.gz: 1f2ccb9b12db0ed152ee3debea8e118b37651ea99388828661af992c6c9d35c31ea1d4ca3b1f268748b514f50f5296e6ae6a8a378baa897e082d73e8e3f6f3ff
7
+ data.tar.gz: 9203e79a4dc5d72cd11bb15f761674a0344617310de0e16ed16c26f63ee978983630def26cc921c0a49a694dfb7977c4a295f4cdf663b1dfd0b0c2d15a4bbc23
@@ -0,0 +1,159 @@
1
+ require 'faraday'
2
+ require 'securerandom'
3
+ require 'active_support/all'
4
+
5
+ def present?(obj)
6
+ obj.respond_to?(:empty?) ? !obj.empty? : obj
7
+ end
8
+
9
+ module AliyunSDKCore
10
+
11
+ class ROAClient
12
+
13
+ attr_accessor :endpoint, :api_version, :access_key_id,
14
+ :access_key_secret, :security_token, :hostname, :opts
15
+
16
+ def initialize(config)
17
+ validate config
18
+
19
+ self.endpoint = config[:endpoint]
20
+ self.api_version = config[:api_version]
21
+ self.access_key_id = config[:access_key_id]
22
+ self.access_key_secret = config[:access_key_secret]
23
+ self.security_token = config[:security_token]
24
+ end
25
+
26
+ def request(method:, uri:, params: {}, body: {}, headers: {}, options: {})
27
+
28
+ mix_headers = default_headers.merge(headers)
29
+
30
+ response = connection.send(method.downcase) do |request|
31
+ request.url uri, params
32
+ if present?(body)
33
+ request_body = body.to_json
34
+ request.body = request_body
35
+ mix_headers['content-md5'] = Digest::MD5.base64digest request_body
36
+ mix_headers['content-length'] = request_body.length.to_s
37
+ end
38
+ string2sign = string_to_sign(method, uri, mix_headers, params)
39
+ mix_headers.merge!(authorization: authorization(string2sign))
40
+ mix_headers.each { |key, value| request.headers[key] = value }
41
+ end
42
+
43
+ return response if options.has_key? :raw_body
44
+
45
+ response_content_type = response.headers['Content-Type'] || ''
46
+ if response_content_type.start_with?('application/json')
47
+ if response.status >= 400
48
+ result = JSON.parse(response.body)
49
+ raise StandardError, "code: #{response.status}, #{result['Message']} requestid: #{result['RequestId']}"
50
+ end
51
+ end
52
+
53
+ if response_content_type.start_with?('text/xml')
54
+ result = Hash.from_xml(response.body)
55
+ raise ACSError, result['Error'] if result['Error']
56
+ end
57
+
58
+ response
59
+ end
60
+
61
+ def connection(adapter = Faraday.default_adapter)
62
+ Faraday.new(:url => self.endpoint) { |faraday| faraday.adapter adapter }
63
+ end
64
+
65
+ def get(uri: '', headers: {}, params: {}, options: {})
66
+ request(method: :get, uri: uri, params: params, body: {}, headers: headers, options: options)
67
+ end
68
+
69
+ def post(uri: '', headers: {}, params: {}, body: {}, options: {})
70
+ request(method: :get, uri: uri, params: params, body: body, headers: headers, options: options)
71
+ end
72
+
73
+ def put(uri: '', headers: {}, params: {}, body: {}, options: {})
74
+ request(method: :get, uri: uri, params: params, body: body, headers: headers, options: options)
75
+ end
76
+
77
+ def delete(uri: '', headers: {}, params: {}, options: {})
78
+ request(method: :get, uri: uri, params: params, body: {}, headers: headers, options: options)
79
+ end
80
+
81
+ def default_headers
82
+ default_headers = {
83
+ 'accept' => 'application/json',
84
+ 'date' => Time.now.httpdate,
85
+ 'host' => URI(self.endpoint).host,
86
+ 'x-acs-signature-nonce' => SecureRandom.hex(16),
87
+ 'x-acs-signature-method' => 'HMAC-SHA1',
88
+ 'x-acs-signature-version' => '1.0',
89
+ 'x-acs-version' => self.api_version,
90
+ 'x-sdk-client' => "RUBY(#{RUBY_VERSION})", # FIXME: 如何获取Gem的名称和版本号
91
+ 'user-agent' => DEFAULT_UA
92
+ }
93
+ if self.security_token
94
+ default_headers.merge!(
95
+ 'x-acs-accesskey-id' => self.access_key_id,
96
+ 'x-acs-security-token' => self.security_token
97
+ )
98
+ end
99
+ default_headers
100
+ end
101
+
102
+ private
103
+
104
+ def string_to_sign(method, uri, headers, query = {})
105
+ header_string = [
106
+ method,
107
+ headers['accept'],
108
+ headers['content-md5'] || '',
109
+ headers['content-type'] || '',
110
+ headers['date'],
111
+ ].join("\n")
112
+ "#{header_string}\n#{canonicalized_headers(headers)}#{canonicalized_resource(uri, query)}"
113
+ end
114
+
115
+ def canonicalized_headers(headers)
116
+ headers.keys.select { |key| key.to_s.start_with? 'x-acs-' }
117
+ .sort.map { |key| "#{key}:#{headers[key].strip}\n" }.join
118
+ end
119
+
120
+ def canonicalized_resource(uri, query_hash = {})
121
+ query_string = query_hash.map { |key, value| "#{key}=#{value}" }.join('&')
122
+ query_string.empty? ? uri : "#{uri}?#{query_string}"
123
+ end
124
+
125
+ def authorization(string_to_sign)
126
+ "acs #{self.access_key_id}:#{signature(string_to_sign)}"
127
+ end
128
+
129
+ def signature(string_to_sign)
130
+ Base64.encode64(OpenSSL::HMAC.digest('sha1', self.access_key_secret, string_to_sign)).strip
131
+ end
132
+
133
+ def validate(config)
134
+ raise ArgumentError, 'must pass "config"' unless config
135
+ raise ArgumentError, 'must pass "config[:endpoint]"' unless config[:endpoint]
136
+ unless config[:endpoint].start_with?('http://') || config[:endpoint].start_with?('https://')
137
+ raise ArgumentError, '"config.endpoint" must starts with \'https://\' or \'http://\'.'
138
+ end
139
+ raise ArgumentError, 'must pass "config[:api_version]"' unless config[:api_version]
140
+ raise ArgumentError, 'must pass "config[:access_key_id]"' unless config[:access_key_id]
141
+ raise ArgumentError, 'must pass "config[:access_key_secret]"' unless config[:access_key_secret]
142
+ end
143
+
144
+ class ACSError < StandardError
145
+
146
+ attr_accessor :code
147
+
148
+ def initialize(error)
149
+ self.code = error['Code']
150
+ message = error['Message']
151
+ host_id = error['HostId']
152
+ request_id = error['RequestId']
153
+ super("#{message} host_id: #{host_id}, request_id: #{request_id}")
154
+ end
155
+
156
+ end
157
+
158
+ end
159
+ end
@@ -0,0 +1,145 @@
1
+ require 'set'
2
+ require 'openssl'
3
+ require 'faraday'
4
+ require 'active_support/all'
5
+
6
+ # Converts just the first character to uppercase.
7
+ #
8
+ # upcase_first('what a Lovely Day') # => "What a Lovely Day"
9
+ # upcase_first('w') # => "W"
10
+ # upcase_first('') # => ""
11
+ def upcase_first(string)
12
+ string.length > 0 ? string[0].upcase.concat(string[1..-1]) : ""
13
+ end
14
+
15
+ module AliyunSDKCore
16
+
17
+ class RPCClient
18
+
19
+ attr_accessor :endpoint, :api_version, :access_key_id, :access_key_secret, :security_token, :codes, :opts, :verbose
20
+
21
+ def initialize(config, verbose = false)
22
+
23
+ validate config
24
+
25
+ self.endpoint = config[:endpoint]
26
+ self.api_version = config[:api_version]
27
+ self.access_key_id = config[:access_key_id]
28
+ self.access_key_secret = config[:access_key_secret]
29
+ self.security_token = config[:security_token]
30
+ self.opts = config[:opts] || {}
31
+ self.verbose = verbose.instance_of?(TrueClass) && verbose
32
+ self.codes = Set.new [200, '200', 'OK', 'Success']
33
+ self.codes.merge config[:codes] if config[:codes]
34
+ end
35
+
36
+ def request(action:, params: {}, opts: {})
37
+ opts = self.opts.merge(opts)
38
+ action = upcase_first(action) if opts[:format_action]
39
+ params = format_params(params) unless opts[:format_params]
40
+ defaults = default_params
41
+ params = { Action: action }.merge(defaults).merge(params)
42
+ method = (opts[:method] || 'GET').upcase
43
+ normalized = normalize(params)
44
+ canonicalized = canonicalize(normalized)
45
+ string_to_sign = "#{method}&#{encode('/')}&#{encode(canonicalized)}"
46
+ key = self.access_key_secret + '&'
47
+ signature = Base64.encode64(OpenSSL::HMAC.digest('sha1', key, string_to_sign)).strip
48
+ normalized.push(['Signature', encode(signature)])
49
+
50
+ querystring = canonicalize(normalized)
51
+
52
+ uri = opts[:method] == 'POST' ? '/' : "/?#{querystring}"
53
+
54
+ response = connection.send(method.downcase, uri) do |request|
55
+ request.headers['User-Agent'] = DEFAULT_UA
56
+ if opts[:method] == 'POST'
57
+ request.headers['Content-Type'] = 'application/x-www-form-urlencoded'
58
+ request.body = querystring
59
+ end
60
+ end
61
+
62
+ response_body = JSON.parse(response.body)
63
+ if response_body['Code'] && !self.codes.include?(response_body['Code'])
64
+ raise StandardError, "#{response_body['Message']}, URL: #{uri}"
65
+ end
66
+
67
+ response_body
68
+ end
69
+
70
+ private
71
+
72
+ def connection(adapter = Faraday.default_adapter)
73
+ Faraday.new(:url => self.endpoint) { |faraday| faraday.adapter adapter }
74
+ end
75
+
76
+ def default_params
77
+ default_params = {
78
+ 'Format' => 'JSON',
79
+ 'SignatureMethod' => 'HMAC-SHA1',
80
+ 'SignatureNonce' => SecureRandom.hex(16),
81
+ 'SignatureVersion' => '1.0',
82
+ 'Timestamp' => Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ'),
83
+ 'AccessKeyId' => self.access_key_id,
84
+ 'Version' => self.api_version,
85
+ }
86
+ default_params.merge!('SecurityToken' => self.security_token) if self.security_token
87
+ default_params
88
+ end
89
+
90
+ def encode(string)
91
+ encoded = CGI.escape string
92
+ encoded.gsub(/[\+]/, '%20')
93
+ end
94
+
95
+ def format_params(param_hash)
96
+ param_hash.keys.each { |key| param_hash[upcase_first(key.to_s).to_sym] = param_hash.delete key }
97
+ param_hash
98
+ end
99
+
100
+ def replace_repeat_list(target, key, repeat)
101
+ repeat.each_with_index do |item, index|
102
+ if item && item.instance_of?(Hash)
103
+ item.each_key { |k| target["#{key}.#{index.next}.#{k}"] = item[k] }
104
+ else
105
+ target["#{key}.#{index.next}"] = item
106
+ end
107
+ end
108
+ target
109
+ end
110
+
111
+ def flat_params(params)
112
+ target = {}
113
+ params.each do |key, value|
114
+ if value.instance_of?(Array)
115
+ replace_repeat_list(target, key, value)
116
+ else
117
+ target[key.to_s] = value
118
+ end
119
+ end
120
+ target
121
+ end
122
+
123
+ def normalize(params)
124
+ flat_params(params)
125
+ .sort
126
+ .to_h
127
+ .map { |key, value| [encode(key), encode(value)] }
128
+ end
129
+
130
+ def canonicalize(normalized)
131
+ normalized.map { |element| "#{element.first}=#{element.last}" }.join('&')
132
+ end
133
+
134
+ def validate(config)
135
+ raise ArgumentError, 'must pass "config"' unless config
136
+ raise ArgumentError, 'must pass "config[:endpoint]"' unless config[:endpoint]
137
+ unless config[:endpoint].start_with?('http://') || config[:endpoint].start_with?('https://')
138
+ raise ArgumentError, '"config.endpoint" must starts with \'https://\' or \'http://\'.'
139
+ end
140
+ raise ArgumentError, 'must pass "config[:api_version]"' unless config[:api_version]
141
+ raise ArgumentError, 'must pass "config[:access_key_id]"' unless config[:access_key_id]
142
+ raise ArgumentError, 'must pass "config[:access_key_secret]"' unless config[:access_key_secret]
143
+ end
144
+ end
145
+ end
data/lib/aliyunsdkcore.rb CHANGED
@@ -1,6 +1,11 @@
1
- require_relative './rpc_client'
2
- require_relative './roa_client'
1
+ require 'aliyunsdkcore/rpc_client'
2
+ require 'aliyunsdkcore/roa_client'
3
3
 
4
4
  module AliyunSDKCore
5
- VERSION = "0.0.7"
5
+ VERSION = "0.0.8"
6
+ DEFAULT_UA = "AlibabaCloud (#{Gem::Platform.local.os}; " +
7
+ "#{Gem::Platform.local.cpu}) Ruby/#{RUBY_VERSION} Core/#{VERSION}"
6
8
  end
9
+
10
+ RPCClient = AliyunSDKCore::RPCClient
11
+ ROAClient = AliyunSDKCore::ROAClient
@@ -1,10 +1,11 @@
1
1
  require 'rspec'
2
- require 'roa_client'
3
2
  require 'webmock/rspec'
4
3
 
4
+ require 'aliyunsdkcore'
5
+
5
6
  describe 'roa request' do
6
7
 
7
- WebMock.disable_net_connect!(allow: %r{ros.aliyuncs.com/regions})
8
+ WebMock.allow_net_connect!
8
9
 
9
10
  let(:roa_client) do
10
11
  ROAClient.new(
@@ -12,7 +13,7 @@ describe 'roa request' do
12
13
  api_version: '2015-09-01',
13
14
  access_key_id: ENV['ACCESS_KEY_ID'],
14
15
  access_key_secret: ENV['ACCESS_KEY_SECRET'],
15
- )
16
+ )
16
17
  end
17
18
 
18
19
  it 'request' do
@@ -1,273 +1,279 @@
1
1
  require 'rspec'
2
- require 'roa_client'
3
2
  require 'webmock/rspec'
4
3
 
5
- describe 'roa core' do
4
+ require "aliyunsdkcore"
6
5
 
7
- it 'should pass into "config"' do
8
- expect {
9
- ROAClient.new(nil)
10
- }.to raise_error(ArgumentError, 'must pass "config"')
11
- end
12
-
13
- it 'should pass into "config[:endpoint]"' do
14
- expect {
15
- ROAClient.new({})
16
- }.to raise_error(ArgumentError, 'must pass "config[:endpoint]"')
17
- end
18
-
19
- it 'should pass into valid "config[:endpoint]"' do
20
- expect {
21
- ROAClient.new(endpoint: 'ecs.aliyuncs.com/')
22
- }.to raise_error(ArgumentError, '"config.endpoint" must starts with \'https://\' or \'http://\'.')
23
- end
6
+ describe 'ROAClient' do
24
7
 
25
- it 'should pass into "config[:api_version]"' do
26
- expect {
27
- ROAClient.new(endpoint: 'http://ecs.aliyuncs.com/')
28
- }.to raise_error(ArgumentError, 'must pass "config[:api_version]"')
29
- end
8
+ describe 'initialize' do
30
9
 
31
- it 'should pass into "config[:access_key_id]"' do
32
- expect {
33
- ROAClient.new(endpoint: 'http://ecs.aliyuncs.com/', api_version: '1.0')
34
- }.to raise_error(ArgumentError, 'must pass "config[:access_key_id]"')
35
- end
10
+ it 'should pass into "config"' do
11
+ expect {
12
+ ROAClient.new(nil)
13
+ }.to raise_error(ArgumentError, 'must pass "config"')
14
+ end
36
15
 
37
- it 'should pass into "config[:access_key_secret]"' do
38
- expect {
39
- ROAClient.new(endpoint: 'http://ecs.aliyuncs.com/', api_version: '1.0', access_key_id: 'access_key_id')
40
- }.to raise_error(ArgumentError, 'must pass "config[:access_key_secret]"')
41
- end
16
+ it 'should pass into "config[:endpoint]"' do
17
+ expect {
18
+ ROAClient.new({})
19
+ }.to raise_error(ArgumentError, 'must pass "config[:endpoint]"')
20
+ end
42
21
 
43
- it 'should ok with http protocol' do
44
- roa_client = ROAClient.new(
45
- endpoint: 'http://ecs.aliyuncs.com/',
46
- api_version: '1.0',
47
- access_key_id: 'access_key_id',
48
- access_key_secret: 'access_key_secret'
49
- )
50
- expect(roa_client.endpoint).to eq('http://ecs.aliyuncs.com/')
51
- end
22
+ it 'should pass into valid "config[:endpoint]"' do
23
+ expect {
24
+ ROAClient.new(endpoint: 'ros.aliyuncs.com/')
25
+ }.to raise_error(ArgumentError, '"config.endpoint" must starts with \'https://\' or \'http://\'.')
26
+ end
52
27
 
53
- it 'should ok with https protocol' do
54
- roa_client = ROAClient.new(
55
- endpoint: 'https://ecs.aliyuncs.com/',
56
- api_version: '1.0',
57
- access_key_id: 'access_key_id',
58
- access_key_secret: 'access_key_secret'
59
- )
60
- expect(roa_client.endpoint).to eq('https://ecs.aliyuncs.com/')
61
- end
28
+ it 'should pass into "config[:api_version]"' do
29
+ expect {
30
+ ROAClient.new(endpoint: 'http://ros.aliyuncs.com/')
31
+ }.to raise_error(ArgumentError, 'must pass "config[:api_version]"')
32
+ end
62
33
 
63
- let(:roa_client) do
64
- ROAClient.new(
65
- endpoint: 'https://ecs.aliyuncs.com/',
66
- api_version: '1.0',
67
- access_key_id: 'access_key_id',
68
- access_key_secret: 'access_key_secret'
69
- )
70
- end
34
+ it 'should pass into "config[:access_key_id]"' do
35
+ expect {
36
+ ROAClient.new(endpoint: 'http://ros.aliyuncs.com/', api_version: '1.0')
37
+ }.to raise_error(ArgumentError, 'must pass "config[:access_key_id]"')
38
+ end
71
39
 
72
- let(:default_header_keys) do
73
- %w(accept date host
74
- x-acs-signature-nonce
75
- x-acs-signature-method
76
- x-acs-signature-version
77
- x-acs-version
78
- x-sdk-client).map(&:to_sym)
79
- end
40
+ it 'should pass into "config[:access_key_secret]"' do
41
+ expect {
42
+ ROAClient.new(endpoint: 'http://ros.aliyuncs.com/', api_version: '1.0', access_key_id: 'access_key_id')
43
+ }.to raise_error(ArgumentError, 'must pass "config[:access_key_secret]"')
44
+ end
80
45
 
81
- it 'default headers should ok' do
82
- expect(roa_client.default_headers.keys).to match_array(default_header_keys)
83
- expect(roa_client.default_headers[:accept]).to eq('application/json')
84
- expect(roa_client.default_headers[:date]).to match(/[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT/)
85
- expect(roa_client.default_headers[:host]).to eq('ecs.aliyuncs.com')
86
- end
46
+ it 'should ok with http protocol' do
47
+ roa_client = ROAClient.new(
48
+ endpoint: 'http://ros.aliyuncs.com/',
49
+ api_version: '1.0',
50
+ access_key_id: 'access_key_id',
51
+ access_key_secret: 'access_key_secret'
52
+ )
53
+ expect(roa_client.endpoint).to eq('http://ros.aliyuncs.com/')
54
+ end
87
55
 
88
- it 'default_headers should ok with security_token' do
89
-
90
- roa_client = ROAClient.new(
91
- endpoint: 'https://ecs.aliyuncs.com/',
92
- api_version: '1.0',
93
- access_key_id: 'access_key_id',
94
- access_key_secret: 'access_key_secret',
95
- security_token: 'security_token'
96
- )
97
-
98
- default_header_keys = %w(
99
- accept
100
- date
101
- host
102
- x-acs-signature-nonce
103
- x-acs-signature-method
104
- x-acs-signature-version
105
- x-acs-version
106
- x-sdk-client
107
- x-acs-accesskey-id
108
- x-acs-security-token
109
- ).map(&:to_sym)
110
-
111
- expect(roa_client.default_headers[:accept]).to eq('application/json')
112
- expect(roa_client.default_headers.keys).to match_array(default_header_keys)
113
- expect(roa_client.default_headers[:date]).to match(/[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT/)
114
- expect(roa_client.default_headers[:accept]).to eq('application/json')
115
- end
56
+ it 'should ok with https protocol' do
57
+ roa_client = ROAClient.new(
58
+ endpoint: 'https://ros.aliyuncs.com/',
59
+ api_version: '1.0',
60
+ access_key_id: 'access_key_id',
61
+ access_key_secret: 'access_key_secret'
62
+ )
63
+ expect(roa_client.endpoint).to eq('https://ros.aliyuncs.com/')
64
+ end
116
65
 
117
- describe 'request with json response should ok' do
118
- it 'json response should ok' do
119
- stub_request(:get, "https://ecs.aliyuncs.com/")
120
- .to_return(status: 200, headers: { 'Content-Type': 'application/json' }, body: { ok: true }.to_json)
121
- response = roa_client.get(uri: '/')
122
- expect(response.body).to eq({ ok: true }.to_json)
66
+ let(:roa_client) do
67
+ ROAClient.new(
68
+ endpoint: 'https://ros.aliyuncs.com/',
69
+ api_version: '1.0',
70
+ access_key_id: 'access_key_id',
71
+ access_key_secret: 'access_key_secret'
72
+ )
123
73
  end
124
- end
125
74
 
126
- describe 'request(204) with json response should ok' do
127
- it 'json response should ok' do
128
- stub_request(:get, "https://ecs.aliyuncs.com/")
129
- .to_return(status: 204, headers: { 'Content-Type': 'application/json' }, body: '')
130
- response = roa_client.get(uri: '/')
131
- expect(response.body).to eq('')
75
+ let(:default_header_keys) do
76
+ %w(accept date host
77
+ x-acs-signature-nonce
78
+ x-acs-signature-method
79
+ x-acs-signature-version
80
+ x-acs-version
81
+ x-sdk-client
82
+ user-agent)
132
83
  end
133
- end
134
84
 
135
- describe 'request(400) with json response should ok' do
136
- let(:status) { 400 }
137
- let(:headers) { { 'Content-Type': 'application/json' } }
138
- let(:body) { { 'Message': 'error message', 'RequestId': 'requestid', 'Code': 'errorcode' }.to_json }
139
- it 'json response should ok' do
140
- stub_request(:get, "https://ecs.aliyuncs.com/")
141
- .to_return(status: status, headers: headers, body: body)
142
- expect {
143
- roa_client.get(uri: '/')
144
- }.to raise_error(StandardError, 'code: 400, error message requestid: requestid')
85
+ it 'default headers should ok' do
86
+ expect(roa_client.default_headers.keys).to match_array(default_header_keys)
87
+ expect(roa_client.default_headers['accept']).to eq('application/json')
88
+ expect(roa_client.default_headers['date']).to match(/[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT/)
89
+ expect(roa_client.default_headers['host']).to eq('ros.aliyuncs.com')
90
+ expect(roa_client.default_headers['user-agent']).to match(/AlibabaCloud \(.*; .*\) Ruby\/\d+.\d+.\d+ Core\/\d+.\d+.\d+/)
145
91
  end
146
- end
147
92
 
148
- describe 'request with xml response should ok' do
149
- let(:status) { 200 }
150
- let(:headers) { { 'Content-Type': 'text/xml' } }
151
- let(:body) do
152
- <<-XML
153
- <note>
154
- <to>George</to>
155
- <from>John</from>
156
- <heading>Reminder</heading>
157
- <body>Don't forget the meeting!</body>
158
- </note>
159
- XML
93
+ it 'default_headers should ok with security_token' do
94
+ roa_client = ROAClient.new(
95
+ endpoint: 'https://ros.aliyuncs.com/',
96
+ api_version: '1.0',
97
+ access_key_id: 'access_key_id',
98
+ access_key_secret: 'access_key_secret',
99
+ security_token: 'security_token'
100
+ )
101
+
102
+ default_header_keys = %w(
103
+ accept
104
+ date
105
+ host
106
+ x-acs-signature-nonce
107
+ x-acs-signature-method
108
+ x-acs-signature-version
109
+ x-acs-version
110
+ x-sdk-client
111
+ x-acs-accesskey-id
112
+ x-acs-security-token
113
+ user-agent
114
+ )
115
+
116
+ expect(roa_client.default_headers['accept']).to eq('application/json')
117
+ expect(roa_client.default_headers.keys).to match_array(default_header_keys)
118
+ expect(roa_client.default_headers['date']).to match(/[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT/)
119
+ expect(roa_client.default_headers['accept']).to eq('application/json')
120
+ expect(roa_client.default_headers['user-agent']).to match(/AlibabaCloud \(.*; .*\) Ruby\/\d+.\d+.\d+ Core\/\d+.\d+.\d+/)
160
121
  end
161
- it 'xml response should ok' do
162
- stub_request(:get, "https://ecs.aliyuncs.com/")
163
- .to_return(status: status, headers: headers, body: body)
164
- response = roa_client.get(uri: '/')
165
- expect(response.body).to eq(body)
122
+
123
+ describe 'request with json response should ok' do
124
+ it 'json response should ok' do
125
+ stub_request(:get, "https://ros.aliyuncs.com/")
126
+ .to_return(status: 200, headers: { 'Content-Type': 'application/json' }, body: { ok: true }.to_json)
127
+ response = roa_client.get(uri: '/')
128
+ expect(response.body).to eq({ ok: true }.to_json)
129
+ end
166
130
  end
167
- end
168
131
 
169
- describe 'request(400) with xml response should ok' do
170
- let(:status) { 400 }
171
- let(:headers) { { 'Content-Type': 'text/xml' } }
172
- let(:body) do
173
- <<-XML
174
- <Error>
175
- <Message>error message</Message>
176
- <RequestId>requestid</RequestId>
177
- <HostId>hostid</HostId>
178
- <Code>errorcode</Code>
179
- </Error>
180
- XML
132
+ describe 'request(204) with json response should ok' do
133
+ it 'json response should ok' do
134
+ stub_request(:get, "https://ros.aliyuncs.com/")
135
+ .to_return(status: 204, headers: { 'Content-Type': 'application/json' }, body: '')
136
+ response = roa_client.get(uri: '/')
137
+ expect(response.body).to eq('')
138
+ end
181
139
  end
182
- it 'xml response should ok' do
183
- stub_request(:get, "https://ecs.aliyuncs.com/")
184
- .to_return(status: status, headers: headers, body: body)
185
- expect {
186
- roa_client.get(uri: '/')
187
- }.to raise_error(ROAClient::ACSError, 'error message host_id: hostid, request_id: requestid')
140
+
141
+ describe 'request(400) with json response should ok' do
142
+ let(:status) { 400 }
143
+ let(:headers) { { 'Content-Type': 'application/json' } }
144
+ let(:body) { { 'Message': 'error message', 'RequestId': 'requestid', 'Code': 'errorcode' }.to_json }
145
+ it 'json response should ok' do
146
+ stub_request(:get, "https://ros.aliyuncs.com/")
147
+ .to_return(status: status, headers: headers, body: body)
148
+ expect {
149
+ roa_client.get(uri: '/')
150
+ }.to raise_error(StandardError, 'code: 400, error message requestid: requestid')
151
+ end
188
152
  end
189
- end
190
153
 
191
- describe 'request(200) with plain response should ok' do
192
- it 'plain response should ok' do
193
- stub_request(:get, "https://ecs.aliyuncs.com/").to_return(status: 200, body: 'plain text')
194
- response = roa_client.get(uri: '/')
195
- expect(response.body).to eq('plain text')
154
+ describe 'request with xml response should ok' do
155
+ let(:status) { 200 }
156
+ let(:headers) { { 'Content-Type': 'text/xml' } }
157
+ let(:body) do
158
+ <<-XML
159
+ <note>
160
+ <to>George</to>
161
+ <from>John</from>
162
+ <heading>Reminder</heading>
163
+ <body>Don't forget the meeting!</body>
164
+ </note>
165
+ XML
166
+ end
167
+ it 'xml response should ok' do
168
+ stub_request(:get, "https://ros.aliyuncs.com/")
169
+ .to_return(status: status, headers: headers, body: body)
170
+ response = roa_client.get(uri: '/')
171
+ expect(response.body).to eq(body)
172
+ end
196
173
  end
197
- end
198
174
 
199
- describe 'post should ok' do
200
- it 'should ok' do
201
- stub_request(:get, "https://ecs.aliyuncs.com/")
202
- .to_return(status: 200, headers: { 'content-type': 'application/json' }, body: { ok: true }.to_json)
203
- response = roa_client.post(uri: '/', body: 'text')
204
- expect(response.body).to eq('{"ok":true}')
175
+ describe 'request(400) with xml response should ok' do
176
+ let(:status) { 400 }
177
+ let(:headers) { { 'Content-Type': 'text/xml' } }
178
+ let(:body) do
179
+ <<-XML
180
+ <Error>
181
+ <Message>error message</Message>
182
+ <RequestId>requestid</RequestId>
183
+ <HostId>hostid</HostId>
184
+ <Code>errorcode</Code>
185
+ </Error>
186
+ XML
187
+ end
188
+ it 'xml response should ok' do
189
+ stub_request(:get, "https://ros.aliyuncs.com/")
190
+ .to_return(status: status, headers: headers, body: body)
191
+ expect {
192
+ roa_client.get(uri: '/')
193
+ }.to raise_error(ROAClient::ACSError, 'error message host_id: hostid, request_id: requestid')
194
+ end
205
195
  end
206
- it 'should ok with query' do
207
- stub_request(:get, "https://ecs.aliyuncs.com/?k=v")
208
- .to_return(status: 200, headers: { 'content-type': 'application/json' }, body: { ok: true }.to_json)
209
- response = roa_client.post(uri: '/', params: { k: 'v' }, body: 'text')
210
- expect(response.body).to eq('{"ok":true}')
196
+
197
+ describe 'request(200) with plain response should ok' do
198
+ it 'plain response should ok' do
199
+ stub_request(:get, "https://ros.aliyuncs.com/").to_return(status: 200, body: 'plain text')
200
+ response = roa_client.get(uri: '/')
201
+ expect(response.body).to eq('plain text')
202
+ end
211
203
  end
212
- end
213
204
 
214
- describe 'put should ok' do
215
- it 'should ok' do
216
- stub_request(:get, "https://ecs.aliyuncs.com/")
217
- .to_return(status: 200, headers: { 'content-type': 'application/json' }, body: { ok: true }.to_json)
218
- response = roa_client.put(uri: '/', body: 'text')
219
- expect(response.body).to eq('{"ok":true}')
205
+ describe 'post should ok' do
206
+ it 'should ok' do
207
+ stub_request(:get, "https://ros.aliyuncs.com/")
208
+ .to_return(status: 200, headers: { 'content-type': 'application/json' }, body: { ok: true }.to_json)
209
+ response = roa_client.post(uri: '/', body: 'text')
210
+ expect(response.body).to eq('{"ok":true}')
211
+ end
212
+ it 'should ok with query' do
213
+ stub_request(:get, "https://ros.aliyuncs.com/?k=v")
214
+ .to_return(status: 200, headers: { 'content-type': 'application/json' }, body: { ok: true }.to_json)
215
+ response = roa_client.post(uri: '/', params: { k: 'v' }, body: 'text')
216
+ expect(response.body).to eq('{"ok":true}')
217
+ end
220
218
  end
221
- end
222
219
 
223
- describe 'delete should ok' do
224
- it 'should ok' do
225
- stub_request(:get, "https://ecs.aliyuncs.com/")
226
- .to_return(status: 200, headers: { 'content-type': 'application/json' }, body: { ok: true }.to_json)
227
- response = roa_client.delete(uri: '/')
228
- expect(response.body).to eq('{"ok":true}')
220
+ describe 'put should ok' do
221
+ it 'should ok' do
222
+ stub_request(:get, "https://ros.aliyuncs.com/")
223
+ .to_return(status: 200, headers: { 'content-type': 'application/json' }, body: { ok: true }.to_json)
224
+ response = roa_client.put(uri: '/', body: 'text')
225
+ expect(response.body).to eq('{"ok":true}')
226
+ end
229
227
  end
230
- end
231
228
 
232
- it 'signature should ok' do
233
- expect(roa_client.send(:signature, '123456')).to eq('BeAYlq/e5iWAoTNmzf8jbcBxdq0=')
234
- end
229
+ describe 'delete should ok' do
230
+ it 'should ok' do
231
+ stub_request(:get, "https://ros.aliyuncs.com/")
232
+ .to_return(status: 200, headers: { 'content-type': 'application/json' }, body: { ok: true }.to_json)
233
+ response = roa_client.delete(uri: '/')
234
+ expect(response.body).to eq('{"ok":true}')
235
+ end
236
+ end
235
237
 
236
- it 'authorization should ok' do
237
- expect(roa_client.send(:authorization, '123456')).to eq('acs access_key_id:BeAYlq/e5iWAoTNmzf8jbcBxdq0=')
238
- end
238
+ it 'signature should ok' do
239
+ expect(roa_client.send(:signature, '123456')).to eq('BeAYlq/e5iWAoTNmzf8jbcBxdq0=')
240
+ end
239
241
 
240
- it 'canonicalized_headers should ok' do
241
- expect(roa_client.send(:canonicalized_headers, {})).to be_empty
242
- expect(roa_client.send(:canonicalized_headers, { key: 'value' })).to be_empty
243
- expect(roa_client.send(:canonicalized_headers, { 'x-acs-key': 'value' })).to eq("x-acs-key:value\n")
244
- end
242
+ it 'authorization should ok' do
243
+ expect(roa_client.send(:authorization, '123456')).to eq('acs access_key_id:BeAYlq/e5iWAoTNmzf8jbcBxdq0=')
244
+ end
245
245
 
246
- it 'canonicalized_resource should ok' do
247
- expect(roa_client.send(:canonicalized_resource, '/')).to eq('/')
248
- expect(roa_client.send(:canonicalized_resource, '/', { key: 'value' })).to eq('/?key=value')
249
- expect(roa_client.send(:canonicalized_resource, '/', { key: 'value', 'key1': 'value2' })).to eq('/?key=value&key1=value2')
250
- end
246
+ it 'canonicalized_headers should ok' do
247
+ expect(roa_client.send(:canonicalized_headers, {})).to be_empty
248
+ expect(roa_client.send(:canonicalized_headers, { key: 'value' })).to be_empty
249
+ expect(roa_client.send(:canonicalized_headers, { 'x-acs-key': 'value' })).to eq("x-acs-key:value\n")
250
+ end
251
251
 
252
- it 'string_to_sign should ok ' do
253
- expect(
254
- roa_client.send(:string_to_sign, 'GET', '/', { 'accept' => 'application/json' })
255
- ).to eq("GET\napplication/json\n\n\n\n/")
256
- end
252
+ it 'canonicalized_resource should ok' do
253
+ expect(roa_client.send(:canonicalized_resource, '/')).to eq('/')
254
+ expect(roa_client.send(:canonicalized_resource, '/', { key: 'value' })).to eq('/?key=value')
255
+ expect(roa_client.send(:canonicalized_resource, '/', { key: 'value', 'key1': 'value2' })).to eq('/?key=value&key1=value2')
256
+ end
257
257
 
258
- describe 'ROAClient::ACSError class' do
259
- it 'ACSError should ok' do
260
- expect {
261
- error_info = {
262
- 'Message' => 'error message',
263
- 'Code' => 'errorcode',
264
- 'HostId' => 'hostid',
265
- 'RequestId' => 'requestid',
266
- }
267
- raise ROAClient::ACSError, error_info
268
- }.to raise_error(ROAClient::ACSError, 'error message host_id: hostid, request_id: requestid')
258
+ it 'string_to_sign should ok ' do
259
+ expect(
260
+ roa_client.send(:string_to_sign, 'GET', '/', { 'accept' => 'application/json' })
261
+ ).to eq("GET\napplication/json\n\n\n\n/")
269
262
  end
270
- end
271
263
 
264
+ describe 'ROAClient::ACSError class' do
265
+ it 'ACSError should ok' do
266
+ expect {
267
+ error_info = {
268
+ 'Message' => 'error message',
269
+ 'Code' => 'errorcode',
270
+ 'HostId' => 'hostid',
271
+ 'RequestId' => 'requestid',
272
+ }
273
+ raise ROAClient::ACSError, error_info
274
+ }.to raise_error(ROAClient::ACSError, 'error message host_id: hostid, request_id: requestid')
275
+ end
276
+ end
272
277
 
278
+ end
273
279
  end
@@ -1,10 +1,11 @@
1
1
  require 'rspec'
2
- require 'rpc_client'
3
2
  require 'webmock/rspec'
4
3
 
4
+ require "aliyunsdkcore"
5
+
5
6
  describe 'rpc request' do
6
7
 
7
- WebMock.disable_net_connect!(allow: %r{ecs.aliyuncs.com})
8
+ WebMock.allow_net_connect!
8
9
 
9
10
  let(:rpc_client) do
10
11
  RPCClient.new(
@@ -1,10 +1,11 @@
1
1
  require 'rspec'
2
- require 'rpc_client'
3
2
  require 'webmock/rspec'
4
3
 
5
- describe 'rpc core' do
4
+ require "aliyunsdkcore"
6
5
 
7
- describe 'RPCClient' do
6
+ describe 'RPCClient' do
7
+
8
+ describe 'initialize' do
8
9
  it 'should pass into "config"' do
9
10
  expect {
10
11
  RPCClient.new(nil)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aliyunsdkcore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alibaba Cloud SDK
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-05 00:00:00.000000000 Z
11
+ date: 2019-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -44,28 +44,28 @@ dependencies:
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: 0.16.1
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
- version: '0'
54
+ version: 0.16.1
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
- version: '0'
61
+ version: 3.8.0
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: 3.8.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: codecov
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -89,8 +89,8 @@ extra_rdoc_files: []
89
89
  files:
90
90
  - README.md
91
91
  - lib/aliyunsdkcore.rb
92
- - lib/roa_client.rb
93
- - lib/rpc_client.rb
92
+ - lib/aliyunsdkcore/roa_client.rb
93
+ - lib/aliyunsdkcore/rpc_client.rb
94
94
  - spec/roa_client_integration_spec.rb
95
95
  - spec/roa_client_spec.rb
96
96
  - spec/rpc_client_integration_spec.rb
data/lib/roa_client.rb DELETED
@@ -1,153 +0,0 @@
1
- require 'faraday'
2
- require 'securerandom'
3
- require 'active_support/all'
4
-
5
- def present?(obj)
6
- obj.respond_to?(:empty?) ? !obj.empty? : obj
7
- end
8
-
9
- class ROAClient
10
-
11
- attr_accessor :endpoint, :api_version, :access_key_id, :access_key_secret, :security_token, :hostname, :opts
12
-
13
- def initialize(config)
14
-
15
- validate config
16
-
17
- self.endpoint = config[:endpoint]
18
- self.api_version = config[:api_version]
19
- self.access_key_id = config[:access_key_id]
20
- self.access_key_secret = config[:access_key_secret]
21
- self.security_token = config[:security_token]
22
-
23
- end
24
-
25
- def request(method:, uri:, params: {}, body: {}, headers: {}, options: {})
26
-
27
- mix_headers = default_headers.merge(headers)
28
-
29
- response = connection.send(method.downcase) do |request|
30
- request.url uri, params
31
- if present?(body)
32
- request_body = body.to_json
33
- request.body = request_body
34
- mix_headers['content-md5'] = Digest::MD5.base64digest request_body
35
- mix_headers['content-length'] = request_body.length.to_s
36
- end
37
- string_to_sign = string_to_sign(method, uri, mix_headers, params)
38
- mix_headers.merge!(authorization: authorization(string_to_sign))
39
- mix_headers.each { |key, value| request.headers[key] = value }
40
- end
41
-
42
- return response if options.has_key? :raw_body
43
-
44
- response_content_type = response.headers['Content-Type'] || ''
45
- if response_content_type.start_with?('application/json')
46
- if response.status >= 400
47
- result = JSON.parse(response.body)
48
- raise StandardError, "code: #{response.status}, #{result['Message']} requestid: #{result['RequestId']}"
49
- end
50
- end
51
-
52
- if response_content_type.start_with?('text/xml')
53
- result = Hash.from_xml(response.body)
54
- raise ACSError, result['Error'] if result['Error']
55
- end
56
-
57
- response
58
- end
59
-
60
- def connection(adapter = Faraday.default_adapter)
61
- Faraday.new(:url => self.endpoint) { |faraday| faraday.adapter adapter }
62
- end
63
-
64
- def get(uri: '', headers: {}, params: {}, options: {})
65
- request(method: :get, uri: uri, params: params, body: {}, headers: headers, options: options)
66
- end
67
-
68
- def post(uri: '', headers: {}, params: {}, body: {}, options: {})
69
- request(method: :get, uri: uri, params: params, body: body, headers: headers, options: options)
70
- end
71
-
72
- def put(uri: '', headers: {}, params: {}, body: {}, options: {})
73
- request(method: :get, uri: uri, params: params, body: body, headers: headers, options: options)
74
- end
75
-
76
- def delete(uri: '', headers: {}, params: {}, options: {})
77
- request(method: :get, uri: uri, params: params, body: {}, headers: headers, options: options)
78
- end
79
-
80
- def default_headers
81
- default_headers = {
82
- 'accept': 'application/json',
83
- 'date': Time.now.httpdate,
84
- 'host': URI(self.endpoint).host,
85
- 'x-acs-signature-nonce': SecureRandom.hex(16),
86
- 'x-acs-signature-method': 'HMAC-SHA1',
87
- 'x-acs-signature-version': '1.0',
88
- 'x-acs-version': self.api_version,
89
- 'x-sdk-client': "RUBY(#{RUBY_VERSION})" # FIXME: 如何获取Gem的名称和版本号
90
- }
91
- if self.security_token
92
- default_headers.merge!('x-acs-accesskey-id': self.access_key_id, 'x-acs-security-token': self.security_token)
93
- end
94
- default_headers
95
- end
96
-
97
- private
98
-
99
- def string_to_sign(method, uri, headers, query = {})
100
- header_string = [
101
- method,
102
- headers['accept'],
103
- headers['content-md5'] || '',
104
- headers['content-type'] || '',
105
- headers['date'],
106
- ].join("\n")
107
- "#{header_string}\n#{canonicalized_headers(headers)}#{canonicalized_resource(uri, query)}"
108
- end
109
-
110
- def canonicalized_headers(headers)
111
- headers.keys.select { |key| key.to_s.start_with? 'x-acs-' }
112
- .sort.map { |key| "#{key}:#{headers[key].strip}\n" }.join
113
- end
114
-
115
- def canonicalized_resource(uri, query_hash = {})
116
- query_string = query_hash.map { |key, value| "#{key}=#{value}" }.join('&')
117
- query_string.empty? ? uri : "#{uri}?#{query_string}"
118
- end
119
-
120
- def authorization(string_to_sign)
121
- "acs #{self.access_key_id}:#{signature(string_to_sign)}"
122
- end
123
-
124
- def signature(string_to_sign)
125
- Base64.encode64(OpenSSL::HMAC.digest('sha1', self.access_key_secret, string_to_sign)).strip
126
- end
127
-
128
- def validate(config)
129
- raise ArgumentError, 'must pass "config"' unless config
130
- raise ArgumentError, 'must pass "config[:endpoint]"' unless config[:endpoint]
131
- unless config[:endpoint].start_with?('http://') || config[:endpoint].start_with?('https://')
132
- raise ArgumentError, '"config.endpoint" must starts with \'https://\' or \'http://\'.'
133
- end
134
- raise ArgumentError, 'must pass "config[:api_version]"' unless config[:api_version]
135
- raise ArgumentError, 'must pass "config[:access_key_id]"' unless config[:access_key_id]
136
- raise ArgumentError, 'must pass "config[:access_key_secret]"' unless config[:access_key_secret]
137
- end
138
-
139
- class ACSError < StandardError
140
-
141
- attr_accessor :code
142
-
143
- def initialize(error)
144
- self.code = error['Code']
145
- message = error['Message']
146
- host_id = error['HostId']
147
- request_id = error['RequestId']
148
- super("#{message} host_id: #{host_id}, request_id: #{request_id}")
149
- end
150
-
151
- end
152
-
153
- end
data/lib/rpc_client.rb DELETED
@@ -1,141 +0,0 @@
1
- require 'set'
2
- require 'openssl'
3
- require 'faraday'
4
- require 'active_support/all'
5
-
6
- # Converts just the first character to uppercase.
7
- #
8
- # upcase_first('what a Lovely Day') # => "What a Lovely Day"
9
- # upcase_first('w') # => "W"
10
- # upcase_first('') # => ""
11
- def upcase_first(string)
12
- string.length > 0 ? string[0].upcase.concat(string[1..-1]) : ""
13
- end
14
-
15
- class RPCClient
16
-
17
- attr_accessor :endpoint, :api_version, :access_key_id, :access_key_secret, :security_token, :codes, :opts, :verbose
18
-
19
- def initialize(config, verbose = false)
20
-
21
- validate config
22
-
23
- self.endpoint = config[:endpoint]
24
- self.api_version = config[:api_version]
25
- self.access_key_id = config[:access_key_id]
26
- self.access_key_secret = config[:access_key_secret]
27
- self.security_token = config[:security_token]
28
- self.opts = config[:opts] || {}
29
- self.verbose = verbose.instance_of?(TrueClass) && verbose
30
- self.codes = Set.new [200, '200', 'OK', 'Success']
31
- self.codes.merge config[:codes] if config[:codes]
32
- end
33
-
34
- def request(action:, params: {}, opts: {})
35
- opts = self.opts.merge(opts)
36
- action = upcase_first(action) if opts[:format_action]
37
- params = format_params(params) unless opts[:format_params]
38
- defaults = default_params
39
- params = { Action: action }.merge(defaults).merge(params)
40
- method = (opts[:method] || 'GET').upcase
41
- normalized = normalize(params)
42
- canonicalized = canonicalize(normalized)
43
- string_to_sign = "#{method}&#{encode('/')}&#{encode(canonicalized)}"
44
- key = self.access_key_secret + '&'
45
- signature = Base64.encode64(OpenSSL::HMAC.digest('sha1', key, string_to_sign)).strip
46
- normalized.push(['Signature', encode(signature)])
47
-
48
- querystring = canonicalize(normalized)
49
-
50
- uri = opts[:method] == 'POST' ? '/' : "/?#{querystring}"
51
-
52
- response = connection.send(method.downcase, uri) do |request|
53
- if opts[:method] == 'POST'
54
- request.headers['Content-Type'] = 'application/x-www-form-urlencoded'
55
- request.body = querystring
56
- end
57
- end
58
-
59
- response_body = JSON.parse(response.body)
60
- if response_body['Code'] && !self.codes.include?(response_body['Code'])
61
- raise StandardError, "#{response_body['Message']}, URL: #{uri}"
62
- end
63
-
64
- response_body
65
- end
66
-
67
- private
68
-
69
- def connection(adapter = Faraday.default_adapter)
70
- Faraday.new(:url => self.endpoint) { |faraday| faraday.adapter adapter }
71
- end
72
-
73
- def default_params
74
- default_params = {
75
- 'Format' => 'JSON',
76
- 'SignatureMethod' => 'HMAC-SHA1',
77
- 'SignatureNonce' => SecureRandom.hex(16),
78
- 'SignatureVersion' => '1.0',
79
- 'Timestamp' => Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ'),
80
- 'AccessKeyId' => self.access_key_id,
81
- 'Version' => self.api_version,
82
- }
83
- default_params.merge!('SecurityToken' => self.security_token) if self.security_token
84
- default_params
85
- end
86
-
87
- def encode(string)
88
- encoded = CGI.escape string
89
- encoded.gsub(/[\+]/, '%20')
90
- end
91
-
92
- def format_params(param_hash)
93
- param_hash.keys.each { |key| param_hash[upcase_first(key.to_s).to_sym] = param_hash.delete key }
94
- param_hash
95
- end
96
-
97
- def replace_repeat_list(target, key, repeat)
98
- repeat.each_with_index do |item, index|
99
- if item && item.instance_of?(Hash)
100
- item.each_key { |k| target["#{key}.#{index.next}.#{k}"] = item[k] }
101
- else
102
- target["#{key}.#{index.next}"] = item
103
- end
104
- end
105
- target
106
- end
107
-
108
- def flat_params(params)
109
- target = {}
110
- params.each do |key, value|
111
- if value.instance_of?(Array)
112
- replace_repeat_list(target, key, value)
113
- else
114
- target[key.to_s] = value
115
- end
116
- end
117
- target
118
- end
119
-
120
- def normalize(params)
121
- flat_params(params)
122
- .sort
123
- .to_h
124
- .map { |key, value| [encode(key), encode(value)] }
125
- end
126
-
127
- def canonicalize(normalized)
128
- normalized.map { |element| "#{element.first}=#{element.last}" }.join('&')
129
- end
130
-
131
- def validate(config)
132
- raise ArgumentError, 'must pass "config"' unless config
133
- raise ArgumentError, 'must pass "config[:endpoint]"' unless config[:endpoint]
134
- unless config[:endpoint].start_with?('http://') || config[:endpoint].start_with?('https://')
135
- raise ArgumentError, '"config.endpoint" must starts with \'https://\' or \'http://\'.'
136
- end
137
- raise ArgumentError, 'must pass "config[:api_version]"' unless config[:api_version]
138
- raise ArgumentError, 'must pass "config[:access_key_id]"' unless config[:access_key_id]
139
- raise ArgumentError, 'must pass "config[:access_key_secret]"' unless config[:access_key_secret]
140
- end
141
- end