aliyunsdkcore 0.0.7 → 0.0.8

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