first commit
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KupShop\ProxyCacheBundle\Command;
|
||||
|
||||
use KupShop\ProxyCacheBundle\Util\CookiesFunctionalityTest;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
class CookiesFunctionalityTestCommand extends Command
|
||||
{
|
||||
#[Required]
|
||||
public CookiesFunctionalityTest $test;
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('kupshop:test:cookies')
|
||||
->setDescription('Test if cacheable responses return cookies');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$errors = $this->test->testInitialRequestCookiesAbsence();
|
||||
|
||||
$failed = false;
|
||||
foreach ($errors as $error) {
|
||||
$failed = true;
|
||||
$output->writeln('<error>COOKIE VRÁCENA:</error> '.$error);
|
||||
}
|
||||
|
||||
if ($failed) {
|
||||
$output->writeln('<comment>Více o tom jak funguje proxy_cache a jak má být nastavená: </comment>'.CookiesFunctionalityTest::WIKI_LINK);
|
||||
} else {
|
||||
$output->writeln('<info>Úspěch: na úvodní požadavky nebyly vráceny cookies</info>');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\ProxyCacheBundle\EventListener;
|
||||
|
||||
use KupShop\KupShopBundle\Context\CacheContext;
|
||||
use KupShop\KupShopBundle\Util\Contexts;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\Cookie;
|
||||
use Symfony\Component\HttpKernel\Event\ResponseEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
class CacheKeyChangeListener implements EventSubscriberInterface
|
||||
{
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
KernelEvents::RESPONSE => [
|
||||
['changeCacheKeyCookie', 200],
|
||||
['changeAdminCookie', 200],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected string $cacheKeyword = 'cachekey';
|
||||
protected string $adminLoggedKeyword = 'adminlogged';
|
||||
|
||||
public function changeCacheKeyCookie(ResponseEvent $event): void
|
||||
{
|
||||
/**
|
||||
* @var $cacheContext CacheContext
|
||||
*/
|
||||
$cacheContext = Contexts::get(CacheContext::class);
|
||||
|
||||
$response = $event->getResponse();
|
||||
|
||||
$cacheKey = $this->getCacheKey();
|
||||
|
||||
$key = $this->hashKey($cacheContext->getKey($cacheKey));
|
||||
$activeKey = $event->getRequest()->cookies->get($this->cacheKeyword);
|
||||
|
||||
if ($key == $activeKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
$defaultKey = $this->hashKey($cacheContext->getDefaultKey($cacheKey));
|
||||
|
||||
if ($key == $defaultKey) {
|
||||
if ($activeKey) {
|
||||
$response->headers->clearCookie($this->cacheKeyword);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$response->headers->setCookie(new Cookie($this->cacheKeyword, $key, strtotime('now +1 year')));
|
||||
}
|
||||
|
||||
protected function hashKey(?string $key): ?string
|
||||
{
|
||||
if (is_null($key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
protected function getCacheKey(): array
|
||||
{
|
||||
return [CacheContext::TYPE_FULL_PAGE];
|
||||
}
|
||||
|
||||
public function changeAdminCookie(ResponseEvent $event): void
|
||||
{
|
||||
$adminLoggedCookie = $event->getRequest()->cookies->has($this->adminLoggedKeyword);
|
||||
if (getAdminUser() && !$adminLoggedCookie) {
|
||||
$event->getResponse()->headers->setCookie(new Cookie($this->adminLoggedKeyword, 1));
|
||||
} elseif (!getAdminUser() && $adminLoggedCookie) {
|
||||
$event->getResponse()->headers->clearCookie($this->adminLoggedKeyword);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KupShop\ProxyCacheBundle\EventListener;
|
||||
|
||||
use KupShop\KupShopBundle\Event\CronEvent;
|
||||
use KupShop\KupShopBundle\Util\Logging\SentryLogger;
|
||||
use KupShop\ProxyCacheBundle\Util\CookiesFunctionalityTest;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
class CookiesFunctionalityTestCron implements EventSubscriberInterface
|
||||
{
|
||||
#[Required]
|
||||
public SentryLogger $sentryLogger;
|
||||
|
||||
#[Required]
|
||||
public CookiesFunctionalityTest $test;
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
CronEvent::RUN_EXPENSIVE => [
|
||||
['testCookies', 200],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function testCookies()
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
foreach ($this->test->testInitialRequestCookiesAbsence() as $error) {
|
||||
$errors[] = $error;
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
$this->sentryLogger->captureMessage('PROXY CACHE: kešovatelné odpověďi vrací cookies!!!', [], ['extra' => [
|
||||
'cookies' => $errors,
|
||||
'wiki_link' => CookiesFunctionalityTest::WIKI_LINK,
|
||||
]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
bundles/KupShop/ProxyCacheBundle/ProxyCacheBundle.php
Normal file
9
bundles/KupShop/ProxyCacheBundle/ProxyCacheBundle.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace KupShop\ProxyCacheBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class ProxyCacheBundle extends Bundle
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
KupShop\ProxyCacheBundle\:
|
||||
resource: ../../{EventListener,Inspection}
|
||||
|
||||
KupShop\ProxyCacheBundle\Command\CookiesFunctionalityTestCommand:
|
||||
tags:
|
||||
- { name: console.command }
|
||||
|
||||
KupShop\ProxyCacheBundle\Util\CookiesFunctionalityTest:
|
||||
arguments:
|
||||
$domains: '%shop.domains%'
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KupShop\ProxyCacheBundle\Util;
|
||||
|
||||
use KupShop\I18nBundle\Translations\SectionsTranslation;
|
||||
use KupShop\KupShopBundle\Context\DomainContext;
|
||||
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
|
||||
use KupShop\KupShopBundle\Util\Contexts;
|
||||
use KupShop\KupShopBundle\Util\StringUtil;
|
||||
use Query\Operator;
|
||||
use Query\QueryBuilder;
|
||||
use Query\Translation;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
class CookiesFunctionalityTest
|
||||
{
|
||||
public const WIKI_LINK = 'https://gitlab.wpj.cz/kupshop/engine/-/wikis/Reverzn%C3%AD-ke%C5%A1ovac%C3%AD-proxy-%E2%80%93-full-page-cache';
|
||||
|
||||
public function __construct(
|
||||
private HttpClientInterface $client,
|
||||
private UrlGeneratorInterface $urlGenerator,
|
||||
private array $domains,
|
||||
) {
|
||||
}
|
||||
|
||||
public function testInitialRequestCookiesAbsence()
|
||||
{
|
||||
$scheme = Contexts::get(DomainContext::class)->getActiveWithScheme();
|
||||
$domains = $this->getActiveDomains();
|
||||
|
||||
if (empty($domains)) { // if "shop.domains" config is not set on the shop
|
||||
$domains = [Contexts::get(DomainContext::class)->getActiveId()];
|
||||
}
|
||||
|
||||
foreach ($domains as $domain) {
|
||||
$requestPaths = [
|
||||
'/',
|
||||
$this->getSectionPath($domain),
|
||||
];
|
||||
|
||||
foreach (array_filter($requestPaths) as $requestPath) {
|
||||
$cookies = $this->makeRequest($scheme, $requestPath, $domain);
|
||||
|
||||
foreach ($cookies as $cookie) {
|
||||
yield $domain.$requestPath.': '.$cookie;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getSectionPath($domain): string|false
|
||||
{
|
||||
$lang = $this->domains[$domain]['language'] ?? false;
|
||||
|
||||
$someSectionId = sqlQueryBuilder()->select('s.id, s.figure')->from('sections', 's')
|
||||
->andWhere(Translation::joinTranslations([$lang],
|
||||
ServiceContainer::getService(SectionsTranslation::class),
|
||||
function (QueryBuilder $qb, $columnName, $translatedField) {
|
||||
$qb->andWhere(Operator::coalesce($translatedField, 's.figure').' = "Y" ');
|
||||
|
||||
return false;
|
||||
},
|
||||
['figure']))
|
||||
->andWhere('s.id > 0')
|
||||
->orderBy('RAND()')->setMaxResults(1)->execute()->fetchColumn();
|
||||
|
||||
if (!$someSectionId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->urlGenerator->generate('kupshop_catalog_catalog_section',
|
||||
['parent_sections' => '', 'url_section' => 'test', 'id_section' => $someSectionId]);
|
||||
}
|
||||
|
||||
private function makeRequest($scheme, $path, $domain): array
|
||||
{
|
||||
$bustCacheParam = '?cache_bust='.time();
|
||||
|
||||
$response = $this->client->request('GET', $scheme.$path.$bustCacheParam, [
|
||||
'verify_host' => false,
|
||||
'verify_peer' => false,
|
||||
'headers' => [
|
||||
'X-WPJ-SOURCE' => 'PROXY_CACHE:CookiesFunctionalityTest',
|
||||
'Host' => $domain,
|
||||
]]);
|
||||
|
||||
if ($response->getStatusCode() !== 200) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$responseHeaders = $response->getHeaders();
|
||||
|
||||
$cookies = array_filter($responseHeaders['set-cookie'] ?? [], function ($cookie) {
|
||||
return !str_starts_with($cookie, 'sf_redirect');
|
||||
});
|
||||
|
||||
return $cookies;
|
||||
}
|
||||
|
||||
private function getActiveDomains(): array
|
||||
{
|
||||
$domains = array_keys($this->domains);
|
||||
|
||||
if (isProduction()) {
|
||||
$domains = array_filter($domains, function ($domain) {
|
||||
return !StringUtil::endsWith($domain, '.wpjshop.cz');
|
||||
});
|
||||
}
|
||||
|
||||
if (!isLocalDevelopment()) {
|
||||
$domains = array_filter($domains, function ($domain) {
|
||||
return !StringUtil::endsWith($domain, 'kupshop.local');
|
||||
});
|
||||
}
|
||||
|
||||
return $domains;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user