发送标头之前没有输出!
Functions that send/modify HTTP headers must be invoked before any output is made.
summary ⇊
Otherwise the call fails:
警告:无法修改标头信息 - 标头已发送(输出从 script:line 开始)
修改 HTTP 标头的一些函数是:
-
header / header_remove
-
session_start / session_regenerate_id
-
setcookie / setrawcookie
输出可以是:
-
无意的:
- 前面有空格
<?php
或之后?>
- The UTF-8 字节顺序标记具体来说
- 以前的错误消息或通知
-
故意的:
-
print
, echo
和其他产生输出的函数
- Raw
<html>
之前的章节<?php
code.
为什么会发生这种情况?
要理解为什么必须在输出之前发送标头,这是必要的
看看一个典型的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>
页面/输出始终follows标题。 PHP 必须通过
首先将标头发送到网络服务器。它只能这样做一次。
双换行之后就再也不能修改它们了。
当 PHP 收到第一个输出时 (print
, echo
, <html>
) 它会flush所有收集的标头。之后它可以发送所有输出
它想要。但发送更多的 HTTP 标头是不可能的。
如何找出过早输出发生的位置?
The header()
警告包含所有相关信息
定位问题原因:
警告:无法修改标头信息 - 标头已发送(输出开始于/www/usr2345/htdocs/auth.php:52) 在
/www/usr2345/htdocs/index.php 第 100 行
这里的“第 100 行”指的是脚本header()
调用 failed.
The "输出开始于括号内的注释更有意义。
它表示先前输出的来源。在这个例子中,它是auth.php
and line 52
。这就是你必须寻找过早输出的地方。
典型原因:
-
打印、回显
有意输出print
and echo
语句将终止发送 HTTP 标头的机会。必须重组应用程序流程以避免这种情况。使用功能和模板方案。确保header()
发生呼叫before消息
都写出来了。
产生输出的函数包括
-
print
, echo
, printf
, vprintf
-
trigger_error
, ob_flush
, ob_end_flush
, var_dump
, print_r
-
readfile
, passthru
, flush
, imagepng
, imagejpeg
其中以及用户定义的函数。
-
原始 HTML 区域
中未解析的 HTML 部分.php
文件也是直接输出。
将触发的脚本条件header()
来电必须注明
前any raw <html>
blocks.
<!DOCTYPE html>
<?php
// Too late for headers already.
使用模板方案将处理与输出逻辑分开。
- 将表单处理代码放置在脚本之上。
- 使用临时字符串变量来延迟消息。
- 实际的输出逻辑和混合的 HTML 输出应该放在最后。
-
前面有空格<?php
对于“脚本.phpline 1” 警告
如果警告涉及内联输出1
,那么主要是
领导空白、开头之前的文本或 HTML<?php
token.
<?php
# There's a SINGLE space/newline before <? - Which already seals it.
同样,附加脚本或脚本部分也可能发生这种情况:
?>
<?php
PHP实际上吃掉了一个single关闭标签后换行。但它不会
补偿移入此类间隙的多个换行符或制表符或空格。
-
UTF-8 BOM
仅换行符和空格就可能是一个问题。但也有“看不见的”
可能导致这种情况的字符序列。最著名的是UTF-8 BOM(字节顺序标记)大多数文本编辑器不显示它。这是字节序列EF BB BF
,对于 UTF-8 编码的文档来说,它是可选且多余的。然而 PHP 必须将其视为原始输出。它可能会显示为字符
输出中(如果客户端将文档解释为 Latin-1)或类似的“垃圾”。
特别是图形编辑器和基于 Java 的 IDE 忽略了它
在场。他们没有将其可视化(Unicode 标准规定)。
然而,大多数程序员和控制台编辑器都会:
这样很容易尽早发现问题。其他编辑可能会识别
它存在于文件/设置菜单中(Windows 上的 Notepad++ 可以识别并解决问题),
检查 BOM 存在的另一种选择是采用十六进制编辑器。
在 *nix 系统上hexdump通常可用,
如果不是简化审核这些问题和其他问题的图形变体:
一个简单的解决方法是将文本编辑器设置为将文件保存为“UTF-8(无 BOM)”
或类似于这样的命名法。通常,新手会求助于创建新文件,然后将以前的代码复制并粘贴回来。
Correction utilities
还有自动化工具来检查和重写文本文件
(sed/awk or recode
)。
对于 PHP 特别是phptags标签整理器。
它将关闭和打开标签重写为长和短形式,而且也很容易
修复了前导和尾随空格、Unicode 和 UTF-x BOM 问题:
phptags --whitespace *.php
在整个包含或项目目录上使用是安全的。
-
后面有空格?>
如果后面提到了错误源closing ?>那么这就是一些空白或原始文本被写出的地方。
PHP 结束标记此时不会终止脚本执行。其后的任何文本/空格字符都将作为页面内容写出
仍然。
人们普遍建议,特别是对于新手来说,尾随?>
PHP
应省略关闭标签。这eschews这些案例中的一小部分。
(很常见include()d
脚本是罪魁祸首。)
-
错误源提到为“Unknown on line 0”
如果没有错误源,通常是 PHP 扩展或 php.ini 设置
是具体化的。
- 有时候是这样的
gzip
码流编码设置or the ob_gzhandler.
- 但它也可以是任何双负载的
extension=
模块
生成隐式 PHP 启动/警告消息。
-
前面的错误消息
如果另一个 PHP 语句或表达式导致警告消息或
注意被打印出来,这也算作过早输出。
在这种情况下,您需要避免错误,
延迟语句执行,或使用例如抑制消息isset() or @()-
当任何一个都不妨碍以后的调试时。
无错误信息
如果你有error_reporting
or display_errors
禁用每php.ini
,
那么就不会出现任何警告。但忽略错误并不能解决问题
离开。过早输出后仍然无法发送标头。
So when header("Location: ...")
重定向默默地失败了,这非常
建议探测警告。使用两个简单的命令重新启用它们
在调用脚本之上:
error_reporting(E_ALL);
ini_set("display_errors", 1);
Or set_error_handler("var_dump");
如果一切都失败了。
说到重定向标头,您应该经常使用这样的习惯用法
这对于最终的代码路径:
exit(header("Location: /finished.html"));
最好是一个打印用户消息的实用函数
的情况下header()
失败。
输出缓冲作为解决方法
PHPs 输出缓冲是缓解此问题的解决方法。它通常工作可靠,但不应该
替代正确的应用程序结构并将输出与控制分开
逻辑。其实际目的是最大限度地减少到网络服务器的分块传输。
-
The output_buffering=不过,设置还是有帮助的。
将其配置在php.ini
or via .htaccess甚至.user.ini在
现代 FPM/FastCGI 设置。
启用它将允许 PHP 缓冲输出,而不是立即将其传递到网络服务器。 PHP 因此可以聚合 HTTP 标头。
-
它也可以通过调用来参与ob_start();在调用脚本之上。然而,由于多种原因,该方法不太可靠:
-
Even if <?php ob_start(); ?>
开始第一个脚本、空格或
BOM之前可能会被洗牌,使其无效.
-
它可以隐藏 HTML 输出的空白。但是一旦应用程序逻辑尝试发送二进制内容(例如生成的图像),
缓冲的无关输出成为一个问题。 (需要ob_clean()
作为进一步的解决方法。)
-
缓冲区的大小有限,如果保留默认值,很容易溢出。
而且这种情况也并不少见,难以追踪当它发生时。
因此,这两种方法都可能变得不可靠 - 特别是在两者之间切换时
开发设置和/或生产服务器。这就是为什么输出缓冲是
广泛认为只是一个拐杖/严格来说是一种解决方法。
另请参阅基本用法示例在手册中,以及更多优点和缺点:
- PHP 中的输出缓冲是什么?
- 为什么在 PHP 中使用输出缓冲?
- 使用输出缓冲是否被认为是一种不好的做法?
- 输出缓冲的用例作为“标头已发送”的正确解决方案
但在另一台服务器上却能正常工作!?
如果您之前没有收到标题警告,那么输出缓冲
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>
tag
如果您的应用程序在结构上很难修复,那么一个简单的(但
有点不专业)允许重定向的方法是注入 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>
虽然这通常比<meta>
解决方法,
它会导致对支持 JavaScript 的客户端的依赖。
然而,当真正的 HTTP header() 时,这两种方法都可以接受
呼叫失败。理想情况下,您总是将其与用户友好的消息结合起来,
作为最后手段的可点击链接。 (例如,这就是http_重定向()PECL 扩展可以。)
Why setcookie()
and session_start()
也受到影响
Both setcookie()
and session_start()
需要发送一个Set-Cookie:
HTTP 标头。
因此,适用相同的条件,并且将生成类似的错误消息
对于过早输出的情况。
(当然,它们还受到浏览器中禁用cookie的影响
甚至代理问题。会话功能显然也依赖于免费
磁盘空间和其他 php.ini 设置等)
更多链接
- 谷歌提供了一个类似讨论的冗长列表.
- 而且当然许多具体案例Stack Overflow 上也有介绍。
- WordPress 常见问题解释如何解决标头已发送警告问题?以一般的方式。
- Adobe 社区:PHP 开发:为什么重定向不起作用(标头已发送)
- 核常见问题解答:“页眉已发送”是什么意思?
- 更彻底的解释之一是HTTP 标头和 PHP header() 函数 - NicholasSolutions 提供的教程(互联网档案链接)。
它详细介绍了 HTTP,并提供了一些重写脚本的指南。