Hi,

I want to create a simple redirect for anonymous users in my module. For this I've used the example in the article at https://drupalize.me/blog/201502/responding-events-drupal-8. This article describes a redirect by an "event subscriber service". My redirect should happen based on the user id. The event subscriber service is recognized by Drupal 8. Because my debugger (XDebug) steps into my code.

The problem is, if I'm requesting any URL with an anonymous user, the browser messages me after a while: "Too many redirects".

It follows the code, which I had programmed so far.

OwnNotes.services.yml:

services:
    ownnotes.subscriber:
        class: Drupal\OwnNotes\LoginStateSubscriber
        tags:

          - {name: event_subscriber}

src/LoginStateSubscriber.php (the redirect is defined in the "checkForRedirection" method:

// Declare the namespace that our event subscriber is in. This should follow the
// PSR-4 standard, and use the EventSubscriber sub-namespace.
namespace Drupal\OwnNotes;

// This is the interface we are going to implement.
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
// This class contains the event we want to subscribe to.
use Symfony\Component\HttpKernel\KernelEvents;

use \Drupal\Core\Routing\TrustedRedirectResponse;

/**
 * Subscribe to KernelEvents::REQUEST events and redirect to the login form if an anonymous user
 * is requesting the site.
 */
class LoginStateSubscriber implements EventSubscriberInterface {
  /**
   * {@inheritdoc}
   */
  static function getSubscribedEvents() {
    $events[KernelEvents::REQUEST][] = array('checkForRedirection');
    return $events;
  }

  /**
   * This method is called whenever the KernelEvents::REQUEST event is
   * dispatched.
   *
   * @param GetResponseEvent $event
   */
  public function checkForRedirection(\Symfony\Component\HttpKernel\Event\GetResponseEvent $event) {
    if (intval(\Drupal::currentUser()->getAccount()->id()) == 0 &&
      $_SERVER['REQUEST_URI'] !== '/user/login') {

      $event->setResponse(new TrustedRedirectResponse('/user/login'));

      /**
       * Tried also:
       * $event->setResponse(new RedirectResponse('http://example.com/'));
       */

    }
  }
}

Additionally I need to restart the Apache webserver to see any changes on the code taking effect. A cache-rebuild with Drush isn't enough. Does anybody know why?

I think there "must" be an elegant way to create a simple redirect in Drupal 8.

Thanks in advance to your help!

CommentFileSizeAuthor
#5 xdebug-on.jpg118.37 KBPeter Majmesku
#4 screenshot.jpg113.54 KBPeter Majmesku
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

jepSter created an issue. See original summary.

Peter Majmesku’s picture

Issue summary: View changes

Better description.

claudiu.cristea’s picture

Most likely your code creates an infinite redirection. I suspect that $_SERVER['REQUEST_URI'] !== '/user/login' is returning TRUE also on /user/login. You'll need to use $event->getRequest()->getRequestUri() instead of $_SERVER[]. Also check for anonymous by testing \Drupal::currentUser()->isAnonymous(). But current user should be injected in the constructor.

Peter Majmesku’s picture

FileSize
113.54 KB

Thanks for your answer.

I've modified my code by your tip and the information at https://www.drupal.org/node/2068293 about the send()-method from the response object. My method looks now like follows:

/**
   * This method is called whenever the KernelEvents::REQUEST event is
   * dispatched.
   *
   * @param GetResponseEvent $event
   */
  public function checkForRedirection(\Symfony\Component\HttpKernel\Event\GetResponseEvent $event) {
    if (\Drupal::currentUser()->isAnonymous() === TRUE &&
      $event->getRequest()->getRequestUri() !== '/user/login'
    ) {

      // We want to redirect user to login.
      $response = new RedirectResponse("/user/login");
      $response->send();
      return;

    }
  }

The good thing ist, that this code is working. The problem is: it takes long to execute. About 6 seconds. If XDebug is disabled and OPCache is off.

Below is the screenshot from Google Chrome network tools.

Screenshot redirect Drupal 8

It seems like the page would be rendered twice fully, before it's sent to the user. That must go better with the RedirectResponse object from Symfony components. Does anybody know how the redirect can happen faster?

Peter Majmesku’s picture

FileSize
118.37 KB

By the way: interesting for me is, that if XDebug is turned on and OPCache is switched off, the request takes nearly 17 seconds. See below.

XDebug on

claudiu.cristea’s picture

in \Drupal::currentUser()->isAnonymous() === TRUE you can miss the === TRUE part.

claudiu.cristea’s picture

Here's a useful change record https://www.drupal.org/node/2023537

Peter Majmesku’s picture

They are writing about the same thing there.

$this->redirect();

points "also" to the RedirectResponse object. So my found best practice for Drupal 8 takes 6-7 seconds to execute..

The following method seems to be the way to go in Drupal 8.

  /**
   * This method is called whenever the KernelEvents::REQUEST event is
   * dispatched and redirects the user to login, if necessary.
   *
   * @param GetResponseEvent $event
   */
  public function checkForRedirection(\Symfony\Component\HttpKernel\Event\GetResponseEvent $event) {
    if (\Drupal::currentUser()->isAnonymous() &&
      $event->getRequest()->getRequestUri() !== '/user/login'
    ) {
      // We want to redirect user to login.
      $response = new RedirectResponse("/user/login");
      return $response->send();
    }
  }

A pity that it is so slow. Does anybody know any faster method?

ddproxy’s picture

With xDebug on it's going to take a lot longer to run. Would you happen to have the debug logs from that process? Maybe we could hash up the output and figure out where it's getting to be so slow?

I agree that the redirect process takes way too long. The event should be rather thin but I'm assuming here that either process before throwing the event (GetResponseEvent) is taking a while or the event subscribers before this one is taking longer than it should. Either this subscriber needs to be pushed to the top of the list, which I think would be okay since it's so thin, or the events leading up to this one need to be cleaned up a bit.

Wim Leers’s picture

Status: Active » Fixed

Most likely your code creates an infinite redirection

Not most likely, but with absolute certainty.

    if (intval(\Drupal::currentUser()->getAccount()->id()) == 0 &&
      $_SERVER['REQUEST_URI'] !== '/user/login') {

This code is very very wrong. Should use something like $this->currentUser->isAnonymous() && $this->currentRouteMatch->getRouteName() === 'user.login'

Furthermore, you're passing a relative URL to TrustedRedirectResponse(), it should be an absolute URL.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.