broadcast

This commit is contained in:
Stef Heyenrath
2026-02-22 19:12:03 +01:00
parent 0782dc3998
commit 8287ae79ec
6 changed files with 530 additions and 22 deletions

View File

@@ -35,39 +35,64 @@ public partial class WireMockServer
public async Task CloseWebSocketConnectionAsync(
Guid connectionId,
WebSocketCloseStatus closeStatus = WebSocketCloseStatus.NormalClosure,
string statusDescription = "Closed by server")
string statusDescription = "Closed by server",
CancellationToken cancellationToken = default)
{
foreach (var registry in _options.WebSocketRegistries.Values)
{
if (registry.TryGetConnection(connectionId, out var connection))
if (registry.TryGetConnection(connectionId, out var connection) && !cancellationToken.IsCancellationRequested)
{
await connection.CloseAsync(closeStatus, statusDescription);
await connection.CloseAsync(closeStatus, statusDescription, cancellationToken);
return;
}
}
}
/// <summary>
/// Broadcast a message to all WebSocket connections in a specific mapping
/// Broadcast a text message to all WebSocket connections in a specific mapping
/// </summary>
[PublicAPI]
public async Task BroadcastToWebSocketsAsync(Guid mappingGuid, string text)
public async Task BroadcastToWebSocketsAsync(Guid mappingGuid, string text, Guid? excludeConnectionId = null, CancellationToken cancellationToken = default)
{
if (_options.WebSocketRegistries.TryGetValue(mappingGuid, out var registry))
{
await registry.BroadcastTextAsync(text);
await registry.BroadcastAsync(text, excludeConnectionId, cancellationToken);
}
}
/// <summary>
/// Broadcast a message to all WebSocket connections
/// Broadcast a text message to all WebSocket connections
/// </summary>
[PublicAPI]
public async Task BroadcastToAllWebSocketsAsync(string text)
public async Task BroadcastToAllWebSocketsAsync(string text, Guid? excludeConnectionId = null, CancellationToken cancellationToken = default)
{
foreach (var registry in _options.WebSocketRegistries.Values)
{
await registry.BroadcastTextAsync(text);
await registry.BroadcastAsync(text, excludeConnectionId, cancellationToken);
}
}
/// <summary>
/// Broadcast a binary message to all WebSocket connections in a specific mapping
/// </summary>
[PublicAPI]
public async Task BroadcastToWebSocketsAsync(Guid mappingGuid, byte[] bytes, Guid? excludeConnectionId = null, CancellationToken cancellationToken = default)
{
if (_options.WebSocketRegistries.TryGetValue(mappingGuid, out var registry))
{
await registry.BroadcastAsync(bytes, excludeConnectionId, cancellationToken);
}
}
/// <summary>
/// Broadcast a binary message to all WebSocket connections
/// </summary>
[PublicAPI]
public async Task BroadcastToAllWebSocketsAsync(byte[] bytes, Guid? excludeConnectionId = null, CancellationToken cancellationToken = default)
{
foreach (var registry in _options.WebSocketRegistries.Values)
{
await registry.BroadcastAsync(bytes, excludeConnectionId, cancellationToken);
}
}
}

View File

@@ -3,6 +3,7 @@
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Net.WebSockets;
using static System.Net.Mime.MediaTypeNames;
namespace WireMock.WebSockets;
@@ -48,12 +49,24 @@ internal class WebSocketConnectionRegistry
/// <summary>
/// Broadcast text to all connections
/// </summary>
public async Task BroadcastTextAsync(string text, CancellationToken cancellationToken = default)
public async Task BroadcastAsync(string text, Guid? excludeConnectionId, CancellationToken cancellationToken = default)
{
var tasks = _connections.Values
.Where(c => c.WebSocket.State == WebSocketState.Open)
.Select(c => c.SendAsync(text, cancellationToken));
var tasks = Filter(excludeConnectionId).Select(c => c.SendAsync(text, cancellationToken));
await Task.WhenAll(tasks);
}
/// <summary>
/// Broadcast binary to all connections
/// </summary>
public async Task BroadcastAsync(byte[] bytes, Guid? excludeConnectionId, CancellationToken cancellationToken = default)
{
var tasks = Filter(excludeConnectionId).Select(c => c.SendAsync(bytes, cancellationToken));
await Task.WhenAll(tasks);
}
private IEnumerable<WireMockWebSocketContext> Filter(Guid? excludeConnectionId)
{
return _connections.Values
.Where(c =>c.WebSocket.State == WebSocketState.Open && (!excludeConnectionId.HasValue || c.ConnectionId != excludeConnectionId));
}
}

View File

@@ -94,19 +94,30 @@ public class WireMockWebSocketContext : IWebSocketContext
}
/// <inheritdoc />
public async Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription)
public async Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken = default)
{
await WebSocket.CloseAsync(closeStatus, statusDescription, CancellationToken.None);
await WebSocket.CloseAsync(closeStatus, statusDescription, cancellationToken);
LogWebSocketMessage(WebSocketMessageDirection.Send, WebSocketMessageType.Close, $"CloseStatus: {closeStatus}, Description: {statusDescription}", null);
}
/// <inheritdoc />
public async Task BroadcastTextAsync(string text, CancellationToken cancellationToken = default)
public async Task BroadcastAsync(string text, bool excludeSender = false, CancellationToken cancellationToken = default)
{
if (Registry != null)
{
await Registry.BroadcastTextAsync(text, cancellationToken);
Guid? excludeConnectionId = excludeSender ? ConnectionId : null;
await Registry.BroadcastAsync(text, excludeConnectionId, cancellationToken);
}
}
/// <inheritdoc />
public async Task BroadcastAsync(byte[] bytes, bool excludeSender = false, CancellationToken cancellationToken = default)
{
if (Registry != null)
{
Guid? excludeConnectionId = excludeSender ? ConnectionId : null;
await Registry.BroadcastAsync(bytes, excludeConnectionId, cancellationToken);
}
}

View File

@@ -48,10 +48,15 @@ public interface IWebSocketContext
/// <summary>
/// Close the WebSocket connection
/// </summary>
Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription);
Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken = default);
/// <summary>
/// Broadcast text message to all connections in this mapping
/// </summary>
Task BroadcastTextAsync(string text, CancellationToken cancellationToken = default);
Task BroadcastAsync(string text, bool excludeSender = false, CancellationToken cancellationToken = default);
/// <summary>
/// Broadcast binary message to all connections in this mapping
/// </summary>
Task BroadcastAsync(byte[] bytes, bool excludeSender = false, CancellationToken cancellationToken = default);
}