Apache-File-Resumable
view release on metacpan or search on metacpan
Resumable.pm view on Meta::CPAN
# make HTTP/cookie date string from GMT'ed time
my($sec,$min,$hour,$mday,$mon,$year,$wday) = gmtime($time);
$year += 1900;
return sprintf("%s, %02d %s %04d %02d:%02d:%02d GMT",
$WDAY[$wday],$mday,$MON[$mon],$year,$hour,$min,$sec);
}
#
# Actual Download
#
sub download {
my ($self, $file, $req) = @_;
open T, "/tmp/headers_in";
print T $req->headers_in;
close(T);
### Create an ETag
### The ETag is required, otherwise IE won't resume a download
### The ETag can have any value, but it must be garanteed that is
### unique
### for every file and it changes whenever the file changes
### The idea below is copied form Apache ap_make_etag (http_protocol.c)
# /*
# * Make an ETag header out of various pieces of information. We use
# * the last-modified date and, if we have a real file, the
# * length and inode number - note that this doesn't have to match
# * the content-length (i.e. includes), it just has to be unique
# * for the file.
# *
# * If the request was made within a second of the last-modified date,
# * we send a weak tag instead of a strong one, since it could
# * be modified again later in the second, and the validation
# * would be incorrect.
# */
my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime)
= stat ($file) ;
my $weak = ($req->request_time - $mtime > 1)? "" : "W/";
my $etag ;
if ($mode != 0)
{
$etag = sprintf ('%s"%x-%x-%x"', $weak, $ino, $size, $mtime) ;
}
else
{
$etag = sprintf ('%s"%x"', $weak, $mtime) ;
}
### Set ETag header, without ETag resuming download doesn't work at all
$req->header_out('ETag', $etag) ;
### Check is there is an incoming if-none-match header
my $if_none_match = $req->header_in('If-None-Match') ;
if ($if_none_match eq $etag)
{
### send not_modified headers in case file doesn't have changed
### and return
$req->status (304) ;
$req->send_http_header ;
return OK ;
}
open PATCH, "$file" or die "$file: $!";
### Check is there is an incoming range and if-range header
my $range = $req->header_in('Range') ;
my $if_range = $req->header_in('If-Range') ;
### If there is a correct range header and the if-range header matches
### the etag
### i.e. the file doesn't have changed, add the correct headers for
### resuming the
### download and advance the file pointer to the correct possition
if (($range =~ /bytes=(\d+)-/) && $if_range eq $etag)
{ # continue download
my $start = $1 ;
my $end = $size - 1 ;
$req->status (206) ;
$req->header_out('Content-Range', "bytes $start-$end/$size" ) ;
$size -= $start ;
seek PATCH, $start, 0 ;
}
### To make resuming a download work, we need _all_ of the follwing
### headers!
$req->header_out('Accept-Ranges', 'bytes');
$req->header_out('Last-Modified', formattime ($mtime)) ;
$req->header_out('Content-Length', $size) ;
### Setup the content-type
if ($file =~ /\.zip$/i) {
$req->content_type('application/zip');
}
elsif ($file =~ /\.Z$/i) {
$req->content_type('Content-type: application/compress');
}
else {
$req->content_type('application/octet-stream');
}
### Send the headers
$req->send_http_header ;
### ... and now send the content
no strict 'subs';
$req->send_fd(PATCH);
close PATCH;
return OK ;
}
sub doit {
my $r = shift ;
( run in 2.026 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )