You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

表单提交后下载PDF并跳转:解决headers already sent问题

解决下载后无法跳转的「Headers Already Sent」问题

嘿,这个坑我踩过好多次!你遇到的核心问题是HTTP协议的硬性规则:响应头(比如Location跳转指令)必须在任何实际内容(包括文件内容、echo的文本)之前发送。当你用readfile输出PDF内容后,HTTP响应的"头信息"已经完全发送给浏览器了,这时候再尝试发送Location跳转头,浏览器根本不会理它——甚至会抛出你看到的「Headers Already Sent」错误。

正确的解决思路:拆分请求流程

因为下载文件和页面跳转不能在同一个HTTP请求里完成,我们需要把流程拆成3步:

  1. 处理表单验证,验证通过后标记用户的下载权限
  2. 跳转到一个「下载中转页」,触发文件下载同时准备跳转
  3. 专门的脚本负责输出文件内容,避免干扰页面跳转

重构后的完整代码

1. 主表单页面(index.php)

<?php
session_start(); // 必须在所有输出前调用
$errors = [];
$name = "";
$email = "";

if (isset($_POST["submit"])) {
    $name = htmlspecialchars(stripslashes(trim($_POST["name"])));
    $email = htmlspecialchars(stripslashes(trim($_POST["email"])));
    
    // 保留你的验证规则
    if(!preg_match("/^[A-Za-z .'-]+$/", $name)){
        $errors[] = "Invalid name";
    }
    if(!preg_match("/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/", $email)){
        $errors[] = "Invalid email";
    }
    
    if (empty($errors)) {
        // 用Session标记用户已通过验证,防止直接访问下载链接
        $_SESSION['can_download'] = true;
        // 跳转到中转页,而不是直接输出文件
        header("Location: download_redirect.php");
        exit(); // 必须终止后续代码执行
    }
}
?>
<body>
<form method="POST">
    <label for="name">Name</label>
    <input type="text" id="name" name="name" value="<?= $name ?>" required><br>
    <label for="email">Email</label>
    <input type="email" id="email" name="email" value="<?= $email ?>" required><br>
    <button type="submit" name="submit">Download</button><br>
</form>
<p>
    <?php if (!empty($errors)) : ?>
        <ul>
            <?php foreach ($errors as $error) : ?>
                <li><?= $error ?></li>
            <?php endforeach; ?>
        </ul>
    <?php endif; ?>
</p>
</body>

2. 下载中转页(download_redirect.php)

这个页面负责触发下载,同时执行跳转:

<?php session_start(); ?>
<!DOCTYPE html>
<html>
<body>
<div>📥 你的文件正在下载,请稍候...</div>
<div>⏳ 3秒后自动跳转到目标页面...</div>

<script>
// 创建隐藏iframe触发下载,不影响当前页面的JS执行
const downloadIframe = document.createElement('iframe');
downloadIframe.style.display = 'none';
downloadIframe.src = 'download_handler.php';
document.body.appendChild(downloadIframe);

// 倒计时跳转
setTimeout(() => {
    window.location.href = 'http://something.com';
}, 3000);
</script>
</body>
</html>

3. 文件下载处理脚本(download_handler.php)

专门负责输出文件内容,避免干扰页面逻辑:

<?php
session_start();

// 验证用户是否有权限下载
if (!isset($_SESSION['can_download'])) {
    header("HTTP/1.1 403 Forbidden");
    die("Access denied: Please submit the form first");
}

// 清除权限标记,防止重复下载
unset($_SESSION['can_download']);

$file = "files/live.pdf";
// 检查文件是否存在,避免报错
if (!file_exists($file)) {
    header("HTTP/1.1 404 Not Found");
    die("File not found");
}

// 输出下载头(和你原来的代码一致)
header('Content-Description: File Transfer');
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename=' . basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize($file));

// 清空输出缓冲区,确保头信息纯净
ob_clean();
flush();
readfile($file);
exit(); // 输出文件后立即终止,不要输出任何额外内容
?>

关键注意事项

  • Session必须提前开启session_start()要放在所有HTML输出、空格之前,否则会触发「Headers Already Sent」错误。
  • 权限控制很重要:用Session标记验证状态,防止用户直接访问download_handler.php绕过表单验证。
  • 终止代码执行:每次发送header()后一定要用exit()die(),避免后续代码输出内容破坏HTTP响应。

内容的提问来源于stack exchange,提问作者felixRo

火山引擎 最新活动