slimphp / Slim

Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs.
http://slimframework.com
MIT License
11.94k stars 1.95k forks source link

with output_buffering enabled output from Slim is broken #1828

Closed grikdotnet closed 8 years ago

grikdotnet commented 8 years ago

The default value of output_buffering in php.ini-production is 4096 If this option is set, the output of Slim application gets corrupted. 'settings' => ['outputBuffering' => false] does not affect output

Disabling the output_buffering solves the issue.

Tested on several builds of PHP 7

Code:

<?php

require __DIR__ . '/vendor/autoload.php';

$app = new \Slim\App(['settings' => ['outputBuffering' => false]]);
// or $app = new \Slim\App(); - no matter

echo 'AAA';
$app->get('/', function (Slim\Http\Request $request, $response, $args) {
    $response->write('12345');
});

// Run app
$x = $app->run();
echo 'BBB';

Expected output: AAA12345BBB Actual: AAA12

llvdl commented 8 years ago

It seems to content is not actually cut off by Slim, but instead by the browser.

TL;DR When outputting content, a Content-Length header is set and the browser (not Slm) will disregard content after that set amount of bytes. Either disable the header, or write all output the request body.

I tried your example in PHP 5.6 using the PHP built-in webserver and I got the following result in my brower:

AAA12

Fetching the page with curl on the command line gives:

$ curl -v localhost:8000
* Rebuilt URL to: localhost:8000/
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.43.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Host: localhost:8000
< Connection: close
< X-Powered-By: PHP/5.6.11-1ubuntu3.1
< Content-Type: text/html; charset=UTF-8
< Content-Length: 5
< 
* Excess found in a non pipelined read: excess = 6, size = 5, maxdownload = 5, bytecount = 0
* Closing connection 0
AAA12

Curl (when setting the verbose flag), states that there is excess data, which is the data that is not displayed. To see that the output is actually rendered, I used telnet:

$ telnet localhost 8000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET / HTTP/1.1
Host: localhost:8000

HTTP/1.1 200 OK
Host: localhost:8000
Connection: close
X-Powered-By: PHP/5.6.11-1ubuntu3.1
Content-Type: text/html; charset=UTF-8
Content-Length: 5

AAA12345BBBConnection closed by foreign host.

As you can see on the last line, all characters are sent. It's the browser that discards content after the first 5 characters (the size set in the Content-Length header).

grikdotnet commented 8 years ago

I suppose, skipping the extra content is a correct behavior. And it's not an application that sends the header. PHP does not send this header on it's own, so it's a framework that sends the wrong value.

llvdl commented 8 years ago

The header is indeed sent by the framework. The framework can't determine how many characters are sent before or after it runs, so in hindsight it makes sense that the Content-Length set does not match the actual length of the content (I was surprised out the outcome as well).

Perhaps this should be addressed in the Slim documentation?

grikdotnet commented 8 years ago

Perhaps the framework should not set the header value if it does not know it, and let the application developer do it if required.

llvdl commented 8 years ago

I guess using a framework for creating a HTTP response means you don't add content to the response outside of the framework. But this is a question probably better answered by the project maintainers.

One way to work around this, is to extend \Slim\App and override the finalize method to omit the Content-Length header, e.g.

protected function finalize(\Psr\Http\Message\ResponseInterface $response)
{
    return parent::finalize($response)->withoutHeader('Content-Length');
}
akrabat commented 8 years ago

See issue #1690 - specifically this comment: https://github.com/slimphp/Slim/issues/1690#issuecomment-213161528

tuupola commented 8 years ago

FWIW this is also how Zend Expressive works. Personally I think all content should be outputted inside the route.

$app = AppFactory::create();

print "AAA";

$app->get("/", function ($request, $response, $next) {
    $response->getBody()->write("12345");
    return $response;
});

$app->pipeRoutingMiddleware();
$app->pipeDispatchMiddleware();

$app->run();

print "BBB";
$ curl --include localhost:8080 
HTTP/1.1 200 OK
Host: localhost:8080
Connection: close
X-Powered-By: PHP/5.6.17
Content-Length: 5
Content-type: text/html; charset=UTF-8

AAA12
ImIOImI commented 8 years ago

I think this is, possibly, related to an issue I've found. In my haste (aided by phpstorm's auto complete functionality), I seemed to have found a new and creative way to break stuff. I accidentally used the wrong container interface in the constructor of a controller like:

`use Zend\Memory\Container\ContainerInterface; use \Psr\Http\Message\ServerRequestInterface as Request; use \Psr\Http\Message\ResponseInterface as Response;

class User extends Controller { protected $ci;

function __construct(Zend\Memory\Container\ContainerInterface $ci)
{
    $this->ci;
}

//...`

obviously this is wrong, and I know how to fix it, but the error was unintelligible.

Below I've attached a file from my screen output. I traced the output back to where the error was being thrown and did a var dump of the container above the content that Slim echos to the screen (I drew a blue line on the pic to make the demarcation obvious).

image

I have output buffering disabled, but I believe that because xDebug added all the extra stack trace info to the error message that Slim didn't expect, it cut it off.

Sorry if I'm off base... but this issue looked close enough for me to post here rather than opening a new issue.

akrabat commented 8 years ago

You can now disable Slim's automatic setting of the content-length with:

$config = [
    'settings' => [
        'addContentLengthHeader' => false,
    ]
];
$app = new Slim\App($config);
forty1 commented 8 years ago

@akrabat I tried it your way too, but didn't work for me. What worked for me is this dirty, little hack $response->withHeader('Content-Length', strlen($jsonData) + 3)

JoeBengalen commented 8 years ago

Avoid such hacks ... If its only 3 chars missing check if the response body starts with a BOM.