-
Notifications
You must be signed in to change notification settings - Fork 7.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add headers_send_early_and_clear() function for HTTP Early Hints #7025
base: master
Are you sure you want to change the base?
Conversation
This is intended to be used in conjunction with HTTP Early Hints and other 1xx status codes, which require sending multiple sets of headers. Usage: header('HTTP/1.1 103 Early Hints'); header('Link: </style.css>; rel=preload; as=style'); headers_send_early_and_clear(); // Normal code goes here. You can send more headers and body // here.
I have to admit I was thinking about "early and clear" sending when reading the signature which confused me (what's "clear sending"?) until seeing the code. Maybe it just should be called |
My original name was I'd like to have something that makes it obvious that this only sends one set of headers, and you can send more afterwards. headers_flush() sounds like it will just send the normal headers early, instead of waiting for first output. |
But … that's what it does? Or do you mean the autogenerated headers which are sent on first byte if not set yet? |
No, this allows you to send multiple independent sets of headers, not just flush a single set early. |
Yes exactly, just like on a stream you can flush multiple times. (until the stream is closed, or in this case the first data byte sent) |
|
The function resets all headers (including status), so you'll get the default status of 200, unless it is explicitly changed. |
According to the RFC, the headers sent in the early response must also be included in the final response to be taken into account. While your proposal allows to do this, it means that the user must not forget to re-add the headers in the final response. In Go (golang/go#42597) we followed another approach: headers are preserved by default, but can be removed if needed. Wouldn't a similar approach be more user friendly in PHP too? Having two different functions would allow to do it easily. Example: // Typical case
header('Link: </style.css>; rel=preload; as=style');
headers_send(103);
// Result: the Link header will also be included in the final response:
//
// HTTP/1.1 103 Early Hints
// Link: </style.css>; rel=preload; as=style
//
// HTTP/1.1 200 OK
// Link: </style.css>; rel=preload; as=style
// ... // More advanced case
header('Link: </style.css>; rel=preload; as=style');
headers_send(103);
headers_clear();
headers_send(500);
// Result: the Link header will not be included in the final response and will be discarded by the client
//
// HTTP/1.1 103 Early Hints
// Link: </style.css>; rel=preload; as=style
//
// HTTP/1.1 500 OK
// ... [no Link header] WDYT? |
I like that! Since no HTTP RFC has a use case for not sending the same headers, no need to add headers_clear() actually |
@nikic, I wonder if this patch really works: This works for sure (but maybe by accident) with HTTP/1. However, with HTTP/2 and HTTP/3 things get harder: HTTP/2 and 3 are binary protocols. Responses must be sent using "special" binary frames (for instance, here is the Go implementation of the 103 status code for HTTP/3: quic-go/quic-go#3047). According to the CGI spec (which is inherited by FastCGI), a CGI script can only send one response, and this response cannot have a 1XX status code. I fear that with the current implementation, the web server will send the second (final) response as part of the body frame instead of being "converted" in a new response frame. I've not tested this, but from my understanding of the spec, I don't see how it could behave differently. While it's not an issue with HTTP/1.1 (because it's a text protocol), it will break sites using HTTP/2 or 3. |
@mnot would you have some advice for us here? We might need an update to rfc3875 maybe, or to fastcgi? |
^^ @kazuho |
So, CGI doesn't constraint HTTP/1.1; it just constraints resources that use it. It may be that PHP hosted by FastCGI might not have the ability to send a 1xx response, but I think documenting that limitation should be sufficient. If there's wide enough interest, CGI could be updated to support non-final responses, but that depends mostly on implementation interest... WRT the |
The issue is that FastCGI is by far the most widespread way to run a PHP web app. Updating CGI to 1.2 to add support for 1xx responses looks quite desired from this end. What's the best place to talk about this and let implementers know about it, or just ask them? On the php-fpm side, A pure php-side alternative might be to make php-fpm talk HTTP instead of fastcgi (in addition to actually). But then, we'll loose all the nice integration that CGI provides thanks to meta-variables, which could be a blocker for the community. For the record, because it took me some time to figure this out, CGI clients are expected to parse the response from the CGI server (PHP) and to "fix" the headers, like turning |
I guess the bigger challenge would be then to update the FastCGI spec which is not really standardized anywhere that I know. We partially (not all features are implemented) follow what's here but that's just archive as the main site is no longer available. It would be quite useful to have a real standard for it though. |
FastCGI isn't really maintained, so I think it's reasonable for PHP to prototype an extension and perhaps talk to other implementations about it. |
Early Hints seems to be gaining traction. https://twitter.com/addyosmani/status/1542032618396475392 |
Would be interesting to see this in PHP as now dunglas have add it to GoLang I think we maybe will see it soon supported by webserver like caddy: caddyserver/caddy#4860 |
I've actually been thinking about that sporadically. The main thing I think a new spec of CGI could bring to the table is channels beyond I haven't yet gotten a handle on how to make that portable, though. Hmm. |
For the record, Early Hints have been implemented in FrankenPHP. This SAPI provides a |
…Early Hints) and other 1XX statuses (dunglas) This PR was squashed before being merged into the 6.3 branch. Discussion ---------- [HttpFoundation] Add support for the 103 status code (Early Hints) and other 1XX statuses | Q | A | ------------- | --- | Branch? | 6.3 | Bug fix? | no | New feature? | yes <!-- please update src/**/CHANGELOG.md files --> | Deprecations? | yes <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files --> | Tickets | n/a <!-- prefix each issue number with "Fix #", no need to create an issue if none exists, explain below instead --> | License | MIT | Doc PR | todo This patch adds support for sending informational responses, including [Early Hints responses](https://developer.chrome.com/blog/early-hints/) if supported by the SAPI. It also allows sending other informational status codes such as 102 Processing. According to [Shopify](https://twitter.com/colinbendell/status/1539322190541295616) and [Cloudflare](http://blog.cloudflare.com/early-hints-performance), using Early Hints, the performance improvement to the Largest Contentful Paint can go from several hundred milliseconds, and up to a second faster. Usage: ```php <?php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\WebLink\Link; class HomepageController extends AbstractController { #[Route("/", name: "homepage")] public function index(): Response { $response = $this->sendEarlyHints([ (new Link(href: '/style.css'))->withAttribute('as', 'stylesheet'), (new Link(href: '/script.js'))->withAttribute('as', 'script'), ]); // Do something slow... return $this->render('homepage/index.html.twig', response: $response); } } ``` With this patch, HttpFoundation will leverage the `headers_send()` function provided by [FrankenPHP](https://frankenphp.dev). FrankenPHP is currently the only SAPI supporting Early Hints, but other SAPI such as mod_apache will probably implement this function at some point: php/php-src#7025 (comment) The low-level API is similar to the one provided by Go: golang/go#42597 The high-level API helper in `AbstractController` is similar to Node's one: nodejs/node#44180 Commits ------- 5be52b2 [HttpFoundation] Add support for the 103 status code (Early Hints) and other 1XX statuses
Hello everyone, whats need to be done to make it work? |
@nikic ^^ |
@igoryusha22 currently you need to use frankenphp as your webserver too use such feature: https://github.com/dunglas/frankenphp/blob/main/docs/early-hints.md |
Any news? |
I looked into this again and the current form is quite limited. For FastCGI, we would need to extend it and add support on the client (FPM) server side (httpd/nginx) - it's quite a big project and we were actually looking for a special funding for that work but so far there is not an extra funding for that. I think apache2handler might need also more work but it first needs some fixes to work well with http2 (mainly mpm event). And CLI server is just for development so it's not that useful if it works just there. But I would need to test that part So to sum it up, we are not there yet and it might take some time to get there. |
This is intended to be used in conjunction with HTTP Early Hints
and other 1xx status codes, which require sending multiple sets
of headers.
Usage: