做一个文件上传的网页,想稍微华丽一点,显示进度条出来。在Apache和Catalyst下都有现成的模块,不过Dancer上还没有。看了一下代码,Dancer::Request里没有像Catalyst那样暴露prepare_body_chunk方法。所以需要在plack上利用psgi.input来做。尽量重用现有代码,所以progress.css和progress.js都直接从Catalyst/Plugin/UploadProgress/example/Upload/root/static/里复制。
package Plack::Middleware::UploadProgress;
use strict;
use CHI;
use Carp;
use HTTP::Body;
use Plack::Request;
use Plack::TempBuffer;
use parent qw(Plack::Middleware);
sub call {
my ( $self, $env ) = @_;
my $rm = $env->{REQUEST_METHOD};
my $ct = $env->{CONTENT_TYPE};
my $cl = $env->{CONTENT_LENGTH};
if ( $rm =~ m#^post$#i and (
$ct =~ m#^application/x-www-form-urlencoded#i or
$ct =~ m#^multipart/form-data#i )
) {
my $cache = CHI->new( driver => 'Memory', global => 1 );
my $req = Plack::Request->new($env);
my $id = $req->param('progress_id');
my $body = HTTP::Body->new($ct, $cl);
$env->{'plack.request.http.body'} = $body;
$body->cleanup(1);
my $input = $env->{'psgi.input'};
my $buffer;
if ($env->{'psgix.input.buffered'}) {
$input->seek(0, 0);
} else {
$buffer = Plack::TempBuffer->new($cl);
}
my $spin = 0;
while ($cl) {
$input->read(my $chunk, $cl < 8192 ? $cl : 8192);
my $read = length $chunk;
$cl -= $read;
$body->add($chunk);
$buffer->print($chunk) if $buffer;
my $progress = $cache->get('upload_progress_' . $id);
if ( !defined $progress ) {
$progress = {
size => $body->content_length,
received => length $chunk,
};
$cache->set( 'upload_progress_' . $id, $progress );
} else {
$progress->{received} += $read;
$cache->set( 'upload_progress_' . $id, $progress );
};
if ($read == 0 && $spin++ > 2000) {
Carp::croak "Bad Content-Length: maybe client disconnect? ($cl bytes remaining)";
}
};
if ($buffer) {
$self->env->{'psgix.input.buffered'} = 1;
$self->env->{'psgi.input'} = $buffer->rewind;
} else {
$input->seek(0, 0);
}
};
my $res = $self->app->($env);
return $res;
}
true;
上面的代码,progress部分基本摘抄自Catalyst::Plugin::UploadProgress模块,chunk部分基本摘抄自Plack::Middleware::CSRFBlock模块,当然它也基本摘抄自Plack::Request模块~~ 上面写的不全,没有关于GET /progress?progress_id=*的json输出处理。不过这部分也可以在DancerApp.pm里直接写get ‘/progress’ => sub {};,最后申明,这代码刚写完,没测……CHI或许应该用memcached而不是memory~
在DancerApp/bin/app.pl里这么配置:
use Dancer;
use DancerApp;
use Plack::Builder;
my $app = sub {
my $env = shift;
my $req = Dancer::Request-new( env => $env );
};
builder {
enable "Plack::Middleware::UploadProgress";
$app;
};
dance;