No output before sending headers!
Functions that send/modify HTTP headers must be invoked before any output is made. Otherwise the call fails:Warning: Cannot modify header information - headers already sent (output started at file:line)Some functions modifying the HTTP header are:
Output can be:
- Unintentional:
- Whitespace before
<?php
or after?>
- UTF-8 Byte Order Mark
- Previous error messages or notices
- Whitespace before
- Intentional:
print
,echo
and other functions producing output (likevar_dump
)- Raw
<html>
areas before<?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=about:note> ...
The page/output always follows the headers. PHP is required to pass the headers to the webserver first. It can only do that once. And after the double linebreak it can't ever append to them again.When PHP receives the first output (print, echo, <html>) it will "flush" the collected headers. Afterwards it can send all the output bits it wants. But sending further headers is impossible from then.
How can you find out where the premature output occured?
Theheader()
warning contains all relevant information to locate the problem source: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 100Here "line 100" refers to the script where the header() invocation failed.
The message in the inner parenthesis is more crucial. It mentions
auth.php
and line 52
as the source of premature output. One of the typical problem causes will be there:Print, echo
Intentional output fromprint
and echo
statements will terminate the opportunity to send HTTP headers. The application flow must be restructured to avoid that. Use functionsand templating schemes. Ensure header() calls occur before messages are written out.Functions that can write output include
print
, echo
, printf
, trigger_error
, vprintf
, ob_flush
, var_dump
, readfile
, passthru
among others and user-defined functions.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.
Whitespace before <?php
when "somefile.php line 1" mentioned
If the message says the error is in line 1
, then it is typically leading whitespace, text or HTML before the opening <?php
marker. <?php
# There's a SINGLE! space/newline before <? - Which already seals it.
It can likewise 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.UTF-8 BOM
Linebreaks and spaces alone can be a problem. But there are also "invisible" character sequences which can cause this. Most famously the UTF-8 BOM (Byte-Order-Mark)which isn't displayed by most text editors. It's the byte sequenceEF BB BF
, which is optional and redundant for UTF-8 encoded documents. But the PHP interpreter treats 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). Some programmer and console editors however do:
There it's easy to recognize the problem early on. Without such an editor available (or Notepad++ on Windows, which can remedy the problemper menu command), another resort would be a hexeditor. Programmers should have one, as they simplify auditing these kind of issues:
The easy fix is to set the text editor to save files as "UTF-8 (no BOM)" or similar such nomenclature. Many newcomers resort to creating new files and just copy&pasting the previous code back in.
Correction utilities
Actually there are automated tools to rewrite text files. For PHP specifically there is thephptags
tag tidier. It rewrites close and open tags into long and short forms, but also easily fixes leading and trailing whitespace (and BOM) issues: phptags --whitespace *.php
It's sane to use on a whole include or project directory.Whitespace after ?>
If the error source is mentioned as behind the closing?>
then this is where some whitespace or raw text got written out. The PHP end tag does not terminate script executation at this point. Any characters after it will be output as page content still.It's commonly advised, in particular to newcomers, that trailing
?>
PHP close tags should be omitted. This eschews a significant part of these cases. (Quite commonly include()
scripts are the culprit.)Again
phptags --whitespace *.php
fixes that more easily.Likewise
phptags --unclosed ./includes
could drop redundant ?>
from scripts.Error source mentioned as "Unknown on line 0"
It's typically an PHP extension or php.ini setting if no error source is specified. It's occasionally thegzip
stream encoding extension or the ob_gzhandler. But it could also be any doubly loaded extension=
module, which let to an implicit warning message.Preceding error messages
If another PHP statement or expression causes a warning message or notice being printeded 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 this doesn't obstruct debugging later on.No error message
If you haveerror_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 cannot be sent after premature output still.So when
header("Location: ...")
redirects fail siliently it's good to probe for warnings. Reenable them with two simple commands (atop the very first 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"));
Preferrably even a utility function, which prints a user message in case of header()
failure.Output buffering as workaround
PHPs output bufferingis suitable to alleviate this issue. It often does so quite reliaby, but should be considered strictly a workaround. Its actual purpose is minimizing chunked transfers to the webserver. Restructuring the application to avoid premature output is preferable.Nevertheless does the
output_buffering=
setting help. Configure it in the php.ini
*or via .htaccess
*or even .user.ini
*. With that enabled content gets buffered and not instantly passed on to the webserver. Thus HTTP headers can be aggregated.It can likewise be engaged with a call to
ob_start();
atop the invocation script. This however is less reliable for a few reasons:- Even if
<?php ob_start(); ?>
starts the first script, whitespace or a BOM can 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 spaces become a problem. (Though
ob_clean()
often is another workaround.) - The buffer is limited in size. While usually a hypothetical problem, it might however overrun - which wouldn't be easy to trace.
- 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 php.ini settinghas been changed. Output buffering then was enabled on the other server, but not on the current. See previous section.Checking with headers_sent()
You can always useheaders_sent()
to probe if it's still possible to... send headers. That's useful to conditionally print an 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"));
}
HTML <meta>
tag workaround
If your application is structurally hard to fix, then an easy (but totally unprofessional) way to allow redirects is injecting HTML. A redirect can be achieved by: <meta http-equiv="Location" content="http://example.com/">
Or with a short delay: <meta http-equiv="Refresh" content="2; url=../target.html">
This will make your website non-valid (even if faux XHTML) when printed outside the <head>
part. Most browsers still accept it. As alternative a Javascript redirectcould do: <script> location.replace("target.html"); </script>
It's an acceptable approach if this is used as a fallback by specialized redirect utility functions. Which should first attempt to send a proper header()
, but use the meta tag equivalents and a user-friendly message and link as last resort.Why setcookie() and session_start() are also affected
Both setcookie() and session_start() need to send aSet-Cookie:
HTTP header(). The same conditions therefore apply, and similar error messages will be generated with premature output situations.Header output problems are not the only cause for non-functionality with them of course. Disabled cookies in the browser, or even proxy issues should always be checked. The session functionality also depends on free disk space and other php.ini settings.
Further links
- Google provides a lengthy list of similar discussions.
- And of course many specific cases have been covered on Stackoverflow 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?
- HTTP Headers and the PHP header() Function - A tutorial by NicholasSolutions- only available via Internet Archive now, but is one of the more thorough answers. Explains HTTP en detail and gives some examples for rewriting scripts.
0 comments:
Post a Comment