Thursday 15 August 2019

How to fix "Headers already sent" error in PHP





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?


Answer




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:





Output can be:




  • Unintentional:









  • Intentional:





    • print, echo and other functions producing output

    • Raw sections prior 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

PHP page output page

Content

Some more output follows...


and


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, ) it will
flush all collected headers. Afterwards 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 occured?



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 blocks.




    // 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 for "script.php line 1" warnings



    If the warning refers to output in line 1, then it's mostly

    leading whitespace, text or HTML before the opening token.



     # There's a SINGLE space/newline before 


    Similarly it can occur for appended scripts or script sections:



    ?>




    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 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 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:



    joes editor showing UTF-8 BOM placeholder, and MC editor a dot




    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:



    beav hexeditor showing utf-8 bom




    An easy fix is to set the text editor to save files as "UTF-8 (no BOM)"
    or similar 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 sane 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 raw text got written out.
    The PHP end marker does not terminate script executation 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 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 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"));



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



Output buffering as 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 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 furher workaround.)



    • The buffer is limited in size, and can easily overrun when left to defaults.
      And that's not a rare occurence 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. Which 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:





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
an info or apply other fallback logic.



if (headers_sent()) {
die("Redirect failed. Please click on this link: ");
}

else{
exit(header("Location: /user.php"));
}


Useful fallback workarounds are:





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




No comments:

Post a Comment

php - file_get_contents shows unexpected output while reading a file

I want to output an inline jpg image as a base64 encoded string, however when I do this : $contents = file_get_contents($filename); print &q...