Skip to content

Commit

Permalink
bug #33487 [HttpKernel] Fix Apache mod_expires Session Cache-Control …
Browse files Browse the repository at this point in the history
…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
-------

9e94276 [HttpKernel] Fix Apache mod_expires Session Cache-Control issue
  • Loading branch information
fabpot committed Sep 8, 2019
2 parents fbeef96 + 9e94276 commit 206ad49
Show file tree
Hide file tree
Showing 2 changed files with 9 additions and 0 deletions.
Expand Up @@ -56,6 +56,7 @@ public function onKernelResponse(FilterResponseEvent $event)

if ($session instanceof Session ? $session->getUsageIndex() !== end($this->sessionUsageStack) : $session->isStarted()) {
$event->getResponse()
->setExpires(new \DateTime())
->setPrivate()
->setMaxAge(0)
->headers->addCacheControlDirective('must-revalidate');
Expand Down
Expand Up @@ -75,6 +75,9 @@ public function testResponseIsPrivate()
$this->assertTrue($response->headers->hasCacheControlDirective('private'));
$this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate'));
$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()
Expand Down Expand Up @@ -104,10 +107,15 @@ public function testSurrogateMasterRequestIsPublic()
$this->assertFalse($response->headers->hasCacheControlDirective('must-revalidate'));
$this->assertSame('30', $response->headers->getCacheControlDirective('max-age'));

$this->assertFalse($response->headers->has('Expires'));

$listener->onKernelResponse(new FilterResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response));

$this->assertTrue($response->headers->hasCacheControlDirective('private'));
$this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate'));
$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'))));
}
}

0 comments on commit 206ad49

Please sign in to comment.