Skip to content

Commit

Permalink
added recipe for streaming multipart uploads to cookbook
Browse files Browse the repository at this point in the history
  • Loading branch information
kraih committed Feb 1, 2012
1 parent af6bf96 commit 58af191
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 13 deletions.
62 changes: 62 additions & 0 deletions lib/Mojolicious/Guides/Cookbook.pod
Expand Up @@ -513,6 +513,68 @@ HTTP protocol for transport.
The C<message> event will be emitted for every new log message and the
C<finish> event right after the transaction has been finished.

=head2 Streaming multipart uploads

L<Mojolicious> contains a very sophisticated event system based on
L<Mojo::EventEmitter>, with ready-to-use events on almost all layers, and
which can be combined to solve some of hardest problems in web development.

use Mojolicious::Lite;
use Scalar::Util 'weaken';

# Emit "request" event early for requests that get upgraded to multipart
hook after_build_tx => sub {
my $tx = shift;
weaken $tx;
$tx->req->content->on(upgrade => sub { $tx->emit('request') });
};

# Upload form
get '/' => 'index';

# Streaming upload
post '/upload' => sub {
my $self = shift;

# First invocation, subscribe to "part" event to find the right one
return $self->req->content->on(part => sub {
my ($multi, $single) = @_;

# Subscribe to "body" event or part to make sure we have all headers
$single->on(body => sub {
my $single = shift;

# Make sure we have the right part and replace "read" event
return unless $single->headers->content_disposition =~ /example/;
$single->unsubscribe('read')->on(read => sub {
my ($single, $chunk) = @_;

# Log size of every chunk we receive
$self->app->log->debug(length($chunk) . ' bytes uploaded.');
});
});
}) unless $self->req->is_finished;

# Second invocation, render response
$self->render(text => 'Upload was successful.');
};

app->start;
__DATA__

@@ index.html.ep
<!DOCTYPE html>
<html>
<head><title>Streaming upload</title></head>
<body>
% my @attrs = (method => 'POST', enctype => 'multipart/form-data');
%= form_for upload => @attrs => begin
%= file_field 'example'
%= submit_button 'Upload'
% end
</body>
</html>

=head2 Event loops

Internally the L<Mojo::IOLoop> reactor can use multiple event loop backends,
Expand Down
24 changes: 11 additions & 13 deletions t/mojolicious/upload_stream_lite_app.t
Expand Up @@ -15,25 +15,23 @@ use Mojolicious::Lite;
use Scalar::Util 'weaken';
use Test::Mojo;

# Trigger early request for multipart requests under "/upload"
app->hook(
after_build_tx => sub {
my $tx = shift;
weaken $tx;
$tx->req->content->on(
upgrade => sub {
$tx->emit('request') if $tx->req->url->path->contains('/upload');
}
);
}
);
# Trigger early "request" event for multipart requests under "/upload"
hook after_build_tx => sub {
my $tx = shift;
weaken $tx;
$tx->req->content->on(
upgrade => sub {
$tx->emit('request') if $tx->req->url->path->contains('/upload');
}
);
};

# POST /upload/*
my $cache = {};
post '/upload/:id' => sub {
my $self = shift;

# First invocation, locate part and prepare streaming
# First invocation, prepare streaming
my $id = $self->param('id');
$self->req->content->on(
part => sub {
Expand Down

0 comments on commit 58af191

Please sign in to comment.