如何解决PHP中的“Headers already sent”错误

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

警告:不能修改标题信息 – 第23行的 /some/file.php中已经由( 在/some/file.php:12开始的输出 )发送的标题

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

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

发送标题之前没有输出!

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

警告:不能修改标题信息 – 已经发送的标题(输出在脚本:行开始)

一些修改HTTP头的函数是:

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

输出可以是:

  • 意外:

    • <?php或之后?>之前留有空格
    • UTF-8字节顺序标记具体
    • 先前的错误消息或通知
  • 故意的:

    • printecho等function产生输出
    • Raw <html>段之前的<?php代码。

为什么会发生?

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

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

你怎么知道早产的地方?

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

警告:不能修改标题信息 – 已在 / www / usr2345 / htdocs / auth.php:52 开始的输出)发送的标题在/www/usr2345/htdocs/index.php在线100

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

括号内的“ 输出开始于 ”是更重要的。 它指定了以前输出的来源。 在这个例子中它是auth.php52 。 那就是你不得不寻找早产的地方。

典型原因:

  1. 打印,回声

    printecho语句的意图输出将终止发送HTTP头的机会。 应用程序stream程必须重构以避免这种情况。 使用function和模板scheme。 确保写出消息之前发生header()调用。

    产生输出的函数包括

    • printechoprintfvprintf
    • trigger_errorob_flushob_end_flushvar_dumpprint_r
    • readfilepassthruflushimagepngimagejpeg

    以及其他和用户定义的function。

  2. 原始HTML区域

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

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

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

    • 在脚本上放置表单处理代码。
    • 使用临时stringvariables来推迟消息。
    • 实际的输出逻辑和混合的HTML输出应该是最后一个。
  3. <?php for“script.php line 1 ”警告之前的空格

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

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

    同样,它可以发生在附加的脚本或脚本部分:

     ?> <?php 

    PHP实际上在closures标签之后吃了一个linebreak。 但是它不会补偿多个换行符或制表符或空格转移到这些空白处。

  4. UTF-8 BOM

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

    特别是graphics编辑器和基于Java的IDE不在意它的存在。 他们没有形象化(由Unicode标准承担)。 然而大多数程序员和控制台编辑却这样做:

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

    在那里很容易就可以及早发现问题。 其他编辑可以在文件/设置菜单中识别它的存在(Windows上的记事本++可以识别和解决问题 ),检查物料清单存在的另一个select是使用hexeditor 。 在* nix系统上,通常可以使用hexdump ,如果不是一个简化审计这些和其他问题的graphicsvariables:

    显示utf-8 bom的beav hexeditor

    一个简单的解决方法是设置文本编辑器将文件保存为“UTF-8(无BOM)”或类似的术语。 否则,新手往往会创build新的文件,只是复制并粘贴以前的代码。

    更正实用程序

    还有自动化工具来检查和重写文本文件( sed / awkrecode )。 对于PHP专门有phptags标签phptags 。 它将长时间和短时间的closures和打开标签重写,还可以轻松修复前后的空白,Unicode和UTF-x BOM问题:

     phptags --whitespace *.php 

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

  5. 空格之后?>

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

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

  6. 错误来源提到“在线0未知”

    如果没有错误来源,则通常是PHP扩展或php.ini设置。

    • 偶尔是gzipstream编码设置或者ob_gzhandler
    • 但它也可以是任何双重加载extension=模块生成一个隐式的PHP启动/警告消息。
  7. 先前的错误消息

    如果另一个PHP语句或expression式导致警告消息或通知被打印出来,那也算作过早的输出。

    在这种情况下,您需要避免错误,延迟语句执行,或者使用isset()@()抑制消息 – 当稍后不阻止debugging时。

没有错误信息

如果每个php.ini都禁用了error_reportingdisplay_errors ,则不会显示警告。 但是忽略错误不会使问题消失。 提前输出后仍然无法发送标题。

所以header("Location: ...")静静地redirect失败时,探测警告是非常明智的。 使用调用脚本上的两个简单命令重新启用它们:

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

或者set_error_handler("var_dump"); 如果一切都失败了。

说到redirect头文件,你应该经常使用这样的习惯用于最终的代码path:

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

最好甚至是一个效用函数,在header()失败的情况下打印用户消息。

输出缓冲作为解决方法

PHP 输出缓冲是解决这个问题的解决方法。 它经常可靠地工作,但是不能取代正确的应用程序结构并将输出与控制逻辑分开。 其实际目的是最大限度地减less到networking服务器的分块传输。

  1. output_buffering=设置仍然可以帮助。 在现在的FPM / FastCGI设置中,在php.ini中configuration它,或者通过.htaccess甚至.user.iniconfiguration它。
    启用它将允许PHP缓冲输出,而不是立即传递给networking服务器。 PHP因此可以聚合HTTP标头。

  2. 它也可以通过调用ob_start(); 在调用脚本上。 然而,由于多种原因,这是不太可靠的:

    • 即使<?php ob_start(); ?> <?php ob_start(); ?>开始第一个脚本,空白或BOM可能在之前被洗牌, 使其无效 。

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

    • 缓冲区的大小是有限的,并且在默认情况下可以轻松地溢出。 而且这也不是一个难得的事情,发生时很难追查 。

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

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

  • 什么是输出缓冲?
  • 为什么在PHP中使用输出缓冲?
  • 正在使用输出缓冲被认为是一个不好的做法?
  • 使用输出缓冲作为正确的解决scheme来“头已经发送”

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

如果以前没有收到标题警告,那么输出缓冲php.ini设置已经改变。 这可能在当前/新服务器上未configuration。

使用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>标签

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

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

    或者短暂的延迟:

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

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

  • JavaScriptredirect

    作为替代, JavaScriptredirect可以用于页面redirect:

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

    尽pipe这通常比<meta>解决方法更符合HTML标准,但是却依赖于支持JavaScript的客户端。

但是,当真正的HTTP头()调用失败时,两种方法都可以接受回退。 理想情况下,你总是把这个与用户友好的消息和可点击的链接结合起来作为最后的手段。 (这例如是http_redirect() PECL扩展所做的)。

为什么setcookie()session_start()也受到影响

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

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

更多链接

  • Google提供了类似讨论的冗长列表 。
  • 当然,Stack Overflow也涵盖了很多具体的案例 。
  • WordPress的常见问题解释如何解决已经发送警告问题的头部? 以通用的方式。
  • Adobe社区: PHP开发:为什么redirect不起作用(头文件已经发送)
  • Nucleus FAQ: “页面标题已经发送”是什么意思?
  • 其中一个更深入的解释是HTTP头和PHP头()函数 – NicholasSolutions (互联网档案链接)的教程 。 它详细介绍了HTTP,并提供了一些重写脚本的指导。

在发送HTTP头之前发送任何内容 (使用setcookieheader )时,会触发此错误消息。 在HTTP头之前输出内容的常见原因是:

  • 意外的空白,通常在文件的开头或结尾,像这样:

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

为了避免这种情况,只需要避免closures?> – 这不是必需的。

  • 字节顺序标记在一个PHP文件的开始。 用hex编辑器检查你的php文件,看看是否是这种情况。 他们应该从字节3F 3C 。 您可以安全地从文件的开头删除BOM EF BB BF
  • 显式输出,比如调用echoprintfreadfilepassthru ,之前的代码<? 等等
  • 如果设置了display_errors php.ini属性,则由php输出警告。 而不是崩溃的程序员的错误,PHP默默修复错误,并发出警告。 虽然您可以修改display_errors或error_reportingconfiguration,但您应该修复此问题。
    常见的原因是访问数组中未定义的元素(比如$_POST['input']而不使用empty或者isset来testinginput是否被设置),或者使用未定义的常量而不是string文本(如$_POST[input] ,注意缺less的引号)。

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

但是,尽pipe输出缓冲避免了这些问题,但是您应该确定为什么您的应用程序在HTTP标头之前输出HTTP主体。 这就像接电话,讨论你的一天和天气,然后告诉来电者他的电话号码是错误的。

我得到了这个错误很多次,我相信所有的PHP程序员至less有一次得到这个错误。 要解决这个错误,你可以根据你的问题级别来解决使用的解决scheme:

可能解决scheme1:

你可能在之前之后留下空格(在?>之后的文件末尾),

 THERE SHOULD BE NO BLANK SPACES HERE <?php echo "your code here"; ?> DO CHECK FOR BLANK SPACES HERE AS WELL; THIS LINE (blank line) SHOULD NOT EXIST. 

大多数情况下,这应该可以解决您的问题。请检查所有与您require文件相关的文件。

注意: 有时EDITOR(IDE)像gedit(一个默认的Linux编辑器)在保存文件上添加一个空白行。这不应该发生。 如果你使用的是Linux。 您可以使用VI编辑器在页面末尾的?>之后删除空格/行。

如果这不是你的情况,那么你可以使用ob_start来输出缓冲,如下所示:

可能的scheme2:

 <?php ob_start(); // code ob_end_flush(); ?> 

而不是下面的线

 //header("Location:".ADMIN_URL."/index.php"); 

 echo("<script>location.href = '".ADMIN_URL."/index.php?msg=$msg';</script>"); 

要么

 ?><script><?php echo("location.href = '".ADMIN_URL."/index.php?msg=$msg';");?></script><?php 

这一定会解决你的问题。 我面临同样的问题,但我通过以上述方式编写标题位置解决。

你做

 printf ("Hi %s,</br />", $name); 

在设置cookie之前,这是不允许的。 你不能在头部之前发送任何输出,甚至没有空白行。

这是因为这一行:

 printf ("Hi %s,</br />", $name); 

发送标题之前,不应该打印/回显任何内容。

一个简单的提示:脚本中的一个简单的空间(或不可见的特殊字符),就在第一个<?php标记之前,可以导致这个! 特别是当你在一个团队中工作,有人正在使用一个“弱”的IDE,或者用奇怪的文本编辑器搞乱了这些文件。

我看过这些东西;)

另一个不好的做法可能会引起这个尚未阐明的问题。

看到这段代码片段:

 <?php include('a_important_file.php'); //really really really bad practise header("Location:A location"); ?> 

事情没事吧?

如果“a_important_file.php”是这样的话:

 <?php //some php code //another line of php code //no line above is generating any output ?> ----------This is the end of the an_important_file------------------- 

这不行? 为什么呢?因为已经有了新的一行。

现在,虽然这不是一个常见的情况,如果你正在使用一个MVC框架,在切换之前将大量的文件加载到你的控制器上? 这并不罕见。 为此做好准备。

从PSR-2 2.2:


  • 所有的PHP文件都必须使用Unix LF (linefeed) line ending
  • 所有的PHP文件必须以一个single blank line结束。
  • closures?>标签务必从only php包含only php文件中omitted

相信我,遵循这些标准可以节省你很多时间从你的生活:)

使用

ob_start();

在脚本的最上面,

ob_end_flush()函数;

在脚本的底部。 这将打开输出缓冲,您的标题将在页面缓冲后创build。

有时,当开发过程同时拥有WIN工作站和LINUX系统(托pipe),并且在代码中,在相关行之前没有看到任何输出,这可能是文件的格式化以及缺lessUnix LF(换

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

对于我们通过FTPpipe理的网站来说,这个修复方法是一个简单的修复方法,有时可以节省我们的新成员。

通常,当我们在回显或打印后发送标题时,会出现这个错误。 如果在特定页面上出现此错误,请确保该页面在调用start_session()之前不会回显任何内容。

不可预知的错误示例:

  <?php //a white-space before <?php also send for output and arise error session_start(); session_regenerate_id(); //your page content 

再举一个例子:

 <?php includes 'functions.php'; ?> <!-- This new line will also arise error --> <?php session_start(); session_regenerate_id(); //your page content 

结论:在调用session_start()header()函数之前,不要输出任何字符,甚至不能使用空格或换行符