发送标头前无输出!
必须调用发送/修改 HTTP 标头的函数在进行任何输出之前。
summary ⇊
否则调用失败:
警告:无法修改标头信息 - 标头已发送(输出开始于 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 接收到第一个输出(print、echo、<html>)时,它将
flush 所有收集的标头。之后它可以发送所有输出
它想要。但是,发送更多的 HTTP 标头是不可能的。
如何找出过早输出发生的位置?
header() 警告包含所有相关信息
定位问题原因:
警告:无法修改标头信息 - 标头已由
(输出开始于 /www/usr2345/htdocs/auth.php:52)在
/www/usr2345/htdocs/index.php 第 100 行
这里的“第 100 行”指的是 header() 调用失败的脚本。
括号内的“输出开始于”注释更重要。
它指定先前输出的来源。在这个例子中,它是auth.php
和线52。这就是你必须寻找过早输出的地方。
典型原因:
-
打印,回显
print 和 echo 语句的有意输出将终止发送 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() 调用的脚本条件
在任何原始<html>块之前。
<!DOCTYPE html>
<?php
// Too late for headers already.
使用模板方案将处理与输出逻辑分开。
- 将表单处理代码置于脚本之上。
- 使用临时字符串变量来延迟消息。
- 实际的输出逻辑和混合的 HTML 输出应该在最后。
-
<?php 之前的空格用于“script.php line 1”警告
如果警告是指输出内联1,那么它主要是
在开头的<?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/awk 或 recode)。
对于 PHP,特别是有 phptags tag tidier。
它将关闭和打开标签重写为长格式和短格式,但也很容易
修复前导和尾随空格、Unicode 和 UTF-x BOM 问题:
phptags --whitespace *.php
在整个包含或项目目录中使用是安全的。
-
?> 后面的空格
如果错误源在后面提到
closing ?>
然后这是一些空白或原始文本被写出的地方。
PHP 结束标记此时不会终止脚本执行。其后的任何文本/空格字符都将作为页面内容写出
还是。
通常建议,特别是对于新手,尾随 ?> PHP
应省略关闭标签。这避开这些案例的一小部分。
(通常include()d 脚本是罪魁祸首。)
-
提到的错误源为“第 0 行未知”
如果没有错误源,通常是 PHP 扩展或 php.ini 设置
被具体化了。
-
前面的错误消息
如果另一个 PHP 语句或表达式导致警告消息或
注意被打印出来,也算过早输出。
在这种情况下,您需要避免错误,
延迟语句执行,或使用例如抑制消息
isset() 或 @() -
当任何一个都不会妨碍以后的调试时。
没有错误信息
如果您根据php.ini 禁用了error_reporting 或display_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();
在调用脚本之上。然而,由于多种原因,这不太可靠:
-
即使<?php ob_start(); ?> 启动第一个脚本,空格或
BOM 之前可能会被洗牌,rendering it ineffective。
-
它可以隐藏 HTML 输出的空白。但是一旦应用程序逻辑尝试发送二进制内容(例如生成的图像),
缓冲的无关输出成为问题。 (需要ob_clean()
作为进一步的解决方法。)
-
缓冲区的大小是有限的,并且在保留默认值时很容易溢出。
这也不是罕见的事情,difficult to track down
当它发生时。
因此,这两种方法都可能变得不可靠——尤其是在两者之间切换时
开发设置和/或生产服务器。这就是为什么输出缓冲是
被广泛认为只是一种拐杖/严格来说是一种解决方法。
另见basic usage example
在手册中,以及更多的利弊:
但它在另一台服务器上工作!?
如果您之前没有收到标题警告,那么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 <meta> 标签
如果您的应用程序在结构上难以修复,那么一个简单(但
有点不专业)允许重定向的方法是注入 HTML
<meta> 标签。可以通过以下方式实现重定向:
<meta http-equiv="Location" content="http://example.com/">
或稍作延迟:
<meta http-equiv="Refresh" content="2; url=../target.html">
当使用超过 <head> 部分时,这会导致 HTML 无效。
大多数浏览器仍然接受它。
-
JavaScript 重定向
作为替代JavaScript redirect
可用于页面重定向:
<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 设置等)
更多链接