lib/aliyun/connector/rpc_client.rb in aliyun-rails-0.1.18 vs lib/aliyun/connector/rpc_client.rb in aliyun-rails-0.1.19
- old
+ new
@@ -1,116 +1,147 @@
-# frozen_string_literal: true
-
require "set"
require "openssl"
require "faraday"
-require "erb"
require "active_support/all"
module Aliyun
- module Connector
- class RPCClient
- attr_accessor :endpoint, :api_version, :access_key_id, :access_key_secret,
- :security_token, :codes, :opts, :verbose
- # 对象初始化属性
- def initialize(config = {}, verbose = false)
- validate config
+ class RPCClient
- self.endpoint = config[:endpoint]
- self.api_version = config[:api_version]
- self.access_key_id = config[:access_key_id] || Aliyun.access_key_id
- self.access_key_secret = config[:access_key_secret] || Aliyun.access_key_secret
- self.security_token = config[:security_token]
- self.opts = config[:opts] || {}
- self.verbose = verbose.instance_of?(TrueClass) && verbose
+ attr_accessor :endpoint, :api_version, :access_key_id, :access_key_secret,
+ :security_token, :codes, :opts, :verbose
- self.codes = Set.new [200, "200", "OK", "Success"]
- self.codes.merge config[:codes] if config[:codes]
- end
+ # 对象初始化属性
+ def initialize(config, verbose = false)
- # 通用请求接口
- def request(action:, params: {}, opts: {})
- opts = self.opts.merge(opts)
- action = action.upcase_first if opts[:format_action]
- params = format_params(params) unless opts[:format_params]
- defaults = default_params
- params = { Action: action }.merge(defaults).merge(params)
- method = (opts[:method] || "GET").upcase
- sign = "#{method}&#{encode('/')}&#{encode(params.to_query)}"
- secret = "#{self.access_key_secret}&"
- signature = Base64.encode64(OpenSSL::HMAC.digest("sha1", secret, sign)).strip
- params["Signature"] = signature
+ validate config
- # 转换为 query 样式
- query_string = params.to_query
+ self.endpoint = config[:endpoint]
+ self.api_version = config[:api_version]
+ self.access_key_id = config[:access_key_id] || Aliyun.access_key_id
+ self.access_key_secret = config[:access_key_secret] || Aliyun.access_key_secret
+ self.security_token = config[:security_token]
+ self.opts = config[:opts] || {}
+ self.verbose = verbose.instance_of?(TrueClass) && verbose
- # 特殊处理 POST
- uri = opts[:method] == "POST" ? "/" : "/?#{query_string}"
+ self.codes = Set.new [200, "200", "OK", "Success"]
+ self.codes.merge config[:codes] if config[:codes]
+ end
- # 初始化会话
- response = connection.send(method.downcase, uri) do |r|
- if opts[:method] == "POST"
- r.headers["Content-Type"] = "application/x-www-form-urlencoded"
- r.body = query_string
- end
- r.headers["User-Agent"] = DEFAULT_UA
- end
+ # 通用请求接口
+ def request(action:, params: {}, opts: {})
+ opts = self.opts.merge(opts)
+ action = action.upcase_first if opts[:format_action]
+ params = format_params(params) unless opts[:format_params]
+ defaults = default_params
+ params = { Action: action }.merge(defaults).merge(params)
+ method = (opts[:method] || "GET").upcase
+ normalized = normalize(params)
+ canonicalized = canonicalize(normalized)
+ string_to_sign = "#{method}&#{encode('/')}&#{encode(canonicalized)}"
+ secret = "#{self.access_key_secret}&"
+ signature = Base64.encode64(OpenSSL::HMAC.digest("sha1", secret, string_to_sign)).strip
+ normalized.push(["Signature", encode(signature)])
- # 解析接口响应
- response_body = JSON.parse(response.body)
- if response_body["Code"] && !response_body["Code"].to_s.empty? && !self.codes.include?(response_body["Code"])
- raise StandardError, "Code: #{response_body['Code']}, Message: #{response_body['Message']}, URL: #{uri}"
+ # 转换为 query 样式
+ querystring = canonicalize(normalized)
+
+ # 特殊处理 POST
+ uri = opts[:method] == "POST" ? "/" : "/?#{querystring}"
+
+ # 初始化会话
+ response = connection.send(method.downcase, uri) do |request|
+ if opts[:method] == "POST"
+ request.headers["Content-Type"] = "application/x-www-form-urlencoded"
+ request.body = querystring
end
+ request.headers["User-Agent"] = DEFAULT_UA
+ end
- response_body
+ # 解析接口响应
+ response_body = JSON.parse(response.body)
+ if response_body["Code"] && !response_body["Code"].to_s.empty? && !self.codes.include?(response_body["Code"])
+ raise StandardError, "Code: #{response_body['Code']}, Message: #{response_body['Message']}, URL: #{uri}"
end
- private
- def connection(adapter = Faraday.default_adapter)
- Faraday.new(url: self.endpoint) { |f| f.adapter adapter }
- end
+ response_body
+ end
- # 设置缺省参数
- def default_params
- params = {
- Format: "JSON",
- SignatureMethod: "HMAC-SHA1",
- SignatureNonce: SecureRandom.hex(8),
- SignatureVersion: "1.0",
- Timestamp: Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ"),
- AccessKeyId: self.access_key_id,
- Version: self.api_version,
- }
- params[:SecurityToken] = self.security_token if self.security_token.present?
- params
- end
+ private
+ def connection(adapter = Faraday.default_adapter)
+ Faraday.new(url: self.endpoint) { |f| f.adapter adapter }
+ end
- # 消息签名需要
- def encode(input)
- ERB::Util.url_encode input
- end
+ # 设置缺省参数
+ def default_params
+ default_params = {
+ "Format" => "JSON",
+ "SignatureMethod" => "HMAC-SHA1",
+ "SignatureNonce" => SecureRandom.hex(16),
+ "SignatureVersion" => "1.0",
+ "Timestamp" => Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ"),
+ "AccessKeyId" => self.access_key_id,
+ "Version" => self.api_version,
+ }
+ default_params["SecurityToken"] = self.security_token if self.security_token
+ default_params
+ end
- # 转换 HASH key 样式
- def format_params(param_hash)
- param_hash.keys.each { |key| param_hash[(key.to_s.upcase_first).to_sym] = param_hash.delete key }
- param_hash
+ # 消息签名需要
+ def encode(string)
+ ERB::Util.url_encode(string)
+ end
+
+ # 转换 HASH key 样式
+ def format_params(param_hash)
+ param_hash.keys.each { |key| param_hash[(key.to_s.upcase_first).to_sym] = param_hash.delete key }
+ param_hash
+ end
+
+ def replace_repeat_list(target, key, repeat)
+ repeat.each_with_index do |item, index|
+ if item && item.instance_of?(Hash)
+ item.each_key { |k| target["#{key}.#{index.next}.#{k}"] = item[k] }
+ else
+ target["#{key}.#{index.next}"] = item
+ end
end
+ target
+ end
- def validate(config = {})
- config.with_indifferent_access
- raise ArgumentError, 'must pass "config"' unless config
- raise ArgumentError, 'must pass "config[:endpoint]"' unless config[:endpoint]
- unless config[:endpoint].match?(/^http[s]?:/i)
- raise ArgumentError, '"config.endpoint" must starts with \'https://\' or \'http://\'.'
+ def flat_params(params)
+ target = {}
+ params.each do |key, value|
+ if value.instance_of?(Array)
+ replace_repeat_list(target, key, value)
+ else
+ target[key.to_s] = value
end
- raise ArgumentError, 'must pass "config[:api_version]"' unless config[:api_version]
- unless config[:access_key_id] || Aliyun.access_key_id
- raise ArgumentError, 'must pass "config[:access_key_id]" or define "Aliyun.access_key_id"'
- end
- unless config[:access_key_secret] || Aliyun.access_key_secret
- raise ArgumentError, 'must pass "config[:access_key_secret]" or define "Aliyun.access_key_secret"'
- end
end
- end
+ target
+ end
+
+ def normalize(params)
+ flat_params(params).sort.to_h.map { |key, value| [encode(key), encode(value)] }
+ end
+
+ def canonicalize(normalized)
+ normalized.map { |element| "#{element.first}=#{element.last}" }.join("&")
+ end
+
+ def validate(config = {})
+ config.with_indifferent_access
+ raise ArgumentError, 'must pass "config"' unless config
+ raise ArgumentError, 'must pass "config[:endpoint]"' unless config[:endpoint]
+ unless config[:endpoint].match?(/^http[s]?:/i)
+ raise ArgumentError, '"config.endpoint" must starts with \'https://\' or \'http://\'.'
+ end
+ raise ArgumentError, 'must pass "config[:api_version]"' unless config[:api_version]
+ unless config[:access_key_id] || Aliyun.access_key_id
+ raise ArgumentError, 'must pass "config[:access_key_id]" or define "Aliyun.access_key_id"'
+ end
+ unless config[:access_key_secret] || Aliyun.access_key_secret
+ raise ArgumentError, 'must pass "config[:access_key_secret]" or define "Aliyun.access_key_secret"'
+ end
+ end
end
end