如何修复 PHP 中的“标头已发送”错误

Posted

技术标签:

【中文标题】如何修复 PHP 中的“标头已发送”错误【英文标题】:How to fix "Headers already sent" error in PHP 【发布时间】:2011-11-06 17:41:22 【问题描述】:

在运行我的脚本时,我遇到了几个类似这样的错误:

警告:无法修改标头信息 - 标头已由 /some/file.php 中的(输出开始于 /some/file.php:12)发送strong>第 23 行

错误消息中提到的行包含header()setcookie() 调用。

这可能是什么原因?以及如何解决?

【问题讨论】:

阅读:***.com/questions/1912029/… 确保不输出任何文本(ob_startob_end_clean() 在这里可能很有用)。然后您可以设置一个等于ob_get_contents() 的cookie 或会话,然后使用ob_end_clean() 清除缓冲区。 在我的 php 库中使用 safeRedirect 函数:github.com/heinkasner/PHP-Library/blob/master/extra.php ~~~~~~~~~~ 你的文件编码不应该是UTF-8,而是UTF-8 (Without BOM)~~~~~~~~~~~ 【参考方案1】:

发送标头前无输出!

必须调用发送/修改 HTTP 标头的函数在进行任何输出之前summary ⇊ 否则调用失败:

警告:无法修改标头信息 - 标头已发送(输出开始于 script:line

一些修改HTTP头的函数有:

header / header_remove session_start / session_regenerate_id setcookie / setrawcookie

输出可以是:

无意:

<?php 之前或?> 之后的空格 UTF-8 Byte Order Mark 专门 以前的错误消息或通知

故意的:

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

为什么会这样?

要了解为什么必须在输出之前发送标头,有必要 看一个典型的HTTP 回复。 PHP 脚本主要生成 HTML 内容,同时也传递一个 一组 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 必须通过 首先将标头发送到网络服务器。它只能这样做一次。 双换行后再也不能修改了。

当 PHP 接收到第一个输出(printecho&lt;html&gt;)时,它将 flush 所有收集的标头。之后它可以发送所有输出 它想要。但是,发送更多的 HTTP 标头是不可能的。

如何找出过早输出发生的位置?

header() 警告包含所有相关信息 定位问题原因:

警告:无法修改标头信息 - 标头已由 (输出开始于 /www/usr2345/htdocs/auth.php:52)在 /www/usr2345/htdocs/index.php 第 100 行

这里的“第 100 行”指的是 header() 调用失败的脚本。

括号内的“输出开始于”注释更重要。 它指定先前输出的来源。在这个例子中,它是auth.php线52。这就是你必须寻找过早输出的地方。

典型原因:

    打印,回显

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

    产生输出的函数包括

    print, echo, printf, vprintf trigger_error, ob_flush, ob_end_flush, var_dump, print_r readfile, passthru, flush, imagepng, imagejpeg

    以及用户定义的函数。

    原始 HTML 区域

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

    <!DOCTYPE html>
    <?php
        // Too late for headers already.
    

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

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

    &lt;?php 之前的空格用于“script.php line 1”警告

    如果警告是指输出内联1,那么它主要是 在开头的&lt;?php 标记前前导空格、文本或HTML。

     <?php
    # There's a SINGLE space/newline before <? - Which already seals it.
    

    附加的脚本或脚本部分也可能发生这种情况:

    ?>
    
    <?php
    

    PHP 实际上在关闭标签之后吃掉了一个 single 换行符。但它不会 补偿移入此类间隙的多个换行符或制表符或空格。

    UTF-8 物料清单

    单独的换行符和空格可能是个问题。但也有“隐形” 可能导致这种情况的字符序列。最著名的是 UTF-8 BOM (Byte-Order-Mark) 大多数文本编辑器不显示。它是字节序列EF BB BF,对于 UTF-8 编码的文档是可选的和冗余的。然而,PHP 必须将其视为原始输出。它可能在输出中显示为字符 (如果客户端将文档解释为 Latin-1)或类似的“垃圾”。

    尤其是图形编辑器和基于 Java 的 IDE 在场。他们没有将其可视化(Unicode 标准要求)。 然而,大多数程序员和控制台编辑器都会这样做:

    很容易尽早发现问题。其他编辑可能会识别 它出现在文件/设置菜单中(Windows 上的 Notepad++ 可以识别和 remedy the problem), 检查 BOM 存在的另一种选择是使用 hexeditor。 在 *nix 系统上hexdump 通常可用, 如果不是简化审核这些问题和其他问题的图形变体:

    一个简单的解决方法是将文本编辑器设置为将文件保存为“UTF-8(无 BOM)” 或类似这样的命名法。新手通常会求助于创建新文件,然后将以前的代码复制并粘贴回去。

    更正实用程序

    还有用于检查和重写文本文件的自动化工具 (sed/awkrecode)。 对于 PHP,特别是有 phptags tag tidier。 它将关闭和打开标签重写为长格式和短格式,但也很容易 修复前导和尾随空格、Unicode 和 UTF-x BOM 问题:

    phptags  --whitespace  *.php
    

    在整个包含或项目目录中使用是安全的。

    ?&gt; 后面的空格

    如果错误源在后面提到 closing ?&gt; 然后这是一些空白或原始文本被写出的地方。 PHP 结束标记此时不会终止脚本执行。其后的任何文本/空格字符都将作为页面内容写出 还是。

    通常建议,特别是对于新手,尾随 ?&gt; PHP 应省略关闭标签。这避开这些案例的一小部分。 (通常include()d 脚本是罪魁祸首。)

    提到的错误源为“第 0 行未知”

    如果没有错误源,通常是 PHP 扩展或 php.ini 设置 被具体化了。

    偶尔是gzip 流编码设置 or the ob_gzhandler。 但它也可以是任何双重加载的extension= 模块 生成隐式 PHP 启动/警告消息。

    前面的错误消息

    如果另一个 PHP 语句或表达式导致警告消息或 注意被打印出来,也算过早输出。

    在这种情况下,您需要避免错误, 延迟语句执行,或使用例如抑制消息 isset()@() - 当任何一个都不会妨碍以后的调试时。

没有错误信息

如果您根据php.ini 禁用了error_reportingdisplay_errors, 然后不会出现任何警告。但是忽略错误不会解决问题 离开。过早输出后仍然无法发送标头。

所以当header("Location: ...") 重定向静默失败时,它非常 建议探测警告。使用两个简单的命令重新启用它们 在调用脚本之上:

error_reporting(E_ALL);
ini_set("display_errors", 1);

或者set_error_handler("var_dump");,如果一切都失败了。

说到重定向标头,您应该经常使用类似的成语 这是最终代码路径:

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

最好是实用功能,打印用户消息 在header() 失败的情况下。

输出缓冲作为一种解决方法

PHP output buffering 是缓解此问题的一种解决方法。它通常可靠地工作,但不应该 替代适当的应用程序结构并将输出与控制分离 逻辑。它的实际目的是尽量减少到网络服务器的分块传输。

    output_buffering= 设置仍然可以提供帮助。 配置在php.ini 或通过.htaccess 甚至.user.ini on 现代 FPM/FastCGI 设置。 启用它将允许 PHP 缓冲输出,而不是立即将其传递给网络服务器。因此 PHP 可以聚合 HTTP 标头。

    同样可以拨打ob_start(); 在调用脚本之上。然而,由于多种原因,这不太可靠:

    即使&lt;?php ob_start(); ?&gt; 启动第一个脚本,空格或 BOM 之前可能会被洗牌,rendering it ineffective。

    它可以隐藏 HTML 输出的空白。但是一旦应用程序逻辑尝试发送二进制内容(例如生成的图像)​​, 缓冲的无关输出成为问题。 (需要ob_clean() 作为进一步的解决方法。)

    缓冲区的大小是有限的,并且在保留默认值时很容易溢出。 这也不是罕见的事情,difficult to track down 当它发生时。

因此,这两种方法都可能变得不可靠——尤其是在两者之间切换时 开发设置和/或生产服务器。这就是为什么输出缓冲是 被广泛认为只是一种拐杖/严格来说是一种解决方法。

另见basic usage example 在手册中,以及更多的利弊:

What is output buffering? Why use output buffering in PHP? Is using output buffering considered a bad practice? Use case for output buffering as the correct solution to "headers already sent"

但它在另一台服务器上工作!?

如果您之前没有收到标题警告,那么output buffering php.ini setting 已经改变。它可能在当前/新服务器上未配置。

检查headers_sent()

您可以随时使用headers_sent() 来探测是否 仍然可以...发送标头。这对有条件地打印很有用 信息或应用其他后备逻辑。

if (headers_sent()) 
    die("Redirect failed. Please click on this link: <a href=...>");

else
    exit(header("Location: /user.php"));

有用的后备解决方法是:

HTML &lt;meta&gt; 标签

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

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

或稍作延迟:

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

当使用超过 &lt;head&gt; 部分时,这会导致 HTML 无效。 大多数浏览器仍然接受它。

javascript 重定向

作为替代JavaScript redirect 可用于页面重定向:

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

虽然这通常比 &lt;meta&gt; 解决方法更符合 HTML, 它会导致对支持 JavaScript 的客户端的依赖。

然而,当真正的 HTTP header() 通话失败。理想情况下,您总是将其与用户友好的消息结合起来,并且 可点击的链接作为最后的手段。 (例如http_redirect() PECL 扩展确实如此。)

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

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

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

更多链接

Google 提供了lengthy list of similar discussions。 当然,many specific cases 也被 Stack Overflow 报道过。 WordPress 常见问题解答以通用方式解释了 How do I solve the Headers already sent warning problem?。 Adobe 社区:PHP development: why redirects don't work (headers already sent) Nucleus 常见问题解答:What does "page headers already sent" mean? 更详尽的解释之一是HTTP Headers and the PHP header() Function - A tutorial by NicholasSolutions(Internet 存档链接)。 它详细介绍了 HTTP,并提供了一些重写脚本的指南。

【讨论】:

常规 notepad.exe 也很棘手。我通常使用不添加 BOM 的 NetBeans,即使文件是这样编码的。稍后在记事本中编辑文件会使事情变得混乱,尤其是将 IIS 作为网络服务器。似乎 apache 丢弃了(无意添加的)BOM。 从 php 文件的末尾删除关闭 ?&gt; 通常是一种很好的做法,它也有助于最大限度地减少这些错误。不需要的空格不会出现在文件的末尾,您仍然可以稍后将标头添加到响应中。如果您使用输出缓冲,它也很方便,并且不希望在包含的文件生成的部分末尾看到添加的不需要的空白。 奇怪的事情,我把我的文件从 cPanel Linux Hosting 移到了 VPS。在它正常工作之前,但在这里它显示了这个错误。(我在标题之前有一些 html 代码)。为什么? @PeterSMcIntyre UTF8 BOM 大概(修复)/没有启用输出缓冲(不要依赖它)。 只是遗漏了一个细节,您可能需要添加。如果错误消息省略了该信息,$file = $line = null; headers_sent($file, $line); die("$file:$line"); 将准确告诉您发送标头的内容。不知道为什么,但有时似乎是这样。【参考方案2】:

在您发送 HTTP 标头(使用 setcookieheader)之前发送 anything 时会触发此错误消息。在 HTTP 标头之前输出内容的常见原因是:

意外的空格,通常在文件的开头或结尾,如下所示:

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

       为避免这种情况,只需省略结尾的?&gt; - 无论如何都不需要。

Byte order marks 在 php 文件的开头。使用十六进制编辑器检查您的 php 文件,看看是否是这种情况。它们应该以字节3F 3C 开头。您可以安全地从文件开头删除 BOM EF BB BF。 显式输出,例如调用echoprintfreadfilepassthru&lt;? 之前的代码等。 如果设置了display_errors php.ini 属性,则php 输出警告。 php 不会因为程序员的错误而崩溃,而是默默地修复错误并发出警告。虽然您可以修改 display_errors 或 error_reporting 配置,但您应该解决问题。 常见原因是访问数组的未定义元素(例如 $_POST['input'] 未使用 emptyisset 来测试是否设置了输入),或使用未定义常量而不是字符串文字(如 $_POST[input] ,请注意缺少的引号)。

打开output buffering 应该可以解决问题;调用ob_start 后的所有输出都缓存在内存中,直到您释放缓冲区,例如ob_end_flush

但是,虽然输出缓冲可以避免这些问题,但您应该真正确定应用程序在 HTTP 标头之前输出 HTTP 正文的原因。这就像在告诉来电者他打错号码之前,先打个电话,讨论一下你的日子和天气。

【讨论】:

【参考方案3】:

我之前多次遇到过这个错误,我敢肯定所有 PHP 程序员之前至少遇到过一次。

可能的解决方案 1

此错误可能是由文件开头之前或文件结尾之后的空格引起的。这些空格不应该在这里。

例如) 这里不应该有空格

   echo "your code here";

?>
THERE SHOULD BE NO BLANK SPACES HERE

检查与导致此错误的文件关联的所有文件。

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

可能的解决方案 2: 如果这不是你的情况,那么使用ob_start 输出缓冲:

<?php
  ob_start();

  // code 

 ob_end_flush();
?> 

这将打开输出缓冲,并在页面缓冲后创建标题。

【讨论】:

ob_start() 只是隐藏了问题;不要用它来解决这个特定的问题。 ob_start() 没有“隐藏”问题,它解决了问题。 我上传文件到服务器时遇到了这样的问题,它甚至支持 PHP5.3 使用 PHP 5.6 或更高版本的服务器 @jack 是的,我同意你的看法。理想的方法是在开始php标签&lt;?之前删除空格 解决了我的问题。【参考方案4】:

而不是下面的行

//header("Location:".ADMIN_URL."/index.php");

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

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

它肯定会解决您的问题。 我遇到了同样的问题,但我通过上面的方式写标题位置解决了。

【讨论】:

【参考方案5】:

你会的

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

在设置 cookie 之前,这是不允许的。您不能在标题之前发送任何输出,甚至是空行。

【讨论】:

【参考方案6】:

常见问题:

(复制自:source)

=====================

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

2) 删除&lt;?php 之前和?&gt; 标记之后的所有空白(或换行符)。

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 很重要? 如果设置了 php.ini 文件中的“output_buffering”,就可以有输出。我在我家的 Debian 系统上设置为 4096。我使用的服务器上的那个说它没有设置。【参考方案7】:

正是因为这一行:

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

在发送标头之前,您不应打印/回显任何内容。

【讨论】:

【参考方案8】:

一个简单的提示:脚本中的一个简单空格(或不可见的特殊字符),就在第一个 &lt;?php 标记之前,可能会导致这种情况! 尤其是当您在团队中工作并且有人使用“弱”的 IDE 或使用奇怪的文本编辑器处理文件时。

我见过这些东西;)

【讨论】:

【参考方案9】:

另一个不好的做法可能会引发这个尚未说明的问题。

查看这段代码sn-p:

<?php
include('a_important_file.php'); //really really really bad practise
header("Location:A location");
?>

一切都还好,对吧?

如果“a_important_file.php”是这样的:

<?php
//some php code 
//another line of php code
//no line above is generating any output
?>

 ----------This is the end of the an_important_file-------------------

这行不通?为什么?因为已经生成了一个新行。

现在,虽然这不是一个常见的场景,但如果您使用的是 MVC 框架,该框架会在将内容移交给控制器之前加载大量文件,该怎么办?这种情况并不少见。做好准备。

来自PSR-2 2.2 :


所有 PHP 文件必须使用 Unix LF (linefeed) line ending 所有 PHP 文件必须以 single blank line 结尾。 结束 ?> 标记必须是 omitted,来自包含 only php 的文件

相信我,遵循这些标准可以为您节省大量时间:)

【讨论】:

根据几个标准(例如 Zend),在任何情况下,您都不应该将结束 ?&gt; 标记放在任何文件中 我无法在 Windows 环境中重现它,因为它可以使用任何组合(添加结束标签、空格、按 Enter 键等)。似乎这个问题主要发生在 Linux 环境中。 @JuniorM 它应该是可重现的。你能分享一下你在 gist 或类似的东西上试验的代码吗? 我在 Windows 7 上,安装了最新的 Wamp。我认为这个错误与行尾的隐藏字符有关。我的 Wordpress 的 shortcodes.php 是导致问题的原因。我在这个文件中添加了一个简单的函数,它开始触发这个“headers sent”错误。我已经将我的 shortcodes.php 与 wordpress' 进行了比较,除了 CR LF (典型的 Windows 行尾)之外,一切正常。我通过从具有LF(Linux 行尾)而不是CR LF 的Wordpress repo 下载原始文件来解决它,并且我还将我的函数移动到主题的functions.php。基于:bit.ly/1Gh6mzN @Sahib,请注意我仍然无法重现此答案中所述的内容。答案对于 Linux 环境来说完全没问题。我已经测试了?&gt;&lt;?php之间的空白,删除和添加单个空白行,添加和省略结束标记?&gt;。在 Windows+ Wamp 中,所有这些组合都可以正常工作。奇怪...【参考方案10】:

有时当开发进程同时具有 WIN 工作站和 LINUX 系统(托管)并且在代码中您在相关行之前看不到任何输出时,可能是文件的格式和缺少 Unix LF(换行) 行结束。

为了快速解决这个问题,我们通常会做的是重命名文件并在 LINUX 系统上创建一个新文件而不是重命名的文件,然后将内容复制到该文件中。很多时候这解决了这个问题,因为在 WIN 中创建的一些文件一旦移动到主机就会导致这个问题。

对于我们通过 FTP 管理的网站来说,此修复是一个简单的修复,有时可以为我们的新团队成员节省一些时间。

【讨论】:

【参考方案11】:

通常当我们在回显或打印后发送标题时会出现此错误。如果此错误出现在特定页面上,请确保该页面没有回显任何内容,然后再调用 start_session()

不可预测的错误示例:

 <?php //a white-space before <?php also send for output and arise error
session_start();
session_regenerate_id();

//your page content

再举一个例子:

<?php
includes 'functions.php';
?> <!-- This new line will also arise error -->
<?php
session_start();
session_regenerate_id();

//your page content

结论:在调用session_start()header() 函数之前不要输出任何字符,即使是空格或换行符也不行

【讨论】:

以上是关于如何修复 PHP 中的“标头已发送”错误的主要内容,如果未能解决你的问题,请参考以下文章

如何修复 PHP 中的“标头已发送”错误

如何修复 PHP 中的“标头已发送”错误

WAMP localhost 中的错误“标头已发送”

基于 header(location) 命令重定向 php 页面不起作用;标头已发送错误[重复]

PHP标头已发送[重复]

如何修复wordpress中的php致命错误和http错误500