Engine Application
Socket Handlers
Three transport handlers deliver conversations. All of them do the same three things — derive a
PipelineConfiguration, feed elements into PipelineService, serialize the output back — and
differ only in framing and in how the pipeline key and the caller identity are obtained.
The externally visible protocol is documented in Pipeline Socket Communication and Wire Formats. This page covers the implementation.
Handler Registration
EngineConfiguration maps the WebSocket paths through a SimpleUrlHandlerMapping:
| Path | Handler |
|---|---|
|
|
|
Upgrades use the Reactor Netty strategy with a 1 MB maximum frame size. The Socket.IO server is a
separate listener on port 9011, started by PipelineSocketService.
PipelineWebSocketHandler
engine/socket/PipelineWebSocketHandler.kt.
-
The pipeline key is the remainder of the request path after
/socket/pipeline/; configuration parameters come from the query string and the handshake headers. -
Text frames are deserialized with
ElementMapperusing the connection’sinputFormat. -
Binary frames are deserialized as MessagePack when
inputFormatisMSGPACK; otherwise the payload is taken as raw audio and wrapped in aBinaryelement. This is what lets a client stream audio without base64 overhead. -
Output is written as a binary frame for
MSGPACKand as a text frame otherwise, with the connection’sprotocolVersionapplied. -
AbortedExceptionandSocketExceptionon the input stream are treated as a closed connection and complete the stream rather than propagating — a client disappearing is normal, not an error. -
Authentication is taken from the reactive security context populated by the security filters during the handshake.
TwilioWebSocketHandler
engine/socket/TwilioWebSocketHandler.kt.
-
The key is the last path segment, with
-translated to:so a colon need not appear in the stream URL. -
Messages are Twilio Media Streams JSON, deserialized into the sealed
InputMessagehierarchy inengine/dto/twilio/(discriminated on theeventproperty); responses areOutputMessagesubtypes. -
The pipeline configuration is not read from the URL but constructed from the
startevent: μ-law encoding, the announced sample rate for input and output, continuous input mode, speech processing and caching disabled. ThecustomParametersof the stream supply the caller number, locale, time zone, tracking ref and the first input. -
The caller’s phone number becomes the identity: the handler synthesizes a
UsernamePasswordAuthenticationTokenfrom the qualified username derived from it, so a returning caller resolves to the same user. -
Output mapping: a
transcriptcommand becomes aclearmessage (barge-in), anaudio-endcommand becomes amark, speech items are converted from 16-bit PCM to μ-law and sent asmedia, and anexitcommand becomes theListeningmark unless the session is ending. -
The conversation is closed only after Twilio echoes back the final
SessionEndmark, so the caller hears the closing utterance.
See Twilio Telephony Integration for the protocol details and
TwilioCallService for the outbound direction.
PipelineSocketService
engine/service/PipelineSocketService.kt — the Socket.IO side, on the server bean declared by
EngineConfiguration (port 9011, 1 MB frame and content limits, worker threads from
PipelineProperties.socketIOWorkerThreads).
-
One namespace,
/socket/io/pipeline, with connect and disconnect listeners and two event listeners:textfor string payloads andmsgpackfor binary payloads. The event names are the lower-casedElementFormatvalues. -
On connect, the configuration is built from the handshake query parameters (
key,sessionIdand the rest) and headers, and a per-client session holding aSinks.Many<Element>is stored in a concurrent map keyed by the socket ID. This sink is what makes the conversation independent of individual events. -
Authentication is resolved from the handshake: Basic credentials, an
Authorization: Bearerheader, or anauthorizationquery parameter carrying a raw JWT, decoded with the reactive JWT decoder and mapped throughAuthorityMapper. -
Incoming payloads are deserialized per the session’s
inputFormatand emitted into the sink. The literal text#endcompletes the sink instead of being forwarded, which ends the session gracefully. -
A deserialization or emit failure sends an error element back to the client without necessarily disconnecting it.
-
Output is sent back on the same event name — bytes for
MSGPACK, text otherwise — and the socket is disconnected once the output stream completes or a fatal error is emitted. -
The number of entries in the session map backs the
activeSessionsgauge described in Observability.
Shared Serialization
All handlers use ElementMapper from common, which is the single place implementing the three
wire formats: the #-prefixed text protocol, JSON with a type discriminator, and MessagePack
with a wrapper object. Adding an element type therefore only requires touching the type hierarchy
and, if it needs a text form, the mapper.