<?php
/**
* Demonstration of a PHP bug: Capturing output as a string with an output buffer stops working
* as soon as PHP has detected a client disconnect.
*
* Verified with different PHP 5.4 and 5.5 builds on Linux and Win7 with Apache 2.2, Apache 2.4
* and PHP-FPM SAPIs.
*
* PHP-FPM note: When testing with PHP-FPM, make sure the web server does not buffer the reponse.
* For nginx, set "gzip off;" and "fastcgi_buffering off;" (only supported by version 1.6 and
* above) in the vhost configuration.
*/
// We record the results in this file after the client connection was closed
define('LOG_FILE', __DIR__ . '/ob-bug-test' . PHP_VERSION . '.log');
ini_set('error_log', __DIR__ . '/error.log');
// Let's not bother with HTML...
header('Content-Type: text/plain; charset=utf-8');
// Prevent script from exiting when client disconnects
ignore_user_abort(true);
// Flush & close all output buffers to make the echo/flush calls
// below send data to the client immediately
while (ob_get_level()) {
ob_end_flush();
}
// Init log file
echo "Results will go to " . LOG_FILE . "\n";
log_message("\n" . date('# Y-m-d H:i:s'));
// Run the tests once before the client disconnects
test_echo_output_buffer_capturing();
test_gd_output_buffer_capturing();
// Wait until client has closed connection
while (!connection_aborted()) {
log_message("\nClient is still connected");
// PHP only detects a client disconnect when trying to actually
// send rsponse data to the client
echo "Abort the request in your browser now by pressing "
. "<Esc> or clicking the little X in the adress bar...\n";
flush();
sleep(1);
}
log_message("\nClient is now disconnected (connection_aborted() returns true)");
// Run the tests again, after client has disconnected
test_echo_output_buffer_capturing();
test_gd_output_buffer_capturing();
// =============================================================================
/**
* This is what e.g. the popular template engine Twig does when rendering a template.
*
* A common case when this might happen after client diconnect: when Twig is used to
* render the body of an e-mail at the end of a long-running request.
*/
function test_echo_output_buffer_capturing()
{
ob_start();
echo 'Hello World';
$captured_output = ob_get_clean();
log_message("\nCapture echo in output buffer:");
log_message(' - captured string length: ' . strlen($captured_output));
}
/**
* The GD image output functions can either write to a file or to stdout.
*
* When the binary image data is needed as a string to e.g. write it to a DB blob field,
* using an output buffer is a shorter and faster method than writing to a temp file.
*/
function test_gd_output_buffer_capturing()
{
// $img = imagecreatetruecolor(100, 100);
// ob_start();
// // second imagejpeg() arg === null means "send image data to client/stdout"
// // Really strange behaviour: when the client connection is closed, imagejpeg()
// // triggers a warning (unrecoverable error) but returns true!?
// $success = imagejpeg($img, null, 100);
// $captured_output = ob_get_clean();
// imagedestroy($img);
// log_message("\nCapture binary image data in output buffer:");
// log_message(' - image data length: ' . strlen($captured_output));
// log_message(' - imagejpeg() return value: ' . ($success ? 'true' : 'false'));
}
function log_message( $message )
{
if (false === file_put_contents(LOG_FILE, $message . "\n", FILE_APPEND)) {
die("Sorry, it seems " . LOG_FILE . " is not writable. Aborting...\n");
}
}
Output for git.master, git.master_jit, rfc.property-hooks
Results will go to /in/ob-bug-test8.3.0-dev.log
Warning: file_put_contents(/in/ob-bug-test8.3.0-dev.log): Failed to open stream: Permission denied in /in/pHAms on line 98
Sorry, it seems /in/ob-bug-test8.3.0-dev.log is not writable. Aborting...
This tab shows result from various feature-branches currently under review by the php developers. Contact me to have additional branches featured.