11 KiB
WireMock.Net WebSocket Implementation - Implementation Summary
Overview
This document summarizes the WebSocket implementation for WireMock.Net that enables mocking real-time WebSocket connections for testing purposes.
Implementation Status
✅ COMPLETED - Core WebSocket infrastructure implemented and ready for middleware integration
Project Structure
New Project: src/WireMock.Net.WebSockets/
Created a new dedicated project to house all WebSocket-specific functionality:
src/WireMock.Net.WebSockets/
├── WireMock.Net.WebSockets.csproj # Project file
├── GlobalUsings.cs # Global using statements
├── README.md # User documentation
├── Models/
│ ├── WebSocketMessage.cs # Message representation
│ ├── WebSocketHandlerContext.cs # Connection context
│ └── WebSocketConnectRequest.cs # Upgrade request details
├── Matchers/
│ └── WebSocketRequestMatcher.cs # WebSocket upgrade detection
├── ResponseProviders/
│ └── WebSocketResponseProvider.cs # WebSocket connection handler
├── RequestBuilders/
│ └── IWebSocketRequestBuilder.cs # Request builder interface
└── ResponseBuilders/
└── IWebSocketResponseBuilder.cs # Response builder interface
Extensions to Existing Files
src/WireMock.Net.Minimal/RequestBuilders/Request.WebSocket.cs
- Added
IWebSocketRequestBuilderimplementation to Request class - Methods:
WithWebSocketPath(string path)- Match WebSocket pathsWithWebSocketSubprotocol(params string[])- Match subprotocolsWithCustomHandshakeHeaders(params (string, string)[])- Match headers
- Internal method
GetWebSocketMatcher()- Creates matcher for middleware
src/WireMock.Net.Minimal/ResponseBuilders/Response.WebSocket.cs
- Added
IWebSocketResponseBuilderimplementation to Response class - Properties:
WebSocketHandler- Raw WebSocket connection handlerWebSocketMessageHandler- Message-based routing handlerWebSocketKeepAliveInterval- Keep-alive heartbeat timingWebSocketTimeout- Connection timeoutIsWebSocketConfigured- Indicator if WebSocket is configured
- Methods:
WithWebSocketHandler(Func<WebSocketHandlerContext, Task>)WithWebSocketHandler(Func<WebSocket, Task>)WithWebSocketMessageHandler(Func<WebSocketMessage, Task<WebSocketMessage?>>)WithWebSocketKeepAlive(TimeSpan)WithWebSocketTimeout(TimeSpan)WithWebSocketMessage(WebSocketMessage)
Project References Updated
src/WireMock.Net/WireMock.Net.csproj
- Added reference to
WireMock.Net.WebSocketsfor .NET Core 3.1+
src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj
- Added reference to
WireMock.Net.WebSocketsfor .NET Core 3.1+
Core Components
1. WebSocketMessage Model
Represents a WebSocket message in either text or binary format:
public class WebSocketMessage
{
public string Type { get; set; }
public DateTime Timestamp { get; set; }
public object? Data { get; set; }
public bool IsBinary { get; set; }
public byte[]? RawData { get; set; }
public string? TextData { get; set; }
}
2. WebSocketHandlerContext
Provides full context to handlers including the WebSocket, request details, headers, and user state:
public class WebSocketHandlerContext
{
public WebSocket WebSocket { get; init; }
public IRequestMessage RequestMessage { get; init; }
public IDictionary<string, string[]> Headers { get; init; }
public string? SubProtocol { get; init; }
public IDictionary<string, object> UserState { get; init; }
}
3. WebSocketConnectRequest
Represents the upgrade request for matching purposes:
public class WebSocketConnectRequest
{
public string Path { get; init; }
public IDictionary<string, string[]> Headers { get; init; }
public IList<string> SubProtocols { get; init; }
public string? RemoteAddress { get; init; }
public string? LocalAddress { get; init; }
}
4. WebSocketRequestMatcher
Detects and matches WebSocket upgrade requests:
- Checks for
Upgrade: websocketheader - Checks for
Connection: Upgradeheader - Matches paths using configured matchers
- Validates subprotocols
- Supports custom predicates
5. WebSocketResponseProvider
Manages WebSocket connections:
- Handles raw WebSocket connections
- Supports message-based routing
- Provides default echo behavior
- Manages keep-alive heartbeats
- Handles connection timeouts
- Properly closes connections
Usage Examples
Basic Echo Server
var server = WireMockServer.Start();
server
.Given(Request.Create()
.WithPath("/echo")
)
.RespondWith(Response.Create()
.WithWebSocketHandler(async ctx =>
{
var buffer = new byte[1024 * 4];
var result = await ctx.WebSocket.ReceiveAsync(
new ArraySegment<byte>(buffer),
CancellationToken.None);
await ctx.WebSocket.SendAsync(
new ArraySegment<byte>(buffer, 0, result.Count),
result.MessageType,
result.EndOfMessage,
CancellationToken.None);
})
);
Message-Based Routing
server
.Given(Request.Create()
.WithPath("/api/ws")
)
.RespondWith(Response.Create()
.WithWebSocketMessageHandler(async msg =>
{
return msg.Type switch
{
"subscribe" => new WebSocketMessage { Type = "subscribed" },
"ping" => new WebSocketMessage { Type = "pong" },
_ => null
};
})
);
Authenticated WebSocket
server
.Given(Request.Create()
.WithPath("/secure-ws")
.WithHeader("Authorization", "Bearer token123")
)
.RespondWith(Response.Create()
.WithWebSocketHandler(async ctx =>
{
// Only authenticated connections reach here
await SendWelcomeAsync(ctx.WebSocket);
})
);
Testing
Created comprehensive test suite in test/WireMock.Net.Tests/WebSockets/WebSocketTests.cs:
- Echo handler functionality
- Message handler configuration
- Keep-alive interval storage
- Timeout configuration
- IsConfigured property validation
- Path matching validation
- Subprotocol matching validation
Next Steps for Middleware Integration
To fully enable WebSocket support, the following middleware changes are needed:
1. Update WireMock.Net.AspNetCore.Middleware
Add WebSocket middleware handler:
if (context.WebSockets.IsWebSocketRequest)
{
var requestMatcher = mapping.RequestMatcher;
// Check if this is a WebSocket request
if (requestMatcher.Match(requestMessage).IsPerfectMatch)
{
// Accept WebSocket
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
// Get the response provider
var provider = mapping.Provider;
if (provider is WebSocketResponseProvider wsProvider)
{
await wsProvider.HandleWebSocketAsync(webSocket, requestMessage);
}
}
}
2. Update Routing
Ensure WebSocket upgrade requests are properly routed through mapping evaluation before being passed to the middleware.
3. Configuration
Add WebSocket settings to WireMockServerSettings:
public WebSocketOptions? WebSocketOptions { get; set; }
Features Implemented
✅ Request matching for WebSocket upgrades
✅ Path-based routing
✅ Subprotocol negotiation support
✅ Custom header validation
✅ Raw WebSocket handler support
✅ Message-based routing support
✅ Keep-alive heartbeat configuration
✅ Connection timeout configuration
✅ Binary and text message support
✅ Fluent builder API
✅ Comprehensive documentation
✅ Unit tests
Features Not Yet Implemented
⏳ Middleware integration (requires AspNetCore.Middleware updates)
⏳ Admin API support
⏳ Response message transformers
⏳ Proxy mode for WebSockets
⏳ Compression support (RFC 7692)
⏳ Connection lifecycle events (OnConnect, OnClose, OnError)
Compatibility
- .NET Framework: Not supported (WebSockets API not available)
- .NET Standard 1.3, 2.0, 2.1: Supported (no actual WebSocket server)
- .NET Core 3.1+: Fully supported
- .NET 5.0+: Fully supported
Architecture Decisions
- Separate Project - Created
WireMock.Net.WebSocketsto keep concerns separated while maintaining discoverability - Fluent API - Followed WireMock.Net's existing fluent builder pattern for consistency
- Properties-Based - Used properties in Response class to store configuration, allowing for extensibility
- Matcher-Based - Created dedicated matcher to integrate with existing request matching infrastructure
- Async/Await - All handlers are async to support long-lived connections and concurrent requests
Code Quality
- Follows WireMock.Net coding standards
- Includes XML documentation comments
- Uses nullable reference types (
#nullable enable) - Implements proper error handling
- No external dependencies beyond existing WireMock.Net packages
- Comprehensive unit test coverage
File Locations
| File | Purpose |
|---|---|
src/WireMock.Net.WebSockets/WireMock.Net.WebSockets.csproj |
Project file |
src/WireMock.Net.WebSockets/Models/*.cs |
Data models |
src/WireMock.Net.WebSockets/Matchers/WebSocketRequestMatcher.cs |
Request matching |
src/WireMock.Net.WebSockets/ResponseProviders/WebSocketResponseProvider.cs |
Connection handling |
src/WireMock.Net.WebSockets/RequestBuilders/IWebSocketRequestBuilder.cs |
Request builder interface |
src/WireMock.Net.WebSockets/ResponseBuilders/IWebSocketResponseBuilder.cs |
Response builder interface |
src/WireMock.Net.Minimal/RequestBuilders/Request.WebSocket.cs |
Request builder implementation |
src/WireMock.Net.Minimal/ResponseBuilders/Response.WebSocket.cs |
Response builder implementation |
test/WireMock.Net.Tests/WebSockets/WebSocketTests.cs |
Unit tests |
examples/WireMock.Net.Console.WebSocketExamples/WebSocketExamples.cs |
Usage examples |
src/WireMock.Net.WebSockets/README.md |
User documentation |
Integration Notes
When integrating with middleware:
- The
Request.GetWebSocketMatcher()method returns aWebSocketRequestMatcherthat should be added to request matchers - The
Response.WebSocketHandlerandResponse.WebSocketMessageHandlerproperties contain the configured handlers - The
Response.IsWebSocketConfiguredproperty indicates if WebSocket is configured - The
WebSocketResponseProvider.HandleWebSocketAsync()method accepts the WebSocket and request - Always check
context.WebSockets.IsWebSocketRequestbefore attempting to accept WebSocket
Performance Considerations
- Each WebSocket connection maintains a single long-lived task
- Message processing is sequential per connection (not concurrent)
- Keep-alive heartbeats prevent timeout of idle connections
- Connection timeout prevents zombie connections
- No additional memory overhead for non-WebSocket requests