PHP Output buffering, Content Encoding Error caused by ob_gzhandler?

tjm picture tjm · Jun 19, 2011 · Viewed 15.5k times · Source

Can anyone explain why I am receiving the following error?

In the code, if the echo $gz; is commented out I receive no error (but also no output!), if it isn't I get (from Firefox),

Content Encoding Error


The page you are trying to view cannot be shown because it uses an invalid or unsupported form of compression.


Thanks for your help, here's the code:

ob_start('ob_gzhandler') OR ob_start();
echo 'eh?';
$gz = ob_get_clean();
echo $gz;

Answer

hakre picture hakre · Jun 19, 2011

The output of your application should only contain one output encoding. If you have multiple chunks that are encoded differently, then the browser will get a result that it is impossible to work with. Hence the encoding error.

Kohana itself makes already use of the output buffer. If you want to combine that with your ob_gzhandler output buffer, you need to start your buffer before kohana initialized it's own. That's because output buffer are stackable. When kohana has finished it's output buffering, yours will apply:

ob_start('ob_gzhandler'); # your buffer:
   ob_starts and ends by kohana

So whenever kohana has done some output, these chunks will get passed on into your output callback (ob_gzhandler()) and will be gz-encoded.

The browser should then only get gz-encoded data as it was the output buffer at the topmost level.

Using ob_gzhandler and manually echo'ing the buffer

If you make use of ob_start('ob_gzhandler') to let PHP deal with the compression and you then echo ob_get_clean(), you will create an unreliable output. That's related to how the compression togther with output buffering works:

PHP will buffer chunks of output. That means, PHP starts to compress the output but keeps some bytes to continue compressing. So ob_get_clean() returns the so-far compressed part of the buffer. Often that result is not complete.

To deal with that, flush the buffer first:

ob_start('ob_gzhandler') OR ob_start();
echo 'eh?';
ob_flush();
$gz = ob_get_clean();
echo $gz;

And ensure you don't have any more output after that.

If you would have PHP reached the end of your script, it would have taken care of that: Flushing and outputting.

Now you need to manually call ob_flush() to explicitly make PHP push the buffer through the callbacks.

Inspecting HTTP Compression Problems with Curl

As firefox will return an error, another tool to inspect what's causing the encoding error is needed. You can use curl to track what's going on:

curl --compress -i URL

Will request the URL with compression enabled while displaying all response headers and the body unencoded. This is necessary as PHP transparently enables / disables compression of the ob_gzhandler callback based on request headers.

A response also shows that PHP will set the needed response headers as well. So no need to specify them manually. That would be even dangerously, because only by calling ob_start('ob_gzhandler') you can not say if compression is enabled or not.

In case the compression is broken, curl will give an error description but would not display the body.

Following is such a curl error message provoked with an incompletely generated output by a faulty php script:

HTTP/1.1 200 OK
X-Powered-By: PHP/5.3.6
Content-Encoding: gzip
...

curl: (23) Error while processing content unencoding: invalid code lengths set

By adding the --raw switch, you can even peak into the raw response body:

curl --compress --raw -i URL

That can give an impression what's going wrong, like uncompressed parts within the body.