Amazon-S3-Thin

 view release on metacpan or  search on metacpan

lib/Amazon/S3/Thin.pm  view on Meta::CPAN

package Amazon::S3::Thin;
use 5.008001;
use strict;
use warnings;
use Carp;
use LWP::UserAgent;
use Digest::MD5;
use Encode;
use Amazon::S3::Thin::Resource;
use Amazon::S3::Thin::Credentials;

our $VERSION = '0.32';

my $METADATA_PREFIX      = 'x-amz-meta-';
my $MAIN_HOST = 's3.amazonaws.com';

sub new {
    my $class = shift;
    my $self  = shift;

    # If we have an explicitly-configured credential provider then use that here, otherwise
    # existing behaviour will be followed
    if ($self->{credential_provider} and $self->{credential_provider} eq 'env') {
        $self->{credentials} = Amazon::S3::Thin::Credentials->from_env;
    }
    elsif ($self->{credential_provider} and $self->{credential_provider} eq 'metadata') {
        $self->{credentials} = Amazon::S3::Thin::Credentials->from_metadata($self);
    }
    elsif ($self->{credential_provider} and $self->{credential_provider} eq 'ecs_container') {
        $self->{credentials} = Amazon::S3::Thin::Credentials->from_ecs_container($self);
    }
    else {
        # check existence of credentials
        croak "No aws_access_key_id"     unless $self->{aws_access_key_id};
        croak "No aws_secret_access_key" unless $self->{aws_secret_access_key};

        # wrap credentials
        $self->{credentials} = Amazon::S3::Thin::Credentials->new(
            $self->{aws_access_key_id},
            $self->{aws_secret_access_key},
            $self->{aws_session_token},
        );
        delete $self->{aws_access_key_id};
        delete $self->{aws_secret_access_key};
        delete $self->{aws_session_token};
    }
    delete $self->{credential_provider};

    bless $self, $class;

    $self->secure(0)                unless defined $self->secure;
    $self->ua($self->_default_ua)   unless defined $self->ua;
    $self->debug(0)                 unless defined $self->debug;
    $self->virtual_host(0)          unless defined $self->virtual_host;

    $self->{signature_version} = 4  unless defined $self->{signature_version};
    if ($self->{signature_version} == 4 && ! $self->{region}) {
        croak "Please set region when you use signature v4";
    }

    $self->{signer} = $self->_load_signer($self->{signature_version});
    return $self;
}

sub _load_signer {
  my $self = shift;
  my $version = shift;
  my $signer_class = "Amazon::S3::Thin::Signer::V$version";
  eval "require $signer_class" or die $@;

  if ($version == 2) {
      return $signer_class->new($self->{credentials}, $MAIN_HOST);
  } elsif ($version == 4) {
      return $signer_class->new($self->{credentials}, $self->{region});
  }
}


sub _default_ua {
    my $self = shift;

    my $ua = LWP::UserAgent->new(
        keep_alive            => 10,
        requests_redirectable => [qw(GET HEAD DELETE PUT)],
        );
    $ua->timeout(30);
    $ua->env_proxy;
    return $ua;
}

# Accessors

sub secure {
    my $self = shift;
    if (@_) {
        $self->{secure} = shift;
    } else {
        return $self->{secure};
    }
}

sub debug {
    my $self = shift;
    if (@_) {
        $self->{debug} = shift;
    } else {
        return $self->{debug};
    }
}

sub ua {
    my $self = shift;
    if (@_) {
        $self->{ua} = shift;
    } else {
        return $self->{ua};
    }
}

sub virtual_host {
    my $self = shift;
    if (@_) {
        $self->{virtual_host} = shift;
    } else {
        return $self->{virtual_host};
    }
}

sub _send {
    my ($self, $request) = @_;
    warn "[Request]\n" , $request->as_string if $self->{debug};
    my $response = $self->ua->request($request);
    warn "[Response]\n" , $response->as_string if $self->{debug};
    return $response;

lib/Amazon/S3/Thin.pm  view on Meta::CPAN

    {
        croak "$policy_name is not a supported canned access policy";
    }
}

# make the HTTP::Request object
sub _compose_request {
    my ($self, $method, $resource, $headers, $content, $metadata) = @_;
    croak 'must specify method' unless $method;
    croak 'must specify resource'   unless defined $resource;
    if (ref $resource ne 'Amazon::S3::Thin::Resource') {
        croak 'resource must be an instance of Amazon::S3::Thin::Resource';
    }
    $headers ||= {};
    $metadata ||= {};

    # generates an HTTP::Headers objects given one hash that represents http
    # headers to set and another hash that represents an object's metadata.
    my $http_headers = HTTP::Headers->new;
    while (my ($k, $v) = each %$headers) {
        $http_headers->header($k => $v);
    }
    while (my ($k, $v) = each %$metadata) {
        $http_headers->header("$METADATA_PREFIX$k" => $v);
    }

    my $protocol = $self->secure ? 'https' : 'http';

    my $url;

    if ($self->{signature_version} == 4) {
        if ($self->virtual_host) {
            $url = $resource->to_virtual_hosted_style_url($protocol);
        } else {
            $url = $resource->to_path_style_url($protocol, $self->{region});
        }
    } else {
        $url = $resource->to_url_without_region($protocol, $MAIN_HOST);
    }

    my $request = HTTP::Request->new($method, $url, $http_headers, $content);
    # sign the request using the signer, unless already signed
    if (!$request->header('Authorization')) {
        $self->{signer}->sign($request);
    }
    return $request;
}

1;

__END__

=head1 NAME

Amazon::S3::Thin - A thin, lightweight, low-level Amazon S3 client

=head1 SYNOPSIS

  use Amazon::S3::Thin;

  # Pass in explicit credentials
  my $s3client = Amazon::S3::Thin->new({
        aws_access_key_id     => $aws_access_key_id,
        aws_secret_access_key => $aws_secret_access_key,
        aws_session_token     => $aws_session_token, # optional
        region                => $region, # e.g. 'ap-northeast-1'
      });

  # Get credentials from environment
  my $s3client = Amazon::S3::Thin->new({region => $region, credential_provider => 'env'});

  # Get credentials from instance metadata
  my $s3client = Amazon::S3::Thin->new({
      region              => $region,
      credential_provider => 'metadata',
      version             => 2,         # optional (default 2)
      role                => 'my-role', # optional
    });

  # Get credentials from ECS task role
  my $s3client = Amazon::S3::Thin->new({
      region              => $region,
      credential_provider => 'ecs_container',
    });

  my $bucket = "mybucket";
  my $key = "dir/file.txt";
  my $response;

  $response = $s3client->put_bucket($bucket);

  $response = $s3client->put_object($bucket, $key, "hello world");

  $response = $s3client->get_object($bucket, $key);
  print $response->content; # => "hello world"

  $response = $s3client->delete_object($bucket, $key);

  $response = $s3client->list_objects(
                              $bucket,
                              {prefix => "foo", delimiter => "/"}
                             );

You can also pass any useragent as you like

  my $s3client = Amazon::S3::Thin->new({
          ...
          ua => $any_LWP_copmatible_useragent,
      });

Signature version 4 is used by default. 
To use signature version 2, add a C<signature_version> option:

  my $s3client = Amazon::S3::Thin->new({
          ...
          signature_version     => 2,
      });

=head1 DESCRIPTION

Amazon::S3::Thin is a thin, lightweight, low-level Amazon S3 client.

It's designed for only ONE purpose: Send a request and get a response.

In detail, it offers the following features:

=over

=item Low Level

It returns an L<HTTP::Response> object so you can easily inspect
what's happening inside, and can handle errors as you like.

=item Low Dependency

It does not require any XML::* modules, so installation is easy;

=item Low Learning Cost

The interfaces are designed to follow S3 official REST APIs.
So it is easy to learn.

=back

=head2 Comparison to precedent modules

There are already some useful modules like L<Amazon::S3>, L<Net::Amazon::S3>
 on CPAN. They provide a "Perlish" interface, which looks pretty
 for Perl programmers, but they also hide low-level behaviors.
For example, the "get_key" method translate HTTP status 404 into C<undef> and
 HTTP 5xx status into exception.

In some situations, it is very important to see the raw HTTP communications.
That's why I made this module.

=head1 CONSTRUCTOR

=head2 new( \%params )

B<Receives:> hashref with options.

B<Returns:> Amazon::S3::Thin object

It can receive the following arguments:

=over 4

=item * C<credential_provider> (B<default: credentials>) - specify where to source credentials from. Options are:

=over 2

=item * C<credentials> - existing behaviour, pass in credentials via C<aws_access_key_id> and C<aws_secret_access_key>

=item * C<env> - fetch credentials from environment variables

=item * C<metadata> - fetch credentials from EC2 instance metadata service

=item * C<ecs_container> - fetch credentials from ECS task role

=back

=item * C<region> - (B<REQUIRED>) region of your buckets you access- (currently used only when signature version is 4)

=item * C<aws_access_key_id> (B<REQUIRED [provider: credentials]>) - an access key id
of your credentials.

=item * C<aws_secret_access_key> (B<REQUIRED [provider: credentials]>) - an secret access key
 of your credentials.

=item * C<version> (B<OPTIONAL [provider: metadata]>) - version of metadata service to use, either 1 or 2.
L<read more|https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html>

=item * C<role> (B<OPTIONAL [provider: metadata]>) - IAM instance role to use, otherwise the first is selected

=item * C<secure> - whether to use https or not. Default is 0 (http).

=item * C<ua> - a user agent object, compatible with LWP::UserAgent.
Default is an instance of L<LWP::UserAgent>.

=item * C<signature_version> - AWS signature version to use. Supported values
are 2 and 4. Default is 4.

=item * C<debug> - debug option. Default is 0 (false). 
If set 1, contents of HTTP request and response are shown on stderr

=item * C<virtual_host> - whether to use virtual-hosted style request format. Default is 0 (path-style).

=back

=head1 ACCESSORS

The following accessors are provided. You can use them to get/set your
object's attributes.

=head2 secure

Whether to use https (1) or http (0) when connecting to S3.

=head2 ua

The user agent used internally to perform requests and return responses.
If you set this attribute, please make sure you do so with an object
compatible with L<LWP::UserAgent> (i.e. providing the same interface).

=head2 debug

Debug option.

=head1 Operations on Buckets

=head2 put_bucket( $bucket [, $headers])

B<Arguments>:

=over 2

=item 1. bucket - a string with the bucket

=item 2. headers (B<optional>) - hashref with extra header information

=back

=head2 delete_bucket( $bucket [, $headers])

B<Arguments>:

=over 3



( run in 0.634 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )