diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 8a0ce3e16e..48f5334bcb 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -26,6 +26,7 @@ Yii Framework 2 Change Log - Enh #11979: Added `yii\mutex\OracleMutex` which implements mutex "lock" mechanism via Oracle locks (zlakomanoff) - Enh #12082: Used `jQuery.on(` instead of event method to ensure forwards compatibility (newerton) - Enh #12028: Add -h|--help option to console command to display help information (pana1990) +- Enh #12099: HttpCache no longer returns 304 HTTP code when callbacks return null (sergeymakinen) - Bug #12053: `./yii migrate/create` was generating wrong code when using `bigPrimaryKey` (VojtechH, samdark) - Bug #11907: Fixed `yii\helpers\Console::getScreenSize()` on Windows was giving out width and height swapped (Spell6inder, samdark, cebe) - Bug #11973: Fixed `yii\helpers\BaseHtml::getAttributeValue()` to work with `items[]` notation correctly (silverfire) diff --git a/framework/filters/HttpCache.php b/framework/filters/HttpCache.php index 8df842f78b..072df0d941 100644 --- a/framework/filters/HttpCache.php +++ b/framework/filters/HttpCache.php @@ -130,7 +130,9 @@ class HttpCache extends ActionFilter } if ($this->etagSeed !== null) { $seed = call_user_func($this->etagSeed, $action, $this->params); - $etag = $this->generateEtag($seed); + if ($seed !== null) { + $etag = $this->generateEtag($seed); + } } $this->sendCacheControlHeader(); @@ -140,20 +142,22 @@ class HttpCache extends ActionFilter $response->getHeaders()->set('Etag', $etag); } - if ($this->validateCache($lastModified, $etag)) { + $cacheValid = $this->validateCache($lastModified, $etag); + // https://tools.ietf.org/html/rfc7232#section-4.1 + if ($lastModified !== null && (!$cacheValid || ($cacheValid && $etag === null))) { + $response->getHeaders()->set('Last-Modified', gmdate('D, d M Y H:i:s', $lastModified) . ' GMT'); + } + if ($cacheValid) { $response->setStatusCode(304); return false; } - if ($lastModified !== null) { - $response->getHeaders()->set('Last-Modified', gmdate('D, d M Y H:i:s', $lastModified) . ' GMT'); - } - return true; } /** * Validates if the HTTP cache contains valid content. + * If both Last-Modified and ETag are null, returns false. * @param integer $lastModified the calculated Last-Modified value in terms of a UNIX timestamp. * If null, the Last-Modified header will not be validated. * @param string $etag the calculated ETag value. If null, the ETag header will not be validated. @@ -168,7 +172,7 @@ class HttpCache extends ActionFilter } elseif (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { return $lastModified !== null && @strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $lastModified; } else { - return $etag === null && $lastModified === null; + return false; } } diff --git a/tests/framework/filters/HttpCacheTest.php b/tests/framework/filters/HttpCacheTest.php index ecd354d5a2..c2234d7ca6 100644 --- a/tests/framework/filters/HttpCacheTest.php +++ b/tests/framework/filters/HttpCacheTest.php @@ -38,7 +38,7 @@ class HttpCacheTest extends \yiiunit\TestCase $method->setAccessible(true); unset($_SERVER['HTTP_IF_MODIFIED_SINCE'], $_SERVER['HTTP_IF_NONE_MATCH']); - $this->assertTrue($method->invoke($httpCache, null, null)); + $this->assertFalse($method->invoke($httpCache, null, null)); $this->assertFalse($method->invoke($httpCache, 0, null)); $this->assertFalse($method->invoke($httpCache, 0, '"foo"')); @@ -65,6 +65,14 @@ class HttpCacheTest extends \yiiunit\TestCase { $httpCache = new HttpCache; $httpCache->weakEtag = false; + + $httpCache->etagSeed = function($action, $params) { + return null; + }; + $httpCache->beforeAction(null); + $response = Yii::$app->getResponse(); + $this->assertFalse($response->getHeaders()->offsetExists('ETag')); + $httpCache->etagSeed = function($action, $params) { return ''; };