Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit d33bb6b

Browse files
Improve User-Agent
1 parent fe12c9c commit d33bb6b

11 files changed

+561
-536
lines changed

‎Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
.PHONY: test install build deploy
22

33
test:
4-
rspec --format doc
4+
bundle exec rspec --format doc --exclude-pattern spec/**/*_integration_spec.rb
5+
bundle exec rspec --format doc -P spec/**/*_integration_spec.rb
56

67
deploy:
78
$(eval VERSION := $(shell cat lib/aliyunsdkcore.rb | grep 'VERSION = ' | cut -d\" -f2))

‎aliyunsdkcore.gemspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Gem::Specification.new do |s|
2424
s.add_dependency 'faraday', '~> 0.15.4'
2525
s.add_dependency 'activesupport', '>= 4.0.0'
2626

27-
s.add_development_dependency "simplecov"
28-
s.add_development_dependency "rspec"
27+
s.add_development_dependency "simplecov",">= 0.16.1"
28+
s.add_development_dependency "rspec",">= 3.8.0"
2929
s.add_development_dependency "codecov", ">= 0.1.10"
3030
end

‎lib/aliyunsdkcore.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
require_relative'./rpc_client'
2-
require_relative'./roa_client'
1+
require'aliyunsdkcore/rpc_client'
2+
require'aliyunsdkcore/roa_client'
33

44
module AliyunSDKCore
55
VERSION = "0.0.7"
6+
DEFAULT_UA = "AlibabaCloud (#{Gem::Platform.local.os}; " +
7+
"#{Gem::Platform.local.cpu}) Ruby/#{RUBY_VERSION} Core/#{VERSION}"
68
end
9+
10+
RPCClient = AliyunSDKCore::RPCClient
11+
ROAClient = AliyunSDKCore::ROAClient

‎lib/aliyunsdkcore/roa_client.rb

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

‎lib/aliyunsdkcore/rpc_client.rb

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /