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

新手上路,请多包涵

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

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

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

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

原文由 Moses89 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 593
2 个回答

发送标头之前没有输出!

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

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

一些修改 HTTP 标头的函数是:

输出可以是:

  • 无意:

  • 故意的:

    • print , echo 和其他产生输出的函数
    • 原始 <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<html> )时,它将 刷新 所有收集的标头。之后它可以发送它想要的所有输出。但是那时发送更多的 HTTP 标头是不可能的。

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

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

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

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

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

典型原因:

  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 输出应该在最后。
  1. ### <?php 之前的空格用于“script.php line 1 ”警告

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

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

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

    ?>

   <?php

PHP 实际上在关闭标签后吃掉了 _一个_ 换行符。但它不会补偿多个换行符或制表符或空格转移到此类间隙中。
  1. ### UTF-8 物料清单

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

尤其是图形编辑器和基于 Java 的 IDE 并没有注意到它的存在。他们没有将其可视化(Unicode 标准要求)。然而,大多数程序员和控制台编辑器会: joes 编辑器显示 UTF-8 BOM 占位符,MC 编辑器显示一个点 很容易在早期发现问题。其他编辑器可能会在文件/设置菜单中识别它的存在(Windows 上的 Notepad++ 可以识别和 解决问题),检查 BOM 存在的另一个选项是使用 hexeditor 。在 *nix 系统上 hexdump 通常可用,如果不是简化审计这些和其他问题的图形变体: 显示 utf-8 bom 的 beav hexeditor 一个简单的解决方法是将文本编辑器设置为将文件保存为“UTF-8(无 BOM)”或类似于此类命名法。通常,新手会求助于创建新文件,然后将以前的代码复制并粘贴回去。

### 校正实用程序

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

    phptags  --whitespace  *.php

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

  1. ### ?>

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

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

  1. ### 错误源被提及为“第 0 行未知”

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

  • 有时是 gzip 流编码设置 ob_gzhandler
  • 但它也可能是任何双重加载的 extension= 模块生成隐式 PHP 启动/警告消息。
  1. ### 前面的错误消息

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

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

没有错误信息

如果您根据 display_errors 禁用了 error_reporting 或 --- 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 输出缓冲 是缓解此问题的一种解决方法。它通常可靠地工作,但不应替代适当的应用程序结构和将输出与控制逻辑分离。它的实际目的是尽量减少到网络服务器的分块传输。

  1. output_buffering= 设置仍然可以提供帮助。在现代 FPM/FastCGI 设置中,在 php.ini 或通过 .htaccess 甚至 .user.ini 配置它。

启用它将允许 PHP 缓冲输出,而不是立即将其传递给网络服务器。因此 PHP 可以聚合 HTTP 标头。

  1. 它也可以在调用脚本顶部调用 ob_start(); 。然而,由于多种原因,它不太可靠:

    • 即使 <?php ob_start(); ?> 启动第一个脚本,空白或 BOM 可能会在之前被打乱, 使其无效

    • 它可以隐藏 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"));
}

有用的后备解决方法是:

  • ### 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 设置等。)

更多链接

原文由 mario 发布,翻译遵循 CC BY-SA 4.0 许可协议

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

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

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

原文由 Lupin 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏