[HttpFoundation] added support for streamed responses #2935

Merged
merged 5 commits into from Dec 31, 2011

Projects

None yet
@fabpot
Member
fabpot commented Dec 21, 2011

To stream a Response, use the StreamedResponse class instead of the
standard Response class:

$response = new StreamedResponse(function () {
    echo 'FOO';
});

$response = new StreamedResponse(function () {
    echo 'FOO';
}, 200, array('Content-Type' => 'text/plain'));

As you can see, a StreamedResponse instance takes a PHP callback instead of
a string for the Response content. It's up to the developer to stream the
response content from the callback with standard PHP functions like echo.
You can also use flush() if needed.

From a controller, do something like this:

$twig = $this->get('templating');

return new StreamedResponse(function () use ($templating) {
    $templating->stream('BlogBundle:Annot:streamed.html.twig');
}, 200, array('Content-Type' => 'text/html'));

If you are using the base controller, you can use the stream() method instead:

return $this->stream('BlogBundle:Annot:streamed.html.twig');

You can stream an existing file by using the PHP built-in readfile() function:

new StreamedResponse(function () use ($file) {
    readfile($file);
}, 200, array('Content-Type' => 'image/png');

Read http://php.net/flush for more information about output buffering in PHP.

Note that you should do your best to move all expensive operations to
be "activated/evaluated/called" during template evaluation.

Templates

If you are using Twig as a template engine, everything should work as
usual, even if are using template inheritance!

However, note that streaming is not supported for PHP templates. Support
is impossible by design (as the layout is rendered after the main content).

Exceptions

Exceptions thrown during rendering will be rendered as usual except that
some content might have been rendered already.

Limitations

As the getContent() method always returns false for streamed Responses, some
event listeners won't work at all:

  • Web debug toolbar is not available for such Responses (but the profiler works fine);
  • ESI is not supported.

Also note that streamed responses cannot benefit from HTTP caching for obvious
reasons.

@fabpot fabpot [HttpFoundation] added support for streamed responses
To stream a Response, use the StreamedResponse class instead of the
standard Response class:

    $response = new StreamedResponse(function () {
        echo 'FOO';
    });

    $response = new StreamedResponse(function () {
        echo 'FOO';
    }, 200, array('Content-Type' => 'text/plain'));

As you can see, a StreamedResponse instance takes a PHP callback instead of
a string for the Response content. It's up to the developer to stream the
response content from the callback with standard PHP functions like echo.
You can also use flush() if needed.

From a controller, do something like this:

    $twig = $this->get('templating');

    return new StreamedResponse(function () use ($templating) {
        $templating->stream('BlogBundle:Annot:streamed.html.twig');
    }, 200, array('Content-Type' => 'text/html'));

If you are using the base controller, you can use the stream() method instead:

    return $this->stream('BlogBundle:Annot:streamed.html.twig');

You can stream an existing file by using the PHP built-in readfile() function:

    new StreamedResponse(function () use ($file) {
        readfile($file);
    }, 200, array('Content-Type' => 'image/png');

Read http://php.net/flush for more information about output buffering in PHP.

Note that you should do your best to move all expensive operations to
be "activated/evaluated/called" during template evaluation.

Templates
---------

If you are using Twig as a template engine, everything should work as
usual, even if are using template inheritance!

However, note that streaming is not supported for PHP templates. Support
is impossible by design (as the layout is rendered after the main content).

Exceptions
----------

Exceptions thrown during rendering will be rendered as usual except that
some content might have been rendered already.

Limitations
-----------

As the getContent() method always returns false for streamed Responses, some
event listeners won't work at all:

* Web debug toolbar is not available for such Responses (but the profiler works fine);
* ESI is not supported.

Also note that streamed responses cannot benefit from HTTP caching for obvious
reasons.
0038d1b
@lsmith77 lsmith77 and 1 other commented on an outdated diff Dec 21, 2011
src/Symfony/Bundle/FrameworkBundle/HttpKernel.php
@@ -146,7 +147,11 @@ public function render($controller, array $options = array())
throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $request->getUri(), $response->getStatusCode()));
}
- return $response->getContent();
+ if ($response instanceof StreamedResponse) {
@lsmith77
lsmith77 Dec 21, 2011 Contributor

imho it would be nicer to check the negation to return early and to get rid of the else

@fabpot
fabpot Dec 21, 2011 Member

changed

@jalliot jalliot commented on the diff Dec 21, 2011
...Symfony/Component/HttpFoundation/StreamedResponse.php
+ * @api
+ */
+ public function __construct($callback, $status = 200, $headers = array())
+ {
+ parent::__construct(null, $status, $headers);
+
+ $this->callback = $callback;
+ $this->streamed = false;
+ }
+
+ /**
+ * @{inheritdoc}
+ */
+ public function prepare(Request $request)
+ {
+ if ('1.0' != $request->server->get('SERVER_PROTOCOL')) {
@jalliot
jalliot Dec 21, 2011 Contributor

Should be !== for CS no?

@stof stof commented on the diff Dec 21, 2011
...HttpKernel/EventListener/StreamedResponseListener.php
+ {
+ if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
+ return;
+ }
+
+ $response = $event->getResponse();
+
+ if ($response instanceof StreamedResponse) {
+ $response->send();
+ }
+ }
+
+ static public function getSubscribedEvents()
+ {
+ return array(
+ KernelEvents::RESPONSE => array('onKernelResponse', -1024),
@stof
stof Dec 21, 2011 Member

why do we need this listener at the end of the kernel.response event ? The event is the last step before the end of the request and send will be called by the front controller just after that.

@fabpot
fabpot Dec 21, 2011 Member

It must be the last one as we are sending the content here. The send() in the front controller is a no-op for streamed responses.

@Seldaek
Seldaek Dec 21, 2011 Member

Why do you have this event listener at all actually? Can't you just let the send() method be called as usual by the front controller? Sounds like it's equivalent to me.

@fabpot
fabpot Dec 22, 2011 Member

The event listener is needed because we need to be in the request scope to be able to generate the response. If you send the response in the front controller, the request is not available anymore.

@stloyd stloyd and 1 other commented on an outdated diff Dec 21, 2011
...sts/Component/HttpFoundation/StreamedResponseTest.php
+ $response->sendContent();
+ }
+
+ /**
+ * @expectedException \LogicException
+ */
+ public function testSetContent()
+ {
+ $response = new StreamedResponse(function () { echo 'foo'; });
+ $response->setContent('foo');
+ }
+
+ public function testGetContent()
+ {
+ $response = new StreamedResponse(function () { echo 'foo'; });
+ $this->assertEquals(false, $response->getContent());
@stloyd
stloyd Dec 21, 2011 Contributor

Use assertFalse() instead.

@fabpot
fabpot Dec 21, 2011 Member

changed

@lsmith77 lsmith77 and 1 other commented on an outdated diff Dec 21, 2011
...fony/Bundle/FrameworkBundle/Controller/Controller.php
@@ -99,6 +100,24 @@ public function render($view, array $parameters = array(), Response $response =
}
/**
+ * Streams a view.
+ *
+ * @param string $view The view name
+ * @param array $parameters An array of parameters to pass to the view
+ * @param Response $response A response instance
+ *
+ * @return StreamedResponse A StreamedResponse instance
+ */
+ public function stream($view, array $parameters = array(), Response $response = null)
+ {
+ $templating = $this->container->get('templating');
+
+ return new StreamedResponse(function () use ($templating, $view, $parameters) {
+ $templating->stream($view, $parameters);
+ }, null === $response ? 200 : $response->getStatusCode(), null === $response ? array() : $response->headers->all());
@lsmith77
lsmith77 Dec 21, 2011 Contributor

is there a specific reason why you don't want to assign these parameters to variables for better readability?

@fabpot
fabpot Dec 21, 2011 Member

no specific reason.

@lsmith77
lsmith77 Dec 21, 2011 Contributor

then i think it would be better to use variables to make the code easier to read.

@fabpot
fabpot Dec 22, 2011 Member

done in 1d368e75fc 473741b

@Tobion Tobion and 1 other commented on an outdated diff Dec 21, 2011
...Symfony/Component/HttpFoundation/StreamedResponse.php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * StreamedResponse represents a streamed HTTP response.
+ *
+ * A StreamedResponse uses a callback for its the content.
@Tobion
Tobion Dec 21, 2011 Member

typo: the

@Tobion Tobion commented on the diff Dec 21, 2011
...HttpKernel/EventListener/StreamedResponseListener.php
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpKernel\EventListener;
+
+use Symfony\Component\HttpFoundation\StreamedResponse;
+use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * StreamedResponseListener is responsible for sending the Response
@Tobion
Tobion Dec 21, 2011 Member

type: response

@lsmith77
lsmith77 Dec 22, 2011 Contributor

i think we do this in other places too when we want to refer to a class instance.

@Tobion Tobion and 1 other commented on an outdated diff Dec 21, 2011
src/Symfony/Component/Templating/EngineInterface.php
@@ -47,6 +47,20 @@
function render($name, array $parameters = array());
/**
+ * Streams a template.
+ *
+ * The implementation should outputs the content directly to the client.
@Tobion
Tobion Dec 21, 2011 Member

typo: output

@stof stof and 2 others commented on an outdated diff Dec 21, 2011
src/Symfony/Component/Templating/EngineInterface.php
@@ -47,6 +47,20 @@
function render($name, array $parameters = array());
/**
+ * Streams a template.
+ *
+ * The implementation should outputs the content directly to the client.
+ *
+ * @param mixed $name A template name or a TemplateReferenceInterface instance
+ * @param array $parameters An array of parameters to pass to the template
+ *
+ * @throws \RuntimeException if the template cannot be rendered
+ *
+ * @api
+ */
+ function stream($name, array $parameters = array());
@stof
stof Dec 21, 2011 Member

this should be marked as a BC break as the interface was tagged with @api

@lsmith77
lsmith77 Dec 21, 2011 Contributor

or we instead create a new StreamingEngineInterface which imho is cleaner than the exception

@stof
stof Dec 21, 2011 Member

this would be difficult for the DelegatingEngine as it would need to implement the interface selectively depending of the template passed to the method :)

@fabpot
fabpot Dec 21, 2011 Member

We can also make the DelegatingEngine implement the StreamingEngineInterface and throw an exception if the engine does not implement this interface in the stream method.

@lsmith77
lsmith77 Dec 21, 2011 Contributor

sounds good to me

@Seldaek Seldaek and 2 others commented on an outdated diff Dec 21, 2011
...fony/Bundle/FrameworkBundle/Controller/Controller.php
@@ -99,6 +100,24 @@ public function render($view, array $parameters = array(), Response $response =
}
/**
+ * Streams a view.
+ *
+ * @param string $view The view name
+ * @param array $parameters An array of parameters to pass to the view
+ * @param Response $response A response instance
+ *
+ * @return StreamedResponse A StreamedResponse instance
+ */
+ public function stream($view, array $parameters = array(), Response $response = null)
@Seldaek
Seldaek Dec 21, 2011 Member

The render() call does accept a Response, and if you pass one it is reused, but in this case it is not reused it's replaced by a StreamingResponse. If anything this should only accept a StreamingResponse so that it can be reused, except it's not possible to set the callback later than in the constructor, so that does not make sense either. I would just remove it.

@fabpot
fabpot Dec 21, 2011 Member

It is useful to set the status code or some headers.

@Seldaek
Seldaek Dec 21, 2011 Member

I don't agree. That can easily be done on the returned response. Allowing a response to passed while it's not going to be used (only read and then discarded) is misleading.

@lsmith77
lsmith77 Dec 21, 2011 Contributor

I agree with @Seldaek. unless we think its a very common case that one has a Response coming from somewhere, i think it would be better to leave this feature out. even then we should rather add a method to the StreamedResponse class that populates a defined set of properties from a Response.

@fabpot
fabpot Dec 22, 2011 Member

Instead, I have added the possibility to changed the StreamedResponse callback after its creation and I've forced the $response argument to be an instance of StreamedResponse (see 1d368e75fc 473741b).

@Seldaek
Member
Seldaek commented Dec 21, 2011

Just an idea: what about exposing flush() to twig? Possibly in a way that it will not call it if the template is not streaming. That way you could always add a flush() after your tag to make sure that goes out as fast as possible, but it wouldn't mess with non-streamed responses. Although it appears flush() doesn't affect output buffers, so I guess it doesn't need anything special.

When you say "ESI is not supported.", that means only the AppCache right? I don't see why this would affect Varnish, but then again as far as I know Varnish will buffer if ESI is used so the benefit of streaming there is non-existent.

@cordoval
Contributor

wonder what the use case is for streaming a response, very interesting.

@johnkary
Contributor

@cordoval Common use cases are present fairly well by this RailsCast video: http://railscasts.com/episodes/266-http-streaming

Essentially it allows faster fetching of web assets (JS, CSS, etc) located in the <head></head>, allowing those assets to be fetched as soon as possible before the remainder of the content body is computed and sent to the browser. The end goal is to improve page load speed.

There are other uses cases too like making large body content available quickly to the service consuming it. Think if you were monitoring a live feed of JSON data of newest Twitter comments.

@lsmith77
Contributor

How does this relate the limitations mentioned in:
http://yehudakatz.com/2010/09/07/automatic-flushing-the-rails-3-1-plan/

Am I right to understand that due to how twig works we are not really streaming the content pieces when we call render(), but instead the entire template with its layout is rendered and only then will we flush? or does it mean that the render call will work its way to the top level layout template and form then on it can send the content until it hits another block, which it then first renders before it continues to send the data?

@stof
Member
stof commented Dec 21, 2011

@lsmith77 this is why the stream method calls display in Twig instead of render. display uses echo to print the output of the template line by line (and blocks are simply method calls in the middle). Look at your compiled templates to see it (the doDisplay method)
Rendering a template with Twig simply use an output buffer around the rendering.

@fabpot
Member
fabpot commented Dec 21, 2011

@lsmith77: We don't have the Rails problem thanks to Twig as the order of execution is the right one by default (the layout is executed first); it means that we can have the flush feature without any change to how the core works. As @stof mentioned, we are using display, not render, so we are streaming your templates for byte one.

@fabpot
Member
fabpot commented Dec 21, 2011

@Seldaek: yes, I meant ESI with the PHP reverse proxy.

@fabpot
Member
fabpot commented Dec 21, 2011

@Seldaek: I have flush() support for Twig on my todo-list. As you mentioned, It should be trivial to implement.

@vicb vicb and 1 other commented on an outdated diff Dec 21, 2011
...Symfony/Component/HttpFoundation/StreamedResponse.php
+ }
+
+ /**
+ * @{inheritdoc}
+ *
+ * This method only sends the content once.
+ */
+ public function sendContent()
+ {
+ if ($this->streamed) {
+ return;
+ }
+
+ $this->streamed = true;
+
+ if (!is_callable($this->callback)) {
@vicb
vicb Dec 21, 2011 Contributor

should this test be moved to the ctor ?

@fabpot
fabpot Dec 21, 2011 Member

moved.

@fzaninotto
Contributor

How do streaming responses deal with assets that must be called in the head, but are declared in the body?

@fabpot
Member
fabpot commented Dec 21, 2011

@fzaninotto: What do you mean?

With Twig, your layout is defined with blocks ("holes"). These blocks are overridden by child templates, but evaluated as they are encountered in the layout. So, everything works as expected.

As noted in the commit message, this does not work with PHP templates for the problems mentioned in the Rails post (as the order of execution is not the right one -- the child template is first evaluated and then the layout).

@fzaninotto
Contributor

I was referring to using Assetic. Not sure if this compiles to Twig the same way as javascript and stylesheet blocks placed in the head - and therefore executed in the right way.

@fabpot
Member
fabpot commented Dec 21, 2011

@Seldaek: I've just added a flush tag in Twig 1.5: fabpot/Twig@1d6dfad

@catchamonkey
Contributor

I'm really happy you've got this into the core, it's a great feature to have! Good work.

@vicb vicb commented on the diff Dec 21, 2011
...Symfony/Component/HttpFoundation/StreamedResponse.php
+ }
+
+ $this->streamed = false;
+ }
+
+ /**
+ * @{inheritdoc}
+ */
+ public function prepare(Request $request)
+ {
+ if ('1.0' != $request->server->get('SERVER_PROTOCOL')) {
+ $this->setProtocolVersion('1.1');
+ $this->headers->set('Transfer-Encoding', 'chunked');
+ }
+
+ $this->headers->set('Cache-Control', 'no-cache');
@vicb
vicb Dec 21, 2011 Contributor

There is probably a good reason to avoid caching but I can not figure out, any hint ?

semantic: maybe this should be moved after the call to the parent method ?

@fabpot fabpot added a commit that referenced this pull request Dec 31, 2011
@fabpot fabpot merged branch symfony/streaming (PR #2935)
Commits
-------

887c0e9 moved EngineInterface::stream() to a new StreamingEngineInterface to keep BC with 2.0
473741b added the possibility to change a StreamedResponse callback after its creation
8717d44 moved a test in the constructor
e44b8ba made some cosmetic changes
0038d1b [HttpFoundation] added support for streamed responses

Discussion
----------

[HttpFoundation] added support for streamed responses

To stream a Response, use the StreamedResponse class instead of the
standard Response class:

    $response = new StreamedResponse(function () {
        echo 'FOO';
    });

    $response = new StreamedResponse(function () {
        echo 'FOO';
    }, 200, array('Content-Type' => 'text/plain'));

As you can see, a StreamedResponse instance takes a PHP callback instead of
a string for the Response content. It's up to the developer to stream the
response content from the callback with standard PHP functions like echo.
You can also use flush() if needed.

From a controller, do something like this:

    $twig = $this->get('templating');

    return new StreamedResponse(function () use ($templating) {
        $templating->stream('BlogBundle:Annot:streamed.html.twig');
    }, 200, array('Content-Type' => 'text/html'));

If you are using the base controller, you can use the stream() method instead:

    return $this->stream('BlogBundle:Annot:streamed.html.twig');

You can stream an existing file by using the PHP built-in readfile() function:

    new StreamedResponse(function () use ($file) {
        readfile($file);
    }, 200, array('Content-Type' => 'image/png');

Read http://php.net/flush for more information about output buffering in PHP.

Note that you should do your best to move all expensive operations to
be "activated/evaluated/called" during template evaluation.

Templates
---------

If you are using Twig as a template engine, everything should work as
usual, even if are using template inheritance!

However, note that streaming is not supported for PHP templates. Support
is impossible by design (as the layout is rendered after the main content).

Exceptions
----------

Exceptions thrown during rendering will be rendered as usual except that
some content might have been rendered already.

Limitations
-----------

As the getContent() method always returns false for streamed Responses, some
event listeners won't work at all:

* Web debug toolbar is not available for such Responses (but the profiler works fine);
* ESI is not supported.

Also note that streamed responses cannot benefit from HTTP caching for obvious
reasons.

---------------------------------------------------------------------------

by Seldaek at 2011/12/21 06:34:13 -0800

Just an idea: what about exposing flush() to twig? Possibly in a way that it will not call it if the template is not streaming. That way you could always add a flush() after your </head> tag to make sure that goes out as fast as possible, but it wouldn't mess with non-streamed responses. Although it appears flush() doesn't affect output buffers, so I guess it doesn't need anything special.

When you say "ESI is not supported.", that means only the AppCache right? I don't see why this would affect Varnish, but then again as far as I know Varnish will buffer if ESI is used so the benefit of streaming there is non-existent.

---------------------------------------------------------------------------

by cordoval at 2011/12/21 08:04:21 -0800

wonder what the use case is for streaming a response, very interesting.

---------------------------------------------------------------------------

by johnkary at 2011/12/21 08:19:48 -0800

@cordoval Common use cases are present fairly well by this RailsCast video: http://railscasts.com/episodes/266-http-streaming

Essentially it allows faster fetching of web assets (JS, CSS, etc) located in the &lt;head>&lt;/head>, allowing those assets to be fetched as soon as possible before the remainder of the content body is computed and sent to the browser. The end goal is to improve page load speed.

There are other uses cases too like making large body content available quickly to the service consuming it. Think if you were monitoring a live feed of JSON data of newest Twitter comments.

---------------------------------------------------------------------------

by lsmith77 at 2011/12/21 08:54:35 -0800

How does this relate the limitations mentioned in:
http://yehudakatz.com/2010/09/07/automatic-flushing-the-rails-3-1-plan/

Am I right to understand that due to how twig works we are not really streaming the content pieces when we call render(), but instead the entire template with its layout is rendered and only then will we flush? or does it mean that the render call will work its way to the top level layout template and form then on it can send the content until it hits another block, which it then first renders before it continues to send the data?

---------------------------------------------------------------------------

by stof at 2011/12/21 09:02:53 -0800

@lsmith77 this is why the ``stream`` method calls ``display`` in Twig instead of ``render``. ``display`` uses echo to print the output of the template line by line (and blocks are simply method calls in the middle). Look at your compiled templates to see it (the ``doDisplay`` method)
Rendering a template with Twig simply use an output buffer around the rendering.

---------------------------------------------------------------------------

by fabpot at 2011/12/21 09:24:33 -0800

@lsmith77: We don't have the Rails problem thanks to Twig as the order of execution is the right one by default (the layout is executed first); it means that we can have the flush feature without any change to how the core works. As @stof mentioned, we are using `display`, not `render`, so we are streaming your templates for byte one.

---------------------------------------------------------------------------

by fabpot at 2011/12/21 09:36:41 -0800

@Seldaek: yes, I meant ESI with the PHP reverse proxy.

---------------------------------------------------------------------------

by fabpot at 2011/12/21 09:37:34 -0800

@Seldaek: I have `flush()` support for Twig on my todo-list. As you mentioned, It should be trivial to implement.

---------------------------------------------------------------------------

by fzaninotto at 2011/12/21 09:48:18 -0800

How do streaming responses deal with assets that must be called in the head, but are declared in the body?

---------------------------------------------------------------------------

by fabpot at 2011/12/21 09:52:12 -0800

@fzaninotto: What do you mean?

With Twig, your layout is defined with blocks ("holes"). These blocks are overridden by child templates, but evaluated as they are encountered in the layout. So, everything works as expected.

As noted in the commit message, this does not work with PHP templates for the problems mentioned in the Rails post (as the order of execution is not the right one -- the child template is first evaluated and then the layout).

---------------------------------------------------------------------------

by fzaninotto at 2011/12/21 10:07:35 -0800

I was referring to using Assetic. Not sure if this compiles to Twig the same way as javascript and stylesheet blocks placed in the head - and therefore executed in the right way.

---------------------------------------------------------------------------

by fabpot at 2011/12/21 10:34:59 -0800

@Seldaek: I've just added a `flush` tag in Twig 1.5: fabpot/Twig@1d6dfad

---------------------------------------------------------------------------

by catchamonkey at 2011/12/21 13:29:22 -0800

I'm really happy you've got this into the core, it's a great feature to have! Good work.
899e252
@fabpot fabpot merged commit 887c0e9 into master Dec 31, 2011
@b00giZm b00giZm commented on the diff Dec 31, 2011
src/Symfony/Component/Templating/PhpEngine.php
@@ -28,7 +28,7 @@
*
* @api
*/
-class PhpEngine implements EngineInterface, \ArrayAccess
+class PhpEngine implements EngineInterface, StreamingEngineInterface, \ArrayAccess
@b00giZm
b00giZm Dec 31, 2011

Is there a reason why the PhpEngine implements the StreamingEngineInterface just to throw an Exception? Didn't you say that streaming responses are impossible for PHP templates by design?

@fabpot
fabpot Dec 31, 2011 Member

You are right. This is a nice side-effect of having another interface for streaming. Fixed here: e462f7b

@igorw
Contributor
igorw commented Dec 31, 2011

I ran into some problems with the way this is implemented. The issues are related to transfer-encoding.

First, it is not possible to force identity (the default) encoding, because StreamedResponse overrides it in any case. It should only set the encoding if none is set yet. Note: Even if it is explicitly set to identity and not overriden to chunked, apache will change it to "identity, chunked" because no content-length is set.

Now this brings us to the second issue. When you send a transfer-encoding=chunked header, apache will expect the content to already be chunked. Of course this is not the case, and thus it does not work. Unless other webservers require it to be present, I would suggest simply not setting the transfer-encoding header. Since no content-length is set, the server should switch to chunked automatically.

@igorw
Contributor
igorw commented Dec 31, 2011

Nginx does not have that problem. But: Proposed solution confirmed working with nginx 1.0.5.

@fabpot fabpot added a commit that referenced this pull request Jan 2, 2012
@fabpot fabpot merged branch igorw/streaming (PR #3017)
Commits
-------

7ae9348 [streaming] Document and test that Transfer-Encoding is absent
83c23ca [streaming] Do not set a Transfer-Encoding header of chunked

Discussion
----------

[HttpFoundation] Do not set a Transfer-Encoding header of chunked for streaming responses

Bug fix: yes
Feature addition: no
Backwards compatibility break: no
Symfony2 tests pass: yes

This is an adjustment to the streaming introduced in #2935.

Apache expects the response to already be in chunked format when the Transfer-Encoding header is set to chunked, which causes it to not deliver the streamed body.

If no Content-Length is set on the response (regardless of Transfer-Encoding), web servers will automatically switch to chunked Transfer-Encoding, and handle the chunking for you.

I have tested this with Apache 2.2.2 and nginx 1.0.5. Testing with other webservers is appreciated.
99f65ec
@akucherevskiy akucherevskiy pushed a commit to akucherevskiy/silex_auttaa that referenced this pull request Jul 26, 2014
@fabpot fabpot merged branch igorw/stream (PR #229)
Commits
-------

8dd779e [stream] Fix rst formatting
73fe0b1 [stream] Add a nice API for streaming responses

Discussion
----------

[stream] Add a nice API for streaming responses

This PR adds a nice API for creating streaming responses with the new StreamedResponse class that was added to symfony [just recently](symfony/symfony#2935).

Usage is documented in the usage doc, but for lazy people:

```php
<?php

$app->get('/images/{file}', function ($file) use ($app) {
    if (!file_exists(__DIR__.'/images/'.$file)) {
        return $app->abort(404, 'The image was not found.');
    }

    $stream = function () use ($file) {
        readfile($file);
    };

    return $app->stream($stream, 200, array('Content-Type' => 'image/png'));
});
```

Note: I had to point the autoloader to a copy of symfony/symfony because the subtree splits are not up-to-date. Thus the vendors are out of date too right now.
87149e5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment