class MSS::S3::Client
Constants
- API_VERSION
- CACHEABLE_REQUESTS
@api private
- EMPTY_BODY_ERRORS
@api private
- XMLNS
Public Class Methods
include RegionDetection
MSS::Core::Client::new
# File lib/mss/s3/client.rb, line 29 def initialize(options = {}) super(options.merge(:http_continue_threshold => 0)) end
Protected Class Methods
# File lib/mss/s3/client.rb, line 87 def self.bucket_method(method_name, verb, *args, &block) method_options = (args.pop if args.last.kind_of?(Hash)) || {} xml_grammar = (args.pop if args.last.respond_to?(:rules)) verb = verb.to_s.upcase subresource = args.first add_client_request_method(method_name) do configure_request do |req, options| require_bucket_name!(options[:bucket_name]) req.http_method = verb req.bucket = options[:bucket_name] req.add_param(subresource) if subresource if header_options = method_options[:header_options] header_options.each do |(opt, header)| if value = options[opt] # for backwards compatability we translate canned acls # header values from symbols to strings (e.g. # :public_read translates to 'public-read') value = (opt == :acl ? value.to_s.tr('_', '-') : value) req.headers[header] = value end end end end instance_eval(&block) if block if xml_grammar parser = Core::XML::Parser.new(xml_grammar.rules) process_response do |resp| resp.data = parser.parse(resp.http_response.body) super(resp) end simulate_response do |resp| resp.data = parser.simulate super(resp) end end end end
Public Instance Methods
@param [Core::Http::Request] request @api private
# File lib/mss/s3/client.rb, line 57 def sign_request request case @config.s3_signature_version.to_sym when :v4 then v4_signer.sign_request(request) when :v3 then v3_signer.sign_request(request) else raise "invalid signature version #{@config.s3_signature_version.inspect}" end end
Protected Instance Methods
@param [Http::Request] req @return [Boolean]
# File lib/mss/s3/client.rb, line 82 def chunk_sign? req req.http_method == 'PUT' && req.headers['content-length'].to_i > 2 * 1024 * 1024 # 2MB end
# File lib/mss/s3/client.rb, line 183 def empty_response_body? response_body response_body.nil? or response_body == '' end
def set_server_side_encryption request, options
sse = options[:server_side_encryption] if sse.is_a?(Symbol) request.headers['x-amz-server-side-encryption'] = sse.to_s.upcase elsif sse request.headers['x-amz-server-side-encryption'] = sse end
end
# File lib/mss/s3/client.rb, line 171 def extract_error_details response if (response.http_response.status >= 300 || response.request_type == :complete_multipart_upload) and body = response.http_response.body and error = Core::XML::Parser.parse(body) and error[:code] then [error[:code], error[:message]] end end
# File lib/mss/s3/client.rb, line 257 def extract_object_headers resp meta = {} resp.http_response.headers.each_pair do |name,value| if name =~ /^x-amz-meta-(.+)$/i meta[$1] = [value].flatten.join end end resp.data[:meta] = meta if expiry = resp.http_response.headers['x-amz-expiration'] expiry.first =~ /^expiry-date="(.+)", rule-id="(.+)"$/ exp_date = DateTime.parse($1) exp_rule_id = $2 else exp_date = nil exp_rule_id = nil end resp.data[:expiration_date] = exp_date if exp_date resp.data[:expiration_rule_id] = exp_rule_id if exp_rule_id restoring = false restore_date = nil if restore = resp.http_response.headers['x-amz-restore'] if restore.first =~ /ongoing-request="(.+?)", expiry-date="(.+?)"/ restoring = $1 == "true" restore_date = $2 && DateTime.parse($2) elsif restore.first =~ /ongoing-request="(.+?)"/ restoring = $1 == "true" end end resp.data[:restore_in_progress] = restoring resp.data[:restore_expiration_date] = restore_date if restore_date { 'x-amz-version-id' => :version_id, 'content-type' => :content_type, 'content-encoding' => :content_encoding, 'cache-control' => :cache_control, 'expires' => :expires, 'etag' => :etag, 'x-amz-website-redirect-location' => :website_redirect_location, 'accept-ranges' => :accept_ranges, 'x-amz-server-side-encryption-customer-algorithm' => :sse_customer_algorithm, 'x-amz-server-side-encryption-customer-key-MD5' => :sse_customer_key_md5 }.each_pair do |header,method| if value = resp.http_response.header(header) resp.data[method] = value end end if time = resp.http_response.header('Last-Modified') resp.data[:last_modified] = Time.parse(time) end if length = resp.http_response.header('content-length') resp.data[:content_length] = length.to_i end if sse = resp.http_response.header('x-amz-server-side-encryption') resp.data[:server_side_encryption] = sse.downcase.to_sym end end
S3
may return a 200 response code in response to complete_multipart_upload and then start streaming whitespace until it knows the final result. At that time it sends an XML
message with success or failure.
# File lib/mss/s3/client.rb, line 209 def failed_multipart_upload? response response.request_type == :complete_multipart_upload && extract_error_details(response) end
@param [String] possible_xml @return [Boolean] Returns `true` if the given string is a valid xml
document.
# File lib/mss/s3/client.rb, line 236 def is_xml? possible_xml begin REXML::Document.new(possible_xml).has_elements? rescue false end end
# File lib/mss/s3/client.rb, line 244 def md5 str Base64.encode64(OpenSSL::Digest::MD5.digest(str)).strip end
Previously the access control policy could be specified via :acl as a string or an object that responds to to_xml. The prefered method now is to pass :access_control_policy an xml document.
# File lib/mss/s3/client.rb, line 223 def move_access_control_policy options if acl = options[:acl] if acl.is_a?(String) and is_xml?(acl) options[:access_control_policy] = options.delete(:acl) elsif acl.respond_to?(:to_xml) options[:access_control_policy] = options.delete(:acl).to_xml end end end
# File lib/mss/s3/client.rb, line 214 def new_request req = S3::Request.new req.force_path_style = config.s3_force_path_style? req end
# File lib/mss/s3/client.rb, line 248 def parse_copy_part_response resp doc = REXML::Document.new(resp.http_response.body) resp[:etag] = doc.root.elements["ETag"].text resp[:last_modified] = doc.root.elements["LastModified"].text if header = resp.http_response.headers['x-amzn-requestid'] data[:request_id] = [header].flatten.first end end
There are a few of s3 requests that can generate empty bodies and yet still be errors. These return empty bodies to comply with the HTTP spec. We have to detect these errors specially.
MSS::Core::Client#populate_error
# File lib/mss/s3/client.rb, line 190 def populate_error resp code = resp.http_response.status if EMPTY_BODY_ERRORS.include?(code) and empty_response_body?(resp.http_response.body) error_class = EMPTY_BODY_ERRORS[code] resp.error = error_class.new(resp.http_request, resp.http_response) else super end end
MSS::Core::Client#retryable_error?
# File lib/mss/s3/client.rb, line 200 def retryable_error? response super or failed_multipart_upload?(response) or response.error.is_a?(Errors::RequestTimeout) end
# File lib/mss/s3/client.rb, line 149 def set_copy_content_length request, options request.headers["Content-Length"] = 0 end
# File lib/mss/s3/client.rb, line 141 def set_metadata request, options if metadata = options[:metadata] Array(metadata).each do |name, value| request.headers["x-amz-meta-#{name}"] = value end end end
# File lib/mss/s3/client.rb, line 153 def set_storage_class request, options storage_class = options[:storage_class] if storage_class.kind_of?(Symbol) request.headers["x-amz-storage-class"] = storage_class.to_s.upcase elsif storage_class request.headers["x-amz-storage-class"] = storage_class end end
@return [Core::Signers::S3]
# File lib/mss/s3/client.rb, line 69 def v3_signer @v3_signer ||= Core::Signers::S3.new(credential_provider) end
@return [Core::Signers::Version4]
# File lib/mss/s3/client.rb, line 74 def v4_signer @v4_signer ||= begin Core::Signers::Version4.new(credential_provider, 's3', @region) end end