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.

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.

Appendix: 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)