如何用PHP分块下载大文件?服务器限单文件≤4MB
PHP实现无JS自动分块下载大文件(规避4MB下载限制)
我明白你的场景:服务器拦截超过4MB的下载请求,只能用PHP实现自动刷新分块下载,且工作环境不支持JavaScript。你的原代码核心问题出在分块计数逻辑错误、请求头设置不当,以及没有向客户端返回正确的分块响应标识,导致浏览器无法识别续传逻辑,也没法触发后续自动刷新。
下面是修正后的完整代码,我会逐一解释关键修复点:
<?php session_start(); ini_set('max_execution_time', 0); // 获取目标文件URL $url = $_GET['url'] ?? ''; if (empty($url)) { die('请提供要下载的文件URL'); } // 计算文件总大小,增强兼容性 function getFileSize($url) { if (str_starts_with($url, 'http')) { $headers = array_change_key_case(get_headers($url, 1), CASE_LOWER); // 处理重定向后的Content-Length(如果存在多值情况) if (isset($headers['content-length'])) { return is_array($headers['content-length']) ? end($headers['content-length']) : $headers['content-length']; } // 若直接获取失败,用HEAD请求重试 $ch = curl_init($url); curl_setopt($ch, CURLOPT_NOBODY, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_exec($ch); $size = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD); curl_close($ch); return $size; } else { return @filesize($url); } } $fileTotalSize = getFileSize($url); if (!$fileTotalSize) { die('无法获取目标文件大小'); } // 分块大小:3.999MB(比4MB限制小1字节) $chunkSize = (4 * 1024 * 1024) - 1; // 初始化session中的下载偏移量 if (!isset($_SESSION['currentOffset'])) { $_SESSION['currentOffset'] = 0; } $currentOffset = $_SESSION['currentOffset']; // 计算当前分块的结束位置(避免超出文件总大小) $endOffset = min($currentOffset + $chunkSize, $fileTotalSize - 1); $rangeHeader = "bytes=$currentOffset-$endOffset"; // 用CURL请求目标分块内容 $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'); curl_setopt($ch, CURLOPT_BINARYTRANSFER, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_TIMEOUT, 0); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0); // 仅向目标服务器发送Range请求头,原代码错误混入了响应头 curl_setopt($ch, CURLOPT_HTTPHEADER, array($rangeHeader)); curl_setopt($ch, CURLOPT_HEADER, false); $chunkContent = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 206 && $httpCode !== 200) { die("分块请求失败,HTTP状态码:$httpCode"); } // 向客户端返回响应头,区分首次下载与续传 if ($currentOffset === 0) { // 首次请求,设置下载文件的基础头信息 header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename=' . basename(parse_url($url, PHP_URL_PATH))); header('Content-Transfer-Encoding: binary'); header('Connection: Keep-Alive'); header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Pragma: public'); } // 设置分块响应标识,让浏览器识别这是续传内容 header("HTTP/1.1 206 Partial Content"); header("Accept-Ranges: bytes"); header("Content-Range: bytes $currentOffset-$endOffset/$fileTotalSize"); header("Content-Length: " . strlen($chunkContent)); // 输出当前分块内容并强制刷新缓冲区 echo $chunkContent; flush(); ob_flush(); // 更新session中的下载偏移量 $_SESSION['currentOffset'] = $endOffset + 1; // 判断是否需要继续下载下一个分块 if ($_SESSION['currentOffset'] < $fileTotalSize) { // 立即刷新当前页面,继续下载下一块 header("Refresh: 0; url=" . $_SERVER['REQUEST_URI']); } else { // 下载完成,销毁session释放资源 session_destroy(); session_write_close(); } ?>
关键修复点说明:
- 分块计数逻辑修正:用
currentOffset记录当前下载的起始位置,每次下载后更新为endOffset + 1,彻底避免了原代码中模运算的优先级错误。 - 请求头与响应头分离:原代码错误地在CURL请求中混入了响应头(
HTTP/1.1 206 Partial Content),现在仅向目标服务器发送Range请求头,206相关头是返回给客户端的响应标识。 - 客户端响应优化:返回
Content-Range头让浏览器识别这是分块下载的一部分,部分浏览器/下载工具可以自动续传合并分块。 - 文件大小获取增强:处理了重定向场景下的多值
Content-Length,并增加HEAD请求重试逻辑,提升兼容性。 - 刷新逻辑适配性:用
$_SERVER['REQUEST_URI']自动获取当前请求地址,避免硬编码URL,适配不同部署环境。
使用注意事项:
- 确保服务器开启Session支持,且Session存储目录有写入权限。
- 目标服务器必须支持Range请求(大部分现代服务器都支持),否则分块下载无法工作。
- 由于每次刷新都是独立请求,部分浏览器可能会弹出多次下载提示,建议使用专业下载管理器来自动合并分块。
内容的提问来源于stack exchange,提问作者Rakib Hasan
相关产品推荐
相关产品推荐

