Handshake middleware
Apply policy before Upgrade, resolve scoped services, and return a validated HTTP rejection.
Handshake middleware runs before HTTP 101. It runs after endpoint, Origin, and subprotocol validation and before identity middleware. A failed check prevents connection establishment.
Write middleware#
RequireDirectTls in the file below is for deployments where TLS terminates directly at the Lily listener. The same file includes ConnectionAudit, explained on the next page. Adding this policy to the first application's plaintext listener rejects every request; configure WSS first.
use std::sync::Arc;
use lilyrs::websocket::{CleanupCancellation, ExecutionCancellation, Extensions,
WebSocketContext, WsAppBuilder, async_trait};
use lilyrs::websocket::middleware::{MiddlewareDescriptor, MiddlewareErrorCode, MiddlewareKind,
WebSocketHandshakeMiddleware, WsConnectionCloseCategory, WsConnectionMiddleware,
WsHandshakeExchange, WsHandshakeRejection, WsMiddlewareError, WsMiddlewareInitError};
pub struct RequireDirectTls;
#[async_trait]
impl WebSocketHandshakeMiddleware for RequireDirectTls {
async fn new(_: Arc<Extensions>) -> Result<Self, WsMiddlewareInitError> { Ok(Self) }
fn descriptor(&self) -> MiddlewareDescriptor {
MiddlewareDescriptor::new("direct_tls", MiddlewareKind::WebSocketHandshake)
}
async fn handle(&self, exchange: &mut WsHandshakeExchange, _: ExecutionCancellation)
-> Result<(), WsHandshakeRejection>
{
if exchange.request().is_secure() { Ok(()) }
else { Err(WsHandshakeRejection::forbidden(
MiddlewareErrorCode::new("TLS_REQUIRED").expect("sabit kod"))) }
}
}
pub struct ConnectionAudit;
#[async_trait]
impl WsConnectionMiddleware for ConnectionAudit {
async fn new(_: Arc<Extensions>) -> Result<Self, WsMiddlewareInitError> { Ok(Self) }
fn descriptor(&self) -> MiddlewareDescriptor {
MiddlewareDescriptor::new("connection_audit", MiddlewareKind::WebSocketConnection)
}
async fn admit(&self, _: Arc<WebSocketContext>, _: ExecutionCancellation)
-> Result<(), WsMiddlewareError> { Ok(()) }
async fn opened(&self, context: Arc<WebSocketContext>, _: ExecutionCancellation)
-> Result<(), WsMiddlewareError>
{
let _namespace = context.namespace();
Ok(())
}
async fn closed(&self, _: Arc<WebSocketContext>, category: WsConnectionCloseCategory,
stopping: CleanupCancellation) -> Result<(), WsMiddlewareError>
{
let _ = (category, stopping.is_cancelled());
Ok(())
}
}
pub fn with_connection_policy(builder: WsAppBuilder) -> WsAppBuilder {
builder.handshake_middleware::<RequireDirectTls>()
.connection_middleware::<ConnectionAudit>()
}Download the complete example.
descriptor returns the correct MiddlewareKind::WebSocketHandshake. handle returns Ok(()) for success and WsHandshakeRejection for rejection. The constructor runs once per app.
Registration and scoped data#
Global registration is .handshake_middleware::<RequireDirectTls>(); controller-specific registration is #[handshake_middleware(RequireDirectTls)]. Do not add the same type twice within one effective plan. Global middleware runs before controller middleware.
exchange.service::<T>().await resolves a service from the temporary handshake scope. insert_local can pass typed data to a subsequent handshake or identity step; at most 32 local types can be stored. This data does not automatically become connection-local data. Explicitly copy only the required owned values into the identity result.
Choose an HTTP rejection#
try_new(status, code) creates an ordinary rejection. For 401, use unauthorized(code, challenge), which validates the requirement for a valid WWW-Authenticate challenge. The general constructor does not accept statuses such as 401, 405, 407, and 426 as ordinary errors because they carry additional protocol obligations.
try_with_body adds a bounded body, and try_with_header adds a permitted header. Response headers are limited to WWW-Authenticate, Retry-After, Set-Cookie, and Cache-Control. There is no general hook for writing arbitrary headers/cookies to a successful 101 response.
TLS and time limits#
is_secure() reports the Lily listener's TLS state. If a proxy terminates TLS and connects to Lily over plaintext, it remains false; X-Forwarded-Proto does not change it. This middleware cannot serve that architecture without an adapted edge-trust policy.
ExecutionCancellation grants observation to the callback. Observe it during long verifier or backend operations and use your own bounded operation contract. Identity middleware converts an authentication result into connection identity.
Sources for this page v0.1.0
This guide is based on the reviewed framework commit.