ratchetphp/Ratchet · error · UnexpectedValueException

$request can not be null

Error message

$request can not be null

What it means

Ratchet's Http\Router::onOpen() receives the PSR-7 RequestInterface that was attached to the connection by an upstream HttpServer component. It throws UnexpectedValueException immediately when $request is null, because routing (host/path matching) is impossible without a request. This guards against the Router being wired into a stack that never injects a request, or onOpen() being called directly with only a connection.

Solutions

  1. Ensure Router sits under an HttpServer component: new IoServer(new HttpServer(new Router(...), ...), ...) so onOpen receives a PSR-7 request.
  2. If invoking onOpen in tests, pass a valid RequestInterface instance implementing PSR-7 (e.g. GuzzleHttp\Psr7\ServerRequest) as the second argument.
  3. Audit any custom components between HttpServer and Router to confirm they preserve $conn->httpHeadersReceived / forward the request rather than calling onOpen($conn) themselves.
  4. Verify you are not wrapping Router directly in WsServer/IoServer in a way that bypasses the HTTP handshake that produces the request.

Example fix

// before
$server = new IoServer(new Router($routeParser), $sock, $loop);

// after
use Ratchet\Http\HttpServer;
$server = new IoServer(new HttpServer(new Router($routeParser)), $sock, $loop);
Defensive patterns

Strategy: try-catch

Validate before calling

use Psr\Http\Message\RequestInterface;
if (!isset($request) || !$request instanceof RequestInterface) {
    throw new \LogicException('Router::onOpen requires a PSR-7 RequestInterface; mount Router under HttpServer.');
}

Type guard

function hasRequest($conn): bool {
    return isset($conn->httpHeadersReceived) && $conn->httpHeadersReceived instanceof \Psr\Http\Message\RequestInterface;
}

Try / catch

try {
    $router->onOpen($conn, $request);
} catch (\UnexpectedValueException $e) {
    if ($e->getMessage() === '$request can not be null') {
        $conn->close(); // stack misconfigured: Router never receives a request
    } else { throw $e; }
}

Prevention

When it happens

Trigger: Calling Router::onOpen($conn) manually without the second argument; putting Router directly under IoServer instead of under HttpServer (which normally provides the PSR-7 request); a custom middleware/component in the socket stack that consumes or fails to re-attach the request before Router runs.

Common situations: Reordering components in the app stack so Router runs before HttpServer; unit tests invoking onOpen with a mock connection only; upgrading Ratchet or swapping the HTTP handshake component so the request no longer reaches the Router.

Related errors


AI-assisted analysis of ratchetphp/Ratchet@e621c6c40b (2026-09-16). Data as JSON: /api/errors/e61952ed754a64de. Report an issue: GitHub.

Appendix: source

Thrown at src/Ratchet/Http/Router.php:32

     * @var \Symfony\Component\Routing\Matcher\UrlMatcherInterface
     */
    protected $_matcher;

    private $_noopController;

    public function __construct(UrlMatcherInterface $matcher) {
        $this->_matcher = $matcher;
        $this->_noopController = new NoOpHttpServerController;
    }

    /**
     * {@inheritdoc}
     * @throws \UnexpectedValueException If a controller is not \Ratchet\Http\HttpServerInterface
     */
    #[HackSupportForPHP8] public function onOpen(ConnectionInterface $conn, ?RequestInterface $request = null) { /*
    public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { /**/
        if (null === $request) {
            throw new \UnexpectedValueException('$request can not be null');
        }

        $conn->controller = $this->_noopController;

        $uri = $request->getUri();

        $context = $this->_matcher->getContext();
        $context->setMethod($request->getMethod());
        $context->setHost($uri->getHost());

        try {
            $route = $this->_matcher->match($uri->getPath());
        } catch (MethodNotAllowedException $nae) {
            return $this->close($conn, 405, array('Allow' => $nae->getAllowedMethods()));
        } catch (ResourceNotFoundException $nfe) {
            return $this->close($conn, 404);
        }

View on GitHub (pinned to e621c6c40b)