Jeg har en klasse her, ikke 100% testet (så vidt jeg husker), men den kan resume (altså understøtter også downloadmanagers - i.e. flere connections), og speed limit:
<?php
set_time_limit(0); // Let script run indefinitely
ignore_user_abort(TRUE); // Force script to continue if user aborts
ini_set("zlib.output_compression", "off"); // Turn off output compression
ob_end_flush(); // Turn off output buffering
header( "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0" );
header( "Pragma: no-cache" );
header( "Expires: 0" );
class Download
{
/* (c) L. Petersen, lars at ordo dot dk -
www.ioflux.net */
var $_limit = 0;
var $_file = NULL;
var $_size = 0;
var $_fp = NULL;
var $_blocksize = 256000;
var $_root = '';
// Set base path for downloading, optional
function SetRoot($root)
{
$this->_root = realpath( $root );
if ( $this->_root !== FALSE )
{
$this->_root .= DIRECTORY_SEPARATOR;
}
else
{
die( "Error: Root $root does not exist" );
}
}
function SetFile( $file )
{
// Set the file and get the size of it
if ( $this->_root != '' && FALSE !== ( $file = realpath( $this->_root . $file ) ) )
{
if ( substr( $file, 0, strlen( $this->_root ) ) != $this->_root )
{
die( "Permission denied." );
}
}
if ( is_file( $file ) && is_readable( $file ) )
{
$this->_file = $file;
$this->_size = filesize( $file );
}
else
{
die("404 File Not Found");
}
}
function SetLimit( $KbPerSec )
{
// For simplicity set blocksize to limit if there is one
$this->_blocksize = $this->_limit = $KbPerSec * 1024;
if ( $this->_limit == 0 )
{
// Set blocksize to 250KB if there is no limit
$this->_blocksize = 250 * 1024;
}
}
function SendHeaders()
{
header( "Content-Disposition: attachment; filename=".basename( $this->_file ) );
header( "Content-Type: application/octet-stream" );
header( "Content-Length: " . $this->_size );
header( "X-Powered-By: ioflux.net download delivery system" );
header( "Content-Transfer-Encoding: binary" );
if ( $this->_limit == 0 )
{
header( "Accept-Ranges: bytes" );
}
else
{
header( "Accept-Ranges: none" );
}
}
function Start()
{
// Ok start download
$sleep = -1;
$aborted = FALSE;
$offset = FALSE;
$endset = FALSE;
// See if we want to resume or segment downloading
// Only allow this if there is no bandwidth limit
if ( $this->_limit == 0 && isset( $_SERVER['HTTP_RANGE'] ) )
{
// User wants to resume download
if ( preg_match( '/^bytes=([0-9]*?)-$/', $_SERVER['HTTP_RANGE'], $m ) )
{
$offset = $m[1];
}
else if ( preg_match( '/^bytes=([0-9]*?)-([0-9]*?)$/', $_SERVER['HTTP_RANGE'], $m ) )
{
$offset = $m[1];
$endset = $m[2];
}
}
if ( $offset !== FALSE )
{
if ( $offset > $this->_size )
{
// User requested to resume beyond file size!
header( 'HTTP/1.0 500 Internal Server Error' );
exit;
}
// Resume download from somewhere
fseek( $fp, $offset );
$length -= $offset;
if ( $endset )
{
$length = $endset - $offset;
}
}
if ( $endset == FALSE )
{
$endset = $length;
}
if ( $this->_fp = fopen( $this->_file, 'rb' ) )
{
$cur = 0;
$start = time();
while ( !feof( $this->_fp ) )
{
// Get start time for this block
$cnts = fread( $this->_fp, $this->_blocksize );
$cur += strlen( $cnts );
echo $cnts;
$secs = time() - $start;
$Kbsec = ( $cur / $secs );
if ( $this->_limit != 0 && $Kbsec > $this->_limit )
{
sleep( 1 );
}
if (connection_aborted())
{
// No need to keep sending data if the client pressed abort or something
$aborted = TRUE;
break;
}
}
fclose($this->_fp);
}
return $aborted;
}
}
?>
Klasse bruges sådan:
<?php
require_once('download.class.php');
$file = 'En_stor_fil.zip';
$dl = new Download;
$dl->SetRoot('/folder/hvor/filen/er/');
$dl->SetFile($file);
$dl->SetLimit(25); // 25 KB/s begrænsning!
$dl->SendHeaders();
$dl->Start();
?>