2

serve downloads through php with pause/resume support

Posted May 11th, 2010 in Uncategorized by pixelbyter

Using PHP to serve protected files to specific web users can be very useful for the developer, but the end user usually misses out as functionality such as download resuming is often missed out.

I’ve put together the following function which will serve files to the user with support for download resuming after they’ve been paused or the connection has been dropped. Also, by serving the data in 1MB chunks this function is very low on memory usage.

function dl($file_location, $file_name) {
	try {
		//check if file exists
		if(!file_exists($file_location))
			throw new Exception('Could not find file');

		//file size
		$file_size = filesize($file_location);

		//open download file
		$open_download_file = fopen($file_location, 'rb');
		if(!$open_download_file)
			throw new Exception('Could not open file');

		//download range
		$start_point = 0;
		$end_point = $file_size - 1;

		//partial download
		if(isset($_SERVER['HTTP_RANGE']) && !empty($_SERVER['HTTP_RANGE'])) {
			//download range
			$http_range = explode('-', substr($_SERVER['HTTP_RANGE'], strlen('bytes=')));
			$start_point = $http_range[0];
			if($http_range[1] > 0)
				$end_point = $http_range[1];

			//headers
			header('HTTP/1.0 206 Partial Content');
			header('Status: 206 Partial Content');
			header('Accept-Ranges: bytes');
			header('Content-Range: bytes '.$_SERVER['HTTP_RANGE'].'/'.$file_size);
			header('Content-Length: '.($end_point - $start_point + 1));
		}
		//full download
		else {
			//headers
			header('Content-Length: '.$file_size);
		}

		//headers
		header('Content-Type: application/force-download');
		header('Content-Disposition: attachment; filename="'.$file_name.'"');
		header('Last-Modified: '.date('D, d M Y H:i:s \G\M\T', filemtime($file_location)));

		//headers for ie6 & ie7
		header('Expires: 0');
		header('Pragma: public');
		header('Cache-Control: must-revalidate');

		//jump ahead in file to start
		if($start_point > 0)
			fseek($open_download_file, $start_point);

		//serve data chunk and update download progress log
		$download_bytes_position = $start_point;
		while(!feof($open_download_file) && $download_bytes_position <= $end_point) {
			//mark download as aborted if connection has been closed
			if(connection_aborted() || connection_status() != 0)
				throw new Exception('Connection aborted');

			//calculate next chunk size
			$download_data_chunk_size = 1048000;
			if($download_bytes_position + $download_data_chunk_size > $end_point + 1)
				$download_data_chunk_size = $end_point - $download_bytes_position + 1;

			//get data chunk
			$download_data_chunk = fread($open_download_file, $download_data_chunk_size);
			if(!$download_data_chunk)
				throw new Exception('Could not read file');

			//output it
			print($download_data_chunk);
			flush();

			//increment download point
			$download_bytes_position += $download_data_chunk_size;
		}

		//close archive file
		fclose($open_download_file);
	}
	catch(Exception $e) {
		//any error handling you need to do should go here
		return false;
	}

	return true;
}
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • MySpace
  • Reddit
  • StumbleUpon
  • Twitter

2 Responses so far.

  1. Thank you very much. This is a great code.

    I did only few minor changes. I used $file_location as the only argument for dl function and then used this:

    $file_name = basename($file_location)

    I did not know about ‘HTTP_RANGE’ and related header information (line 28-30). I am new to PHP (Started today). This code really helped me to understand.

    Thanks again

  2. Peter says:

    This is nice code, however it has typing mistake which causes it to fail download resume:

    Line 20: if(isset($_SERVER['HTTP_RANGE']) && !emptyempty($_SERVER['HTTP_RANGE'])) {

    should by just !empty(…) Took me a while to figure out why my download resume is not working…

Leave a Reply