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