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 +4 -4
- data/lib/aliyunsdkcore/roa_client.rb +159 -0
- data/lib/aliyunsdkcore/rpc_client.rb +145 -0
- data/lib/aliyunsdkcore.rb +8 -3
- data/spec/roa_client_integration_spec.rb +4 -3
- data/spec/roa_client_spec.rb +235 -229
- data/spec/rpc_client_integration_spec.rb +3 -2
- data/spec/rpc_client_spec.rb +4 -3
- metadata +8 -8
- data/lib/roa_client.rb +0 -153
- data/lib/rpc_client.rb +0 -141
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 32c52b1b01cf9802096c014e087477c1082bd5bd541e132444b35f69036e7572
|
4
|
+
data.tar.gz: aea3c6a0f3701aa6c54752143216d98926666e790e73bcff75487cf83e8bdd39
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
2
|
-
|
1
|
+
require 'aliyunsdkcore/rpc_client'
|
2
|
+
require 'aliyunsdkcore/roa_client'
|
3
3
|
|
4
4
|
module AliyunSDKCore
|
5
|
-
VERSION = "0.0.
|
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.
|
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
|
data/spec/roa_client_spec.rb
CHANGED
@@ -1,273 +1,279 @@
|
|
1
1
|
require 'rspec'
|
2
|
-
require 'roa_client'
|
3
2
|
require 'webmock/rspec'
|
4
3
|
|
5
|
-
|
4
|
+
require "aliyunsdkcore"
|
6
5
|
|
7
|
-
|
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
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
api_version
|
57
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
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
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
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
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
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
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
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
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
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
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
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
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
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
|
-
|
233
|
-
|
234
|
-
|
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
|
-
|
237
|
-
|
238
|
-
|
238
|
+
it 'signature should ok' do
|
239
|
+
expect(roa_client.send(:signature, '123456')).to eq('BeAYlq/e5iWAoTNmzf8jbcBxdq0=')
|
240
|
+
end
|
239
241
|
|
240
|
-
|
241
|
-
|
242
|
-
|
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
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
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
|
-
|
253
|
-
|
254
|
-
roa_client.send(:
|
255
|
-
|
256
|
-
|
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
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
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.
|
8
|
+
WebMock.allow_net_connect!
|
8
9
|
|
9
10
|
let(:rpc_client) do
|
10
11
|
RPCClient.new(
|
data/spec/rpc_client_spec.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'rspec'
|
2
|
-
require 'rpc_client'
|
3
2
|
require 'webmock/rspec'
|
4
3
|
|
5
|
-
|
4
|
+
require "aliyunsdkcore"
|
6
5
|
|
7
|
-
|
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.
|
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-
|
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:
|
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:
|
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:
|
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:
|
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
|