如何修复PHP中的“Headers already sent”错误

831
当运行我的脚本时,我遇到了几个类似这样的错误:
警告:无法修改头信息 - 头信息已经被发送(输出开始于 /some/file.php:12)位于 /some/file.php第23行 错误消息中提到的行包含了 header()setcookie() 的调用。
这可能是什么原因?如何修复它?

阅读:https://dev59.com/znI-5IYBdhLWcg3wVWpi - Book Of Zeus
确保不输出任何文本(这里可能会用到ob_startob_end_clean())。然后,您可以将cookie或会话设置为ob_get_contents(),然后使用ob_end_clean()来清除缓冲区。 - Jack
使用我的PHP库中的safeRedirect函数:https://github.com/heinkasner/PHP-Library/blob/master/extra.php - heinkasner
5
您的文件编码不应该是 UTF-8,而应该是 UTF-8 (无 BOM) - T.Todua
16个回答

3312

发送头部之前不能有输出!

发送/修改HTTP头部的函数必须在任何输出之前调用。 摘要 ⇊ 否则调用将失败:

警告:无法修改头部信息 - 头部已经被发送(输出开始于 脚本:行号

一些修改HTTP头部的函数包括:

输出可以是:

  • 有意的:

    • printecho和其他产生输出的函数
    • <?php代码之前的原始<html>部分。

为什么会发生这种情况?

要理解为什么必须在输出之前发送头部,需要查看一个典型的HTTP响应。PHP脚本主要生成HTML内容,但也向Web服务器传递一组HTTP/CGI头部:

HTTP/1.1 200 OK
Powered-By: PHP/5.3.7
Vary: Accept-Encoding
Content-Type: text/html; charset=utf-8

<html><head><title>PHP page output page</title></head>
<body><h1>Content</h1> <p>Some more output follows...</p>
and <a href="/"> <img src=internal-icon-delayed> </a>

页面/输出总是遵循标题。PHP必须首先将标题传递给Web服务器。它只能这样做一次。在双换行符之后,它再也不能修改它们。
当PHP接收到第一个输出(print、echo、),它将刷新所有已收集的标题。之后,它可以发送所有想要的输出。但此时无法再发送HTTP标题。
如何找出发生过早输出的位置?
header()警告包含了定位问题原因的所有相关信息:
警告:无法修改头信息-头部已经由(output started at /www/usr2345/htdocs/auth.php:52)开始,在/www/usr2345/htdocs/index.php的第100行
这里的"第100行"指的是header()调用失败的脚本。
"

括号内的“output started at”注释更为重要。 它标识了先前输出的来源。在这个例子中,它是auth.php52。那就是你需要寻找过早输出的地方。

典型原因:

"
  1. 打印,回显

    printecho语句的有意输出将终止发送HTTP头部的机会。必须重新构建应用程序流程以避免这种情况。使用functions和模板方案。确保在写出消息之前调用header()

    产生输出的函数包括:

    • printechoprintfvprintf
    • trigger_errorob_flushob_end_flushvar_dumpprint_r
    • readfilepassthruflushimagepngimagejpeg


    等等和用户定义的函数。

  2. 原始HTML区域

    .php文件中未解析的HTML部分也是直接输出的。 必须在任何原始的<html>块之前注意触发header()调用的脚本条件。

    <!DOCTYPE html>
    <?php
        // 头部已经太晚了。
    

    使用模板方案将处理与输出逻辑分离。

    • 将表单处理代码放在脚本的顶部。
    • 使用临时字符串变量来延迟消息。
    • 实际的输出逻辑和混合的HTML输出应该最后出现。


  3. “script.php line 1”警告之前的空格

    如果警告指的是行内1的输出,那么它主要是由于开头的空格、文本或HTML在<?php标记之前。

     <?php
    # 在<?之前有一个空格/换行符 - 这已经封闭了它。
    

    类似地,它也可能发生在附加的脚本或脚本段落中:

    ?>
    
    <?php
    

    PHP实际上会吞掉关闭标签后的单个换行符。但它不会弥补多个换行符或制表符或移入这些间隙的空格。

  4. UTF-8 BOM

    换行符和空格本身可能是一个问题。但还有一些“不可见”的字符序列可能会导致这个问题。最著名的是UTF-8 BOM(字节顺序标记),大多数文本编辑器不显示它。它是字节序列EF BB BF,对于UTF-8编码的文档来说是可选的和多余的。然而,PHP必须将其视为原始输出。它可能显示为输出中的字符(如果客户端将文档解释为Latin-1)或类似的“垃圾”。

    特别是图形编辑器和基于Java的IDE对其存在毫无察觉。它们不可视化它(受Unicode标准的约束)。然而,大多数程序员和控制台编辑

    没有错误消息
    如果你在 php.ini 中禁用了 error_reporting 或 display_errors,那么就不会显示任何警告。但是忽略错误并不能解决问题。在输出之前仍然无法发送头部。
    所以当 header("Location: ...") 重定向失败时,建议检查警告。在调用脚本的顶部使用两个简单的命令重新启用它们。
    error_reporting(E_ALL);
    ini_set("display_errors", 1);
    

    或者在所有其他方法都失败时,使用set_error_handler("var_dump");

    说到重定向头部,你应该经常使用像这样的习惯用法来处理最终的代码路径:

    exit(header("Location: /finished.html"));
    

    最好是一个实用函数,当header()失败时打印用户消息。

    输出缓冲作为解决方法

    PHP的输出缓冲 是一种解决此问题的方法。它通常可靠运行,但不应替代正确的应用结构和将输出与控制逻辑分离。其真正目的是将分块传输最小化到Web服务器。

  5. output_buffering= 设置仍然是有帮助的。可以在 php.ini 中进行配置,或者通过 .htaccess 或者甚至 .user.ini 在现代的 FPM/FastCGI 设置中进行配置。启用它将允许 PHP 缓冲输出而不是立即传递给 Web 服务器。因此,PHP 可以聚合 HTTP 头。

  6. 也可以通过调用 ob_start(); 来启用它。然而,由于多种原因,这种方法不太可靠:

    • 即使 <?php ob_start(); ?> 开始了第一个脚本,空白字符或 BOM 可能会在之前产生,导致其无效

    • 它可能会隐藏 HTML 输出的空白字符。但是一旦应用程序逻辑尝试发送二进制内容(例如生成的图像),缓冲的多余输出就成为一个问题。(需要使用 ob_clean() 进行进一步的解决。)

    • 缓冲区大小有限,在使用默认值时很容易溢出。当发生这种情况时,这也不是罕见的,很难追踪到问题的所在

  7. 因此,这两种方法在切换开发环境和/或生产服务器时可能变得不可靠。这就是为什么输出缓冲被广泛认为只是一个支撑物/严格的权宜之计。
    请参阅手册中的基本用法示例,以及更多的利弊:

    但在另一台服务器上却可以工作!?

    如果您之前没有收到标题警告,则表示 output buffering php.ini 设置 已更改。当前/新服务器上可能尚未配置。

    使用 headers_sent() 进行检查

    您始终可以使用 headers_sent() 来探测是否仍然有可能发送标题。这对于有条件地打印信息或应用其他回退逻辑非常有用。

    if (headers_sent()) {
        die("Redirect failed. Please click on this link: <a href=...>");
    }
    else{
        exit(header("Location: /user.php"));
    }
    

    有用的备选方案包括:
    • HTML <meta>标签

      如果您的应用程序在结构上很难修复,那么一种简单(但有点不专业)的方法是注入一个HTML <meta>标签来允许重定向。可以通过以下方式实现重定向:

       <meta http-equiv="Location" content="http://example.com/">
      

      或者稍作延迟:

       <meta http-equiv="Refresh" content="2; url=../target.html">
      

      这会导致在<head>部分之后使用时产生非有效的HTML。大多数浏览器仍然接受它。

    • JavaScript重定向

      作为替代方案,可以使用JavaScript重定向进行页面重定向:

       <script> location.replace("target.html"); </script>
      

      虽然这通常比<meta>的解决方法更符合HTML规范,但它依赖于支持JavaScript的客户端。

    然而,当真正的HTTP header()调用失败时,这两种方法都可以作为可接受的备选方案。理想情况下,您应该始终将其与用户友好的消息和可点击的链接结合在一起,作为最后的手段。(例如,这就是http_redirect() PECL扩展所做的。)

    为什么setcookie()和session_start()也会受到影响

    setcookie()和session_start()都需要发送Set-Cookie: HTTP头。因此,相同的条件适用,并且会生成类似的错误消息以处理过早输出的情况。

    (当然,它们还受到浏览器禁用cookie甚至代理问题的影响。会话功能显然还取决于可用的磁盘空间和其他php.ini设置等。)

    更多链接

    Google提供了一个大量类似的讨论。 当然,Stack Overflow上也涵盖了许多具体案例。 WordPress FAQ以一种通用的方式解释了如何解决已发送标头警告问题。 Adobe社区:PHP开发:为什么重定向不起作用(标头已发送) Nucleus FAQ:"页面标头已发送"是什么意思? 其中一个更详细的解释是HTTP标头和PHP header()函数 - NicholasSolutions的教程(互联网档案馆链接)。 它详细介绍了HTTP,并给出了一些重写脚本的指导方针。

4
从 PHP 文件的末尾删除 ?> 结束标签通常是一个好习惯,有助于最小化错误。这样文件末尾就不会出现不需要的空格,在稍后添加响应头时也更加方便。此外,如果您使用输出缓冲,并且不希望在包含文件生成的部分末尾添加不必要的空格,这也很实用。 - Nikita 웃
奇怪的事情,我将文件从cPanel Linux托管移动到VPS。在此之前,它正常工作,但在这里却显示了这个错误。(我在标题之前放了一些HTML代码)。为什么? - Pablo Escobar
1
@PeterSMcIntyre UTF8 BOM 可能存在(请修复)/ 未启用输出缓冲区(不要依赖它)。 - mario
2
可能有一个细节你需要添加。如果错误信息省略了发送头信息的内容,$file = $line = null; headers_sent($file, $line); die("$file:$line"); 将会告诉你确切是什么发送了头信息。不确定为什么,但有时似乎是这种情况。 - kmuenkel
1
关于第6种情况,错误的来源可能与浏览器中断的请求有关(查看访问日志中的HTTP状态499,尽管它可能不会在所有情况下出现)。对于这种情况,“ignore_user_abort=On”可以修复/解决“Headers already sent”的通知,至少我是这样做的。 - Jānis Elmeris
显示剩余16条评论

215

当你在发送HTTP头部(使用setcookieheader)之前发送任何内容时,就会触发该错误消息。导致在HTTP头部之前输出内容的常见原因包括:

  • 无意中添加的空白符,通常出现在文件开头或结尾,例如:

       <?php
      // Note the space before "<?php"
      ?>
    

避免这种情况,只需省略结尾的 ?> 即可,因为它并不是必需的。

  • 文件开头的字节顺序标记 (BOM)。使用十六进制编辑器检查你的 PHP 文件是否存在该情况。正确的开头应该是 3F 3C。你可以安全地删除文件开头的 BOM EF BB BF
  • 显式输出,例如调用 echoprintfreadfilepassthru<? 标签之前的代码等。
  • PHP 发出的警告,如果设置了 display_errors php.ini 属性。PHP 不会因程序员的错误而崩溃,而是默默地修复错误并发出警告。虽然可以修改配置项 display_errorserror_reporting,但最好还是修复问题。
    常见原因包括访问未定义的数组元素(如没有使用 emptyisset 来测试输入是否已设置的 $_POST['input']),或者使用未定义的常量而不是字符串文字(例如 $_POST[input],注意缺少引号)。

打开输出缓冲应该可以解决问题。在调用 ob_start 后,所有的输出都被缓存到内存中,直到你释放缓存,例如通过ob_end_flush

然而,虽然输出缓冲可以避免这些问题,但你应该真正确定为什么你的应用程序会在 HTTP 头之前输出 HTTP 内容。这就像接电话并讨论了你的一天和天气,然后再告诉那个打错号码的人他拨错了电话一样。


137

我以前遇到过这个错误,我相信所有的PHP程序员都至少遇到过一次。

可能的解决方案1

这个错误可能是由文件开头或结尾处的空格引起的。这些空格不应该存在。

例如)

<!-- THERE SHOULD BE NO BLANK SPACES HERE -->
<?php  

   echo "your code here";

?>
<!-- THERE SHOULD BE NO BLANK SPACES HERE -->

检查引起这个错误的文件相关的所有文件。

注意: 有时像gedit(一个默认的Linux编辑器)这样的编辑器在保存文件时会添加一个空白行,这应该不会发生。 如果您使用Linux,可以使用VI编辑器删除页面末尾?>后面的空格/行。

可能的解决方案2: 如果这不是您的情况,请使用ob_start进行输出缓冲:

<?php
  ob_start();

  // code 

 ob_end_flush();
?> 

这将打开输出缓冲,页面头部将在页面被缓冲后创建。


23
ob_start()只是隐藏问题,不要使用它来解决这个特定的问题。 - Ja͢ck
7
ob_start() 并不是用来“隐藏”问题的,它解决了这个问题。 - Tomas
1
当我将文件上传到服务器时,我遇到了这样的问题,即使PHP5.3也受支持。请使用带有PHP 5.6或更高版本的服务器。 - GGSoft
1
@jack,我同意你的观点。最好的方法是在 PHP 标记 <? 前删除空格。 - Manish Shrivastava
2
解决了我的问题。 - NDi
显示剩余4条评论

100
不要使用下面的那行
//header("Location:".ADMIN_URL."/index.php");

write

echo "<script>location.href = '".ADMIN_URL."/index.php?msg=$msg';</script>";

或者

?><script><?php echo "location.href = '".ADMIN_URL."/index.php?msg=$msg';";?></script><?php

这肯定能解决你的问题。 我之前也遇到过同样的问题,但是通过上述的方式写入头部位置,我解决了它。

56

通常是由于在启动会话之前脚本意外输出引起的。根据您当前的代码,您可以尝试使用输出缓冲来解决它。

尝试在脚本的顶部添加对 ob_start(); 函数的调用,并在文档的末尾添加 ob_end_flush();


43
你可以这样翻译:

你需要

printf ("Hi %s,</br />", $name);

在设置 cookie 之前,这是不允许的。你不能在头部之前发送任何输出,甚至不能有一个空行。


42

第45-47行:

?>

<?php
那段代码输出了几个换行符,所以头部信息已经发送。只需删除那3行(毕竟这是一个完整的PHP块,不需要结束PHP解析然后重新开始),以及第60-62行类似的块,它就会正常工作。
注意,您收到的错误消息实际上为您提供了很多有助于自行查找问题的信息:
警告:无法修改标头信息 - 标头已由(输出从C:\xampp\htdocs\speedycms\deleteclient.php:47开始)在C:\xampp\htdocs\speedycms\deleteclient.php的第106行。
加粗的两个部分告诉您在头部信息之前发送了输出的项目在哪里(第47行),以及尝试在输出后发送标头的项目在哪里(第106行)。

39

常见问题:

1)header(.......);命令之前不应有任何输出(即echo..或HTML代码)。

2) 删除<?php?>标签之前和之后的任何空格(或换行符)。

3) 黄金法则! - 检查该php文件(以及如果您include其他文件)是否具有UTF8无BOM编码(而不仅仅是UTF-8)。这在许多情况下是个问题(因为UTF8编码的文件在php文件开头有一个特殊字符,您的文本编辑器不会显示)!!!!!!!!!!

4)header(...);之后必须使用exit;

5) 始终使用301或302引用:

header("location: http://example.com",  true,  301 );  exit;

6) 打开错误报告并找到错误。您的错误可能是由一个不起作用的函数引起的。当您打开错误报告时,应始终先修复最顶部的错误。例如,可能是“警告:date_default_timezone_get():依赖于系统的时区设置是不安全的。” - 然后在下面可能会看到“未发送标头”错误。修复最顶部(第一个)错误后,重新加载页面。如果仍然有错误,则再次修复最顶部的错误。
7) 如果以上方法都没有帮助,请使用JAVSCRIPT重定向(但这是非常不推荐的方法),可能是自定义情况下的最后机会...
echo "<script type='text/javascript'>window.top.location='http://website.com/';</script>"; exit;

为什么明确设置 301302 是重要的? - Jānis Elmeris
如果在php.ini文件中设置了“output_buffering”,则可以有输出。我在我的家用Debian系统上的设置为4096。我使用的服务器上的设置显示没有设置。 - user9329083

34

这是因为这一行代码:

printf ("Hi %s,</br />", $name);

在发送头部信息之前,您不应该输出/打印任何内容。


27
一个简单的技巧:在你的脚本中,在第一个<?php标记之前添加一个空格(或不可见的特殊字符)可能会导致这个问题!尤其是当你在团队中工作,有人使用“弱”IDE或者用奇怪的文本编辑器搞乱了文件时,就会出现这种情况。我曾经见过这些事情发生过 ;)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接