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

2022-01-11 00:00:00 header php

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

<块引用>

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

错误消息中提到的行包含 ),检查 BOM 存在的另一种选择是使用 hexeditor.在 *nix 系统上 或 重新编码).对于 PHP,特别是有 phptags 标签 tidier.它将关闭和打开标签重写为长格式和短格式,但也很容易修复前导和尾随空格、Unicode 和 UTF-x BOM 问题:

phptags --whitespace *.php

在整个包含或项目目录中使用是安全的.

  • ?>

    后面的空格

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

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

  • 提到的错误源为第 0 行未知"

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

    • 偶尔是 gzip 流编码设置或 ob_gzhandler.
    • 但它也可以是任何双重加载的 extension= 模块生成隐式 PHP 启动/警告消息.

  • 前面的错误消息

    如果另一个 PHP 语句或表达式导致警告消息或注意被打印出来,也算过早输出.

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

  • 没有错误信息

    如果您根据 php.ini 禁用了 error_reportingdisplay_errors,然后不会出现任何警告.但是忽略错误不会解决问题离开.过早输出后仍然无法发送标头.

    所以当 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=设置仍然可以提供帮助.在 php.ini 中配置它或通过 .htaccess甚至是 .user.ini现代 FPM/FastCGI 设置.
      启用它将允许 PHP 缓冲输出,而不是立即将其传递给网络服务器.因此 PHP 可以聚合 HTTP 标头.

    2. 同样可以调用 ob_start();在调用脚本之上.然而,由于多种原因,这不太可靠:

      • 即使 开始第一个脚本,空格或BOM 之前可能会被打乱,渲染它无效.

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

      • 缓冲区的大小是有限的,如果保留为默认值,很容易溢出.这也不是罕见的情况,难以追踪当它发生时.

    因此,这两种方法都可能变得不可靠——尤其是在两者之间切换时开发设置和/或生产服务器.这就是为什么输出缓冲是被广泛认为只是一种拐杖/严格来说是一种解决方法.

    另请参阅基本用法示例在手册中,以及更多的利弊:

    • 什么是输出缓冲?
    • 为什么在 PHP 中使用输出缓冲?
    • 使用输出缓冲是否被认为是一种不好的做法?
    • 用例输出缓冲作为标头已发送"的正确解决方案

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

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

    检查 headers_sent()

    您始终可以使用 headers_sent() 来探测是否仍然可以...发送标头.这对有条件地打印很有用信息或应用其他后备逻辑.

    if (headers_sent()) {die("重定向失败.请点击此链接:<a href=...>");}别的{exit(header("位置:/user.php"));}

    有用的后备解决方法是:

    • HTML <meta> 标签

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

       

      或稍作延迟:

       

      当在 <head> 部分之后使用时,这会导致 HTML 无效.大多数浏览器仍然接受它.

    • JavaScript 重定向

      作为替代 JavaScript 重定向可用于页面重定向:

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

      虽然这通常比 <meta> 解决方法更符合 HTML,它会导致对支持 JavaScript 的客户端的依赖.

    然而,当真正的 HTTP header()通话失败.理想情况下,您总是将其与用户友好的消息结合起来,并且可点击的链接作为最后的手段.(例如 http_redirect()PECL 扩展确实如此.)

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

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

    (当然,它们还会受到浏览器中禁用的 cookie 的影响甚至代理问题.会话功能显然也依赖于免费磁盘空间和其他 php.ini 设置等)

    更多链接

    • Google 提供了类似讨论的冗长列表.
    • 当然 许多具体案例 也已在 Stack Overflow 上进行了介绍.
    • WordPress 常见问题解答解释了 如何解决标头已发送警告问题?一种通用的方式.
    • Adobe 社区:PHP 开发:为什么重定向不起作用(标头已发送)
    • Nucleus 常见问题解答:页眉已发送"是什么意思?什么意思?
    • 其中一个更彻底的解释是 HTTP 标头和 PHP header() 函数 - NicholasSolutions 的教程(Internet 存档链接).它详细介绍了 HTTP,并提供了一些重写脚本的指南.

    When running my script, I am getting several errors like this:

    Warning: Cannot modify header information - headers already sent by (output started at /some/file.php:12) in /some/file.php on line 23

    The lines mentioned in the error messages contain header() and setcookie() calls.

    What could be the reason for this? And how to fix it?

    解决方案

    No output before sending headers!

    Functions that send/modify HTTP headers must be invoked before any output is made. summary ⇊ Otherwise the call fails:

    Warning: Cannot modify header information - headers already sent (output started at script:line)

    Some functions modifying the HTTP header are:

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

    Output can be:

    • Unintentional:

      • Whitespace before <?php or after ?>
      • The UTF-8 Byte Order Mark specifically
      • Previous error messages or notices
    • Intentional:

      • print, echo and other functions producing output
      • Raw <html> sections prior <?php code.

    Why does it happen?

    To understand why headers must be sent before output it's necessary to look at a typical HTTP response. PHP scripts mainly generate HTML content, but also pass a set of HTTP/CGI headers to the webserver:

    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>
    

    The page/output always follows the headers. PHP has to pass the headers to the webserver first. It can only do that once. After the double linebreak it can nevermore amend them.

    When PHP receives the first output (print, echo, <html>) it will flush all collected headers. Afterward it can send all the output it wants. But sending further HTTP headers is impossible then.

    How can you find out where the premature output occurred?

    The header() warning contains all relevant information to locate the problem cause:

    Warning: Cannot modify header information - headers already sent by (output started at /www/usr2345/htdocs/auth.php:52) in /www/usr2345/htdocs/index.php on line 100

    Here "line 100" refers to the script where the header() invocation failed.

    The "output started at" note within the parenthesis is more significant. It denominates the source of previous output. In this example, it's auth.php and line 52. That's where you had to look for premature output.

    Typical causes:

    1. Print, echo

      Intentional output from print and echo statements will terminate the opportunity to send HTTP headers. The application flow must be restructured to avoid that. Use functions and templating schemes. Ensure header() calls occur before messages are written out.

      Functions that produce output include

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


      among others and user-defined functions.

    2. Raw HTML areas

      Unparsed HTML sections in a .php file are direct output as well. Script conditions that will trigger a header() call must be noted before any raw <html> blocks.

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

      Use a templating scheme to separate processing from output logic.

      • Place form processing code atop scripts.
      • Use temporary string variables to defer messages.
      • The actual output logic and intermixed HTML output should follow last.

    3. Whitespace before <?php for "script.php line 1" warnings

      If the warning refers to output inline 1, then it's mostly leading whitespace, text or HTML before the opening <?php token.

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

      Similarly it can occur for appended scripts or script sections:

      ?>
      
      <?php
      

      PHP actually eats up a single linebreak after close tags. But it won't compensate multiple newlines or tabs or spaces shifted into such gaps.

    4. UTF-8 BOM

      Linebreaks and spaces alone can be a problem. But there are also "invisible" character sequences that can cause this. Most famously the UTF-8 BOM (Byte-Order-Mark) which isn't displayed by most text editors. It's the byte sequence EF BB BF, which is optional and redundant for UTF-8 encoded documents. PHP however has to treat it as raw output. It may show up as the characters  in the output (if the client interprets the document as Latin-1) or similar "garbage".

      In particular graphical editors and Java-based IDEs are oblivious to its presence. They don't visualize it (obliged by the Unicode standard). Most programmer and console editors however do:

      There it's easy to recognize the problem early on. Other editors may identify its presence in a file/settings menu (Notepad++ on Windows can identify and remedy the problem), Another option to inspect the BOMs presence is resorting to an hexeditor. On *nix systems hexdump is usually available, if not a graphical variant which simplifies auditing these and other issues:

      An easy fix is to set the text editor to save files as "UTF-8 (no BOM)" or similar to such nomenclature. Often newcomers otherwise resort to creating new files and just copy&pasting the previous code back in.

      Correction utilities

      There are also automated tools to examine and rewrite text files (sed/awk or recode). For PHP specifically there's the phptags tag tidier. It rewrites close and open tags into long and short forms, but also easily fixes leading and trailing whitespace, Unicode and UTF-x BOM issues:

      phptags  --whitespace  *.php
      

      It's safe to use on a whole include or project directory.

    5. Whitespace after ?>

      If the error source is mentioned as behind the closing ?> then this is where some whitespace or the raw text got written out. The PHP end marker does not terminate script execution at this point. Any text/space characters after it will be written out as page content still.

      It's commonly advised, in particular to newcomers, that trailing ?> PHP close tags should be omitted. This eschews a small portion of these cases. (Quite commonly include()d scripts are the culprit.)

    6. Error source mentioned as "Unknown on line 0"

      It's typically a PHP extension or php.ini setting if no error source is concretized.

      • It's occasionally the gzip stream encoding setting or the ob_gzhandler.
      • But it could also be any doubly loaded extension= module generating an implicit PHP startup/warning message.

    7. Preceding error messages

      If another PHP statement or expression causes a warning message or notice being printed out, that also counts as premature output.

      In this case you need to eschew the error, delay the statement execution, or suppress the message with e.g. isset() or @() - when either doesn't obstruct debugging later on.

    No error message

    If you have error_reporting or display_errors disabled per php.ini, then no warning will show up. But ignoring errors won't make the problem go away. Headers still can't be sent after premature output.

    So when header("Location: ...") redirects silently fail it's very advisable to probe for warnings. Reenable them with two simple commands atop the invocation script:

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

    Or set_error_handler("var_dump"); if all else fails.

    Speaking of redirect headers, you should often use an idiom like this for final code paths:

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

    Preferably even a utility function, which prints a user message in case of header() failures.

    Output buffering as a workaround

    PHPs output buffering is a workaround to alleviate this issue. It often works reliably, but shouldn't substitute for proper application structuring and separating output from control logic. Its actual purpose is minimizing chunked transfers to the webserver.

    1. The output_buffering= setting nevertheless can help. Configure it in the php.ini or via .htaccess or even .user.ini on modern FPM/FastCGI setups.
      Enabling it will allow PHP to buffer output instead of passing it to the webserver instantly. PHP thus can aggregate HTTP headers.

    2. It can likewise be engaged with a call to ob_start(); atop the invocation script. Which however is less reliable for multiple reasons:

      • Even if <?php ob_start(); ?> starts the first script, whitespace or a BOM might get shuffled before, rendering it ineffective.

      • It can conceal whitespace for HTML output. But as soon as the application logic attempts to send binary content (a generated image for example), the buffered extraneous output becomes a problem. (Necessitating ob_clean() as a further workaround.)

      • The buffer is limited in size, and can easily overrun when left to defaults. And that's not a rare occurrence either, difficult to track down when it happens.

    Both approaches therefore may become unreliable - in particular when switching between development setups and/or production servers. This is why output buffering is widely considered just a crutch / strictly a workaround.

    See also the basic usage example in the manual, and for more pros and cons:

    • What is output buffering?
    • Why use output buffering in PHP?
    • Is using output buffering considered a bad practice?
    • Use case for output buffering as the correct solution to "headers already sent"

    But it worked on the other server!?

    If you didn't get the headers warning before, then the output buffering php.ini setting has changed. It's likely unconfigured on the current/new server.

    Checking with headers_sent()

    You can always use headers_sent() to probe if it's still possible to... send headers. Which is useful to conditionally print info or apply other fallback logic.

    if (headers_sent()) {
        die("Redirect failed. Please click on this link: <a href=...>");
    }
    else{
        exit(header("Location: /user.php"));
    }
    

    Useful fallback workarounds are:

    • HTML <meta> tag

      If your application is structurally hard to fix, then an easy (but somewhat unprofessional) way to allow redirects is injecting a HTML <meta> tag. A redirect can be achieved with:

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

      Or with a short delay:

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

      This leads to non-valid HTML when utilized past the <head> section. Most browsers still accept it.

    • JavaScript redirect

      As alternative a JavaScript redirect can be used for page redirects:

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

      While this is often more HTML compliant than the <meta> workaround, it incurs a reliance on JavaScript-capable clients.

    Both approaches however make acceptable fallbacks when genuine HTTP header() calls fail. Ideally you'd always combine this with a user-friendly message and clickable link as last resort. (Which for instance is what the http_redirect() PECL extension does.)

    Why setcookie() and session_start() are also affected

    Both setcookie() and session_start() need to send a Set-Cookie: HTTP header. The same conditions therefore apply, and similar error messages will be generated for premature output situations.

    (Of course, they're furthermore affected by disabled cookies in the browser or even proxy issues. The session functionality obviously also depends on free disk space and other php.ini settings, etc.)

    Further links

    • Google provides a lengthy list of similar discussions.
    • And of course many specific cases have been covered on Stack Overflow as well.
    • The WordPress FAQ explains How do I solve the Headers already sent warning problem? in a generic manner.
    • Adobe Community: PHP development: why redirects don't work (headers already sent)
    • Nucleus FAQ: What does "page headers already sent" mean?
    • One of the more thorough explanations is HTTP Headers and the PHP header() Function - A tutorial by NicholasSolutions (Internet Archive link). It covers HTTP in detail and gives a few guidelines for rewriting scripts.

    相关文章