view release on metacpan or search on metacpan
#!perl
use Module::Build;
my $build_pkg = eval { require Apache::TestMB } ? 'Apache::TestMB' : 'Module::Build';
my $build = $build_pkg->new(
module_name => 'Apache2::UploadProgress',
dist_author => 'Christian Hansen <chansen@cpan.org>',
dist_abstract => 'Track the progress and give realtime feedback of file uploads',
license => 'perl',
create_readme => 1,
create_makefile_pl => 'passthrough',
add_to_cleanup => [qw(t/logs/cache_file)],
requires => {
'perl' => 5.006,
'mod_perl2' => 2.00,
'Cache::FastMmap' => 0,
'HTTP::Headers::Util' => 0,
'Time::HiRes' => 0
Revision history for Perl extension Apache2::UploadProgress
0.2 2006-04-23 23:36
- Simplified the usage to the point of only needing to call
one js function from an onsubmit handler in the upload form
- All support files (js/css/xsl) are automatically Aliased in
Apache, to the /UploadProgress location (can be altered using
UploadProgressBaseURI directive in Apache, if you want to
customize the js/css/xsl files)
- Simplified Apache configuration by automatically setuping the
content handler that provides progress updates to
/UpdateProgress in Apache
- include an XSL stylesheet to render XML doc in a popup (will
automatically fall back to plain HTML if XSL transforms not
supported by browser)
extra/progress.xsd
extra/progress.xsl
lib/Apache2/UploadProgress.pm
Makefile.PL
MANIFEST This list of files
META.yml
README
t/01use.t
t/02pod.t
t/03podcoverage.t
t/apache/upload.t
t/conf/extra.conf.in
t/response/TestApache/Upload.pm
TODO
---
name: Apache2-UploadProgress
version: 0.2
author:
- 'Christian Hansen <chansen@cpan.org>'
abstract: Track the progress and give realtime feedback of file uploads
license: perl
requires:
Cache::FastMmap: 0
HTTP::Headers::Util: 0
Time::HiRes: 0
mod_perl2: 2
perl: 5.006
build_requires:
Apache::Test: 1.12
CGI: 3.08
NAME
Apache2::UploadProgress - Track the progress and give realtime feedback
of file uploads
SYNOPSIS
In Apache:
PerlLoadModule Apache2::UploadProgress
PerlPostReadRequestHandler Apache2::UploadProgress
In your HTML form:
<script src="/UploadProgress/progress.js"></script>
<form action="/cgi-bin/script.cgi"
method="post"
enctype="multipart/form-data"
onsubmit="return startEmbeddedProgressBar(this)">
<input type="file" name="file"/>
<input type="submit" name=".submit"/>
</form>
<div id="progress"></div>
DESCRIPTION
This module allows you to track the progress of a file upload in order
to provide a user with realtime updates on the progress of their file
upload.
The information that is provided by this module is very basic. It just
includes the total size of the upload, and the current number of bytes
that have been received. However, this information is sufficient to
display lots of information about the upload to the user. At it's
simplest, you can trigger a popup window that will automatically refresh
until the upload completes. However, popups can be a problem sometimes,
so it is also possible to embed a progress monitor directly into the
page using some JavaScript and AJAX calls. Examples using both
techniques are discussed below in the EXAMPLES section.
EXAMPLES
Simple Popup Upload Monitor
The simplest way to add a progress monitor to your forms is to use the
popup technique. This will launch a popup window with a progress monitor
that will automatically refresh until the upload is complete. The popup
will use the XML method by default, and format the page using an
included XSL stylesheet (which can be customized to suit your needs). If
the browser does not support XML transformations, then content
negotiation will automatically fall back on a basic HTML page.
Here is what you need to do to get the popup technique working:
<script src="/UploadProgress/progress.js"></script>
<form action="/cgi-bin/script.cgi"
method="post"
Make sure that you copy all the support files found in the 'extra'
directory to this new location and then you can customize them to
your liking.
This currently only affects the urls used in the XML/XSL and HTML
mime handlers used in the popup progress monitor.
HANDLERS
handler
This handler should be run at the PerlPostReadRequestHandler stage,
and will detect whether we need to track the upload progress of the
current request. There are 5 ways for the handler to determine if
the upload progress should be tracked:
X-Upload-ID
There is an incoming header called X-Upload-ID which contains
the progess ID
X-Progress-ID
There is an incoming header called X-Progress-ID which contains
the progess ID
Query contains ID
The query portion of the URL consists of just a 32 character
hexadecimal string (for example
http://localhost/upload.cgi?1234567890abcdef1234567890abcdef)
Query contains progress_id
There is a query parameter in the query string called
progress_id, and it contains a 32 character hexadecimal number
(for example
http://localhost/upload.cgi?progress_id=1234567890abcdef12345678
90abcdef)
Query contains upload_id
There is a query parameter in the query string called upload_id,
and it contains a 32 character hexadecimal number (for example
http://localhost/upload.cgi?upload_id=1234567890abcdef1234567890
abcdef)
Note that you can not pass the progress_id as a hidden POST
parameter, since the Apache2::UploadProgress module never actually
decodes the POST request so it will not be able to determine what
the ID is. The reason for this is that we are trying to track the
rate at which the POST request takes to upload, so we need that ID
before we even start counting the incoming POST request. So the ID
must be passed as a header, or as a simple query parameter, as part
of the action attribute of the form.
progress
When called, this handler will return the upload progress of the
request identified by the given ID. The ID can be provided in
exactly the same way as in the handler method given above (Although
is usually easiest to just provide is as a query parameter called
progress_id).
This handler can return the results in several different formats. By
default, it will return XML data, but that can be changed by
altering the Accept header of the request (if multiple mimes are
present in the Accept header, they are tried in order of qvalue
according to RFC 2616).
track_progress
An Input filter handler that totals up the number of bytes that have
been sent as part of the current request, and updates the current
progress through calls to "store_progress".
BUGS
Safari
The JavaScript for the embedded progress meter is currently failing
in Safari
Cancelled uploads
When a user cancels an upload, but leaves the page with the progress
meter active, the progress meter may continue to reload indefinately
SEE ALSO
<http://perl.apache.org/docs/2.0/>.
<http://www.modperlbook.org/>.
Apache2::Filter.
Apache2::RequestRec.
TODO
- Automatically detect when a file upload has been cancelled
and notify the client on the next progress update
- abstract out the cache so that it can work in distributed
environments, and on platforms where Cache::FastMmap is
not available (Windows)
- Make the cache size and location configurable
- add support to indicate which file is currently being uploaded
when uploading multiple files at once
- Allow finer grained configuration in Apache (UploadProgress On/Off)
- clean up JS, XSL and CSS files
- add more stylesheets
- add more examples
examples/embedded.cgi view on Meta::CPAN
$q->start_html(
-title => 'Apache2::UploadProgress Embedded Example',
-encoding => 'UTF-8',
-script => [ { -src => '/UploadProgress/progress.js' },
{ -src => '/UploadProgress/progress.jmpl.js' }, ],
-style => [ { -src => '/UploadProgress/progress.css', -rel => 'StyleSheet', -title => 'Default' },
{ -src => 'css/progress_blueblock.css', -rel => 'Alternate StyleSheet', -title => 'Blue Blocks' },
{ -src => 'css/progress_bluebar.css', -rel => 'Alternate StyleSheet', -title => 'Blue Animated Bar' }, ],
),
$q->h1( $q->param('file') ? 'Upload complete!' : 'Apache2::UploadProgress Example' ),
$q->p('Apache2::UploadProgress is a mod_perl module designed to make it easy to add progress monitors to your form uploads. Use the form below to upload a file, and if your browser supports it, you will see a progress bar appear under the form...
$q->p('Choose a StyleSheet: ',
$q->a({-href => "#", -onclick => "setActiveStyleSheet('Default'); return false;" }, 'Default'),
', ',
$q->a({-href => "#", -onclick => "setActiveStyleSheet('Blue Blocks'); return false;" }, 'Blue Blocks'),
', ',
$q->a({-href => "#", -onclick => "setActiveStyleSheet('Blue Animated Bar'); return false;" }, 'Blue Animated Bar'),
),
$q->start_form(
-action => $q->script_name,
-enctype => 'multipart/form-data',
extra/progress.xsd view on Meta::CPAN
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="upload">
<xs:complexType>
<xs:sequence>
<xs:element name="size" type="xs:positiveInteger" use="required"/>
<xs:element name="received" type="xs:positiveInteger" use="required"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
extra/progress.xsl view on Meta::CPAN
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" version='1.0' encoding='UTF-8' indent="yes" />
<!-- root rule -->
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<!-- main rule for document element -->
<xsl:template match="upload">
<xsl:variable name="percent" select="round(received div size * 100)"/>
<xsl:variable name="remaining" select="size - received"/>
<xsl:if test="$remaining">
<meta http-equiv="refresh" content="1" />
</xsl:if>
<link rel="stylesheet" type="text/css" href="/UploadProgress/progress.css" />
<div style="margin: 2% 10%">
<h3>Upload Progress</h3>
<div class="progressmeter">
<div class="meter">
lib/Apache2/UploadProgress.pm view on Meta::CPAN
EOF
$TEMPLATES->{yaml} = <<'EOF';
---
size: %d
received: %d
EOF
$TEMPLATES->{xml} = <<'EOF';
<?xml version="1.0" encoding="UTF-8"?>
%s<upload%s>
<size>%d</size>
<received>%d</received>
</upload>
EOF
$MIMES = {
'application/x-json' => sub { sprintf( $TEMPLATES->{json}, @_ ) },
'application/x-yaml' => sub { sprintf( $TEMPLATES->{yaml}, @_ ) },
'application/xhtml+xml' => sub { sprintf( $TEMPLATES->{html}, @_ ) },
'application/xml' => \&xml_template,
'text/html' => sub { sprintf( $TEMPLATES->{html}, @_ ) },
'text/plain' => sub { sprintf( $TEMPLATES->{text}, @_ ) },
'text/x-json' => sub { sprintf( $TEMPLATES->{json}, @_ ) },
lib/Apache2/UploadProgress.pm view on Meta::CPAN
return undef;
}
sub progress_id {
my ( $class, $r ) = @_;
return $r->headers_in->get('X-Upload-ID')
|| $r->headers_in->get('X-Progress-ID') # lighttpd compat
|| ( $r->unparsed_uri =~ m/\?([a-fA-F0-9]{32})$/ )[0] # lighttpd compat
|| ( $r->unparsed_uri =~ m/(?:progress|upload)_id=([a-fA-F0-9]{32})/ )[0];
}
sub fetch_progress {
my ( $class, $progress_id ) = @_;
my $progress = $CACHE->get($progress_id)
or return undef;
return [ unpack( 'LL', $progress ) ];
}
lib/Apache2/UploadProgress.pm view on Meta::CPAN
return Apache2::Const::OK;
}
sub progress : method {
my ( $class, $r ) = @_;
my $progress_id = $class->progress_id($r)
or return Apache2::Const::NOT_FOUND;
my $progress = undef;
my $tries = 16; # wait a max of 4 seconds for the upload to start
while ( $tries && !$progress ) {
$progress = $class->fetch_progress($progress_id)
or sleep(0.250);
$tries--;
}
unless ( $progress ) {
lib/Apache2/UploadProgress.pm view on Meta::CPAN
return Apache2::Const::OK;
}
1;
__END__
=head1 NAME
Apache2::UploadProgress - Track the progress and give realtime feedback of file uploads
=head1 SYNOPSIS
In Apache:
PerlLoadModule Apache2::UploadProgress
PerlPostReadRequestHandler Apache2::UploadProgress
In your HTML form:
lib/Apache2/UploadProgress.pm view on Meta::CPAN
enctype="multipart/form-data"
onsubmit="return startEmbeddedProgressBar(this)">
<input type="file" name="file"/>
<input type="submit" name=".submit"/>
</form>
<div id="progress"></div>
=head1 DESCRIPTION
This module allows you to track the progress of a file upload in order
to provide a user with realtime updates on the progress of their file
upload.
The information that is provided by this module is very basic. It just
includes the total size of the upload, and the current number of bytes that
have been received. However, this information is sufficient to display lots of
information about the upload to the user. At it's simplest, you can trigger a
popup window that will automatically refresh until the upload completes.
However, popups can be a problem sometimes, so it is also possible to embed a
progress monitor directly into the page using some JavaScript and AJAX calls.
Examples using both techniques are discussed below in the EXAMPLES section.
=head1 EXAMPLES
=head2 Simple Popup Upload Monitor
The simplest way to add a progress monitor to your forms is to use the popup
technique. This will launch a popup window with a progress monitor that will
automatically refresh until the upload is complete. The popup will use the XML
method by default, and format the page using an included XSL stylesheet (which
can be customized to suit your needs). If the browser does not support XML
transformations, then content negotiation will automatically fall back on a
basic HTML page.
Here is what you need to do to get the popup technique working:
<script src="/UploadProgress/progress.js"></script>
<form action="/cgi-bin/script.cgi"
method="post"
lib/Apache2/UploadProgress.pm view on Meta::CPAN
=back
=head1 HANDLERS
=over 4
=item handler
This handler should be run at the PerlPostReadRequestHandler stage,
and will detect whether we need to track the upload progress of the current
request. There are 5 ways for the handler to determine if the upload progress
should be tracked:
=over 4
=item X-Upload-ID
There is an incoming header called X-Upload-ID which contains the progess ID
=item X-Progress-ID
There is an incoming header called X-Progress-ID which contains the progess ID
=item Query contains ID
The query portion of the URL consists of just a 32 character hexadecimal
string (for example http://localhost/upload.cgi?1234567890abcdef1234567890abcdef)
=item Query contains progress_id
There is a query parameter in the query string called progress_id, and it
contains a 32 character hexadecimal number (for example
http://localhost/upload.cgi?progress_id=1234567890abcdef1234567890abcdef)
=item Query contains upload_id
There is a query parameter in the query string called upload_id, and it
contains a 32 character hexadecimal number (for example
http://localhost/upload.cgi?upload_id=1234567890abcdef1234567890abcdef)
=back
Note that you can not pass the progress_id as a hidden POST parameter,
since the Apache2::UploadProgress module never actually decodes the POST
request so it will not be able to determine what the ID is. The reason
for this is that we are trying to track the rate at which the POST request
takes to upload, so we need that ID before we even start counting the incoming
POST request. So the ID must be passed as a header, or as a simple query parameter,
as part of the action attribute of the form.
=item progress
When called, this handler will return the upload progress of the request
identified by the given ID. The ID can be provided in exactly the same way
as in the handler method given above (Although is usually easiest to just provide
is as a query parameter called progress_id).
This handler can return the results in several different formats. By default,
it will return XML data, but that can be changed by altering the Accept header
of the request (if multiple mimes are present in the Accept header, they are
tried in order of qvalue according to RFC 2616).
For example, if you set the Accept header to the following:
lib/Apache2/UploadProgress.pm view on Meta::CPAN
=head1 BUGS
=over 4
=item Safari
The JavaScript for the embedded progress meter is currently failing in
Safari
=item Cancelled uploads
When a user cancels an upload, but leaves the page with the progress
meter active, the progress meter may continue to reload indefinately
=back
=head1 SEE ALSO
L<http://perl.apache.org/docs/2.0/>.
L<http://www.modperlbook.org/>.
t/apache/upload.t view on Meta::CPAN
#file:t/apache/upload.t
#----------------------
use strict;
use warnings FATAL => 'all';
use Apache::Test;
use Apache::TestUtil;
use Apache::TestRequest 'UPLOAD';
plan tests => 5;
ok 1; # simple load test
my $uri = '/TestApache__Upload?progress_id=1234567890abcdef1234567890abcdef';
my $res = UPLOAD $uri, undef, content => ('1' x 40_000);
my $data = $res->content;
ok t_cmp( $data, qr/read 40000 characters from file/, "upload succeeded", );
ok t_cmp( $data, qr/file is ok/, "upload file intact", );
ok t_cmp( $data, qr/cache entry: \d+, \d+/, "cache contains valid entries", );
ok t_cmp( $data, qr/upload progress finished successfully/, "upload progress finished successfully", );
t/response/TestApache/Upload.pm view on Meta::CPAN
use Apache2::Const -compile => 'OK';
use CGI qw();
sub handler {
my $r = shift;
$r->content_type('text/plain');
# Using CGI so we don't have to depend on Apache2::Request
my $q = CGI->new();
my $fh = $q->upload('HTTPUPLOAD');
if ($fh) {
# Test the file that was uploaded.
# It consists of 40_000 1's,
# so we make sure that we only receive 1's,
# and add up the total number of characters.
my $chars = 0;
my $file_ok = 1;
my $buffer;
while ( my $bytesread = read( $fh, $buffer, 1024 ) ) {
$buffer =~ tr/1//d;
$file_ok = 0 if $buffer;
$chars += $bytesread;
}
$r->write("read $chars characters from file\n");
$r->write( "file is " . ( $file_ok ? 'ok' : 'not ok' ) . "\n" );
}
else {
$r->write("file upload not found\n");
}
# Look directly into the cache that Apache2::UploadProgress
# maintains to make sure that there is an entry for the file
# we uploaded.
my $entry = $Apache2::UploadProgress::CACHE->get('1234567890abcdef1234567890abcdef');
if ($entry) {
my @values = unpack( 'LL', $entry );
$r->write("cache entry: ".join(', ', @values)."\n");
if ($values[0] > 0 && $values[0] == $values[1]) {
$r->write("upload progress finished successfully\n");
}
else {
$r->write("upload progress did not finished successfully\n");
}
}
else {
$r->write("no cache entry found\n");
}
Apache2::Const::OK;
}
1;