From: Brian Phillips Date: Wed, 21 Sep 2011 12:24:42 +0000 (-0500) Subject: new action class to handle deserializing multi-part HTTP request data X-Git-Tag: 0.92~3 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=8bf1f20e52df6d2650bb9c5f68cca300b04a5b64;p=catagits%2FCatalyst-Action-REST.git new action class to handle deserializing multi-part HTTP request data --- diff --git a/lib/Catalyst/Action/DeserializeMultiPart.pm b/lib/Catalyst/Action/DeserializeMultiPart.pm new file mode 100644 index 0000000..8a882c3 --- /dev/null +++ b/lib/Catalyst/Action/DeserializeMultiPart.pm @@ -0,0 +1,103 @@ +package Catalyst::Action::DeserializeMultiPart; + +use Moose; +use namespace::autoclean; + +extends 'Catalyst::Action::Deserialize'; +use HTTP::Body; + +our $VERSION = '0.91'; +$VERSION = eval $VERSION; + +our $NO_HTTP_BODY_TYPES_INITIALIZATION; +$HTTP::Body::TYPES->{'multipart/mixed'} = 'HTTP::Body::MultiPart' unless $NO_HTTP_BODY_TYPES_INITIALIZATION; + +override execute => sub { + my $self = shift; + my ( $controller, $c ) = @_; + if($c->request->content_type =~ m{^multipart/}i && !defined($c->request->body)){ + my $REST_part = $self->attributes->{DeserializePart} || []; + my($REST_body) = $c->request->upload($REST_part->[0] || 'REST'); + if($REST_body){ + $c->request->_body->body( $REST_body->fh ); + $c->request->content_type( $REST_body->type ); + } + } + super; +}; + +__PACKAGE__->meta->make_immutable; + +1; + +=head1 NAME + +Catalyst::Action::DeserializeMultiPart - Deserialize Data in a Multi-Part Request + +=head1 SYNOPSIS + + package Foo::Controller::Bar; + + __PACKAGE__->config( + # see Catalyst::Action::Deserialize for standard config + ); + + sub begin :ActionClass('DeserializeMultiPart') DeserializePart('REST') {} + +=head1 DESCRIPTION + +This action will deserialize multi-part HTTP POST, PUT, OPTIONS and DELETE +requests. It is a simple extension of L +with the exception that rather than using the entire request body (which +may contain multiple sections), it will look for a single part in the request +body named according to the C attribute on that action +(defaulting to C). If a part is found under that name, it then +proceeds to deserialize the request as normal based on the content-type +of that individual part. If no such part is found, the request would +be processed as if no data was sent. + +This module's code will only come into play if the following conditions are met: + +=over 4 + +=item * The C of the request is C + +=item * The request body (as returned by C<$c->request->body> is not defined + +=item * There is a part of the request body (as returned by C<$c->request->upload($DeserializePart)>) available + +=back + +=head1 CONFIGURING HTTP::Body + +By default, L parses C requests as an +L. L does not separate +out the individual parts of the request body. In order to make use of +the individual parts, L must be told which content types +to map to L. This module makes the assumption +that you would like to have all C requests parsed by +L module. This is done by a package variable +inside L: C<$HTTP::Body::Types> (a HASH ref). Feel free to +add other content-types to this hash if needed or if you would prefer +that C NOT be added to this hash, simply delete it +after loading this module. + + # in your controller + use Catalyst::Action::DeserializeMultiPart; + + delete $HTTP::Body::Types->{'multipart/mixed'}; + $HTTP::Body::Types->{'multipart/my-crazy-content-type'} = 'HTTP::Body::MultiPart'; + +=head1 SEE ALSO + +This is a simple sub-class of L. + +=head1 AUTHORS + +See L for authors. + +=head1 LICENSE + +You may distribute this code under the same terms as Perl itself. + +=cut diff --git a/lib/Catalyst/Action/REST.pm b/lib/Catalyst/Action/REST.pm index 110c61d..fcc1cce 100644 --- a/lib/Catalyst/Action/REST.pm +++ b/lib/Catalyst/Action/REST.pm @@ -214,6 +214,8 @@ Daisuke Maki Edaisuke@endeworks.jpE Hans Dieter Pearcey +Brian Phillips Ebphillips@cpan.orgE + Dave Rolsky Eautarch@urth.orgE Luke Saunders diff --git a/lib/Catalyst/Controller/REST.pm b/lib/Catalyst/Controller/REST.pm index 3bd6daa..defe764 100644 --- a/lib/Catalyst/Controller/REST.pm +++ b/lib/Catalyst/Controller/REST.pm @@ -594,6 +594,10 @@ action classes: sub serialize : ActionClass('Serialize') {} +If you need to deserialize multipart requests (i.e. REST data in +one part and file uploads in others) you can do so by using the +L action class. + =back =head1 A MILD WARNING diff --git a/t/catalyst-action-deserialize-multipart.t b/t/catalyst-action-deserialize-multipart.t new file mode 100644 index 0000000..4d449ca --- /dev/null +++ b/t/catalyst-action-deserialize-multipart.t @@ -0,0 +1,21 @@ +use strict; +use warnings; +use Test::More; +use YAML::Syck; +use FindBin; + +use lib ("$FindBin::Bin/lib", "$FindBin::Bin/../lib", "$FindBin::Bin/broken"); +use Test::Rest; + +my $t = Test::Rest->new('content_type' => 'multipart/mixed; boundary=----------------------------0b922a55b662'); + +use_ok 'Catalyst::Test', 'Test::Catalyst::Action::REST'; +my $url = '/deserializemultipart/test'; + +my $req = $t->put( url => $url, data => qq(------------------------------0b922a55b662\r\nContent-Disposition: form-data; name="REST"; filename="-"\r\nContent-Type: text/x-yaml\r\n\r\n---\r\nkitty: LouLou\r\n------------------------------0b922a55b662\r\nContent-Disposition: form-data; name="other"; filename="foo.txt"\r\nContent-Type: application/octet-stream\r\n\r\nanother part\r\n------------------------------0b922a55b662--\r\n)); +my $res = request($req); + +ok( $res->is_success, 'PUT Deserialize request succeeded' ); +is( $res->content, "LouLou|12", "Request returned deserialized data"); + +done_testing; diff --git a/t/lib/Test/Catalyst/Action/REST/Controller/DeserializeMultiPart.pm b/t/lib/Test/Catalyst/Action/REST/Controller/DeserializeMultiPart.pm new file mode 100644 index 0000000..66c5101 --- /dev/null +++ b/t/lib/Test/Catalyst/Action/REST/Controller/DeserializeMultiPart.pm @@ -0,0 +1,22 @@ +package Test::Catalyst::Action::REST::Controller::DeserializeMultiPart; +use Moose; +use namespace::autoclean; + +BEGIN { extends 'Catalyst::Controller' } + +__PACKAGE__->config( + 'stash_key' => 'rest', + 'map' => { + 'text/x-yaml' => 'YAML', + 'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ], + 'text/broken' => 'Broken', + }, +); + +sub test :Local ActionClass('DeserializeMultiPart') DeserializePart('REST') { + my ( $self, $c ) = @_; + $DB::single=1; + $c->res->output($c->req->data->{'kitty'} . '|' . $c->req->uploads->{other}->size); +} + +1;