- 1 require_once('download.class.php');
- 2 date_default_timezone_set('Asia/Shanghai');
- 3 error_reporting(E_STRICT);
- 4
- 5 function errorHandler($errno, $errstr, $errfile, $errline) {
- 6 echo '<p>error:', $errstr, '</p>';
- 7 exit();
- 8 }
- 9
- 10 set_error_handler('errorHandler');
- 11 define('IS_DEBUG', true);
- 12
- 13 $filePath = 'test.zip';
- 14 $mimeType = 'audio/x-matroska';
- 15 $range = isset($_SERVER['HTTP_RANGE']) ? $_SERVER['HTTP_RANGE'] : null;
- 16 if (IS_DEBUG) {
- 17 // $range = "bytes=1000-1999\n2000";
- 18 // $range = "bytes=1000-1999,2000";
- 19 // $range = "bytes=1000-1999,-2000";
- 20 // $range = "bytes=1000-1999,2000-2999";
- 21 }
- 22 set_time_limit(0);
- 23 $transfer = new Transfer($filePath, $mimeType, $range);
- 24 if (IS_DEBUG) {
- 25 $transfer->setIsLog(true);
- 26 }
- 27 $transfer->send();
- 1 /**
- 2 * 文件传输,支持断点续传。
- 3 * 2g以上超大文件也有效
- 4 * @author MoXie
- 5 */
- 6 class Transfer {
- 7
- 8 /**
- 9 * 缓冲单元
- 10 */
- 11 const BUFF_SIZE = 5120; // 1024 * 5
- 12
- 13 /**
- 14 * 文件地址
- 15 * @var <String>
- 16 */
- 17
- 18 private $filePath;
- 19
- 20 /**
- 21 * 文件大小
- 22 * @var <String> Php超大数字 字符串形式描述
- 23 */
- 24 private $fileSize;
- 25
- 26 /**
- 27 * 文件类型
- 28 * @var <String>
- 29 */
- 30 private $mimeType;
- 31
- 32 /**
- 33 * 请求区域(范围)
- 34 * @var <String>
- 35 */
- 36 private $range;
- 37
- 38 /**
- 39 * 是否写入日志
- 40 * @var <Boolean>
- 41 */
- 42 private $isLog = false;
- 43
- 44 /**
- 45 *
- 46 * @param <String> $filePath 文件路径
- 47 * @param <String> $mimeType 文件类型
- 48 * @param <String> $range 请求区域(范围)
- 49 */
- 50 function __construct($filePath, $mimeType = null, $range = null) {
- 51 $this->filePath = $filePath;
- 52 $this->fileSize = sprintf('%u', filesize($filePath));
- 53 $this->mimeType = ($mimeType != null) ? $mimeType : "application/octet-stream"; // bin
- 54 $this->range = trim($range);
- 55 }
- 56
- 57 /**
- 58 * 获取文件区域
- 59 * @return <Map> {'start':long,'end':long} or null
- 60 */
- 61 private function getRange() {
- 62 /**
- 63 * Range: bytes=-128
- 64 * Range: bytes=-128
- 65 * Range: bytes=28-175,382-399,510-541,644-744,977-980
- 66 * Range: bytes=28-175\n380
- 67 * type 1
- 68 * RANGE: bytes=1000-9999
- 69 * RANGE: bytes=2000-9999
- 70 * type 2
- 71 * RANGE: bytes=1000-1999
- 72 * RANGE: bytes=2000-2999
- 73 * RANGE: bytes=3000-3999
- 74 */
- 75 if (!empty($this->range)) {
- 76 $range = preg_replace('/[\s|,].*/', '', $this->range);
- 77 $range = explode('-', substr($range, 6));
- 78 if (count($range) < 2) {
- 79 $range[1] = $this->fileSize; // Range: bytes=-100
- 80 }
- 81 $range = array_combine(array('start', 'end'), $range);
- 82 if (empty($range['start'])) {
- 83 $range['start'] = 0;
- 84 }
- 85 if (!isset($range['end']) || empty($range['end'])) {
- 86 $range['end'] = $this->fileSize;
- 87 }
- 88 return $range;
- 89 }
- 90 return null;
- 91 }
- 92
- 93 /**
- 94 * 向客户端发送文件
- 95 */
- 96 public function send() {
- 97 $fileHande = fopen($this->filePath, 'rb');
- 98 if ($fileHande) {
- 99 // setting
- 100 ob_end_clean(); // clean cache
- 101 ob_start();
- 102 ini_set('output_buffering', 'Off');
- 103 ini_set('zlib.output_compression', 'Off');
- 104 $magicQuotes = get_magic_quotes_gpc();
- 105 // set_magic_quotes_runtime(0);
- 106 // init
- 107 $lastModified = gmdate('D, d M Y H:i:s', filemtime($this->filePath)) . ' GMT';
- 108 $etag = sprintf('w/"%s:%s"', md5($lastModified), $this->fileSize);
- 109 $ranges = $this->getRange();
- 110 // headers
- 111 header(sprintf('Last-Modified: %s', $lastModified));
- 112 header(sprintf('ETag: %s', $etag));
- 113 header(sprintf('Content-Type: %s', $this->mimeType));
- 114 $disposition = 'attachment';
- 115 if (strpos($this->mimeType, 'image/') !== FALSE) {
- 116 $disposition = 'inline';
- 117 }
- 118 header(sprintf('Content-Disposition: %s; filename="%s"', $disposition, basename($this->filePath)));
- 119
- 120 if ($ranges != null) {
- 121 if ($this->isLog) {
- 122 $this->log(json_encode($ranges) . ' ' . $_SERVER['HTTP_RANGE']);
- 123 }
- 124 header('HTTP/1.1 206 Partial Content');
- 125 header('Accept-Ranges: bytes');
- 126 header(sprintf('Content-Length: %u', $ranges['end'] - $ranges['start']));
- 127 header(sprintf('Content-Range: bytes %s-%s/%s', $ranges['start'], $ranges['end'], $this->fileSize));
- 128 //
- 129 fseek($fileHande, sprintf('%u', $ranges['start']));
- 130 } else {
- 131 header("HTTP/1.1 200 OK");
- 132 header(sprintf('Content-Length: %s', $this->fileSize));
- 133 }
- 134 // read file
- 135 $lastSize = 0;
- 136 while (!feof($fileHande) && !connection_aborted()) {
- 137 $lastSize = sprintf("%u", bcsub($this->fileSize, sprintf("%u", ftell($fileHande))));
- 138 if (bccomp($lastSize, self::BUFF_SIZE) > 0) {
- 139 $lastSize = self::BUFF_SIZE;
- 140 }
- 141 echo fread($fileHande, $lastSize);
- 142 ob_flush();
- 143 flush();
- 144 }
- 145 set_magic_quotes_runtime($magicQuotes);
- 146 ob_end_flush();
- 147 }
- 148 if ($fileHande != null) {
- 149 fclose($fileHande);
- 150 }
- 151 }
- 152
- 153 /**
- 154 * 设置记录
- 155 * @param <Boolean> $isLog 是否记录
- 156 */
- 157 public function setIsLog($isLog = true) {
- 158 $this->isLog = $isLog;
- 159 }
- 160
- 161 /**
- 162 * 记录
- 163 * @param <String> $msg 记录信息
- 164 */
- 165 private function log($msg) {
- 166 try {
- 167 $handle = fopen('transfer_log.txt', 'a');
- 168 fwrite($handle, sprintf('%s : %s' . PHP_EOL, date('Y-m-d H:i:s'), $msg));
- 169 fclose($handle);
- 170 } catch (Exception $e) {
- 171 // null;
- 172 }
- 173 }
- 174
- 175 }