Apache2-UploadProgress

 view release on metacpan or  search on metacpan

Build.PL  view on Meta::CPAN

#!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

Changes  view on Meta::CPAN

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)

MANIFEST  view on Meta::CPAN

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

META.yml  view on Meta::CPAN

---
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

README  view on Meta::CPAN

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>

README  view on Meta::CPAN

     <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"

README  view on Meta::CPAN

        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).

README  view on Meta::CPAN

    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  view on Meta::CPAN

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;



( run in 3.385 seconds using v1.01-cache-2.11-cpan-b16cb0d3907 )