Kemal::Session::Handler
Inherits HTTP::Handler < Reference < Object
Writes the session cookie once per request, at the last moment it can still be written.
Session mutations only mark the store dirty (Session#touch), so a route
that sets twenty keys pays for one encryption instead of twenty. Something
has to perform that one write, and this handler is it — mutating a
request's session without it in the chain raises HandlerRequired:
use Kemal::Session::Handler.new
Kemal.run
Why it wraps the response output
Set-Cookie is a header, so it has to be decided before the first byte of
the body reaches the socket. Crystal serializes the cookie jar into
headers on the first unbuffered write and silently ignores cookies added
afterwards, so committing after call_next would lose the session for any
response big enough to flush mid-route (the output buffer is 8 KB — an
ordinary HTML page).
So instead of buffering the body, the handler wraps response.output and
commits just before the first write passes through: routes run to
completion before Kemal prints their return value, and streaming routes
commit at their first print. Responses with no body at all commit when
the handler unwinds. Mutating the session after that point raises
ResponseAlreadySent instead of quietly dropping the cookie.
NOTE: HTTP::Server::Response#upgrade writes its headers directly rather
than through the output, so it is the one path this cannot intercept: a
session mutated inside a WebSocket handshake has nowhere to put its cookie.
Set the session in an ordinary route before opening the socket.