walkor/workerman · error · RuntimeException

Request->session() fail, header already send

Error message

Request->session() fail, header already send

What it means

sessionId() must mint a new session id and emit a Set-Cookie header when the request carries no valid session cookie. That is only possible while the Request still owns its connection ($request->connection); once the response/headers have already been sent or the request was detached, no header can be added, so Workerman throws instead of silently creating an unusable session.

Source

Thrown at src/Protocols/Http/Request.php:385

     * @return string
     * @throws Exception
     */
    public function sessionId(?string $sessionId = null): string
    {
        if ($sessionId) {
            unset($this->context['sid'], $this->context['session']);
        }
        if (!isset($this->context['sid'])) {
            $sessionName = Session::$name;
            $sid = $sessionId ? '' : $this->cookie($sessionName);
            // Strip surrounding double quotes (RFC 6265 allows DQUOTE-wrapped cookie values).
            if (is_string($sid) && isset($sid[1]) && $sid[0] === '"' && $sid[-1] === '"') {
                $sid = substr($sid, 1, -1);
            }
            $sid = $this->isValidSessionId($sid) ? $sid : '';
            if ($sid === '') {
                if (!$this->connection) {
                    throw new RuntimeException('Request->session() fail, header already send');
                }
                $sid = $sessionId ?: static::createSessionId();
                $cookieParams = Session::getCookieParams();
                $this->setSidCookie($sessionName, $sid, $cookieParams);
            }
            $this->context['sid'] = $sid;
        }
        return $this->context['sid'];
    }

    /**
     * Check if session id is valid.
     *
     * @param mixed $sessionId
     * @return bool
     */
    public function isValidSessionId(mixed $sessionId): bool
    {

View on GitHub (pinned to 1391112a61)

Solutions

  1. Open the session before any output: call $request->session() at the top of onMessage, before sending anything on the connection
  2. Do not use the Request in deferred/background tasks; extract the session id ($request->sessionId()) first and pass that string to the job
  3. Ensure the client actually receives the PHPSSESSID cookie so later requests have a valid sid and never need the header-write path

Example fix

// before
$connection->send($response);
$session = $request->session(); // throws 'header already send'

// after
$session = $request->session();
$session->set('uid', 1);
$connection->send($response);
Defensive patterns

Strategy: type-guard

Type guard

function canCreateSession(Workerman\Protocols\Http\Request $request): bool
{
    // A new Set-Cookie can only be emitted while the request still owns its connection.
    return $request->connection !== null;
}

Try / catch

try {
    $session = $request->session();
} catch (RuntimeException $e) {
    if (str_contains($e->getMessage(), 'header already send')) {
        // headers already sent: log and degrade (no session this request)
        return $response->withStatus(500);
    }
    throw $e;
}

Prevention

When it happens

Trigger: Calling $request->session() (or sessionId(null)) after already sending the response on the connection; using the Request object inside a deferred context (Timer callback, queue consumer, coroutine resumed later) where connection is null; storing the request and touching session after onMessage returned.

Common situations: Code that pushes an early response (e.g. $connection->send('ok')) then continues processing and reads the session; background jobs that receive the whole Request object; middleware that emits headers before session start; workerman v5 coroutine code awaiting something before touching session.

Related errors


AI-assisted analysis of walkor/workerman@1391112a61 (2026-08-21). Data as JSON: /api/errors/084fb68de3b7ed06. Report an issue: GitHub.