class AWS::S3::Client

Client class for Amazon Simple Storage Service (S3).

Constants

API_VERSION
CACHEABLE_REQUESTS

@api private

EMPTY_BODY_ERRORS

@api private

XMLNS

Protected Class Methods

bucket_method(method_name, verb, *args, &block) click to toggle source
Calls superclass method
# File lib/aws/s3/client.rb, line 85
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

sign_request(request) click to toggle source

@param [Core::Http::Request] request @api private

# File lib/aws/s3/client.rb, line 52
def sign_request request
  version = @config.s3_signature_version ?
    @config.s3_signature_version.to_sym :
    (@region =~ /cn-/ ? :v4 : :v3)
  case version
  when :v4 then v4_signer.sign_request(request)
  when :v3 then v3_signer.sign_request(request)
  else
    raise "invalid signature version #{version.inspect}"
  end
end

Protected Instance Methods

chunk_sign?(req) click to toggle source

@param [Http::Request] req @return [Boolean]

# File lib/aws/s3/client.rb, line 80
def chunk_sign? req
  req.http_method == 'PUT' &&
  req.headers['content-length'].to_i > 2 * 1024 * 1024 # 2MB
end
empty_response_body?(response_body) click to toggle source
# File lib/aws/s3/client.rb, line 177
def empty_response_body? response_body
  response_body.nil? or response_body == ''
end
extract_error_details(response) click to toggle source
# File lib/aws/s3/client.rb, line 165
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
extract_object_headers(resp) click to toggle source
# File lib/aws/s3/client.rb, line 251
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
failed_multipart_upload?(response) click to toggle source

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/aws/s3/client.rb, line 203
def failed_multipart_upload? response
  response.request_type == :complete_multipart_upload &&
  extract_error_details(response)
end
is_xml?(possible_xml) click to toggle source

@param [String] possible_xml @return [Boolean] Returns `true` if the given string is a valid xml

document.
# File lib/aws/s3/client.rb, line 230
def is_xml? possible_xml
  begin
    REXML::Document.new(possible_xml).has_elements?
  rescue
    false
  end
end
md5(str) click to toggle source
# File lib/aws/s3/client.rb, line 238
def md5 str
  Base64.encode64(OpenSSL::Digest::MD5.digest(str)).strip
end
move_access_control_policy(options) click to toggle source

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/aws/s3/client.rb, line 217
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
new_request() click to toggle source
# File lib/aws/s3/client.rb, line 208
def new_request
  req = S3::Request.new
  req.force_path_style = config.s3_force_path_style?
  req
end
parse_copy_part_response(resp) click to toggle source
# File lib/aws/s3/client.rb, line 242
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
populate_error(resp) click to toggle source

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.

Calls superclass method AWS::Core::Client#populate_error
# File lib/aws/s3/client.rb, line 184
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
retryable_error?(response) click to toggle source
Calls superclass method AWS::Core::Client#retryable_error?
# File lib/aws/s3/client.rb, line 194
def retryable_error? response
  super or
  failed_multipart_upload?(response) or
  response.error.is_a?(Errors::RequestTimeout)
end
set_metadata(request, options) click to toggle source
# File lib/aws/s3/client.rb, line 139
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
set_server_side_encryption(request, options) click to toggle source
# File lib/aws/s3/client.rb, line 156
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
set_storage_class(request, options) click to toggle source
# File lib/aws/s3/client.rb, line 147
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
v3_signer() click to toggle source

@return [Core::Signers::S3]

# File lib/aws/s3/client.rb, line 67
def v3_signer
  @v3_signer ||= Core::Signers::S3.new(credential_provider)
end
v4_signer() click to toggle source

@return [Core::Signers::Version4]

# File lib/aws/s3/client.rb, line 72
def v4_signer
  @v4_signer ||= begin
    Core::Signers::Version4.new(credential_provider, 's3', @region)
  end
end