diff --git a/module/Common/src/Factory/CacheFactory.php b/module/Common/src/Factory/CacheFactory.php index c866b980..710c5962 100644 --- a/module/Common/src/Factory/CacheFactory.php +++ b/module/Common/src/Factory/CacheFactory.php @@ -3,6 +3,7 @@ namespace Shlinkio\Shlink\Common\Factory; use Doctrine\Common\Cache\ApcuCache; use Doctrine\Common\Cache\ArrayCache; +use Doctrine\Common\Cache\FilesystemCache; use Interop\Container\ContainerInterface; use Interop\Container\Exception\ContainerException; use Zend\ServiceManager\Exception\ServiceNotCreatedException; diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index eb6168f9..6a5596ee 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -1,6 +1,7 @@ AnnotatedFactory::class, Action\QrCodeAction::class => AnnotatedFactory::class, + Middleware\QrCodeCacheMiddleware::class => AnnotatedFactory::class, ], ], diff --git a/module/Core/config/routes.config.php b/module/Core/config/routes.config.php index 9b499f71..9b0c071f 100644 --- a/module/Core/config/routes.config.php +++ b/module/Core/config/routes.config.php @@ -1,5 +1,6 @@ 'short-url-qr-code', 'path' => '/qr/{shortCode}[/{size:[0-9]+}]', - 'middleware' => Action\QrCodeAction::class, + 'middleware' => [ + Middleware\QrCodeCacheMiddleware::class, + Action\QrCodeAction::class, + ], 'allowed_methods' => ['GET'], ], ], diff --git a/module/Core/src/Middleware/QrCodeCacheMiddleware.php b/module/Core/src/Middleware/QrCodeCacheMiddleware.php new file mode 100644 index 00000000..d0f132bb --- /dev/null +++ b/module/Core/src/Middleware/QrCodeCacheMiddleware.php @@ -0,0 +1,73 @@ +cache = $cache; + } + + /** + * Process an incoming request and/or response. + * + * Accepts a server-side request and a response instance, and does + * something with them. + * + * If the response is not complete and/or further processing would not + * interfere with the work done in the middleware, or if the middleware + * wants to delegate to another process, it can use the `$out` callable + * if present. + * + * If the middleware does not return a value, execution of the current + * request is considered complete, and the response instance provided will + * be considered the response to return. + * + * Alternately, the middleware may return a response instance. + * + * Often, middleware will `return $out();`, with the assumption that a + * later middleware will return a response. + * + * @param Request $request + * @param Response $response + * @param null|callable $out + * @return null|Response + */ + public function __invoke(Request $request, Response $response, callable $out = null) + { + $cacheKey = $request->getUri()->getPath(); + + // If this QR code is already cached, just return it + if ($this->cache->contains($cacheKey)) { + $qrData = $this->cache->fetch($cacheKey); + $response->getBody()->write($qrData['body']); + return $response->withHeader('Content-Type', $qrData['content-type']); + } + + // If not, call the next middleware and cache it + /** @var Response $resp */ + $resp = $out($request, $response); + $this->cache->save($cacheKey, [ + 'body' => $resp->getBody()->__toString(), + 'content-type' => $resp->getHeaderLine('Content-Type'), + ]); + return $resp; + } +}