fix(websocket): configurable WS read idle timeout (WS-01)
This commit is contained in:
@@ -90,6 +90,7 @@ pub struct HttpAdapter {
|
||||
ws_sessions: Arc<crate::websocket::WsSessions>,
|
||||
ws_max_sessions: usize,
|
||||
ws_session_slots: Arc<tokio::sync::Semaphore>,
|
||||
ws_idle_timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
impl HttpAdapter {
|
||||
@@ -117,6 +118,7 @@ impl HttpAdapter {
|
||||
let ws_sessions = Arc::new(crate::websocket::WsSessions::new());
|
||||
let ws_max_sessions = crate::websocket::DEFAULT_WS_MAX_SESSIONS;
|
||||
let ws_session_slots = Arc::new(tokio::sync::Semaphore::new(ws_max_sessions));
|
||||
let ws_idle_timeout = Some(crate::websocket::DEFAULT_WS_IDLE_TIMEOUT);
|
||||
let state = RouterState {
|
||||
registry: Arc::clone(®istry),
|
||||
identity_provider: Arc::clone(&identity_provider),
|
||||
@@ -124,6 +126,7 @@ impl HttpAdapter {
|
||||
openapi_doc: openapi_doc.clone(),
|
||||
ws_sessions: Arc::clone(&ws_sessions),
|
||||
ws_session_slots: Arc::clone(&ws_session_slots),
|
||||
ws_idle_timeout,
|
||||
};
|
||||
let router = build_router(state, None);
|
||||
Self {
|
||||
@@ -137,6 +140,7 @@ impl HttpAdapter {
|
||||
ws_sessions,
|
||||
ws_max_sessions,
|
||||
ws_session_slots,
|
||||
ws_idle_timeout,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +153,7 @@ impl HttpAdapter {
|
||||
openapi_doc: self.openapi_doc.clone(),
|
||||
ws_sessions: Arc::clone(&self.ws_sessions),
|
||||
ws_session_slots: Arc::clone(&self.ws_session_slots),
|
||||
ws_idle_timeout: self.ws_idle_timeout,
|
||||
};
|
||||
// `extra_routes` is borrowed, not consumed (SRV-05): a builder
|
||||
// call after `with_extra_routes` must keep the custom routes in
|
||||
@@ -166,6 +171,7 @@ impl HttpAdapter {
|
||||
openapi_doc: self.openapi_doc.clone(),
|
||||
ws_sessions: Arc::clone(&self.ws_sessions),
|
||||
ws_session_slots: Arc::clone(&self.ws_session_slots),
|
||||
ws_idle_timeout: self.ws_idle_timeout,
|
||||
};
|
||||
self.router = build_router(state, Some(routes.clone()));
|
||||
self.extra_routes = Some(routes);
|
||||
@@ -188,6 +194,29 @@ impl HttpAdapter {
|
||||
openapi_doc: self.openapi_doc.clone(),
|
||||
ws_sessions: Arc::clone(&self.ws_sessions),
|
||||
ws_session_slots: Self::rebuild_session_slots(max_sessions),
|
||||
ws_idle_timeout: self.ws_idle_timeout,
|
||||
};
|
||||
self.router = build_router(state, self.extra_routes.clone());
|
||||
self
|
||||
}
|
||||
|
||||
/// The WS idle-read timeout (WS-01): the read pump closes the
|
||||
/// connection with a 1001 (GoingAway) close frame after this long
|
||||
/// without an inbound WS message — bounding the demux stall a
|
||||
/// dribbling (or silently-stalled) peer can pin. `None` disables
|
||||
/// the knob (not recommended: the stall window is then unbounded).
|
||||
///
|
||||
/// Default: [`crate::websocket::DEFAULT_WS_IDLE_TIMEOUT`] (60 s).
|
||||
pub fn with_ws_idle_timeout(mut self, idle_timeout: Option<Duration>) -> Self {
|
||||
self.ws_idle_timeout = idle_timeout;
|
||||
let state = RouterState {
|
||||
registry: Arc::clone(&self.registry),
|
||||
identity_provider: Arc::clone(&self.identity_provider),
|
||||
decoy: self.decoy.clone(),
|
||||
openapi_doc: self.openapi_doc.clone(),
|
||||
ws_sessions: Arc::clone(&self.ws_sessions),
|
||||
ws_session_slots: Arc::clone(&self.ws_session_slots),
|
||||
ws_idle_timeout: self.ws_idle_timeout,
|
||||
};
|
||||
self.router = build_router(state, self.extra_routes.clone());
|
||||
self
|
||||
@@ -995,6 +1024,7 @@ mod tests {
|
||||
ws_session_slots: Arc::new(tokio::sync::Semaphore::new(
|
||||
crate::websocket::DEFAULT_WS_MAX_SESSIONS,
|
||||
)),
|
||||
ws_idle_timeout: Some(crate::websocket::DEFAULT_WS_IDLE_TIMEOUT),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user