一尘不染

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

php

运行脚本时,出现如下错误:

警告:不能更改头信息-已经(发送了头输出在/some/file.php:12开始)在/some/file.php上线23

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

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


阅读 315

收藏
2020-05-26

共1个答案

一尘不染

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

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

修改HTTP标头的一些功能是:

  • header / header_remove
  • session_start / session_regenerate_id
  • setcookie / setrawcookie

输出可以是:

  • 无意间:

  • 空白之前<?php或之后?>

  • 在UTF-8字节顺序标记明确
  • 先前的错误消息或通知

  • 故意的:

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

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

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

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

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

括号内的“ 输出始于 ”注释更为重要。它表示先前输出的来源。在此示例中,为auth.php 和line52。那是您必须寻找过早输出的地方。

典型原因:

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

产生输出的功能包括

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

以及用户定义的功能。

  1. 原始HTML区域
    .php文件中未解析的HTML部分也可以直接输出。header()必须在任何原始<html>块之前记录将触发调用的脚本条件。
<!DOCTYPE html>
<?php
    // Too late for headers already.

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

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

  • <?php“ script.php 第1行 ”警告之前的空白
    如果警告指的是line中的输出1,则在打开令牌之前,它主要是前导空格,文本或HTML <?php。

<?php

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

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

?>

<?php

PHP实际上吃了一个单一的关闭标签后断行。但这不会补偿多个换行符或制表符或空格之间的空白。

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

特别是图形编辑器和基于Java的IDE忽略了它的存在。他们没有可视化它(受Unicode标准约束)。但是,大多数程序员和控制台编辑器都这样做:

joes编辑器显示UTF-8 BOM占位符,MC编辑器显示一个点

尽早发现问题很容易。其他编辑器可以在文件/设置菜单中识别其存在(Windows上的Notepad ++可以识别和 纠正问题)。检查BOM表存在的另一种方法是使用hexeditor。hexdump通常在* nix系统上可用,如果没有图形版本,则可以简化审核这些问题和其他问题:

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

还有自动工具可以检查和重写文本文件(sed/awkrecode)。专门针对PHP,有phptags标记tidier。它将关闭和打开的标签改写为长号和短号形式,而且还可以轻松解决前导和尾随空格,UnicodeUTF-x BOM的问题:

phptags  --whitespace  *.php

在整个包含目录或项目目录上使用都是理智的。

  1. 之后的空白 ?>
    如果在关闭?> 后提到错误源, 那么这是写出一些空白或原始文本的地方。此时,PHP结束标记不会终止脚本执行。之后的任何文本/空格字符都将作为页面内容写出。

通常建议,特别是对新手来说,?>应该省略尾随的PHP关闭标记。这避免了这些情况的一小部分。(include()d罪魁祸首通常是剧本。)

  1. 错误源提到为“第0行未知”
    如果没有具体的错误源,通常是PHP扩展名或php.ini设置。

  2. 有时是gzip流编码设置 或ob_gzhandler

  3. 但是也可能是任何双重加载的extension=模块生成隐式的PHP启动/警告消息。

  4. 前面的错误消息

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

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

没有错误讯息
如果您已根据设置error_reportingdisplay_errors禁用php.ini,则不会显示警告。但是,忽略错误并不能解决问题。标头过早输出后仍无法发送标头。

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

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

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

说到重定向头,对于最终的代码路径,应该经常使用这样的惯用法:

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

最好甚至还有一个实用程序功能,在header()出现故障时会打印用户消息。

解决方法是输出缓冲
PHP的输出缓冲 是缓解此问题的一种解决方法。它通常可以可靠地运行,但不能替代适当的应用程序结构并将输出与控制逻辑分开。其实际目的是最大程度地减少到Web服务器的分块传输。

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

  2. 它`同样可以与ob_start(); 调用脚本顶部的调用联系在一起。但是,由于多种原因,它的可靠性较差:

  3. 即使<?php ob_start(); ?>`启动第一个脚本,空格或BOM之前也可能会被改组,使其无效。

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

  5. 缓冲区的大小是有限的,当保留默认值时,缓冲区很容易溢出。而且这也不是少见的 事情,发生时很难追踪。

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

  • 但是它在另一台服务器上工作了!

如果您之前未收到标头警告,则输出缓冲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 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设置等。)

2020-05-26