Question

I have 2 versions of site (with same code), one for Switzerland (.ch) and other for rest of the world (.com).

What I need is that if someone from Switzerland visits .com is redirected to .ch And other way, if someone out of Switzerland visits .ch should be redirected to .com

I registered even subscriber like:

services:
  smart_ip_redirect.event_subscriber:
    class: Drupal\smart_ip_redirect\EventSubscriber\SmartIpRedirectSubscriber
    tags:
      - {name: event_subscriber}

The inside my event subscriber I have code like:

if ($host != 'site.ch' && $countryCode == 'CH'){
  $event->setResponse(new TrustedRedirectResponse('https://site.ch/'));
  return;
} else if ($host == 'site.ch' && $countryCode != 'CH'){
  $event->setResponse(new TrustedRedirectResponse('https://site.com/'));
  return;
}

Everything works well for the first time after clearing the cache. First user is redirected (if needed) to correct domain.

However that redirection is cached somehow.

I.e. first user (i.e. using first browser) is from CH, visits .com domain. He is well redirected to .ch domain.

But then if second user (second browser) visits from some other country visits .com domain previous redirection is somehow remembered and he is also redirected to .ch domain and that request falls into endless loop.

Then I clear drupal cache (on both sites), it again works well for first user and after that every other attempt gets stuck into endless loop.

How should I register my event subscriber so it's called for every page opening and avoid page caching?

Update:

Maybe I was not clear enough so I'll try to be more specific. I have the same code for 2 totally independent sites. So one will always have $host == "site.com" and other $site=="site.ch" So its not multisite setup. They do not share database nor current code (even code is the same for simplicity sake - they have the same ancestor).

Other thing is that I don't need caching based on ip address (and obviously not on host since it will be always the same for each site) . Everything should work as default. Only difference is that at very beginning of handling the request my code should be executed and it will decide should redirection happen or request processing should continue as normal.

So only that my code should be excluded from caching and executed before everything else and after that everything else including caching should happen as normal.

Hope that this makes some more sense.

Update 2: Followed @sonfd suggestion and my code now looks like:

$response_headers = [
  'Cache-Control' => 'no-cache, no-store, must-revalidate',
];

if ($host != 'site.ch' && $countryCode == 'CH'){
  $event->setResponse(new TrustedRedirectResponse('https://site.ch/', '302', $response_headers));
} else if ($host == 'site.ch' && $countryCode != 'CH'){
  $event->setResponse(new TrustedRedirectResponse('https://site.com/', '302', $response_headers));
}

}

Also set higher priority in services file:

services:
  smart_ip_redirect.event_subscriber:
    class: Drupal\smart_ip_redirect\EventSubscriber\SmartIpRedirectSubscriber
    tags:
      - {name: event_subscriber, priority: 250}

But didn't help. I still get stuck in infinite redirection loop after changing country (using HideMyAss for testing).

Update 3:

Trying that middleware soluiton, but now inside my handle method I can not use SmartIp module functionality any more. Before I could read current user country code like:

$location = \Drupal::service('smart_ip.smart_ip_location');
$countryCode = $location->get('countryCode');

Now it's shooting error:

The website encountered an unexpected error. Please try again later. TypeError: Argument 1 passed to Drupal\Core\Session\SessionConfiguration::getOptions() must be an instance of Symfony\Component\HttpFoundation\Request, null given, called in /var/www/html/drupal/web/core/lib/Drupal/Core/Session/SessionManager.php on line 111 in Drupal\Core\Session\SessionConfiguration->getOptions() (line 40 of core/lib/Drupal/Core/Session/SessionConfiguration.php).

Tried avoid services and calling static method directly like:

$client_ip = $request->getClientIp();
$location = SmartIp::query($client_ip);
$countryCode = isset($location['countryCode']) ? strtolower($location['countryCode']) : '';

but error remains.

Noticed that inside handle method getting request like before:

$request = \Drupal::request();

is not working. However $request is passed to handle method, but point is that some functionality is not available here.

And experimented additionally on this: will this error appear on not depends on priority parameter from services.yml file. If priority is bellow 100 error will not appear, so I guess some needed initialization is done before and Smart Ip works well. However, if priority is that low page and this code is cached. By looking at suggested middleware soluiton I need to set priority above 200 (it's set to 250 at example). And with priority that hing SmartIp module call won't work. So request are contradictory (less than 100 so Smart Ip will work but more than 200 so caching will be avoided).

Any idea?

Was it helpful?

Solution

The code depends on host name and IP address. So you could add the cache contexts url.site and ip:

$response = new TrustedRedirectResponse('https://site.ch/');
$response->getCacheableMetadata()->addCacheContexts(['ip', 'url.site']);
$event->setResponse($response);

In this case, though, caching doesn't make much sense because it doesn't save many cpu cycles and fills the cache tables with ip addresses of site visitors. Set a cache max age of 0 instead of the two contexts:

$response->getCacheableMetadata()->setCacheMaxAge(0);

Furthermore the code doesn't depend on any Drupal internals since the location could be resolved through a 3rd party library only based on the IP. So you could move the code outside of the Drupal kernel in a middleware as @NoSssweat commented.

In any case you need to disable reverse proxies. Uninstall the built-in Internal Page Cache (in case of the middleware you can achieve this by setting a higher priority) and external ones through response headers. See @sonf comment.

Licensed under: CC-BY-SA with attribution
Not affiliated with drupal.stackexchange
scroll top