bug #33487 [HttpKernel] Fix Apache mod_expires Session Cache-Control issue (pbowyer)
This PR was squashed before being merged into the 3.4 branch (closes #33487).
Discussion
----------
[HttpKernel] Fix Apache mod_expires Session Cache-Control issue
| Q | A
| ------------- | ---
| Branch? | 3.4 for bug fixes <!-- see below -->
| Bug fix? | yes
| New feature? | no <!-- please update src/**/CHANGELOG.md files -->
| BC breaks? | no <!-- see https://symfony.com/bc -->
| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tests pass? | yes <!-- please add some, will be required by reviewers -->
| License | MIT
Apaches's [mod_expires](https://httpd.apache.org/docs/current/mod/mod_expires.html) is a widely used module to set HTTP caching headers. It allows you to set a default cache lifetime as well as lifetimes by mime_type.
When an application server has set a `Cache-Control` header, mod_expires ignores this and sets its own, resulting in duplicate `Cache-Control` headers and conflicting information. It does this _unless_ the application server sets an `Expires` header, in which case mod_expires does nothing. This is documented on the link above:
> When the `Expires` header is already part of the response generated by the server, for example when generated by a CGI script or proxied from an origin server, this module does not change or add an `Expires` or `Cache-Control` header.
Symfony automatically sets a `Cache-Control` header if a session exists. This patch adds an `Expires` header to ensure it's respected by mod_expires.
## Example 1
With the following Apache config:
```apache
<IfModule mod_expires.c>
ExpiresActive on
ExpiresDefault "access plus 1 month"
</IfModule>
```
The HTTP response headers are:
### Without the patch
```
HTTP/1.1 200 OK
Date: Fri, 06 Sep 2019 08:02:02 GMT
Server: Apache/2.4.37 (Ubuntu)
Cache-Control: max-age=0, must-revalidate, private
Cache-Control: max-age=2592000
Expires: Sun, 06 Oct 2019 08:02:00 GMT
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 13099
Connection: close
Content-Type: text/html; charset=UTF-8
```
### With the patch
```
HTTP/1.1 200 OK
Date: Fri, 06 Sep 2019 08:21:34 GMT
Server: Apache/2.4.37 (Ubuntu)
Cache-Control: max-age=0, must-revalidate, private
Expires: Fri, 06 Sep 2019 08:21:34 GMT
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 13098
Connection: close
Content-Type: text/html; charset=UTF-8
```
## Example 2
With the following Apache config:
```apache
<IfModule mod_expires.c>
ExpiresActive on
ExpiresDefault "access plus 1 month"
ExpiresByType text/html "access plus 0 seconds"
</IfModule>
```
### Without the patch
```
HTTP/1.1 200 OK
Date: Fri, 06 Sep 2019 08:18:40 GMT
Server: Apache/2.4.37 (Ubuntu)
Cache-Control: max-age=0, must-revalidate, private
Cache-Control: max-age=0
Expires: Fri, 06 Sep 2019 08:18:39 GMT
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 13099
Connection: close
Content-Type: text/html; charset=UTF-8
```
### With the patch
```
HTTP/1.1 200 OK
Date: Fri, 06 Sep 2019 08:20:40 GMT
Server: Apache/2.4.37 (Ubuntu)
Cache-Control: max-age=0, must-revalidate, private
Expires: Fri, 06 Sep 2019 08:20:40 GMT
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 13100
Connection: close
Content-Type: text/html; charset=UTF-8
```
Commits
-------
9e942768c9
[HttpKernel] Fix Apache mod_expires Session Cache-Control issue
This commit is contained in:
commit
206ad498c1
@ -56,6 +56,7 @@ abstract class AbstractSessionListener implements EventSubscriberInterface
|
|||||||
|
|
||||||
if ($session instanceof Session ? $session->getUsageIndex() !== end($this->sessionUsageStack) : $session->isStarted()) {
|
if ($session instanceof Session ? $session->getUsageIndex() !== end($this->sessionUsageStack) : $session->isStarted()) {
|
||||||
$event->getResponse()
|
$event->getResponse()
|
||||||
|
->setExpires(new \DateTime())
|
||||||
->setPrivate()
|
->setPrivate()
|
||||||
->setMaxAge(0)
|
->setMaxAge(0)
|
||||||
->headers->addCacheControlDirective('must-revalidate');
|
->headers->addCacheControlDirective('must-revalidate');
|
||||||
|
@ -75,6 +75,9 @@ class SessionListenerTest extends TestCase
|
|||||||
$this->assertTrue($response->headers->hasCacheControlDirective('private'));
|
$this->assertTrue($response->headers->hasCacheControlDirective('private'));
|
||||||
$this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate'));
|
$this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate'));
|
||||||
$this->assertSame('0', $response->headers->getCacheControlDirective('max-age'));
|
$this->assertSame('0', $response->headers->getCacheControlDirective('max-age'));
|
||||||
|
|
||||||
|
$this->assertTrue($response->headers->has('Expires'));
|
||||||
|
$this->assertLessThanOrEqual((new \DateTime('now', new \DateTimeZone('UTC'))), (new \DateTime($response->headers->get('Expires'))));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSurrogateMasterRequestIsPublic()
|
public function testSurrogateMasterRequestIsPublic()
|
||||||
@ -104,10 +107,15 @@ class SessionListenerTest extends TestCase
|
|||||||
$this->assertFalse($response->headers->hasCacheControlDirective('must-revalidate'));
|
$this->assertFalse($response->headers->hasCacheControlDirective('must-revalidate'));
|
||||||
$this->assertSame('30', $response->headers->getCacheControlDirective('max-age'));
|
$this->assertSame('30', $response->headers->getCacheControlDirective('max-age'));
|
||||||
|
|
||||||
|
$this->assertFalse($response->headers->has('Expires'));
|
||||||
|
|
||||||
$listener->onKernelResponse(new FilterResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response));
|
$listener->onKernelResponse(new FilterResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response));
|
||||||
|
|
||||||
$this->assertTrue($response->headers->hasCacheControlDirective('private'));
|
$this->assertTrue($response->headers->hasCacheControlDirective('private'));
|
||||||
$this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate'));
|
$this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate'));
|
||||||
$this->assertSame('0', $response->headers->getCacheControlDirective('max-age'));
|
$this->assertSame('0', $response->headers->getCacheControlDirective('max-age'));
|
||||||
|
|
||||||
|
$this->assertTrue($response->headers->has('Expires'));
|
||||||
|
$this->assertLessThanOrEqual((new \DateTime('now', new \DateTimeZone('UTC'))), (new \DateTime($response->headers->get('Expires'))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user