summaryrefslogtreecommitdiff
path: root/lib/Net/HTTP/Spore
diff options
context:
space:
mode:
authorfranck cuny <franck@lumberjaph.net>2010-09-13 13:31:56 +0200
committerfranck cuny <franck@lumberjaph.net>2010-09-13 13:31:56 +0200
commit3e3dc478fc9b4eb90681df89156dfcc8f7f81481 (patch)
treeb9788b0d48f524bc4c0aeeb48c744a8f7b097910 /lib/Net/HTTP/Spore
downloadnet-http-spore-3e3dc478fc9b4eb90681df89156dfcc8f7f81481.tar.gz
initial import
Diffstat (limited to 'lib/Net/HTTP/Spore')
-rw-r--r--lib/Net/HTTP/Spore/Core.pm5
-rw-r--r--lib/Net/HTTP/Spore/Meta.pm47
-rw-r--r--lib/Net/HTTP/Spore/Meta/Class.pm13
-rw-r--r--lib/Net/HTTP/Spore/Meta/Method.pm159
-rw-r--r--lib/Net/HTTP/Spore/Meta/Method/Spore.pm113
-rw-r--r--lib/Net/HTTP/Spore/Middleware.pm31
-rw-r--r--lib/Net/HTTP/Spore/Middleware/Auth/Basic.pm24
-rw-r--r--lib/Net/HTTP/Spore/Middleware/Auth/OAuth.pm37
-rw-r--r--lib/Net/HTTP/Spore/Middleware/Format.pm37
-rw-r--r--lib/Net/HTTP/Spore/Middleware/Format/Auto.pm17
-rw-r--r--lib/Net/HTTP/Spore/Middleware/Format/JSON.pm19
-rw-r--r--lib/Net/HTTP/Spore/Middleware/Format/XML.pm13
-rw-r--r--lib/Net/HTTP/Spore/Middleware/Format/YAML.pm12
-rw-r--r--lib/Net/HTTP/Spore/Middleware/LogDispatch.pm7
-rw-r--r--lib/Net/HTTP/Spore/Middleware/Runtime.pm22
-rw-r--r--lib/Net/HTTP/Spore/Middleware/Test.pm12
-rw-r--r--lib/Net/HTTP/Spore/Middleware/Test/Response.pm28
-rw-r--r--lib/Net/HTTP/Spore/Middleware/UserAgent.pm15
-rw-r--r--lib/Net/HTTP/Spore/Request.pm156
-rw-r--r--lib/Net/HTTP/Spore/Response.pm103
-rw-r--r--lib/Net/HTTP/Spore/Role/Middleware.pm44
-rw-r--r--lib/Net/HTTP/Spore/Role/Request.pm82
-rw-r--r--lib/Net/HTTP/Spore/Role/UserAgent.pm22
23 files changed, 1018 insertions, 0 deletions
diff --git a/lib/Net/HTTP/Spore/Core.pm b/lib/Net/HTTP/Spore/Core.pm
new file mode 100644
index 0000000..2251af8
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Core.pm
@@ -0,0 +1,5 @@
+package Net::HTTP::Spore::Core;
+
+use Net::HTTP::Spore::Meta;
+
+1;
diff --git a/lib/Net/HTTP/Spore/Meta.pm b/lib/Net/HTTP/Spore/Meta.pm
new file mode 100644
index 0000000..8b4942a
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Meta.pm
@@ -0,0 +1,47 @@
+package Net::HTTP::Spore::Meta;
+
+use Moose;
+use Moose::Exporter;
+use Moose::Util::MetaRole;
+
+our $VERSION = '0.14';
+
+Moose::Exporter->setup_import_methods(
+ with_meta => [qw/spore_method/],
+ also => [qw/Moose/]
+);
+
+sub spore_method {
+ my $meta = shift;
+ my $name = shift;
+ $meta->add_spore_method($name, @_);
+}
+
+sub init_meta {
+ my ($class, %options) = @_;
+
+ my $for = $options{for_class};
+ Moose->init_meta(%options);
+
+ my $meta = Moose::Util::MetaRole::apply_metaroles(
+ for => $for,
+ class_metaroles => {
+ class => ['Net::HTTP::Spore::Meta::Class'],
+ },
+ );
+
+ Moose::Util::MetaRole::apply_base_class_roles(
+ for => $for,
+ roles => [
+ qw/
+ Net::HTTP::Spore::Role::UserAgent
+ Net::HTTP::Spore::Role::Request
+ Net::HTTP::Spore::Role::Middleware
+ /
+ ],
+ );
+
+ $meta;
+};
+
+1;
diff --git a/lib/Net/HTTP/Spore/Meta/Class.pm b/lib/Net/HTTP/Spore/Meta/Class.pm
new file mode 100644
index 0000000..4ddd5c6
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Meta/Class.pm
@@ -0,0 +1,13 @@
+package Net::HTTP::Spore::Meta::Class;
+
+# ABSTRACT: metaclass for all API client
+
+use Moose::Role;
+
+with qw/Net::HTTP::Spore::Meta::Method::Spore/;
+
+1;
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
diff --git a/lib/Net/HTTP/Spore/Meta/Method.pm b/lib/Net/HTTP/Spore/Meta/Method.pm
new file mode 100644
index 0000000..0087147
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Meta/Method.pm
@@ -0,0 +1,159 @@
+package Net::HTTP::Spore::Meta::Method;
+
+# ABSTRACT: create api method
+
+use Moose;
+use Moose::Util::TypeConstraints;
+
+use MooseX::Types::Moose qw/Str Int ArrayRef/;
+use MooseX::Types::URI qw/Uri/;
+
+extends 'Moose::Meta::Method';
+use Net::HTTP::Spore::Response;
+
+subtype UriPath
+ => as 'Str'
+ => where { $_ =~ m!^/! }
+ => message {"path must start with /"};
+
+enum Method => qw(HEAD GET POST PUT DELETE);
+
+has path => ( is => 'ro', isa => 'UriPath', required => 1 );
+has method => ( is => 'ro', isa => 'Method', required => 1 );
+has description => ( is => 'ro', isa => 'Str', predicate => 'has_description' );
+
+has authentication => (
+ is => 'ro',
+ isa => 'Bool',
+ predicate => 'has_authentication',
+ default => 0
+);
+has api_base_url => (
+ is => 'ro',
+ isa => Uri,
+ coerce => 1,
+ predicate => 'has_api_base_url',
+);
+has expected => (
+ traits => ['Array'],
+ is => 'ro',
+ isa => ArrayRef [Int],
+ auto_deref => 1,
+ required => 0,
+ predicate => 'has_expected',
+ handles => {find_expected_code => 'grep',},
+);
+has params => (
+ traits => ['Array'],
+ is => 'ro',
+ isa => ArrayRef [Str],
+ required => 0,
+ default => sub { [] },
+ auto_deref => 1,
+ handles => {find_request_parameter => 'first',}
+);
+has required => (
+ traits => ['Array'],
+ is => 'ro',
+ isa => ArrayRef [Str],
+ default => sub { [] },
+ auto_deref => 1,
+ required => 0,
+);
+has documentation => (
+ is => 'ro',
+ isa => 'Str',
+ lazy => 1,
+ default => sub {
+ my $self = shift;
+ my $doc;
+ $doc .= "name: " . $self->name . "\n";
+ $doc .= "description: " . $self->description . "\n"
+ if $self->has_description;
+ $doc .= "method: " . $self->method . "\n";
+ $doc .= "path: " . $self->path . "\n";
+ $doc .= "arguments: " . join(', ', $self->params) . "\n"
+ if $self->params;
+ $doc .= "required: " . join(', ', $self->required) . "\n"
+ if $self->required;
+ $doc;
+ }
+);
+
+sub wrap {
+ my ( $class, %args ) = @_;
+
+ my $code = sub {
+ my ( $self, %method_args ) = @_;
+
+ my $method = $self->meta->find_spore_method_by_name( $args{name} );
+
+ my $payload =
+ ( defined $method_args{spore_payload} )
+ ? delete $method_args{spore_payload}
+ : delete $method_args{payload};
+
+ foreach my $required ( $method->required ) {
+ if ( !grep { $required eq $_ } keys %method_args ) {
+ die Net::HTTP::Spore::Response->new(
+ 599,
+ [],
+ {
+ error =>
+ "$required is marked as required but is missing",
+ }
+ );
+ }
+ }
+
+ my $params;
+ foreach (keys %method_args) {
+ push @$params, $_, $method_args{$_};
+ }
+
+ my $api_base_url =
+ $method->has_api_base_url
+ ? $method->api_base_url
+ : $self->api_base_url;
+
+ my $env = {
+ REQUEST_METHOD => $method->method,
+ SERVER_NAME => $api_base_url->host,
+ SERVER_PORT => $api_base_url->port,
+ SCRIPT_NAME => (
+ $api_base_url->path eq '/'
+ ? ''
+ : $api_base_url->path
+ ),
+ PATH_INFO => $method->path,
+ REQUEST_URI => '',
+ QUERY_STRING => '',
+ SERVER_PROTOCOL => $api_base_url->scheme,
+ HTTP_USER_AGENT => $self->api_useragent->agent,
+ 'spore.expected' => [ $method->expected ],
+ 'spore.authentication' => $method->authentication,
+ 'spore.params' => $params,
+ 'spore.payload' => $payload,
+ 'spore.errors' => *STDERR,
+ 'spore.url_scheme' => $api_base_url->scheme,
+ };
+
+ my $response = $self->http_request($env);
+ my $code = $response->status;
+
+ die $response if ( $method->has_expected
+ && !$method->find_expected_code( sub { /$code/ } ) );
+
+ $response;
+ };
+ $args{body} = $code;
+
+ $class->SUPER::wrap(%args);
+}
+
+1;
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
diff --git a/lib/Net/HTTP/Spore/Meta/Method/Spore.pm b/lib/Net/HTTP/Spore/Meta/Method/Spore.pm
new file mode 100644
index 0000000..4c6fe71
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Meta/Method/Spore.pm
@@ -0,0 +1,113 @@
+package Net::HTTP::Spore::Meta::Method::Spore;
+
+# ABSTRACT: declare API method
+
+use Moose::Role;
+use Net::HTTP::API::Error;
+use Net::HTTP::Spore::Meta::Method;
+use MooseX::Types::Moose qw/Str ArrayRef/;
+
+has local_spore_methods => (
+ traits => ['Array'],
+ is => 'rw',
+ isa => ArrayRef [Str],
+ required => 1,
+ default => sub { [] },
+ auto_deref => 1,
+ handles => {
+ _find_spore_method_by_name => 'first',
+ _add_spore_method => 'push',
+ get_all_spore_methods => 'elements',
+ },
+);
+
+sub find_spore_method_by_name {
+ my ($meta, $name) = @_;
+ my $method_name = $meta->_find_spore_method_by_name(sub {/^$name$/});
+ return unless $method_name;
+ my $method = $meta->find_method_by_name($method_name);
+ if ($method->isa('Class::MOP::Method::Wrapped')) {
+ return $method->get_original_method;
+ }
+ else {
+ return $method;
+ }
+}
+
+sub remove_spore_method {
+ my ($meta, $name) = @_;
+ my @methods = grep { !/$name/ } $meta->get_all_spore_methods;
+ $meta->local_spore_methods(\@methods);
+ $meta->remove_method($name);
+}
+
+before add_spore_method => sub {
+ my ($meta, $name) = @_;
+ if ($meta->_find_spore_method_by_name(sub {/^$name$/})) {
+ die Net::HTTP::API::Error->new(
+ reason => "method '$name' is already declared in " . $meta->name);
+ }
+};
+
+sub add_spore_method {
+ my ($meta, $name, %options) = @_;
+
+ my $code = delete $options{code};
+
+ $meta->add_method(
+ $name,
+ Net::HTTP::Spore::Meta::Method->wrap(
+ name => $name,
+ package_name => $meta->name,
+ body => $code,
+ %options
+ ),
+ );
+ $meta->_add_spore_method($name);
+}
+
+after add_spore_method => sub {
+ my ($meta, $name) = @_;
+ $meta->add_before_method_modifier(
+ $name,
+ sub {
+ my $self = shift;
+ die Net::HTTP::API::Error->new(
+ reason => "'api_base_url' have not been defined")
+ unless $self->api_base_url;
+ }
+ );
+};
+
+1;
+
+=head1 SYNOPSIS
+
+ my $api_client = MyAPI->new;
+
+ my @methods = $api_client->meta->get_all_api_methods();
+
+ my $method = $api_client->meta->find_spore_method_by_name('users');
+
+ $api_client->meta->remove_spore_method($method);
+
+ $api_client->meta->add_spore_method('users', sub {...},
+ description => 'this method does...',);
+
+=head1 DESCRIPTION
+
+=method get_all_spore_methods
+
+Return a list of net api methods
+
+=method find_spore_method_by_name
+
+Return a net api method
+
+=method remove_spore_method
+
+Remove a net api method
+
+=method add_spore_method
+
+Add a net api method
diff --git a/lib/Net/HTTP/Spore/Middleware.pm b/lib/Net/HTTP/Spore/Middleware.pm
new file mode 100644
index 0000000..0b8584c
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Middleware.pm
@@ -0,0 +1,31 @@
+package Net::HTTP::Spore::Middleware;
+
+use strict;
+use warnings;
+
+sub new {
+ my $class = shift;
+ bless {@_}, $class;
+}
+
+sub response_cb {
+ my ($self, $cb) = @_;
+
+ my $body_filter = sub {
+ my $filter = $cb->(@_);
+ };
+ return $body_filter;
+}
+
+sub wrap {
+ my ($self, @args) = @_;
+
+ if (!ref $self) {
+ $self = $self->new(@args);
+ }
+ return sub {
+ $self->call(@_);
+ };
+}
+
+1;
diff --git a/lib/Net/HTTP/Spore/Middleware/Auth/Basic.pm b/lib/Net/HTTP/Spore/Middleware/Auth/Basic.pm
new file mode 100644
index 0000000..18c1e16
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Middleware/Auth/Basic.pm
@@ -0,0 +1,24 @@
+package Net::HTTP::Spore::Middleware::Auth::Basic;
+
+use Moose;
+use MIME::Base64;
+
+extends 'Net::HTTP::Spore::Middleware';
+
+has username => (isa => 'Str', is => 'rw', predicate => 'has_username');
+has password => (isa => 'Str', is => 'rw', predicate => 'has_password');
+
+sub call {
+ my ( $self, $req ) = @_;
+
+ if ( $self->has_username && $self->has_password ) {
+ $req->header(
+ 'Authorization' => 'Basic '
+ . MIME::Base64::encode(
+ $self->username . ':' . $self->password, ''
+ )
+ );
+ }
+}
+
+1;
diff --git a/lib/Net/HTTP/Spore/Middleware/Auth/OAuth.pm b/lib/Net/HTTP/Spore/Middleware/Auth/OAuth.pm
new file mode 100644
index 0000000..e30a45b
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Middleware/Auth/OAuth.pm
@@ -0,0 +1,37 @@
+package Net::HTTP::Spore::Middleware::Auth::OAuth;
+
+use Moose;
+extends 'Net::HTTP::Spore::Middleware';
+
+use Net::OAuth;
+use MIME::Base64;
+
+has [qw/consumer_key consumer_secret token token_secret/] => (
+ is => 'ro',
+ isa => 'Str',
+ required => 1,
+);
+
+sub call {
+ my ( $self, $req ) = @_;
+
+ my $request = Net::OAuth->request('protected resource')->new(
+ version => '1.0',
+ consumer_key => $self->consumer_key,
+ consumer_secret => $self->consumer_secret,
+ token => $self->token,
+ token_secret => $self->token_secret,
+ request_method => $req->method,
+ signature_method => 'HMAC-SHA1',
+ timestamp => time,
+ nonce => MIME::Base64::encode( time . $$ . rand ),
+ request_url => $req->uri,
+ # extra_params => \%post_args,
+ );
+
+ $request->sign;
+ my $auth = $request->to_authorization_header;
+ $req->header( 'Authorization' => $auth );
+}
+
+1;
diff --git a/lib/Net/HTTP/Spore/Middleware/Format.pm b/lib/Net/HTTP/Spore/Middleware/Format.pm
new file mode 100644
index 0000000..7acd376
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Middleware/Format.pm
@@ -0,0 +1,37 @@
+package Net::HTTP::Spore::Middleware::Format;
+
+use Moose;
+extends 'Net::HTTP::Spore::Middleware';
+
+sub encode { die "must be implemented" }
+sub decode { die "must be implemented" }
+sub accept_type { die "must be implemented" }
+sub content_type { die "must be implemented" }
+
+sub call {
+ my ( $self, $req ) = @_;
+
+ return
+ if ( exists $req->env->{'sporex.format'}
+ && $req->env->{'sporex.format'} == 1 );
+
+ $req->header( $self->accept_type );
+
+ if ( $req->env->{'spore.payload'} ) {
+ $req->env->{'spore.payload'} =
+ $self->encode( $req->env->{'spore.payload'} );
+ $req->header( $self->content_type );
+ }
+
+ $req->env->{'sporex.format'} = 1;
+
+ return $self->response_cb(
+ sub {
+ my $res = shift;
+ my $content = $self->decode( $res->body );
+ $res->body($content);
+ }
+ );
+}
+
+1;
diff --git a/lib/Net/HTTP/Spore/Middleware/Format/Auto.pm b/lib/Net/HTTP/Spore/Middleware/Format/Auto.pm
new file mode 100644
index 0000000..fd66b8c
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Middleware/Format/Auto.pm
@@ -0,0 +1,17 @@
+package Net::HTTP::Spore::Middleware::Format::Auto;
+
+use Moose;
+extends 'Net::HTTP::Spore::Middleware::Format';
+
+sub call {
+ my ( $self, $req ) = @_;
+
+ $req->env->{'sporex.format'} = 1;
+
+ return $self->response_cb( sub {
+ my $res = shift;
+ return $res;
+ });
+}
+
+1;
diff --git a/lib/Net/HTTP/Spore/Middleware/Format/JSON.pm b/lib/Net/HTTP/Spore/Middleware/Format/JSON.pm
new file mode 100644
index 0000000..61326cd
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Middleware/Format/JSON.pm
@@ -0,0 +1,19 @@
+package Net::HTTP::Spore::Middleware::Format::JSON;
+
+use JSON;
+use Moose;
+extends 'Net::HTTP::Spore::Middleware::Format';
+
+has _json_parser => (
+ is => 'rw',
+ isa => 'JSON',
+ lazy => 1,
+ default => sub { JSON->new->allow_nonref },
+);
+
+sub encode { $_[0]->_json_parser->encode( $_[1] ); }
+sub decode { $_[0]->_json_parser->decode( $_[1] ); }
+sub accept_type { ( 'Accept' => 'application/json' ) }
+sub content_type { ( 'Content-Type' => 'application/json' ) }
+
+1;
diff --git a/lib/Net/HTTP/Spore/Middleware/Format/XML.pm b/lib/Net/HTTP/Spore/Middleware/Format/XML.pm
new file mode 100644
index 0000000..c4ae038
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Middleware/Format/XML.pm
@@ -0,0 +1,13 @@
+package Net::HTTP::Spore::Middleware::Format::XML;
+
+use Moose;
+extends 'Net::HTTP::Spore::Middleware::Format';
+
+use XML::Simple;
+
+sub accept_type { ( 'Accept' => 'text/xml' ); }
+sub content_type { ( 'Content-Type' => 'text/xml' ) }
+sub encode { XMLout( $_[1] ) }
+sub decode { XMLin( $_[1] ) }
+
+1;
diff --git a/lib/Net/HTTP/Spore/Middleware/Format/YAML.pm b/lib/Net/HTTP/Spore/Middleware/Format/YAML.pm
new file mode 100644
index 0000000..bd844ce
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Middleware/Format/YAML.pm
@@ -0,0 +1,12 @@
+package Net::HTTP::Spore::Middleware::Format::YAML;
+
+use YAML;
+use Moose;
+extends 'Net::HTTP::Spore::Middleware::Format';
+
+sub encode { YAML::Decode( $_[1] ); }
+sub decode { YAML::Load( $_[1] ); }
+sub accept_type { ( 'Accept' => 'text/x-yaml' ) }
+sub content_type { ( 'Content-Type' => 'text/x-yaml' ) }
+
+1;
diff --git a/lib/Net/HTTP/Spore/Middleware/LogDispatch.pm b/lib/Net/HTTP/Spore/Middleware/LogDispatch.pm
new file mode 100644
index 0000000..2724fcf
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Middleware/LogDispatch.pm
@@ -0,0 +1,7 @@
+package Net::HTTP::Spore::Middleware::LogDispatch;
+
+use Moose;
+extends 'Net::HTTP::Spore::Middleware';
+
+
+1;
diff --git a/lib/Net/HTTP/Spore/Middleware/Runtime.pm b/lib/Net/HTTP/Spore/Middleware/Runtime.pm
new file mode 100644
index 0000000..1614c31
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Middleware/Runtime.pm
@@ -0,0 +1,22 @@
+package Net::HTTP::Spore::Middleware::Runtime;
+
+use Moose;
+extends 'Net::HTTP::Spore::Middleware';
+use Time::HiRes;
+
+sub call {
+ my ( $self, $req) = @_;
+
+ my $start_time = [Time::HiRes::gettimeofday];
+
+ $self->response_cb(
+ sub {
+ my $res = shift;
+ my $req_time = sprintf '%.6f',
+ Time::HiRes::tv_interval($start_time);
+ $res->header('X-Spore-Runtime' => $req_time);
+ }
+ );
+}
+
+1;
diff --git a/lib/Net/HTTP/Spore/Middleware/Test.pm b/lib/Net/HTTP/Spore/Middleware/Test.pm
new file mode 100644
index 0000000..6cf2c9e
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Middleware/Test.pm
@@ -0,0 +1,12 @@
+package Net::HTTP::Spore::Middleware::Test;
+
+use strict;
+use warnings;
+
+use parent qw/Net::HTTP::Spore::Middleware/;
+
+sub call {
+# use YAML::Syck; warn Dump \@_;
+}
+
+1;
diff --git a/lib/Net/HTTP/Spore/Middleware/Test/Response.pm b/lib/Net/HTTP/Spore/Middleware/Test/Response.pm
new file mode 100644
index 0000000..ca216c5
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Middleware/Test/Response.pm
@@ -0,0 +1,28 @@
+package Net::HTTP::Spore::Middleware::Test::Response;
+
+use Moose;
+extends 'Net::HTTP::Spore::Middleware';
+
+has status => ( isa => 'Int', is => 'ro', lazy => 1, default => 200 );
+has headers => ( isa => 'ArrayRef', is => 'ro', default => sub { [] } );
+has callback => (
+ isa => 'CodeRef',
+ is => 'ro',
+ lazy => 1,
+ default => sub {
+ sub {
+ my ( $self, $req ) = @_;
+ $req->new_response( $self->status, $self->headers, $self->body, );
+ }
+ }
+);
+
+has body =>
+ ( isa => 'HashRef', is => 'ro', lazy => 1, default => sub { { foo => 1 } } );
+
+sub call {
+ my ( $self, $req ) = @_;
+ $self->callback->($self, $req);
+}
+
+1;
diff --git a/lib/Net/HTTP/Spore/Middleware/UserAgent.pm b/lib/Net/HTTP/Spore/Middleware/UserAgent.pm
new file mode 100644
index 0000000..0517c26
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Middleware/UserAgent.pm
@@ -0,0 +1,15 @@
+package Net::HTTP::Spore::Middleware::UserAgent;
+
+use Moose;
+extends qw/Net::HTTP::Spore::Middleware/;
+
+has useragent => (is => 'ro', isa => 'Str', required => 1);
+
+sub call {
+ my ($self, $req) = @_;
+
+ $req->header('User-Agent' => $self->useragent);
+}
+
+
+1;
diff --git a/lib/Net/HTTP/Spore/Request.pm b/lib/Net/HTTP/Spore/Request.pm
new file mode 100644
index 0000000..655f128
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Request.pm
@@ -0,0 +1,156 @@
+package Net::HTTP::Spore::Request;
+
+use strict;
+use warnings;
+
+use URI;
+use HTTP::Headers;
+use HTTP::Request;
+use URI::Escape;
+use Hash::MultiValue;
+
+use Net::HTTP::Spore::Response;
+
+sub new {
+ my ( $class, $env ) = @_;
+ bless { env => $env }, $class;
+}
+
+sub env { $_[0]->{env}; }
+sub method { $_[0]->{env}->{REQUEST_METHOD} }
+sub port { $_[0]->{env}->{SERVER_PORT} }
+sub script_name { $_[0]->{env}->{SCRIPT_NAME} }
+sub path { $_[0]->{env}->{PATH_INFO} || '/' }
+sub request_uri { $_[0]->{env}->{REQUEST_URI} }
+sub protocol { $_[0]->{env}->{SERVER_PROTOCOL} }
+sub content { $_[0]->{env}->{'spore.payload'} }
+sub scheme { $_[0]->{env}->{'spore.scheme'} }
+
+sub path_info {
+ my $self = shift;
+ my ($path) = $self->_path;
+ $path;
+}
+
+sub _path {
+ my $self = shift;
+
+ my $query_string;
+ my $path = $self->env->{PATH_INFO};
+ my @params = @{ $self->env->{'spore.params'} || [] };
+
+
+ my $j = 0;
+ for (my $i = 0; $i < scalar @params; $i++) {
+ my $key = $params[$i];
+ my $value = $params[++$i];
+ if (!$value) {
+ $query_string .= $key;
+ last;
+ }
+ unless ( $path && $path =~ s/\:$key/$value/ ) {
+ $query_string .= $key . '=' . $value;
+ $query_string .= '&' if $query_string && scalar @params;
+ }
+ }
+
+ $query_string =~ s/&$// if $query_string;
+ return ( $path, $query_string );
+}
+
+sub query_string {
+ my $self = shift;
+ my ( undef, $query_string ) = $self->_path;
+ $query_string;
+}
+
+sub headers {
+ my $self = shift;
+ if ( !defined $self->{headers} ) {
+ my $env = $self->env;
+ $self->{headers} = HTTP::Headers->new(
+ map {
+ ( my $field = $_ ) =~ s/^HTTPS?_//;
+ ( $field => $env->{$_} );
+ } grep { /^(?:HTTP|CONTENT)/i } keys %$env
+ );
+ }
+ $self->{headers};
+}
+
+sub header {shift->headers->header(@_)}
+
+sub uri {
+ my $self = shift;
+
+ my $path_info = shift;
+ my $query_string = shift;
+
+ if ( !$path_info || !$query_string ) {
+ my @path_info = $self->_path;
+ $path_info = $path_info[0] if !$path_info;
+ $query_string = $path_info[1] if !$query_string;
+ }
+
+ my $base = $self->_uri_base;
+
+ my $path_escape_class = '^A-Za-z0-9\-\._~/';
+
+ my $path = URI::Escape::uri_escape($path_info || '', $path_escape_class);
+
+ if (defined $query_string) {
+ $path .= '?' . $query_string;
+ }
+
+ $base =~ s!/$!! if $path =~ m!^/!;
+ return URI->new( $base . $path )->canonical;
+}
+
+sub query_parameters {
+ my $self = shift;
+}
+
+sub base {
+ my $self = shift;
+ URI->new( $self->_uri_base )->canonical;
+}
+
+sub _uri_base {
+ my $self = shift;
+ my $env = $self->env;
+
+ my $uri =
+ ( $env->{'spore.url_scheme'} || "http" ) . "://"
+ . (
+ $env->{HTTP_HOST}
+ || (( $env->{SERVER_NAME} || "" ) . ":"
+ . ( $env->{SERVER_PORT} || 80 ) )
+ ) . ( $env->{SCRIPT_NAME} || '/' );
+ return $uri;
+}
+
+sub new_response {
+ my $self = shift;
+ my $res = Net::HTTP::Spore::Response->new(@_);
+ $res->request($self);
+ $res;
+}
+
+sub finalize {
+ my $self = shift;
+
+ my ($path_info, $query_string) = $self->_path;
+
+ $self->env->{PATH_INFO} = $path_info;
+ $self->env->{QUERY_STRING} = $query_string || '';
+
+ my $uri = $self->uri($path_info, $query_string);
+
+ my $request =
+ HTTP::Request->new( $self->method => $uri, $self->headers );
+
+ $request->content($self->content) if ($self->content);
+ $request;
+}
+
+1;
diff --git a/lib/Net/HTTP/Spore/Response.pm b/lib/Net/HTTP/Spore/Response.pm
new file mode 100644
index 0000000..d695dfa
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Response.pm
@@ -0,0 +1,103 @@
+package Net::HTTP::Spore::Response;
+
+use strict;
+use warnings;
+
+use overload '@{}' => \&finalize;
+
+use HTTP::Headers;
+
+sub new {
+ my ( $class, $rc, $headers, $body ) = @_;
+
+ my $self = bless {}, $class;
+ $self->status($rc) if defined $rc;
+ if (defined $body) {
+ $self->body($body);
+ $self->raw_body($body);
+ }
+ $self->headers($headers || []);
+ $self;
+}
+
+sub code { shift->status(@_) }
+sub content { shift->body(@_) }
+
+sub content_type { shift->headers->content_type(@_) }
+sub content_length { shift->headers->content_length(@_) }
+
+sub status {
+ my $self = shift;
+ if (@_) {
+ $self->{status} = shift;
+ }
+ else {
+ return $self->{status};
+ }
+}
+
+sub body {
+ my $self = shift;
+ if (@_) {
+ $self->{body} = shift;
+ }
+ else {
+ return $self->{body};
+ }
+}
+
+sub raw_body {
+ my $self = shift;
+ if (@_) {
+ $self->{raw_body} = shift;
+ }else{
+ return $self->{raw_body};
+ }
+}
+
+sub headers {
+ my $self = shift;
+ if (@_) {
+ my $headers = shift;
+ if ( ref $headers eq 'ARRAY' ) {
+ $headers = HTTP::Headers->new(@$headers);
+ }
+ elsif ( ref $headers eq 'HASH' ) {
+ $headers = HTTP::Headers->new(%$headers);
+ }
+ $self->{headers} = $headers;
+ }
+ else {
+ return $self->{headers} ||= HTTP::Headers->new();
+ }
+}
+
+sub request {
+ my $self = shift;
+ if (@_) {
+ $self->{request} = shift;
+ }else{
+ return $self->{request};
+ }
+}
+
+sub header {
+ my $self = shift;
+ $self->headers->header(@_);
+}
+
+sub finalize {
+ my $self = shift;
+ return [
+ $self->status,
+ +[
+ map {
+ my $k = $_;
+ map { ( $k => $_ ) } $self->headers->header($_);
+ } $self->headers->header_field_names
+ ],
+ $self->body,
+ ];
+}
+
+1;
diff --git a/lib/Net/HTTP/Spore/Role/Middleware.pm b/lib/Net/HTTP/Spore/Role/Middleware.pm
new file mode 100644
index 0000000..dd2c1c5
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Role/Middleware.pm
@@ -0,0 +1,44 @@
+package Net::HTTP::Spore::Role::Middleware;
+
+use Moose::Role;
+
+has middlewares => (
+ is => 'rw',
+ isa => 'ArrayRef',
+ traits => ['Array'],
+ lazy => 1,
+ default => sub { [] },
+ auto_deref => 1,
+ handles => { _add_middleware => 'push', _filter_middlewares => 'grep'},
+);
+
+sub _load_middleware {
+ my ( $self, $mw, @args ) = @_;
+
+ Class::MOP::load_class($mw);
+
+ my $code = $mw->wrap( @args );
+ $self->_add_middleware($code);
+}
+
+sub enable {
+ my ($self, $mw, @args) = @_;
+
+ if ($mw !~ /(?:^\+|Net\:\:HTTP\:\:Spore\:\:Middleware)/) {
+ $mw = "Net::HTTP::Spore::Middleware::".$mw;
+ }
+ $self->_load_middleware($mw, @args);
+ $self;
+}
+
+sub enable_if {
+ my ($self, $cond, $mw, @args) = @_;
+ $self;
+}
+
+sub reset_middlewares {
+ my $self = shift;
+ $self->middlewares([]);
+}
+
+1;
diff --git a/lib/Net/HTTP/Spore/Role/Request.pm b/lib/Net/HTTP/Spore/Role/Request.pm
new file mode 100644
index 0000000..840917a
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Role/Request.pm
@@ -0,0 +1,82 @@
+package Net::HTTP::Spore::Role::Request;
+
+# ABSTRACT: make HTTP request
+
+use Try::Tiny;
+use Moose::Role;
+use MooseX::Types::URI qw/Uri/;
+
+use Net::HTTP::Spore::Request;
+
+has api_base_url => (
+ is => 'rw',
+ isa => Uri,
+ coerce => 1,
+ required => 1,
+);
+
+sub http_request {
+ my ( $self, $env ) = @_;
+
+ my ($request, $response);
+ $request = Net::HTTP::Spore::Request->new($env);
+
+ my @middlewares;
+ foreach my $mw ( $self->middlewares ) {
+ my $res;
+ try {
+ $res = $mw->($request);
+ }
+ catch {
+ $res = $request->new_response( 599, [], { error => $_, } );
+ };
+
+ if ( ref $res && ref $res eq 'CODE' ) {
+ push @middlewares, $res;
+ }
+ elsif ( ref $res && ref $res eq 'Net::HTTP::Spore::Response' ) {
+ return $res if ($res->status == 599);
+ $response = $res;
+ last;
+ }
+ }
+
+ if (defined $response) {
+ map { $_->($response) } reverse @middlewares;
+ return $response;
+ }
+
+ my $result = $self->request($request->finalize);
+
+ $response = $request->new_response(
+ $result->code,
+ $result->headers,
+ $result->content,
+ );
+
+ map { $_->($response) } reverse @middlewares;
+
+ $response;
+}
+
+1;
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head2 METHODS
+
+=over 4
+
+=item B<http_request>
+
+=back
+
+=head2 ATTRIBUTES
+
+=over 4
+
+=item B<api_base_url>
+
+=back
diff --git a/lib/Net/HTTP/Spore/Role/UserAgent.pm b/lib/Net/HTTP/Spore/Role/UserAgent.pm
new file mode 100644
index 0000000..6bfaa5a
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Role/UserAgent.pm
@@ -0,0 +1,22 @@
+package Net::HTTP::Spore::Role::UserAgent;
+
+# ABSTRACT: create UserAgent
+
+use Moose::Role;
+use LWP::UserAgent;
+
+has api_useragent => (
+ is => 'rw',
+ isa => 'LWP::UserAgent',
+ lazy => 1,
+ handles => [qw/request/],
+ default => sub {
+ my $self = shift;
+ my $ua = LWP::UserAgent->new();
+ $ua->agent( "Net::HTTP::Spore v" . $Net::HTTP::Spore::VERSION . " (Perl)" );
+ $ua->env_proxy;
+ return $ua;
+ }
+);
+
+1;