Problem/Motivation

Follow-up from #3276186: [upstream] BigPipe breaks with symfony/http-foundation 6.1.

See also the upstream issue: https://github.com/symfony/symfony/issues/46449

Steps to reproduce

Proposed resolution

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

CommentFileSizeAuthor
#3 3283145-3.patch2.96 KBwim leers

Comments

catch created an issue. See original summary.

wim leers’s picture

Issue tags: +Symfony 6.1

https://github.com/symfony/symfony/issues/46449 was closed ~30 mins ago: the upstream change that caused this regression was reverted.

wim leers’s picture

Status: Active » Needs review
StatusFileSize
new2.96 KB

But @stof makes an interesting point at https://github.com/symfony/symfony/issues/46449#issuecomment-1136082446:

@catch56 if you send content in a special way, BigPipeResponse should probably set `false` as the buffered content of the response instead of setting the unprocessed content.

That seems reasonable too. \Symfony\Component\HttpFoundation\StreamedResponse::setContent() does something similar.

This patch does that.

wim leers’s picture

And

> (you could also extend from StreamedResponse if that'd make sense btw)

Unfortunately we can't, because `class BigPipeResponse extends HtmlResponse` and `class HtmlResponse extends Response implements CacheableResponseInterface, AttachmentsInterface`.

> Stream

adds to that.

I was going to respond that unfortunately we can't, because class BigPipeResponse extends HtmlResponse and class HtmlResponse extends Response implements CacheableResponseInterface, AttachmentsInterface.

But … HtmlResponse barely does anything. So we can absolutely change this. Nope, we can't, because we have lots of code doing $response instanceof HtmlResponse checks … 😬

Status: Needs review » Needs work

The last submitted patch, 3: 3283145-3.patch, failed testing. View results

catch’s picture

@Wim Leers I started looking at the same thing after Stof's comment, but the problem is much less HtmlResponse than \Drupal\big_pipe\Render\BigPipe::sendChunk() calls BigPipeResponse::getContent(), which accesses $this->content.

And the comment in BigPipeResponse explaining why it can't use StreamedResponse is explicitly because it wants to make $content available to middlewares, instead of putting it behind a callback.

So to me this part of things seemed 100% by design from our end.

The questions for me are:

1. Can we actually use Transfer-Encoding: chunked? We could probably shim the encoding into BigPipe::sendChunk() (also see comment on the original issue) to find out.

2. Is there definitely a reason we can't put $content behind a callback and use streamed response, or otherwise not use that property.

andypost’s picture

we have lots of code doing $response instanceof HtmlResponse checks … 😬

I bet we can touch all this places

wim leers’s picture

I suppose we should do this before Drupal 10?

andypost’s picture

I think as it's task it doable in 9.5 too

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

bradjones1’s picture

Version: 9.5.x-dev » 10.1.x-dev

This is important also because any post-response processing (e.g., destructable services) keeps the client "spinner" spinning when the server uses mod_php, even though all the content has been sent. See #3295790-69: Post-response task running (destructable services) are actually blocking; add test coverage and warn for common misconfiguration.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

catch’s picture

Title: Reconcile BigPipeResponse with upstream changes regarding content length » Try to use either Transfer-Encoding: chunked or a real streamed response for BigPipeResponse
Issue tags: +Performance

Retitling to make it clearer what this is about.

wim leers’s picture

Idea: if Symfony actually added support for HTTP trailers, then we could change BigPipe to rely on that instead — we wouldn't have to mess with Response::$content anymore!

Still zero interest in this upstream — maybe you and I could collaborate to figure out how to bring that to Symfony?

EDIT: and that would in principle also allow us to bring https://www.drupal.org/project/big_pipe_sessionless to Drupal core — see the description on that page which also mentions HTTP trailers 🤓

catch’s picture

Huh interesting, my main interest here in using fully chunked encoding -

Can we actually use Transfer-Encoding: chunked? We could probably shim the encoding into BigPipe::sendChunk() (also see comment on the original issue) to find out.

so that we can send content length in a way the browser understands. The only drawback is that http2 works against this a bit so it might be already outdated to some extent.

Does trailers help with this or is that mainly for the cache tags bit?

wim leers’s picture

Quoting MDN:

The Trailer response header allows the sender to include additional fields at the end of chunked messages in order to supply metadata that might be dynamically generated while the message body is sent, such as a message integrity check, digital signature, or post-processing status.

IOW: it still requires chunked/streamed responses … but it just would fit better with Symfony's desire/intent to disallow manipulating Response::$content.

That's it.

No HTTP-level reason. Only a Symfony-level reason.

catch’s picture

Ahh gotcha, we might need both things then.

longwave’s picture

Found this after looking into BigPipe behind Amazon CloudFront, where I don't appear to be seeing the content streamed correctly - instead the response is delivered all in one go.

CloudFront docs say:

CloudFront supports only the chunked value of the Transfer-Encoding header. If your origin returns Transfer-Encoding: chunked, CloudFront returns the object to the client as the object is received at the edge location, and caches the object in chunked format for subsequent requests.

BigPipe.php says

    // BigPipe sends responses using "Transfer-Encoding: chunked".

but it appears this is not actually true?

mfb’s picture

As far as I've seen, Big Pipe does send chunked? All you have to do is print something and then call flush(); and the Transfer-Encoding: chunked header should be added automatically. HTTP/2 or HTTP/3 don't support it though, so you might not literally see a chunked transfer, depending on the stack.

catch’s picture

Title: Try to use either Transfer-Encoding: chunked or a real streamed response for BigPipeResponse » Try to use HTTP trailers or a real streaming response in Big Pipe.
All you have to do is print something and then call flush(); and the Transfer-Encoding: chunked header should be added automatically. 

OK yeah so PHP is supposed to send it if you call flush() as long as there's not a content length header (which there isn't for bigpipe), which makes this issue title a bit tautological. Good to figure that out before anything actually got going trying to reimplement the encoding in bigpipe though.

But switching to using trailers would be really nice and maybe more compatible with http/2?

Let's retitle. https://www.fastly.com/blog/supercharging-server-timing-http-trailers suggests some CDN support (although not for this use case at all).

mfb’s picture

re: compatible with HTTP/2, I'd hope that proxies convert HTTP/1.1 chunked transfer to HTTP/2 (or HTTP/3) data frames and pass them along, without excessive buffering, but not something I've analyzed

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.