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

2022-08-30 05:46:23

运行脚本时,我收到几个这样的错误:

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

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

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


答案 1

发送标头之前没有输出!

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

警告:无法修改标头信息 - 标头已发送(输出在脚本:行处启动))

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

输出可以是:

  • 故意:

    • print和其他产生输出的功能echo
    • 原始部分在代码之前。<html><?php

为什么会发生这种情况?

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

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 收到第一个输出 (, , ) 时,它将刷新所有收集的标头。之后,它可以发送所需的所有输出。但是,发送更多的HTTP标头是不可能的。printecho<html>

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

该警告包含查找问题原因的所有相关信息:header()

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

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

括号内的“输出开始于”注释更为重要。它指定了先前输出的源。在此示例中,它是 和第 52 行。这就是你必须寻找过早输出的地方。auth.php

典型原因:

  1. 打印,回声

    和 语句的有意输出将终止发送 HTTP 标头的机会。必须对应用程序流进行重组以避免这种情况。使用函数和模板方案。确保在写出消息之前发生呼叫。printechoheader()

    产生输出的函数包括

    • print, , ,echoprintfvprintf
    • trigger_error, , , ,ob_flushob_end_flushvar_dumpprint_r
    • readfile, , , ,passthruflushimagepngimagejpeg


    其中包括用户定义的函数。

  2. 原始 HTML 区域

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

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

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

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

  3. 前面的空格表示“脚本.php第 1 行”警告<?php

    如果警告指的是输出内联 1,则它主要是在打开标记之前的前导空格、文本或 HTML。<?php

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

    同样,对于追加的脚本或脚本部分,也会发生这种情况:

    ?>
    
    <?php
    

    PHP实际上在关闭标签后会吃掉个换行符。但它不会补偿多个换行符或制表符或空格移动到这样的间隙。

  4. UTF-8 物料清单

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

    特别是图形编辑器和基于Java的IDE忘记了它的存在。他们不会将其可视化(受Unicode标准的约束)。然而,大多数程序员和控制台编辑器都这样做:

    显示 UTF-8 BOM 占位符的编辑器,以及显示点的 MC 编辑器

    在那里,很容易在早期发现问题。其他编辑器可能会在文件/设置菜单中识别它的存在(Windows上的Notepad ++可以识别并解决问题),检查BOM存在的另一个选项是诉诸十六进制编辑器。在*nix系统上,通常可以使用十六转储,如果不是图形变体,可以简化对这些和其他问题的审计:

    显示 utf-8 bom 的 beav 十六进制编辑器

    一个简单的解决方法是将文本编辑器设置为将文件另存为“UTF-8(无 BOM)”或类似的命名法。通常,新手会诉诸于创建新文件,而只是复制并粘贴以前的代码。

    校正实用程序

    还有自动化工具可以检查和重写文本文件(sed/awk 或 )。特别是对于PHP,phptags标签更整洁。它将关闭和打开标记重写为长格式和短格式,但也可以轻松修复前导和尾随空格、Unicode 和 UTF-x BOM 问题:recode

    phptags  --whitespace  *.php
    

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

  5. 之后的空格?>

    如果错误源被提及为结束的后面 -> 那么这就是一些空格或原始文本被写出来的地方。此时,PHP 结束标记不会终止脚本执行。之后的任何文本/空格字符仍将作为页面内容写出。

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

  6. 提及为“第 0 行未知”的错误源

    它通常是 PHP 扩展或 php.ini设置(如果未具体化错误源)。

    • 有时是流编码设置ob_gzhandlergzip
    • 但它也可以是任何生成隐式PHP启动/警告消息的双加载模块。extension=

  7. 前面的错误消息

    如果另一个 PHP 语句或表达式导致打印出警告消息或通知,这也算作过早输出。

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

无错误消息

如果您已禁用 或 禁用了 per ,则不会显示任何警告。但是忽略错误不会使问题消失。过早输出后仍无法发送标头。error_reportingdisplay_errorsphp.ini

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

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

或者,如果其他所有方法都失败了。set_error_handler("var_dump");

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

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

最好甚至是实用程序功能,它在发生故障时打印用户消息。header()

输出缓冲作为解决方法

PHPs 输出缓冲是缓解此问题的解决方法。它通常工作可靠,但不应取代正确的应用程序结构和将输出与控制逻辑分离。它的实际目的是最大限度地减少到Web服务器的分块传输。

  1. 不过,output_buffering= 设置可能会有所帮助。在php.ini或通过.htaccess甚至.user.ini现代FPM / FastCGI设置中配置它。
    启用它将允许PHP缓冲输出,而不是立即将其传递到Web服务器。因此,PHP可以聚合HTTP标头。

  2. 它同样可以通过调用调用脚本顶部的ob_start();来参与。然而,由于多种原因,它不太可靠:

    • 即使启动第一个脚本,空格或 BOM 也可能在之前被洗牌,使其无效<?php ob_start(); ?>

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

    • 缓冲区的大小有限,当保留默认值时,很容易溢出。这种情况也不罕见,很难在发生时追踪

因此,这两种方法都可能变得不可靠 - 特别是在开发设置和/或生产服务器之间切换时。这就是为什么输出缓冲被广泛认为只是一个拐杖/严格意义上的解决方法。

另请参阅手册中的基本用法示例,以及更多优缺点:

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

如果您之前没有收到标头警告,则输出缓冲 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"));
}

有用的回退解决方法是:

  • 标记<meta>

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

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

    或者有短暂的延迟:

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

    这会导致在超过该部分时使用时无效的 HTML。大多数浏览器仍然接受它。<head>

  • JavaScript 重定向

    作为替代方案,JavaScript重定向可用于页面重定向:

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

    虽然这通常比变通办法更符合 HTML 标准,但它会依赖于支持 JavaScript 的客户端。<meta>

但是,这两种方法都会在真正的 HTTP header() 调用失败时进行可接受的回退。理想情况下,您始终将其与用户友好的消息和可点击的链接相结合,作为最后的手段。(例如,http_redirect() PECL 扩展的作用。

为什么以及是否受到影响setcookie()session_start()

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

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

更多链接


答案 2

如果在发送 HTTP 标头(使用 setcookie 或标头)之前发送任何内容,则会触发此错误消息。在 HTTP 标头之前输出某些内容的常见原因是:

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

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

为了避免这种情况,只需省略关闭 - 无论如何都不需要。?>

  • php 文件开头的字节顺序标记。使用十六进制编辑器检查您的php文件,以确定情况是否如此。它们应以字节开头。您可以安全地从文件开头删除 BOM。3F 3CEF BB BF
  • 显式输出,例如调用 、 、 、 、 前面的代码等。echoprintfreadfilepassthru<?
  • 如果设置了 php.ini 属性,则 php display_errors输出的警告。php不会因程序员错误而崩溃,而是静默地修复错误并发出警告。虽然您可以修改display_errorserror_reporting配置,但您应该解决问题。
    常见原因是访问数组的未定义元素(例如,不使用isset 来测试输入是否已设置),或者使用未定义的常量而不是字符串文本(如 中所示,请注意缺少的引号)。$_POST['input']$_POST[input]

打开输出缓冲应该使问题消失;调用ob_start后的所有输出都将缓冲在内存中,直到您释放缓冲区,例如使用ob_end_flush

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


推荐