RuntimeException: Failed to start the session because headers have already been sent by "/var/www/html/vendor/symfony/http-foundation/Response.php" at line 1239. in Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage->start() (line 152 of /var/www/html/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php)

This is the code that causes it:

<?php // $Id: SecureDistrictSubscriber.php,v 1.9 2022/05/04 10:51:16 dwinston Exp $

namespace Drupal\bncreports;

use Drupal\Core\Url;
use Drupal\user\Entity\User;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * Prohibit access to other districts
 */
class SecureDistrictSubscriber implements EventSubscriberInterface
{
  /**
   * @param RequestEvent $event
   */
  public function secureDistrict(RequestEvent $event): void
  {
    $current_user = User::load(\Drupal::currentUser()->id());
    if ($current_user->hasRole('administrator') || $current_user->hasRole('bnc_operator') || $current_user->hasRole('ao_user'))
      return;

    $district_id = $current_user->get('field_district')->value;
    if (!$district_id || strtolower($district_id) == 'all')
      return;

    $pathInfo = $event->getRequest()->getPathInfo();

    if (preg_match('/\/users\/\d{3}(\d|[a-z]|\-)/', $pathInfo)) {
      $params = explode('/', $pathInfo);
      if ($district_id != $params[2]) {
        $url = new Url('system.403');
        $response = new RedirectResponse($url->toString(), 301);
        $response->send();
      }
    } elseif (preg_match('/\/users/', $pathInfo)) {
      $url = Url::fromUri('internal:/users/' . $district_id);
      $response = new RedirectResponse($url->toString(), 301);
      $response->send();
    } elseif (preg_match('/\/user\/\d+\/edit/', $pathInfo)) {
      $params = explode('/', $pathInfo);
      $user = User::load($params[2]);
      if ($district_id != $user->get('field_district')->value) {
        $url = new Url('system.403');
        $response = new RedirectResponse($url->toString(), 301);
        $response->send();
      } elseif (!$current_user->hasRole('district_poc') && $current_user->id() != $user->id()) {
        $url = new Url('system.403');
        $response = new RedirectResponse($url->toString(), 301);
        $response->send();
      }
    }
  }

  /**
   * Listen to kernel.request events and call secureDistrict.
   * {@inheritdoc}
   * @return array Event names to listen to (key) and methods to call (value)
   */
  public static function getSubscribedEvents(): array
  {
    $events[KernelEvents::REQUEST][] = ['secureDistrict'];
    return $events;
  }
}

The user doesn't see the error and it doesn't appear to break anything but I would still like to fix it so the error doesn't occur.

Comments

Jaypan’s picture

Why do you think that code is causing it?

That error often comes from having an echo statement in your code, inserting whitepace before the opening PHP tag, or using a closing PHP tag with whitespace after it. It will be in settings.php, or your custom code, or potentially a poorly coded contrib module or theme (if you're using any dev versions, you could look in there).

donpwinston’s picture

When I comment out $response->send(); The RuntimeException is not logged.

Simpler is always better.

Jaypan’s picture

donpwinston’s picture

... 
$response = new RedirectResponse($url->toString(), 301);
$response->send();
\Drupal::service('request_stack')->getCurrentRequest()->query->set('destination', $url->toString());

This made the problem go away. Got it from https://www.drupal.org/files/issues/2022-02-01/user_default_page_3257494...

Simpler is always better.

donpwinston’s picture

All of the above is incorrect. You have to use some middleware magic. I finally got it working properly with the following:

bncreports.services.yml

bncreports.secure_district_middleware:
    class: Drupal\bncreports\RedirectMiddleware
    tags:
      - { name: http_middleware }
bncreports.secure_district_subscriber:
    class: Drupal\bncreports\SecureDistrictSubscriber
    tags:
      - { name: event_subscriber }
namespace Drupal\bncreports;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;

class RedirectMiddleware implements HttpKernelInterface
{

  /**
   * The wrapped HTTP kernel.
   *
   * @var \Symfony\Component\HttpKernel\HttpKernelInterface
   */
  protected $httpKernel;

  /**
   * The redirect URL.
   *
   * @var \Symfony\Component\HttpFoundation\RedirectResponse
   */
  protected $redirectResponse;

  /**
   * Constructs a RedirectMiddleware
   * object.
   *
   * @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
   * The decorated kernel.
   */
  public function __construct(HttpKernelInterface $http_kernel)
  {
    $this->httpKernel = $http_kernel;
  }

  /**
   * {@inheritdoc}
   */
  public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true)
  {
    $response = $this->httpKernel->handle($request, $type, $catch);
    return $this->redirectResponse ?: $response;
  }

  /**
   * Stores the requested redirect response.
   *
   * @param \Symfony\Component\HttpFoundation\RedirectResponse|null $redirectResponse
   * Redirect response.
   */
  public function setRedirectResponse(?RedirectResponse $redirectResponse)
  {
    $this->redirectResponse = $redirectResponse;
  }
}
namespace Drupal\bncreports;

use Drupal\Core\Url;
use Drupal\user\Entity\User;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * Prohibit access to other districts
 */
class SecureDistrictSubscriber implements EventSubscriberInterface
{
  /**
   * @param RequestEvent $event
   */
  public function secureDistrict(RequestEvent $event): void
  {
    $current_user = User::load(\Drupal::currentUser()->id());
    if ($current_user->hasRole('administrator') || $current_user->hasRole('bnc_operator') || $current_user->hasRole('ao_user'))
      return;

    $district_id = $current_user->get('field_district')->value;
    if (!$district_id || strtolower($district_id) == 'all')
      return;

    $pathInfo = $event->getRequest()->getPathInfo();

    $middleware = \Drupal::service('bncreports.secure_district_middleware');

    if (preg_match('/\/users\/\d{3}(\d|[a-z]|\-)/', $pathInfo)) {
      $params = explode('/', $pathInfo);
      if ($district_id != $params[2]) {
        $url = new Url('system.403');
        $response = new RedirectResponse($url->toString(), 301);
        $event->setResponse($response);
        $middleware->setRedirectResponse($response);
      }
    } elseif (preg_match('/\/users/', $pathInfo)) {
      $url = Url::fromUri('internal:/users/' . $district_id);
      $response = new RedirectResponse($url->toString(), 301);
      $event->setResponse($response);
    } elseif (preg_match('/\/user\/\d+\/edit/', $pathInfo)) {
      $params = explode('/', $pathInfo);
      $user = User::load($params[2]);
      if ($district_id != $user->get('field_district')->value) {
        $url = new Url('system.403');
        $response = new RedirectResponse($url->toString(), 301);
        $event->setResponse($response);
        $middleware->setRedirectResponse($response);
      } elseif (!$current_user->hasRole('district_poc') && $current_user->id() != $user->id()) {
        $url = new Url('system.403');
        $response = new RedirectResponse($url->toString(), 301);
        $event->setResponse($response);
        $middleware->setRedirectResponse($response);
      }
    }
  }

  /**
   * Listen to kernel.request events and call secureDistrict.
   * {@inheritdoc}
   * @return array Event names to listen to (key) and methods to call (value)
   */
  public static function getSubscribedEvents(): array
  {
    $events[KernelEvents::REQUEST][] = ['secureDistrict'];
    return $events;
  }
}

Simpler is always better.