Skip to content

Commit 94dd873

Browse files
dunglasnicolas-grekas
authored andcommitted
[HttpFoundation] Add support for the 103 status code (Early Hints) and other 1XX statuses
1 parent 2f41eac commit 94dd873

File tree

5 files changed

+57
-1
lines changed

5 files changed

+57
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ CHANGELOG
1717
* Allow setting `debug.container.dump` to `false` to disable dumping the container to XML
1818
* Add `framework.http_cache.skip_response_headers` option
1919
* Display warmers duration on debug verbosity for `cache:clear` command
20+
* Add `AbstractController::sendEarlyHints()` to send HTTP Early Hints
2021

2122
6.2
2223
---

Controller/AbstractController.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Bundle\FrameworkBundle\Controller;
1313

1414
use Psr\Container\ContainerInterface;
15+
use Psr\Link\EvolvableLinkInterface;
1516
use Psr\Link\LinkInterface;
1617
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
1718
use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface;
@@ -42,6 +43,7 @@
4243
use Symfony\Component\Serializer\SerializerInterface;
4344
use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener;
4445
use Symfony\Component\WebLink\GenericLinkProvider;
46+
use Symfony\Component\WebLink\HttpHeaderSerializer;
4547
use Symfony\Contracts\Service\Attribute\Required;
4648
use Symfony\Contracts\Service\ServiceSubscriberInterface;
4749
use Twig\Environment;
@@ -92,6 +94,7 @@ public static function getSubscribedServices(): array
9294
'security.token_storage' => '?'.TokenStorageInterface::class,
9395
'security.csrf.token_manager' => '?'.CsrfTokenManagerInterface::class,
9496
'parameter_bag' => '?'.ContainerBagInterface::class,
97+
'web_link.http_header_serializer' => '?'.HttpHeaderSerializer::class,
9598
];
9699
}
97100

@@ -402,4 +405,30 @@ protected function addLink(Request $request, LinkInterface $link): void
402405

403406
$request->attributes->set('_links', $linkProvider->withLink($link));
404407
}
408+
409+
/**
410+
* @param LinkInterface[] $links
411+
*/
412+
protected function sendEarlyHints(iterable $links, Response $response = null): Response
413+
{
414+
if (!$this->container->has('web_link.http_header_serializer')) {
415+
throw new \LogicException('You cannot use the "sendEarlyHints" method if the WebLink component is not available. Try running "composer require symfony/web-link".');
416+
}
417+
418+
$response ??= new Response();
419+
420+
$populatedLinks = [];
421+
foreach ($links as $link) {
422+
if ($link instanceof EvolvableLinkInterface && !$link->getRels()) {
423+
$link = $link->withRel('preload');
424+
}
425+
426+
$populatedLinks[] = $link;
427+
}
428+
429+
$response->headers->set('Link', $this->container->get('web_link.http_header_serializer')->serialize($populatedLinks), false);
430+
$response->sendHeaders(103);
431+
432+
return $response;
433+
}
405434
}

Resources/config/web_link.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,18 @@
1212
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
1313

1414
use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener;
15+
use Symfony\Component\WebLink\HttpHeaderSerializer;
1516

1617
return static function (ContainerConfigurator $container) {
1718
$container->services()
19+
20+
->set('web_link.http_header_serializer', HttpHeaderSerializer::class)
21+
->alias(HttpHeaderSerializer::class, 'web_link.http_header_serializer')
22+
1823
->set('web_link.add_link_header_listener', AddLinkHeaderListener::class)
24+
->args([
25+
service('web_link.http_header_serializer'),
26+
])
1927
->tag('kernel.event_subscriber')
2028
;
2129
};

Tests/Controller/AbstractControllerTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
use Symfony\Component\Security\Core\User\InMemoryUser;
4646
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
4747
use Symfony\Component\Serializer\SerializerInterface;
48+
use Symfony\Component\WebLink\HttpHeaderSerializer;
4849
use Symfony\Component\WebLink\Link;
4950
use Twig\Environment;
5051

@@ -72,6 +73,7 @@ public function testSubscribedServices()
7273
'parameter_bag' => '?Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBagInterface',
7374
'security.token_storage' => '?Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorageInterface',
7475
'security.csrf.token_manager' => '?Symfony\\Component\\Security\\Csrf\\CsrfTokenManagerInterface',
76+
'web_link.http_header_serializer' => '?Symfony\\Component\\WebLink\\HttpHeaderSerializer',
7577
];
7678

7779
$this->assertEquals($expectedServices, $subscribed, 'Subscribed core services in AbstractController have changed');
@@ -677,4 +679,20 @@ public function testAddLink()
677679
$this->assertContains($link1, $links);
678680
$this->assertContains($link2, $links);
679681
}
682+
683+
public function testSendEarlyHints()
684+
{
685+
$container = new Container();
686+
$container->set('web_link.http_header_serializer', new HttpHeaderSerializer());
687+
688+
$controller = $this->createController();
689+
$controller->setContainer($container);
690+
691+
$response = $controller->sendEarlyHints([
692+
(new Link(href: '/style.css'))->withAttribute('as', 'stylesheet'),
693+
(new Link(href: '/script.js'))->withAttribute('as', 'script'),
694+
]);
695+
696+
$this->assertSame('</style.css>; rel="preload"; as="stylesheet",</script.js>; rel="preload"; as="script"', $response->headers->get('Link'));
697+
}
680698
}

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"symfony/deprecation-contracts": "^2.5|^3",
2626
"symfony/error-handler": "^6.1",
2727
"symfony/event-dispatcher": "^5.4|^6.0",
28-
"symfony/http-foundation": "^6.2",
28+
"symfony/http-foundation": "^6.3",
2929
"symfony/http-kernel": "^6.3",
3030
"symfony/polyfill-mbstring": "~1.0",
3131
"symfony/filesystem": "^5.4|^6.0",

0 commit comments

Comments
 (0)