diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index ecd2d50c5d..f955f97571 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.16 under development ------------------------ +- Bug #16766: `yii\filters\ContentNegotiator` was not setting `Vary` header to inform cache recipients (koteq, cebe, samdark) - Bug #11960: Fixed `checked` option ignore in `yii\helpers\BaseHtml::checkbox()` (misantron) - Bug #14759: Fixed `yii\web\JsonResponseFormatter` output for `null` data (misantron) - Bug #16490: Fix schema on rbac init (marcelodeandrade) diff --git a/framework/filters/ContentNegotiator.php b/framework/filters/ContentNegotiator.php index a84b7280cd..84ca104ec5 100644 --- a/framework/filters/ContentNegotiator.php +++ b/framework/filters/ContentNegotiator.php @@ -154,9 +154,15 @@ class ContentNegotiator extends ActionFilter implements BootstrapInterface $request = $this->request ?: Yii::$app->getRequest(); $response = $this->response ?: Yii::$app->getResponse(); if (!empty($this->formats)) { + if (\count($this->formats) > 1) { + $response->getHeaders()->add('Vary', 'Accept'); + } $this->negotiateContentType($request, $response); } if (!empty($this->languages)) { + if (\count($this->languages) > 1) { + $response->getHeaders()->add('Vary', 'Accept-Language'); + } Yii::$app->language = $this->negotiateLanguage($request); } } diff --git a/tests/framework/filters/ContentNegotiatorTest.php b/tests/framework/filters/ContentNegotiatorTest.php index fae753baa0..c8ecc27929 100644 --- a/tests/framework/filters/ContentNegotiatorTest.php +++ b/tests/framework/filters/ContentNegotiatorTest.php @@ -76,4 +76,45 @@ class ContentNegotiatorTest extends TestCase $filter->beforeAction($action); } + + public function testVaryHeader() + { + list($action, $filter) = $this->mockActionAndFilter(); + $filter->formats = []; + $filter->languages = []; + $filter->beforeAction($action); + $this->assertFalse($filter->response->getHeaders()->has('Vary')); + + list($action, $filter) = $this->mockActionAndFilter(); + $filter->formats = ['application/json' => Response::FORMAT_JSON]; + $filter->languages = ['en']; + $filter->beforeAction($action); + $this->assertFalse($filter->response->getHeaders()->has('Vary')); // There is still nothing to vary + + list($action, $filter) = $this->mockActionAndFilter(); + $filter->formats = [ + 'application/json' => Response::FORMAT_JSON, + 'application/xml' => Response::FORMAT_XML, + ]; + $filter->languages = []; + $filter->beforeAction($action); + $this->assertContains('Accept', $filter->response->getHeaders()->get('Vary', [], false)); + + list($action, $filter) = $this->mockActionAndFilter(); + $filter->formats = []; + $filter->languages = ['en', 'de']; + $filter->beforeAction($action); + $this->assertContains('Accept-Language', $filter->response->getHeaders()->get('Vary', [], false)); + + list($action, $filter) = $this->mockActionAndFilter(); + $filter->formats = [ + 'application/json' => Response::FORMAT_JSON, + 'application/xml' => Response::FORMAT_XML, + ]; + $filter->languages = ['en', 'de']; + $filter->beforeAction($action); + $varyHeader = $filter->response->getHeaders()->get('Vary', [], false); + $this->assertContains('Accept', $varyHeader); + $this->assertContains('Accept-Language', $varyHeader); + } }