首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 簡易斷點續傳下載腳本:HTTP Range 核心邏輯 30 行實現

     編輯:PHP 相關知識

核心代碼 (單檔案、無依賴)

<?php
// download.php?file=large.zip
$file = __DIR__ . '/files/' . basename($_GET['file'] ?? '');

if (!is_file($file)) {
  http_response_code(404);
  exit('File not found');
}

$size = filesize($file);
$filename = basename($file);

// 1. 基礎標頭
header('Cache-Control: public');
header('Accept-Ranges: bytes');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . rawurlencode($filename) . '"');

// 2. 處理 Range 請求
$start = 0;
$end = $size - 1;

if (isset($_SERVER['HTTP_RANGE'])) {
  // 解析 bytes=start-end
  $range = $_SERVER['HTTP_RANGE'];
  if (preg_match('/^bytes=(\d*)-(\d*)$/i', $range, $m)) {
    $start = $m[1] !== '' ? (int)$m[1] : 0;
    $end = $m[2] !== '' ? (int)$m[2] : $size - 1;
    
    // 邊界修正
    $start = max(0, min($start, $size - 1));
    $end = max($start, min($end, $size - 1));
    
    // 3. 範圍回應標頭
    http_response_code(206);
    header("Content-Range: bytes {$start}-{$end}/{$size}");
    header("Content-Length: " . ($end - $start + 1));
  }
} else {
  // 全檔下載
  header("Content-Length: {$size}");
}

// 4. 輸出檔案內容
$fp = fopen($file, 'rb');
fseek($fp, $start);

while (!feof($fp) && $start <= $end && !connection_aborted()) {
  $readSize = min(8192, $end - $start + 1);
  $data = fread($fp, $readSize);
  if ($data === false || $data === '') break;
  echo $data;
  @flush();
  $start += strlen($data);
}

fclose($fp);

逐行解析

  1. 安全檢查:basename() 防目錄遍歷,is_file() 確認存在
  2. 基礎標頭:Accept-Ranges: bytes 告訴瀏覽器支援斷點續傳
  3. Range 解析:正則匹配 bytes=start-end,支援 bytes=500- (從 500 到結尾) 和 bytes=-500 (最後 500 位元組)
  4. 206 狀態碼:表示部分內容,瀏覽器/下載工具識別為續傳
  5. Content-Range:bytes start-end/total 格式,必須精確
  6. 分塊輸出:8KB 緩衝區,flush() 即時發送,connection_aborted() 檢測斷線

測試驗證

1. curl 測試範圍請求

# 完整下載
curl -O https://example.com/download.php?file=test.zip

# 只下載前 100KB
curl -H "Range: bytes=0-102399" -o part1.zip https://example.com/download.php?file=test.zip

# 續傳 (從 100KB 開始)
curl -H "Range: bytes=102400-" -o part2.zip https://example.com/download.php?file=test.zip

# 合併
cat part1.zip part2.zip > full.zip

2. 瀏覽器測試

  • 開始下載 → 暫停 → 繼續 (IDM、FDM、瀏覽器內建下載器均支援)
  • 網路斷線重連後自動續傳

進階:支援多範圍請求

// HTTP 支援多範圍: bytes=0-500,600-1000
// 回應需為 multipart/byteranges
// 簡化版通常不實現,單範圍已覆蓋 99% 場景

生產環境必加項目

<?php
// 1. 實際路徑鎖定 (防目錄遍歷)
$baseDir = realpath(__DIR__ . '/files');
$requested = realpath($baseDir . '/' . basename($_GET['file'] ?? ''));
if (!$requested || strpos($requested, $baseDir) !== 0) {
  http_response_code(403);
  exit('Forbidden');
}

// 2. 權限檢查
if (!checkUserPermission($requested)) {
  http_response_code(403);
  exit('Access denied');
}

// 3. 限速 (可選)
$speedLimit = 1024 * 1024; // 1MB/s
// 在輸出迴圈加入 usleep 計算

// 4. 日誌記錄
logDownload($_SERVER['REMOTE_ADDR'], $requested, $start, $end);

// 5. 清理輸出緩衝 (防止前有空白/BOM)
while (ob_get_level()) ob_end_clean();

對比:簡易版 vs 企業級類 (ID: 149205)

特性簡易版 (本文)企業級類
代碼行數~30 行~150 行
錯誤處理基礎完整異常體系
檔名編碼rawurlencodeRFC 5987 完整支援
多範圍支援無可擴展
限速/權限/日誌需自行加內建介面
前端 JS 整合無ResumableDownloader 類
適用場景快速原型、內部工具生產系統、開放下載站

Nginx X-Accel-Redirect 終極優化

將檔案傳輸完全卸載給 Nginx,PHP 僅負責權限驗證:

<?php
// PHP 只做驗證
if (!checkPermission($file)) exit('403');

header('X-Accel-Redirect: /protected/' . basename($file));
header('Content-Disposition: attachment; filename="' . rawurlencode(basename($file)) . '"');
header('Content-Type: application/octet-stream');
// 結束,Nginx 接管傳輸
# Nginx 配置
location /protected/ {
  internal;                    # 禁止直接訪問
  alias /var/www/files/;       # 真實儲存路徑
  sendfile on;                 # 零拷貝
  tcp_nopush on;
  aio threads;                 # 異步 I/O (大檔案)
  output_buffers 16 256k;      # 輸出緩衝
}

優勢:PHP 進程瞬間釋放、零記憶體佔用、內核級 sendfile、支援超大檔案、自動處理 Range/續傳。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved