diff --git a/src/WireMock.Net.Abstractions/Types/BodyType.cs b/src/WireMock.Net.Abstractions/Types/BodyType.cs
index 6894c369..f693bdc0 100644
--- a/src/WireMock.Net.Abstractions/Types/BodyType.cs
+++ b/src/WireMock.Net.Abstractions/Types/BodyType.cs
@@ -50,5 +50,20 @@ public enum BodyType
///
/// Use Server-Sent Events (string)
///
- SseString
+ SseString,
+
+ ///
+ /// WebSocket message in clear text.
+ ///
+ WebSocketText,
+
+ ///
+ /// WebSocket message in binary format.
+ ///
+ WebSocketBinary,
+
+ ///
+ /// WebSocket close message.
+ ///
+ WebSocketClose
}
\ No newline at end of file
diff --git a/src/WireMock.Net.Minimal/ResponseProviders/WebSocketResponseProvider.cs b/src/WireMock.Net.Minimal/ResponseProviders/WebSocketResponseProvider.cs
index 1a1c7cff..2817052e 100644
--- a/src/WireMock.Net.Minimal/ResponseProviders/WebSocketResponseProvider.cs
+++ b/src/WireMock.Net.Minimal/ResponseProviders/WebSocketResponseProvider.cs
@@ -2,21 +2,14 @@
using System.Buffers;
using System.Diagnostics;
-using System.Drawing;
using System.Net;
using System.Net.WebSockets;
using System.Text;
using Microsoft.AspNetCore.Http;
using WireMock.Constants;
-using WireMock.Logging;
-using WireMock.Matchers;
-using WireMock.Matchers.Request;
-using WireMock.Models;
using WireMock.Owin;
using WireMock.Owin.ActivityTracing;
using WireMock.Settings;
-using WireMock.Types;
-using WireMock.Util;
using WireMock.WebSockets;
namespace WireMock.ResponseProviders;
@@ -175,7 +168,7 @@ internal class WebSocketResponseProvider(WebSocketBuilder builder) : IResponsePr
);
}
- LogWebSocketMessage(context, WebSocketMessageDirection.Receive, result.MessageType, null, receiveActivity);
+ context.LogWebSocketMessage(WebSocketMessageDirection.Receive, result.MessageType, null, receiveActivity);
await context.CloseAsync(
WebSocketCloseStatus.NormalClosure,
@@ -204,7 +197,7 @@ internal class WebSocketResponseProvider(WebSocketBuilder builder) : IResponsePr
}
// Log the receive operation
- LogWebSocketMessage(context, WebSocketMessageDirection.Receive, result.MessageType, textContent, receiveActivity);
+ context.LogWebSocketMessage(WebSocketMessageDirection.Receive, result.MessageType, textContent, receiveActivity);
// Echo back (this will be logged by context.SendAsync)
await context.WebSocket.SendAsync(
@@ -276,7 +269,7 @@ internal class WebSocketResponseProvider(WebSocketBuilder builder) : IResponsePr
);
}
- LogWebSocketMessage(context, WebSocketMessageDirection.Receive, result.MessageType, null, receiveActivity);
+ context.LogWebSocketMessage(WebSocketMessageDirection.Receive, result.MessageType, null, receiveActivity);
await context.CloseAsync(
WebSocketCloseStatus.NormalClosure,
@@ -302,7 +295,7 @@ internal class WebSocketResponseProvider(WebSocketBuilder builder) : IResponsePr
// Log the receive operation
object? data = message.Text != null ? message.Text : message.Bytes;
- LogWebSocketMessage(context, WebSocketMessageDirection.Receive, result.MessageType, data, receiveActivity);
+ context.LogWebSocketMessage(WebSocketMessageDirection.Receive, result.MessageType, data, receiveActivity);
// Call custom handler
await handler(message, context).ConfigureAwait(false);
@@ -396,7 +389,7 @@ internal class WebSocketResponseProvider(WebSocketBuilder builder) : IResponsePr
);
}
- LogWebSocketMessage(context, direction, result.MessageType, null, activity);
+ context.LogWebSocketMessage(direction, result.MessageType, null, activity);
await destination.CloseAsync(
result.CloseStatus ?? WebSocketCloseStatus.NormalClosure,
@@ -431,7 +424,7 @@ internal class WebSocketResponseProvider(WebSocketBuilder builder) : IResponsePr
}
// Log the proxy operation
- LogWebSocketMessage(context, direction, result.MessageType, data, activity);
+ context.LogWebSocketMessage(direction, result.MessageType, data, activity);
await destination.SendAsync(
new ArraySegment(buffer, 0, result.Count),
@@ -501,7 +494,7 @@ internal class WebSocketResponseProvider(WebSocketBuilder builder) : IResponsePr
Array.Copy(buffer, (byte[])data, result.Count);
}
- LogWebSocketMessage(context, WebSocketMessageDirection.Receive, result.MessageType, data, receiveActivity);
+ context.LogWebSocketMessage(WebSocketMessageDirection.Receive, result.MessageType, data, receiveActivity);
if (result.MessageType == WebSocketMessageType.Close)
{
@@ -529,101 +522,6 @@ internal class WebSocketResponseProvider(WebSocketBuilder builder) : IResponsePr
}
}
- private static void LogWebSocketMessage(
- WireMockWebSocketContext context,
- WebSocketMessageDirection direction,
- WebSocketMessageType messageType,
- object? data,
- Activity? activity)
- {
- // Skip logging if log count limit is disabled
- if (context.Options.MaxRequestLogCount == 0)
- {
- return;
- }
-
- // Create body data
- IBodyData bodyData;
- if (messageType == WebSocketMessageType.Text && data is string textContent)
- {
- bodyData = new BodyData
- {
- BodyAsString = textContent,
- DetectedBodyType = BodyType.String
- };
- }
- else if (messageType == WebSocketMessageType.Binary && data is byte[] binary)
- {
- bodyData = new BodyData
- {
- BodyAsBytes = binary,
- DetectedBodyType = BodyType.Bytes
- };
- }
- else
- {
- bodyData = new BodyData
- {
- BodyAsString = messageType.ToString(),
- DetectedBodyType = BodyType.String
- };
- }
-
- // Create a pseudo-request or pseudo-response depending on direction
- RequestMessage? requestMessage = null;
- IResponseMessage? responseMessage = null;
-
- var method = $"WS_{direction.ToString().ToUpperInvariant()}";
-
- if (direction == WebSocketMessageDirection.Receive)
- {
- // Received message - log as request
- requestMessage = new RequestMessage(
- new UrlDetails(context.RequestMessage.Url),
- method,
- context.RequestMessage.ClientIP,
- bodyData,
- null,
- null
- )
- {
- DateTime = DateTime.UtcNow
- };
- }
- else
- {
- // Sent message - log as response
- responseMessage = new ResponseMessage
- {
- Method = method,
- StatusCode = HttpStatusCode.SwitchingProtocols, // WebSocket status
- BodyData = bodyData,
- DateTime = DateTime.UtcNow
- };
- }
-
- // Create log entry
- var logEntry = new LogEntry
- {
- Guid = Guid.NewGuid(),
- RequestMessage = requestMessage,
- ResponseMessage = responseMessage,
- MappingGuid = context.Mapping.Guid,
- MappingTitle = context.Mapping.Title
- };
-
- // Enrich activity if present
- if (activity != null && context.Options.ActivityTracingOptions != null)
- {
- WireMockActivitySource.EnrichWithLogEntry(activity, logEntry, context.Options.ActivityTracingOptions);
- }
-
- // Log using LogLogEntry
- context.Logger.LogLogEntry(logEntry, context.Options.MaxRequestLogCount is null or > 0);
-
- activity?.Dispose();
- }
-
private static WebSocketMessage CreateWebSocketMessage(WebSocketReceiveResult result, byte[] buffer)
{
var message = new WebSocketMessage
diff --git a/src/WireMock.Net.Minimal/Serialization/LogEntryMapper.cs b/src/WireMock.Net.Minimal/Serialization/LogEntryMapper.cs
index 01357526..c244c3df 100644
--- a/src/WireMock.Net.Minimal/Serialization/LogEntryMapper.cs
+++ b/src/WireMock.Net.Minimal/Serialization/LogEntryMapper.cs
@@ -42,6 +42,8 @@ internal class LogEntryMapper(IWireMockMiddlewareOptions options)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
+ case BodyType.WebSocketText:
+ case BodyType.WebSocketClose:
logRequestModel.Body = logEntry.RequestMessage.BodyData.BodyAsString;
break;
@@ -51,6 +53,7 @@ internal class LogEntryMapper(IWireMockMiddlewareOptions options)
break;
case BodyType.Bytes:
+ case BodyType.WebSocketBinary:
logRequestModel.BodyAsBytes = logEntry.RequestMessage.BodyData.BodyAsBytes;
break;
}
@@ -126,6 +129,8 @@ internal class LogEntryMapper(IWireMockMiddlewareOptions options)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
+ case BodyType.WebSocketText:
+ case BodyType.WebSocketClose:
if (!string.IsNullOrEmpty(logEntry.ResponseMessage.BodyData.IsFuncUsed) && options.DoNotSaveDynamicResponseInLogEntry == true)
{
logResponseModel.Body = logEntry.ResponseMessage.BodyData.IsFuncUsed;
@@ -141,6 +146,7 @@ internal class LogEntryMapper(IWireMockMiddlewareOptions options)
break;
case BodyType.Bytes:
+ case BodyType.WebSocketBinary:
logResponseModel.BodyAsBytes = logEntry.ResponseMessage.BodyData.BodyAsBytes;
break;
diff --git a/src/WireMock.Net.Minimal/WebSockets/WireMockWebSocketContext.cs b/src/WireMock.Net.Minimal/WebSockets/WireMockWebSocketContext.cs
index 3bceae76..511e2ded 100644
--- a/src/WireMock.Net.Minimal/WebSockets/WireMockWebSocketContext.cs
+++ b/src/WireMock.Net.Minimal/WebSockets/WireMockWebSocketContext.cs
@@ -7,8 +7,6 @@ using System.Text;
using Microsoft.AspNetCore.Http;
using Stef.Validation;
using WireMock.Logging;
-using WireMock.Matchers;
-using WireMock.Matchers.Request;
using WireMock.Models;
using WireMock.Owin;
using WireMock.Owin.ActivityTracing;
@@ -93,51 +91,97 @@ public class WireMockWebSocketContext : IWebSocketContext
);
}
- private async Task SendAsyncInternal(
- ArraySegment buffer,
- WebSocketMessageType messageType,
- bool endOfMessage,
- object? data,
- CancellationToken cancellationToken)
+ ///
+ public async Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription)
{
- Activity? activity = null;
- var shouldTrace = Options.ActivityTracingOptions is not null;
+ await WebSocket.CloseAsync(closeStatus, statusDescription, CancellationToken.None);
- if (shouldTrace)
+ LogWebSocketMessage(WebSocketMessageDirection.Send, WebSocketMessageType.Close, $"CloseStatus: {closeStatus}, Description: {statusDescription}", null);
+ }
+
+ ///
+ public void SetScenarioState(string nextState)
+ {
+ SetScenarioState(nextState, null);
+ }
+
+ ///
+ public void SetScenarioState(string nextState, string? description)
+ {
+ if (Mapping.Scenario == null)
{
- activity = WireMockActivitySource.StartWebSocketMessageActivity(WebSocketMessageDirection.Send, Mapping.Guid);
- WireMockActivitySource.EnrichWithWebSocketMessage(
- activity,
- messageType,
- buffer.Count,
- endOfMessage,
- data as string,
- Options.ActivityTracingOptions
- );
+ return;
}
- try
+ // Use the same logic as WireMockMiddleware
+ if (Options.Scenarios.TryGetValue(Mapping.Scenario, out var scenarioState))
{
- await WebSocket.SendAsync(buffer, messageType, endOfMessage, cancellationToken).ConfigureAwait(false);
+ // Directly set the next state (bypass counter logic for manual WebSocket state changes)
+ scenarioState.NextState = nextState;
+ scenarioState.Started = true;
+ scenarioState.Finished = nextState == null;
- // Log the send operation
- if (Options.MaxRequestLogCount is null or > 0)
+ // Reset counter when manually setting state
+ scenarioState.Counter = 0;
+ }
+ else
+ {
+ // Create new scenario state if it doesn't exist
+ Options.Scenarios.TryAdd(Mapping.Scenario, new ScenarioState
{
- LogWebSocketMessage(WebSocketMessageDirection.Send, messageType, data, activity);
- }
- }
- catch (Exception ex)
- {
- WireMockActivitySource.RecordException(activity, ex);
- throw;
- }
- finally
- {
- activity?.Dispose();
+ Name = Mapping.Scenario,
+ NextState = nextState,
+ Started = true,
+ Finished = nextState == null,
+ Counter = 0
+ });
}
}
- private void LogWebSocketMessage(
+ ///
+ public async Task BroadcastTextAsync(string text, CancellationToken cancellationToken = default)
+ {
+ if (Registry != null)
+ {
+ await Registry.BroadcastTextAsync(text, cancellationToken);
+ }
+ }
+
+ ///
+ /// Update scenario state following the same pattern as WireMockMiddleware.UpdateScenarioState
+ /// This is called automatically when the WebSocket connection is established.
+ ///
+ internal void UpdateScenarioState()
+ {
+ if (Mapping.Scenario == null)
+ {
+ return;
+ }
+
+ // Ensure scenario exists
+ if (!Options.Scenarios.TryGetValue(Mapping.Scenario, out var scenario))
+ {
+ return;
+ }
+
+ // Follow exact same logic as WireMockMiddleware.UpdateScenarioState
+ // Increase the number of times this state has been executed
+ scenario.Counter++;
+
+ // Only if the number of times this state is executed equals the required StateTimes,
+ // proceed to next state and reset the counter to 0
+ if (scenario.Counter == (Mapping.TimesInSameState ?? 1))
+ {
+ scenario.NextState = Mapping.NextState;
+ scenario.Counter = 0;
+ }
+
+ // Else just update Started and Finished
+ scenario.Started = true;
+ scenario.Finished = Mapping.NextState == null;
+ }
+
+ internal void LogWebSocketMessage(
WebSocketMessageDirection direction,
WebSocketMessageType messageType,
object? data,
@@ -150,7 +194,7 @@ public class WireMockWebSocketContext : IWebSocketContext
bodyData = new BodyData
{
BodyAsString = textContent,
- DetectedBodyType = BodyType.String
+ DetectedBodyType = BodyType.WebSocketText
};
}
else if (messageType == WebSocketMessageType.Binary && data is byte[] binary)
@@ -158,7 +202,7 @@ public class WireMockWebSocketContext : IWebSocketContext
bodyData = new BodyData
{
BodyAsBytes = binary,
- DetectedBodyType = BodyType.Bytes
+ DetectedBodyType = BodyType.WebSocketBinary
};
}
else
@@ -166,7 +210,7 @@ public class WireMockWebSocketContext : IWebSocketContext
bodyData = new BodyData
{
BodyAsString = messageType.ToString(),
- DetectedBodyType = BodyType.String
+ DetectedBodyType = BodyType.WebSocketClose
};
}
@@ -225,93 +269,47 @@ public class WireMockWebSocketContext : IWebSocketContext
activity?.Dispose();
}
- ///
- public async Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription)
+ private async Task SendAsyncInternal(
+ ArraySegment buffer,
+ WebSocketMessageType messageType,
+ bool endOfMessage,
+ object? data,
+ CancellationToken cancellationToken)
{
- await WebSocket.CloseAsync(closeStatus, statusDescription, CancellationToken.None);
+ Activity? activity = null;
+ var shouldTrace = Options.ActivityTracingOptions is not null;
- LogWebSocketMessage(WebSocketMessageDirection.Send, WebSocketMessageType.Close, $"CloseStatus: {closeStatus}, Description: {statusDescription}", null);
- }
-
- ///
- public void SetScenarioState(string nextState)
- {
- SetScenarioState(nextState, null);
- }
-
- ///
- public void SetScenarioState(string nextState, string? description)
- {
- if (Mapping.Scenario == null)
+ if (shouldTrace)
{
- return;
+ activity = WireMockActivitySource.StartWebSocketMessageActivity(WebSocketMessageDirection.Send, Mapping.Guid);
+ WireMockActivitySource.EnrichWithWebSocketMessage(
+ activity,
+ messageType,
+ buffer.Count,
+ endOfMessage,
+ data as string,
+ Options.ActivityTracingOptions
+ );
}
- // Use the same logic as WireMockMiddleware
- if (Options.Scenarios.TryGetValue(Mapping.Scenario, out var scenarioState))
+ try
{
- // Directly set the next state (bypass counter logic for manual WebSocket state changes)
- scenarioState.NextState = nextState;
- scenarioState.Started = true;
- scenarioState.Finished = nextState == null;
+ await WebSocket.SendAsync(buffer, messageType, endOfMessage, cancellationToken).ConfigureAwait(false);
- // Reset counter when manually setting state
- scenarioState.Counter = 0;
- }
- else
- {
- // Create new scenario state if it doesn't exist
- Options.Scenarios.TryAdd(Mapping.Scenario, new ScenarioState
+ // Log the send operation
+ if (Options.MaxRequestLogCount is null or > 0)
{
- Name = Mapping.Scenario,
- NextState = nextState,
- Started = true,
- Finished = nextState == null,
- Counter = 0
- });
+ LogWebSocketMessage(WebSocketMessageDirection.Send, messageType, data, activity);
+ }
}
- }
-
- ///
- /// Update scenario state following the same pattern as WireMockMiddleware.UpdateScenarioState
- /// This is called automatically when the WebSocket connection is established.
- ///
- internal void UpdateScenarioState()
- {
- if (Mapping.Scenario == null)
+ catch (Exception ex)
{
- return;
+ WireMockActivitySource.RecordException(activity, ex);
+ throw;
}
-
- // Ensure scenario exists
- if (!Options.Scenarios.TryGetValue(Mapping.Scenario, out var scenario))
+ finally
{
- return;
- }
-
- // Follow exact same logic as WireMockMiddleware.UpdateScenarioState
- // Increase the number of times this state has been executed
- scenario.Counter++;
-
- // Only if the number of times this state is executed equals the required StateTimes,
- // proceed to next state and reset the counter to 0
- if (scenario.Counter == (Mapping.TimesInSameState ?? 1))
- {
- scenario.NextState = Mapping.NextState;
- scenario.Counter = 0;
- }
-
- // Else just update Started and Finished
- scenario.Started = true;
- scenario.Finished = Mapping.NextState == null;
- }
-
- ///
- public async Task BroadcastTextAsync(string text, CancellationToken cancellationToken = default)
- {
- if (Registry != null)
- {
- await Registry.BroadcastTextAsync(text, cancellationToken);
+ activity?.Dispose();
}
}
}
\ No newline at end of file