wse-server 2.3.0__tar.gz → 2.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {wse_server-2.3.0 → wse_server-2.3.1}/PKG-INFO +1 -1
- {wse_server-2.3.0 → wse_server-2.3.1}/pyproject.toml +1 -1
- {wse_server-2.3.0 → wse_server-2.3.1}/rust/Cargo.lock +1 -1
- {wse_server-2.3.0 → wse_server-2.3.1}/rust/Cargo.toml +1 -1
- {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/server.rs +78 -2
- {wse_server-2.3.0 → wse_server-2.3.1}/wse_server/__init__.py +1 -1
- {wse_server-2.3.0 → wse_server-2.3.1}/wse_server/_wse_accel.pyi +11 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/LICENSE +0 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/README.md +0 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/rust/.cargo/config.toml +0 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/jwt.rs +0 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/lib.rs +0 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/cluster.rs +0 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/compression.rs +0 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/filters.rs +0 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/mod.rs +0 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/presence.rs +0 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/queue.rs +0 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/rate_limiter.rs +0 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/recovery.rs +0 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/reliability.rs +0 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/security.rs +0 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/sequencer.rs +0 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/transformer.rs +0 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/wse_server/core/__init__.py +0 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/wse_server/core/filters.py +0 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/wse_server/core/types.py +0 -0
- {wse_server-2.3.0 → wse_server-2.3.1}/wse_server/py.typed +0 -0
|
@@ -2421,6 +2421,11 @@ pub struct RustWSEServer {
|
|
|
2421
2421
|
|
|
2422
2422
|
#[pymethods]
|
|
2423
2423
|
impl RustWSEServer {
|
|
2424
|
+
/// Create a new WSE server instance.
|
|
2425
|
+
///
|
|
2426
|
+
/// The server runs all transport in Rust (TCP, TLS, WS framing, JWT auth,
|
|
2427
|
+
/// compression, encryption, ping/pong). Python handles application logic
|
|
2428
|
+
/// via drain mode or callbacks.
|
|
2424
2429
|
#[new]
|
|
2425
2430
|
#[pyo3(signature = (host, port, max_connections = 1000, jwt_secret = None, jwt_issuer = None, jwt_audience = None, jwt_cookie_name = None, jwt_previous_secret = None, jwt_key_id = None, jwt_algorithm = None, jwt_private_key = None, max_inbound_queue_size = 131072, recovery_enabled = false, recovery_buffer_size = 128, recovery_ttl = 300, recovery_max_messages = 500, recovery_memory_budget = 268435456, presence_enabled = false, presence_max_data_size = 4096, presence_max_members = 0, rate_limit_capacity = 100_000.0, rate_limit_refill = 10_000.0, max_message_size = 1_048_576, ping_interval = 25, idle_timeout = 60, max_outbound_queue_bytes = 16_777_216))]
|
|
2426
2431
|
#[allow(clippy::too_many_arguments)]
|
|
@@ -2596,6 +2601,13 @@ impl RustWSEServer {
|
|
|
2596
2601
|
})
|
|
2597
2602
|
}
|
|
2598
2603
|
|
|
2604
|
+
/// Register Python callbacks for connection events.
|
|
2605
|
+
///
|
|
2606
|
+
/// - on_connect(conn_id, cookies) -- called when a client connects
|
|
2607
|
+
/// - on_message(conn_id, data) -- called when a message is received
|
|
2608
|
+
/// - on_disconnect(conn_id) -- called when a client disconnects
|
|
2609
|
+
///
|
|
2610
|
+
/// Alternative to drain mode. Cannot be used together with enable_drain_mode().
|
|
2599
2611
|
fn set_callbacks(
|
|
2600
2612
|
&self,
|
|
2601
2613
|
py: Python<'_>,
|
|
@@ -2616,6 +2628,10 @@ impl RustWSEServer {
|
|
|
2616
2628
|
Ok(())
|
|
2617
2629
|
}
|
|
2618
2630
|
|
|
2631
|
+
/// Start the WebSocket server on a background thread.
|
|
2632
|
+
///
|
|
2633
|
+
/// Spawns a dedicated tokio runtime, binds to host:port, and begins
|
|
2634
|
+
/// accepting connections. Raises RuntimeError if already running.
|
|
2619
2635
|
fn start(&mut self) -> PyResult<()> {
|
|
2620
2636
|
if self.running.load(Ordering::SeqCst) {
|
|
2621
2637
|
return Err(PyRuntimeError::new_err("Server is already running"));
|
|
@@ -3119,6 +3135,10 @@ impl RustWSEServer {
|
|
|
3119
3135
|
Ok(())
|
|
3120
3136
|
}
|
|
3121
3137
|
|
|
3138
|
+
/// Stop the server and close all connections.
|
|
3139
|
+
///
|
|
3140
|
+
/// Shuts down the cluster mesh, sends a Shutdown command, and joins
|
|
3141
|
+
/// the server thread. Safe to call if not running (returns Ok).
|
|
3122
3142
|
fn stop(&mut self, py: Python<'_>) -> PyResult<()> {
|
|
3123
3143
|
if !self.running.load(Ordering::SeqCst) {
|
|
3124
3144
|
return Ok(());
|
|
@@ -3146,6 +3166,7 @@ impl RustWSEServer {
|
|
|
3146
3166
|
|
|
3147
3167
|
// -- send methods -------------------------------------------------------
|
|
3148
3168
|
|
|
3169
|
+
/// Send a raw text frame to a single connection.
|
|
3149
3170
|
fn send(&self, conn_id: &str, data: &str) -> PyResult<()> {
|
|
3150
3171
|
let tx = self
|
|
3151
3172
|
.cmd_tx
|
|
@@ -3159,6 +3180,7 @@ impl RustWSEServer {
|
|
|
3159
3180
|
Ok(())
|
|
3160
3181
|
}
|
|
3161
3182
|
|
|
3183
|
+
/// Send a raw binary frame to a single connection.
|
|
3162
3184
|
fn send_bytes(&self, conn_id: &str, data: &[u8]) -> PyResult<()> {
|
|
3163
3185
|
let tx = self
|
|
3164
3186
|
.cmd_tx
|
|
@@ -3427,6 +3449,7 @@ impl RustWSEServer {
|
|
|
3427
3449
|
Ok(byte_count)
|
|
3428
3450
|
}
|
|
3429
3451
|
|
|
3452
|
+
/// Send a text frame to all connected clients (no topic filtering).
|
|
3430
3453
|
fn broadcast_all(&self, data: &str) -> PyResult<()> {
|
|
3431
3454
|
let tx = self
|
|
3432
3455
|
.cmd_tx
|
|
@@ -3438,6 +3461,7 @@ impl RustWSEServer {
|
|
|
3438
3461
|
Ok(())
|
|
3439
3462
|
}
|
|
3440
3463
|
|
|
3464
|
+
/// Send a binary frame to all connected clients.
|
|
3441
3465
|
fn broadcast_all_bytes(&self, data: &[u8]) -> PyResult<()> {
|
|
3442
3466
|
let tx = self
|
|
3443
3467
|
.cmd_tx
|
|
@@ -3452,10 +3476,12 @@ impl RustWSEServer {
|
|
|
3452
3476
|
|
|
3453
3477
|
// -- query / management -------------------------------------------------
|
|
3454
3478
|
|
|
3479
|
+
/// Number of active connections (lock-free AtomicUsize read).
|
|
3455
3480
|
fn get_connection_count(&self) -> usize {
|
|
3456
3481
|
self.shared.connection_count.load(Ordering::Relaxed)
|
|
3457
3482
|
}
|
|
3458
3483
|
|
|
3484
|
+
/// List all active connection IDs.
|
|
3459
3485
|
fn get_connections(&self) -> PyResult<Vec<String>> {
|
|
3460
3486
|
let tx = self
|
|
3461
3487
|
.cmd_tx
|
|
@@ -3469,6 +3495,7 @@ impl RustWSEServer {
|
|
|
3469
3495
|
.map_err(|_| PyRuntimeError::new_err("Reply dropped"))
|
|
3470
3496
|
}
|
|
3471
3497
|
|
|
3498
|
+
/// Force-disconnect a connection by sending a Close frame.
|
|
3472
3499
|
fn disconnect(&self, conn_id: &str) -> PyResult<()> {
|
|
3473
3500
|
let tx = self
|
|
3474
3501
|
.cmd_tx
|
|
@@ -3481,6 +3508,7 @@ impl RustWSEServer {
|
|
|
3481
3508
|
Ok(())
|
|
3482
3509
|
}
|
|
3483
3510
|
|
|
3511
|
+
/// Return True if the server is running.
|
|
3484
3512
|
fn is_running(&self) -> bool {
|
|
3485
3513
|
self.running.load(Ordering::SeqCst)
|
|
3486
3514
|
}
|
|
@@ -3497,9 +3525,12 @@ impl RustWSEServer {
|
|
|
3497
3525
|
/// Drain up to max_count inbound events. Blocks up to timeout_ms waiting
|
|
3498
3526
|
/// for at least one event. Returns list of (event_type, conn_id, data) tuples.
|
|
3499
3527
|
///
|
|
3500
|
-
/// event_type: "connect" | "auth_connect" | "msg" | "raw" | "bin" |
|
|
3528
|
+
/// event_type: "connect" | "auth_connect" | "msg" | "raw" | "bin" |
|
|
3529
|
+
/// "disconnect" | "presence_join" | "presence_leave"
|
|
3530
|
+
///
|
|
3501
3531
|
/// data: str (for "connect"=cookies, "auth_connect"=user_id, "raw"),
|
|
3502
|
-
/// dict (for "msg"
|
|
3532
|
+
/// dict (for "msg", "presence_join", "presence_leave"),
|
|
3533
|
+
/// bytes (for "bin"), None (for "disconnect")
|
|
3503
3534
|
///
|
|
3504
3535
|
/// GIL is released while waiting on channel, acquired once for batch conversion.
|
|
3505
3536
|
#[pyo3(signature = (max_count = 256, timeout_ms = 50))]
|
|
@@ -3649,16 +3680,26 @@ impl RustWSEServer {
|
|
|
3649
3680
|
Ok(list.unbind())
|
|
3650
3681
|
}
|
|
3651
3682
|
|
|
3683
|
+
/// Number of inbound events dropped due to queue overflow.
|
|
3652
3684
|
fn inbound_dropped_count(&self) -> u64 {
|
|
3653
3685
|
self.shared.inbound_dropped.load(Ordering::Relaxed)
|
|
3654
3686
|
}
|
|
3655
3687
|
|
|
3688
|
+
/// Current number of events waiting in the inbound queue.
|
|
3656
3689
|
fn inbound_queue_depth(&self) -> usize {
|
|
3657
3690
|
self.shared.inbound_rx.len()
|
|
3658
3691
|
}
|
|
3659
3692
|
|
|
3660
3693
|
// -- Cluster Protocol -----------------------------------------------------
|
|
3661
3694
|
|
|
3695
|
+
/// Connect to cluster peers via TCP mesh protocol.
|
|
3696
|
+
///
|
|
3697
|
+
/// Two modes: static peers (pass peer addresses directly) or seed-based
|
|
3698
|
+
/// discovery (pass seeds + cluster_addr for gossip). TLS is optional;
|
|
3699
|
+
/// falls back to WSE_CLUSTER_TLS_CERT/KEY/CA env vars if not provided.
|
|
3700
|
+
///
|
|
3701
|
+
/// Raises RuntimeError if cluster is already connected or if seeds
|
|
3702
|
+
/// is used without cluster_addr.
|
|
3662
3703
|
#[allow(clippy::too_many_arguments)]
|
|
3663
3704
|
#[pyo3(signature = (peers, tls_cert=None, tls_key=None, tls_ca=None, cluster_port=None, seeds=None, cluster_addr=None))]
|
|
3664
3705
|
fn connect_cluster(
|
|
@@ -3781,6 +3822,7 @@ impl RustWSEServer {
|
|
|
3781
3822
|
Ok(())
|
|
3782
3823
|
}
|
|
3783
3824
|
|
|
3825
|
+
/// Return True if at least one cluster peer is connected.
|
|
3784
3826
|
fn cluster_connected(&self) -> bool {
|
|
3785
3827
|
self.shared
|
|
3786
3828
|
.cluster_metrics
|
|
@@ -3789,6 +3831,7 @@ impl RustWSEServer {
|
|
|
3789
3831
|
> 0
|
|
3790
3832
|
}
|
|
3791
3833
|
|
|
3834
|
+
/// Number of connected cluster peers.
|
|
3792
3835
|
fn cluster_peers_count(&self) -> u64 {
|
|
3793
3836
|
self.shared
|
|
3794
3837
|
.cluster_metrics
|
|
@@ -3819,6 +3862,8 @@ impl RustWSEServer {
|
|
|
3819
3862
|
Ok(())
|
|
3820
3863
|
}
|
|
3821
3864
|
|
|
3865
|
+
/// List connected cluster peers with address, instance_id, capabilities,
|
|
3866
|
+
/// and connected_at timestamp. Returns a list of dicts.
|
|
3822
3867
|
fn cluster_info(&self, py: Python<'_>) -> PyResult<Py<PyList>> {
|
|
3823
3868
|
let list = PyList::empty(py);
|
|
3824
3869
|
for entry in self.shared.cluster_metrics.peer_info.iter() {
|
|
@@ -3849,6 +3894,11 @@ impl RustWSEServer {
|
|
|
3849
3894
|
|
|
3850
3895
|
// -- Topic subscriptions --------------------------------------------------
|
|
3851
3896
|
|
|
3897
|
+
/// Subscribe a connection to topics.
|
|
3898
|
+
///
|
|
3899
|
+
/// Optionally set initial presence data (requires presence_enabled=True).
|
|
3900
|
+
/// If queue_group is set, the connection joins a named group for
|
|
3901
|
+
/// round-robin dispatch instead of standard fan-out.
|
|
3852
3902
|
#[pyo3(signature = (conn_id, topics, presence_data = None, queue_group = None))]
|
|
3853
3903
|
fn subscribe_connection(
|
|
3854
3904
|
&self,
|
|
@@ -4014,6 +4064,13 @@ impl RustWSEServer {
|
|
|
4014
4064
|
Ok(())
|
|
4015
4065
|
}
|
|
4016
4066
|
|
|
4067
|
+
/// Subscribe with message recovery support.
|
|
4068
|
+
///
|
|
4069
|
+
/// Returns a dict with per-topic recovery status:
|
|
4070
|
+
/// {recovered: bool, topics: {topic: {epoch, offset, recoverable, recovered, count}}}
|
|
4071
|
+
///
|
|
4072
|
+
/// When recover=True, replays missed messages from the recovery buffer.
|
|
4073
|
+
/// Pass epoch (hex string) and offset from a previous session to resume.
|
|
4017
4074
|
#[pyo3(signature = (conn_id, topics, recover = false, epoch = None, offset = None))]
|
|
4018
4075
|
fn subscribe_with_recovery(
|
|
4019
4076
|
&self,
|
|
@@ -4149,6 +4206,8 @@ impl RustWSEServer {
|
|
|
4149
4206
|
Ok(result_dict.into())
|
|
4150
4207
|
}
|
|
4151
4208
|
|
|
4209
|
+
/// Unsubscribe a connection from topics.
|
|
4210
|
+
/// If topics is None, unsubscribe from all topics.
|
|
4152
4211
|
#[pyo3(signature = (conn_id, topics = None))]
|
|
4153
4212
|
fn unsubscribe_connection(&self, conn_id: &str, topics: Option<Vec<String>>) -> PyResult<()> {
|
|
4154
4213
|
// Acquire refcount lock first to keep subscription + refcount atomic
|
|
@@ -4351,6 +4410,7 @@ impl RustWSEServer {
|
|
|
4351
4410
|
Ok(())
|
|
4352
4411
|
}
|
|
4353
4412
|
|
|
4413
|
+
/// Number of fan-out subscribers on a topic (excludes queue group members).
|
|
4354
4414
|
fn get_topic_subscriber_count(&self, topic: &str) -> usize {
|
|
4355
4415
|
self.shared
|
|
4356
4416
|
.topic_subscribers
|
|
@@ -4370,6 +4430,13 @@ impl RustWSEServer {
|
|
|
4370
4430
|
Ok(dict.into())
|
|
4371
4431
|
}
|
|
4372
4432
|
|
|
4433
|
+
/// Full server health snapshot as a dict.
|
|
4434
|
+
///
|
|
4435
|
+
/// Keys: connections, inbound_queue_depth, inbound_dropped,
|
|
4436
|
+
/// cluster_connected, cluster_peer_count, cluster_messages_sent/delivered/dropped,
|
|
4437
|
+
/// cluster_bytes_sent/received, cluster_reconnect_count, cluster_dlq_size,
|
|
4438
|
+
/// uptime_secs, recovery_enabled/topic_count/total_bytes,
|
|
4439
|
+
/// presence_enabled/topics/total_users.
|
|
4373
4440
|
fn health_snapshot(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
|
|
4374
4441
|
let dict = PyDict::new(py);
|
|
4375
4442
|
dict.set_item(
|
|
@@ -4451,6 +4518,11 @@ impl RustWSEServer {
|
|
|
4451
4518
|
Ok(dict.unbind())
|
|
4452
4519
|
}
|
|
4453
4520
|
|
|
4521
|
+
/// Return Prometheus exposition format metrics string.
|
|
4522
|
+
///
|
|
4523
|
+
/// Includes gauges (connections, queue depth, uptime, drops),
|
|
4524
|
+
/// per-topic counters (top 50 by volume), cluster metrics,
|
|
4525
|
+
/// and optional recovery/presence gauges.
|
|
4454
4526
|
fn prometheus_metrics(&self) -> String {
|
|
4455
4527
|
let mut out = String::with_capacity(4096);
|
|
4456
4528
|
|
|
@@ -4680,6 +4752,10 @@ impl RustWSEServer {
|
|
|
4680
4752
|
out
|
|
4681
4753
|
}
|
|
4682
4754
|
|
|
4755
|
+
/// Drain and return dead-letter-queue entries from failed cluster sends.
|
|
4756
|
+
///
|
|
4757
|
+
/// Each entry is a dict with keys: topic, payload, peer_addr, error.
|
|
4758
|
+
/// Entries are consumed (not peeked) -- calling twice returns different results.
|
|
4683
4759
|
fn get_cluster_dlq_entries(&self, py: Python<'_>) -> PyResult<Py<PyList>> {
|
|
4684
4760
|
let entries = match self.shared.cluster_dlq.try_lock() {
|
|
4685
4761
|
Ok(mut dlq) => dlq.drain_all(),
|
|
@@ -522,6 +522,17 @@ class RustWSEServer:
|
|
|
522
522
|
def health_snapshot(self) -> dict[str, Any]:
|
|
523
523
|
"""Full server health metrics: connections, queue, cluster, recovery, presence, uptime."""
|
|
524
524
|
...
|
|
525
|
+
def prometheus_metrics(self) -> str:
|
|
526
|
+
"""Return Prometheus exposition format metrics string.
|
|
527
|
+
|
|
528
|
+
Gauges: wse_connections, wse_inbound_queue_depth, wse_uptime_seconds,
|
|
529
|
+
wse_slow_consumer_drops, wse_inbound_dropped.
|
|
530
|
+
Per-topic: wse_topic_messages_total{topic="..."} (top 50 by count).
|
|
531
|
+
Cluster: wse_cluster_peers, wse_cluster_dlq_size.
|
|
532
|
+
Recovery: wse_recovery_topics, wse_recovery_bytes.
|
|
533
|
+
Presence: wse_presence_topics, wse_presence_users.
|
|
534
|
+
"""
|
|
535
|
+
...
|
|
525
536
|
def cluster_info(self) -> list[dict[str, Any]]:
|
|
526
537
|
"""List connected cluster peers with address, instance_id, capabilities, connected_at."""
|
|
527
538
|
...
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|