From 927fd7a5890098021b105be6545d82d5544a82df Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Fri, 4 Apr 2025 12:27:07 +0300 Subject: [PATCH] avatar caching improvement --- application/Espo/EntryPoints/Avatar.php | 40 +++++++++++++++++++++++-- application/Espo/EntryPoints/Image.php | 21 +++++++++---- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/application/Espo/EntryPoints/Avatar.php b/application/Espo/EntryPoints/Avatar.php index 8917e1da37..0037723018 100644 --- a/application/Espo/EntryPoints/Avatar.php +++ b/application/Espo/EntryPoints/Avatar.php @@ -138,7 +138,17 @@ class Avatar extends Image } if ($user->getAvatarId()) { - $this->show($response, $user->getAvatarId(), $size, true); + if ($this->processEtagMatch($request, $response, $user->getAvatarId())) { + return; + } + + $this->show( + response: $response, + id: $user->getAvatarId(), + size: $size, + disableAccessCheck: true, + etag: $user->getAvatarId(), + ); return; } @@ -159,6 +169,12 @@ class Avatar extends Image $name = $this->getName($user, $userId); + $etag = md5($color . $name); + + if ($this->processEtagMatch($request, $response, $etag)) { + return; + } + $avatar = (new InitialAvatar())->name($name); if ($user->getName() && !self::isAllowedLanguage($avatar)) { @@ -176,8 +192,9 @@ class Avatar extends Image ->generate(); $response - ->setHeader('Cache-Control', 'max-age=360000, must-revalidate') - ->setHeader('Content-Type', 'image/png'); + ->setHeader('Cache-Control', 'max-age=600, stale-while-revalidate=864000') + ->setHeader('Content-Type', 'image/png') + ->setHeader('Etag', $etag); $response->writeBody($image->stream('png', 100)); } @@ -299,4 +316,21 @@ class Avatar extends Image return $name; } + + private function processEtagMatch(Request $request, Response $response, string $etag): bool + { + $matchEtag = $request->getHeader('If-None-Match'); + + if (!$matchEtag) { + return false; + } + + if ($matchEtag === $etag) { + $response->setStatus(304); + + return true; + } + + return false; + } } diff --git a/application/Espo/EntryPoints/Image.php b/application/Espo/EntryPoints/Image.php index bdc49d1864..b7e8a619af 100644 --- a/application/Espo/EntryPoints/Image.php +++ b/application/Espo/EntryPoints/Image.php @@ -85,10 +85,15 @@ class Image implements EntryPoint * @throws NotFound * @throws ForbiddenSilent */ - protected function show(Response $response, string $id, ?string $size, bool $disableAccessCheck = false): void - { - /** @var ?Attachment $attachment */ - $attachment = $this->entityManager->getEntityById(Attachment::ENTITY_TYPE, $id); + protected function show( + Response $response, + string $id, + ?string $size, + bool $disableAccessCheck = false, + ?string $etag = null, + ): void { + + $attachment = $this->entityManager->getRDBRepositoryByClass(Attachment::class)->getById($id); if (!$attachment) { throw new NotFoundSilent("Attachment not found."); @@ -147,9 +152,15 @@ class Image implements EntryPoint $response ->setHeader('Content-Disposition', 'inline;filename="' . $fileName . '"') - ->setHeader('Cache-Control', 'private, max-age=864000, immutable') ->setHeader('Content-Length', (string) $fileSize) ->setHeader('Content-Security-Policy', "default-src 'self'"); + + if ($etag) { + $response->setHeader('Cache-Control', 'max-age=600, stale-while-revalidate=864000'); + $response->setHeader('Etag', $etag); + } else { + $response->setHeader('Cache-Control', 'private, max-age=864000, immutable'); + } } /**