diff --git a/src/Cache.php b/src/Cache.php index 769d8ee..61d424c 100644 --- a/src/Cache.php +++ b/src/Cache.php @@ -11,6 +11,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; @@ -45,6 +46,11 @@ class Cache implements MiddlewareInterface */ protected $mustRevalidate; + /** + * @var StreamFactoryInterface + */ + protected $streamFactory; + /** * Create new HTTP cache * @@ -52,11 +58,16 @@ class Cache implements MiddlewareInterface * @param int $maxAge The maximum age of client-side cache * @param bool $mustRevalidate must-revalidate */ - public function __construct(string $type = 'private', int $maxAge = 86400, bool $mustRevalidate = false) - { + public function __construct( + StreamFactoryInterface $streamFactory, + string $type = 'private', + int $maxAge = 86400, + bool $mustRevalidate = false + ) { $this->type = $type; $this->maxAge = $maxAge; $this->mustRevalidate = $mustRevalidate; + $this->streamFactory = $streamFactory; } /** @@ -101,7 +112,8 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface if ($ifNoneMatch) { $etagList = preg_split('@\s*,\s*@', $ifNoneMatch); if (is_array($etagList) && (in_array($etag, $etagList) || in_array('*', $etagList))) { - return $response->withStatus(304); + return $response->withStatus(304) + ->withBody($this->streamFactory->createStream('')); } } } @@ -118,7 +130,8 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $ifModifiedSince = $request->getHeaderLine('If-Modified-Since'); if ($ifModifiedSince && $lastModified <= strtotime($ifModifiedSince)) { - return $response->withStatus(304); + return $response->withStatus(304) + ->withBody($this->streamFactory->createStream('')); } } diff --git a/tests/CacheTest.php b/tests/CacheTest.php index 64fab81..ef31e24 100644 --- a/tests/CacheTest.php +++ b/tests/CacheTest.php @@ -16,11 +16,18 @@ use Slim\Psr7\Factory\ResponseFactory; use Slim\Psr7\Factory\ServerRequestFactory; +use Slim\Psr7\Factory\StreamFactory; use function gmdate; use function time; class CacheTest extends TestCase { + private function createCache(string $type = 'privte', int $maxAge = 86400, bool $mustRevalidate = false): Cache + { + return new Cache(new StreamFactory(), $type, $maxAge, $mustRevalidate); + } + + public function requestFactory(): ServerRequestInterface { $serverRequestFactory = new ServerRequestFactory(); @@ -64,7 +71,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface public function testCacheControlHeader() { - $cache = new Cache('public', 86400); + $cache = $this->createCache('public', 86400); $req = $this->requestFactory(); $res = $cache->process($req, $this->createRequestHandler()); @@ -76,7 +83,7 @@ public function testCacheControlHeader() public function testCacheControlHeaderWithMustRevalidate() { - $cache = new Cache('private', 86400, true); + $cache = $this->createCache('private', 86400, true); $req = $this->requestFactory(); $res = $cache->process($req, $this->createRequestHandler()); @@ -88,7 +95,7 @@ public function testCacheControlHeaderWithMustRevalidate() public function testCacheControlHeaderWithZeroMaxAge() { - $cache = new Cache('private', 0, false); + $cache = $this->createCache('private', 0, false); $req = $this->requestFactory(); $res = $cache->process($req, $this->createRequestHandler()); @@ -100,7 +107,7 @@ public function testCacheControlHeaderWithZeroMaxAge() public function testCacheControlHeaderDoesNotOverrideExistingHeader() { - $cache = new Cache('public', 86400); + $cache = $this->createCache('public', 86400); $req = $this->requestFactory(); $res = $this->createResponse()->withHeader('Cache-Control', 'no-cache,no-store'); @@ -116,7 +123,7 @@ public function testLastModifiedWithCacheHit() $now = time(); $lastModified = gmdate('D, d M Y H:i:s T', $now + 86400); $ifModifiedSince = gmdate('D, d M Y H:i:s T', $now + 86400); - $cache = new Cache('public', 86400); + $cache = $this->createCache('public', 86400); $req = $this->requestFactory()->withHeader('If-Modified-Since', $ifModifiedSince); @@ -131,7 +138,7 @@ public function testLastModifiedWithCacheHitAndNewerDate() $now = time(); $lastModified = gmdate('D, d M Y H:i:s T', $now + 86400); $ifModifiedSince = gmdate('D, d M Y H:i:s T', $now + 172800); // <-- Newer date - $cache = new Cache('public', 86400); + $cache = $this->createCache('public', 86400); $req = $this->requestFactory()->withHeader('If-Modified-Since', $ifModifiedSince); $res = $this->createResponse()->withHeader('Last-Modified', $lastModified); @@ -145,7 +152,7 @@ public function testLastModifiedWithCacheHitAndOlderDate() $now = time(); $lastModified = gmdate('D, d M Y H:i:s T', $now + 86400); $ifModifiedSince = gmdate('D, d M Y H:i:s T', $now); // <-- Older date - $cache = new Cache('public', 86400); + $cache = $this->createCache('public', 86400); $req = $this->requestFactory()->withHeader('If-Modified-Since', $ifModifiedSince); $res = $this->createResponse()->withHeader('Last-Modified', $lastModified); @@ -159,7 +166,7 @@ public function testLastModifiedWithCacheMiss() $now = time(); $lastModified = gmdate('D, d M Y H:i:s T', $now + 86400); $ifModifiedSince = gmdate('D, d M Y H:i:s T', $now - 86400); - $cache = new Cache('public', 86400); + $cache = $this->createCache('public', 86400); $req = $this->requestFactory()->withHeader('If-Modified-Since', $ifModifiedSince); $res = $this->createResponse()->withHeader('Last-Modified', $lastModified); @@ -172,7 +179,7 @@ public function testETagWithCacheHit() { $etag = 'abc'; $ifNoneMatch = 'abc'; - $cache = new Cache('public', 86400); + $cache = $this->createCache('public', 86400); $req = $this->requestFactory()->withHeader('If-None-Match', $ifNoneMatch); $res = $this->createResponse()->withHeader('Etag', $etag); @@ -185,7 +192,7 @@ public function testETagWithCacheMiss() { $etag = 'abc'; $ifNoneMatch = 'xyz'; - $cache = new Cache('public', 86400); + $cache = $this->createCache('public', 86400); $req = $this->requestFactory()->withHeader('If-None-Match', $ifNoneMatch); $res = $this->createResponse()->withHeader('Etag', $etag); @@ -193,4 +200,35 @@ public function testETagWithCacheMiss() $this->assertEquals(200, $res->getStatusCode()); } + + public function testETagReturnsNoBodyOnCacheHit(): void + { + $etag = 'abc'; + $cache = $this->createCache(); + $req = $this->requestFactory()->withHeader('If-None-Match', $etag); + + $res = $this->createResponse()->withHeader('Etag', $etag); + $res->getBody()->write('payload data'); + $res = $cache->process($req, $this->createRequestHandler($res)); + + self::assertSame(304, $res->getStatusCode()); + self::assertSame('', (string) $res->getBody()); + } + + public function testLastModifiedReturnsNoBodyOnCacheHit(): void + { + $now = time() + 86400; + $lastModified = gmdate('D, d M Y H:i:s T', $now); + $ifModifiedSince = gmdate('D, d M Y H:i:s T', $now); + $cache = $this->createCache(); + + $req = $this->requestFactory()->withHeader('If-Modified-Since', $ifModifiedSince); + $res = $this->createResponse()->withHeader('Last-Modified', $lastModified); + $res->getBody()->write('payload data'); + + $res = $cache->process($req, $this->createRequestHandler($res)); + + self::assertEquals(304, $res->getStatusCode()); + self::assertSame('', (string) $res->getBody()); + } }