mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-07-02 02:51:50 +02:00
459 lines
12 KiB
Markdown
459 lines
12 KiB
Markdown
# WebSocket Fluent Interface - Quick Reference
|
|
|
|
## At a Glance
|
|
|
|
### Current Architecture (HTTP Only)
|
|
|
|
```csharp
|
|
// Request matching
|
|
Request.Create()
|
|
.WithPath("/api")
|
|
.WithHeader("...")
|
|
.UsingGet()
|
|
|
|
// Response building
|
|
Response.Create()
|
|
.WithStatusCode(200)
|
|
.WithBodyAsJson(...)
|
|
.WithTransformer()
|
|
|
|
// Mapping
|
|
server.Given(request)
|
|
.AtPriority(1)
|
|
.InScenario("...")
|
|
.RespondWith(response)
|
|
```
|
|
|
|
### Proposed Addition (WebSocket Support)
|
|
|
|
```csharp
|
|
// WebSocket request matching
|
|
Request.Create()
|
|
.WithWebSocketPath("/ws")
|
|
.WithWebSocketSubprotocol("chat")
|
|
|
|
// WebSocket response building
|
|
Response.Create()
|
|
.WithWebSocket(ws => ws
|
|
.WithMessage("Hello")
|
|
.WithJsonMessage(obj)
|
|
.WithTransformer()
|
|
)
|
|
.WithWebSocketCallback(async req => ... messages ...)
|
|
|
|
// Mapping (same as HTTP)
|
|
server.Given(request)
|
|
.RespondWith(response)
|
|
```
|
|
|
|
---
|
|
|
|
## Quick Comparison: HTTP vs WebSocket Fluent API
|
|
|
|
### Request Builder
|
|
|
|
| HTTP | WebSocket |
|
|
|------|-----------|
|
|
| `WithPath(string)` | `WithWebSocketPath(string)` |
|
|
| `WithHeader(string, string)` | `WithHeader(...)` (same) |
|
|
| `UsingGet()` | `WithWebSocketUpgrade()` (implicit) |
|
|
| `WithParam(string, string)` | (not applicable) |
|
|
| `WithBody(string)` | (connection is upgrade, no body) |
|
|
|
|
### Response Builder
|
|
|
|
| HTTP | WebSocket |
|
|
|------|-----------|
|
|
| `WithStatusCode(int)` | `WithWebSocketClose(int)` |
|
|
| `WithBody(string)` | `WithMessage(string)` |
|
|
| `WithBodyAsJson(object)` | `WithJsonMessage(object)` |
|
|
| (binary: rarely used) | `WithBinaryMessage(byte[])` |
|
|
| `WithCallback(...)` | `WithWebSocketCallback(...)` |
|
|
| `WithTransformer()` | `WithTransformer()` (same) |
|
|
|
|
### Mapping Configuration
|
|
|
|
| Feature | HTTP | WebSocket |
|
|
|---------|------|-----------|
|
|
| Priority | ✓ `AtPriority(int)` | ✓ `AtPriority(int)` |
|
|
| Scenario | ✓ `InScenario(...)` | ✓ `InScenario(...)` |
|
|
| Webhook | ✓ `WithWebhook(...)` | ✓ `WithWebhook(...)` |
|
|
| Title/Desc | ✓ `WithTitle(...)` | ✓ `WithTitle(...)` |
|
|
|
|
---
|
|
|
|
## Implementation Checklist
|
|
|
|
### Phase 1: Abstractions
|
|
- [ ] Create `IWebSocketMessage` interface
|
|
- [ ] Create `IWebSocketResponse` interface
|
|
- [ ] Create `IWebSocketResponseBuilder` interface
|
|
- [ ] Add `WebSocketModel` to admin mappings
|
|
- [ ] Extend `IRequestBuilder` with WebSocket methods
|
|
- [ ] Extend `IResponseBuilder` with WebSocket methods
|
|
|
|
### Phase 2: Domain Models
|
|
- [ ] Implement `WebSocketMessage` class
|
|
- [ ] Implement `WebSocketResponse` class
|
|
|
|
### Phase 3: Request Builder Extension
|
|
- [ ] Create `Request.WithWebSocket.cs` partial class
|
|
- [ ] Implement `WithWebSocketUpgrade()`
|
|
- [ ] Implement `WithWebSocketPath()`
|
|
- [ ] Implement `WithWebSocketSubprotocol()`
|
|
- [ ] Add unit tests
|
|
|
|
### Phase 4: Response Builder Extension
|
|
- [ ] Create `Response.WithWebSocket.cs` partial class
|
|
- [ ] Implement WebSocket response methods
|
|
- [ ] Create `WebSocketResponseBuilder.cs`
|
|
- [ ] Add transformer support
|
|
- [ ] Add callback support
|
|
- [ ] Add unit tests
|
|
|
|
### Phase 5: Server Integration
|
|
- [ ] Update `WireMockMiddleware.cs` to handle WebSocket upgrades
|
|
- [ ] Implement WebSocket connection handling
|
|
- [ ] Implement message delivery
|
|
- [ ] Add connection lifecycle management
|
|
- [ ] Add integration tests
|
|
|
|
### Phase 6: Admin Interface
|
|
- [ ] Extend `MappingModel` with WebSocket config
|
|
- [ ] Update mapping serialization
|
|
- [ ] Add REST API endpoints for WebSocket management
|
|
|
|
---
|
|
|
|
## File Changes Summary
|
|
|
|
### New Files (Abstractions)
|
|
```
|
|
src/WireMock.Net.Abstractions/
|
|
├── Models/IWebSocketMessage.cs
|
|
├── Models/IWebSocketResponse.cs
|
|
├── BuilderExtensions/IWebSocketResponseBuilder.cs
|
|
└── Admin/Mappings/WebSocketModel.cs
|
|
```
|
|
|
|
### New Files (Implementation)
|
|
```
|
|
src/WireMock.Net.Minimal/
|
|
├── Models/WebSocketMessage.cs
|
|
├── Models/WebSocketResponse.cs
|
|
├── RequestBuilders/Request.WithWebSocket.cs
|
|
├── ResponseBuilders/Response.WithWebSocket.cs
|
|
└── ResponseBuilders/WebSocketResponseBuilder.cs
|
|
```
|
|
|
|
### Modified Files
|
|
```
|
|
src/WireMock.Net.Minimal/
|
|
├── ResponseBuilders/Response.cs (add interface definitions)
|
|
├── RequestBuilders/Request.cs (add interface definitions)
|
|
├── Server/WireMockServer.cs (WebSocket support)
|
|
├── Owin/WireMockMiddleware.cs (handle upgrades)
|
|
└── Owin/MappingMatcher.cs (WebSocket routing)
|
|
```
|
|
|
|
---
|
|
|
|
## Code Examples
|
|
|
|
### Simple WebSocket
|
|
|
|
```csharp
|
|
// Echo server
|
|
server.Given(Request.Create().WithWebSocketPath("/echo"))
|
|
.RespondWith(Response.Create()
|
|
.WithWebSocketCallback(async req =>
|
|
new[] { new WebSocketMessage { BodyAsString = req.Body } }
|
|
)
|
|
);
|
|
```
|
|
|
|
### Messages with Delays
|
|
|
|
```csharp
|
|
// Multi-message response
|
|
server.Given(Request.Create().WithWebSocketPath("/stream"))
|
|
.RespondWith(Response.Create()
|
|
.WithWebSocket(ws => ws
|
|
.WithMessage("First", 0)
|
|
.WithMessage("Second", 500)
|
|
.WithMessage("Third", 1000)
|
|
)
|
|
);
|
|
```
|
|
|
|
### Dynamic Messages
|
|
|
|
```csharp
|
|
// Request-based generation
|
|
server.Given(Request.Create().WithWebSocketPath("/api"))
|
|
.RespondWith(Response.Create()
|
|
.WithWebSocketCallback(async request =>
|
|
{
|
|
var userId = request.Headers["X-User-Id"].First();
|
|
return new[]
|
|
{
|
|
new WebSocketMessage
|
|
{
|
|
BodyAsString = $"Hello {userId}"
|
|
}
|
|
};
|
|
})
|
|
);
|
|
```
|
|
|
|
### Templated Messages
|
|
|
|
```csharp
|
|
// Handlebars in message content
|
|
server.Given(Request.Create().WithWebSocketPath("/api"))
|
|
.RespondWith(Response.Create()
|
|
.WithWebSocket(ws => ws
|
|
.WithJsonMessage(new
|
|
{
|
|
user = "{{request.headers.X-User}}"
|
|
})
|
|
.WithTransformer()
|
|
)
|
|
);
|
|
```
|
|
|
|
### Subprotocol Negotiation
|
|
|
|
```csharp
|
|
// Version-specific behavior
|
|
server.Given(Request.Create()
|
|
.WithWebSocketPath("/api")
|
|
.WithWebSocketSubprotocol("v2"))
|
|
.RespondWith(Response.Create()
|
|
.WithWebSocketSubprotocol("v2")
|
|
.WithWebSocket(ws => ws
|
|
.WithMessage("v2 protocol")
|
|
)
|
|
);
|
|
```
|
|
|
|
### With State Management
|
|
|
|
```csharp
|
|
// Scenario-aware behavior
|
|
server.Given(Request.Create().WithWebSocketPath("/chat"))
|
|
.InScenario("ChatSession")
|
|
.WhenStateIs("LoggedIn")
|
|
.WillSetStateTo("ChatActive")
|
|
.RespondWith(Response.Create()
|
|
.WithWebSocket(ws => ws
|
|
.WithJsonMessage(new { status = "logged-in" })
|
|
)
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Design Principles
|
|
|
|
1. **Fluent First**: All builder methods return builder for chaining
|
|
2. **Composable**: Messages, transformers, callbacks combine naturally
|
|
3. **Consistent**: Follows HTTP mocking patterns and conventions
|
|
4. **Extensible**: Partial classes allow feature additions without refactoring
|
|
5. **Testable**: Deterministic, controllable message delivery
|
|
|
|
---
|
|
|
|
## Integration Points
|
|
|
|
### With Existing Features
|
|
|
|
```csharp
|
|
// Scenario management
|
|
server.Given(Request.Create().WithWebSocketPath("/ws"))
|
|
.InScenario("Session")
|
|
.WhenStateIs("Connected")
|
|
.WillSetStateTo("Active")
|
|
.RespondWith(...)
|
|
|
|
// Priority ordering
|
|
server.Given(Request.Create().WithWebSocketPath("/ws"))
|
|
.AtPriority(1)
|
|
.RespondWith(...)
|
|
|
|
// Webhooks
|
|
server.Given(Request.Create().WithWebSocketPath("/ws"))
|
|
.WithWebhook(new Webhook { ... })
|
|
.RespondWith(...)
|
|
|
|
// Admin interface
|
|
var mappings = server.MappingModels
|
|
.Where(m => m.Response.WebSocket != null)
|
|
.ToList()
|
|
```
|
|
|
|
---
|
|
|
|
## Testing Patterns
|
|
|
|
### Unit Test Template
|
|
|
|
```csharp
|
|
[Fact]
|
|
public void WebSocket_WithMultipleMessages_MaintainsOrder()
|
|
{
|
|
// Arrange
|
|
var builder = new WebSocketResponseBuilder();
|
|
|
|
// Act
|
|
var response = builder
|
|
.WithMessage("First", 0)
|
|
.WithMessage("Second", 100)
|
|
.Build();
|
|
|
|
// Assert
|
|
Assert.Equal("First", response.Messages[0].BodyAsString);
|
|
Assert.Equal("Second", response.Messages[1].BodyAsString);
|
|
Assert.Equal(100, response.Messages[1].DelayMs);
|
|
}
|
|
```
|
|
|
|
### Integration Test Template
|
|
|
|
```csharp
|
|
[Fact]
|
|
public async Task WebSocket_Client_ReceivesMessages()
|
|
{
|
|
// Arrange
|
|
var server = WireMockServer.Start();
|
|
server.Given(Request.Create().WithWebSocketPath("/test"))
|
|
.RespondWith(Response.Create()
|
|
.WithWebSocket(ws => ws
|
|
.WithMessage("Hello")
|
|
.WithMessage("World")
|
|
)
|
|
);
|
|
|
|
// Act
|
|
using var client = new ClientWebSocket();
|
|
await client.ConnectAsync(new Uri($"ws://localhost:{server.Port}/test"), CancellationToken.None);
|
|
|
|
// Receive first message
|
|
var buffer = new byte[1024];
|
|
var result = await client.ReceiveAsync(buffer, CancellationToken.None);
|
|
var message1 = Encoding.UTF8.GetString(buffer, 0, result.Count);
|
|
|
|
// Assert
|
|
Assert.Equal("Hello", message1);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Considerations
|
|
|
|
| Aspect | Impact | Mitigation |
|
|
|--------|--------|-----------|
|
|
| Message queuing | Linear with message count | Use callbacks for large streams |
|
|
| Memory | One message per connection | Implement cleanup on close |
|
|
| Concurrency | Handle multiple connections | Use async callbacks |
|
|
| Delays | Thread pool usage | Use reasonable delays (< 60s) |
|
|
|
|
---
|
|
|
|
## Common Issues & Solutions
|
|
|
|
### Issue 1: Message ordering
|
|
**Problem**: Messages delivered out of order
|
|
**Solution**: Use explicit delayMs, avoid concurrent message generation
|
|
|
|
### Issue 2: Connection timeout
|
|
**Problem**: Client disconnects before messages sent
|
|
**Solution**: Reduce message delays, increase test timeout
|
|
|
|
### Issue 3: Memory leak
|
|
**Problem**: Connections not closing properly
|
|
**Solution**: Always call `WithClose()` or `WithAutoClose()`
|
|
|
|
### Issue 4: Transformer not working
|
|
**Problem**: Template variables not substituted
|
|
**Solution**: Ensure `WithTransformer()` is called, check variable syntax
|
|
|
|
---
|
|
|
|
## Related Classes & Methods
|
|
|
|
### Request Builder
|
|
- `Request.Create()` - Start building
|
|
- `WithPath(string)` - HTTP path or WebSocket path
|
|
- `WithHeader(string, string)` - Custom headers
|
|
- `UsingGet()`, `UsingPost()`, etc. - HTTP methods
|
|
- `WithWebSocketUpgrade()` - Mark as WebSocket
|
|
- `WithWebSocketPath(string)` - Convenience method
|
|
- `WithWebSocketSubprotocol(string)` - Protocol version
|
|
|
|
### Response Builder
|
|
- `Response.Create()` - Start building
|
|
- `WithStatusCode(int)` - HTTP status
|
|
- `WithBody(string)` - HTTP body
|
|
- `WithBodyAsJson(object)` - JSON response
|
|
- `WithCallback(...)` - Dynamic HTTP response
|
|
- `WithWebSocket(...)` - WebSocket configuration
|
|
- `WithWebSocketMessage(string)` - Single message
|
|
- `WithWebSocketCallback(...)` - Dynamic WebSocket messages
|
|
- `WithWebSocketTransformer()` - Template support
|
|
- `WithWebSocketClose(int, string)` - Graceful close
|
|
|
|
### Mapping Builder
|
|
- `Given(IRequestMatcher)` - Start mapping
|
|
- `AtPriority(int)` - Execution priority
|
|
- `InScenario(string)` - Scenario grouping
|
|
- `WhenStateIs(string)` - State condition
|
|
- `WillSetStateTo(string)` - State change
|
|
- `WithTitle(string)` - Display name
|
|
- `WithDescription(string)` - Documentation
|
|
- `WithWebhook(...)` - Side effects
|
|
- `RespondWith(IResponseProvider)` - Terminal method
|
|
|
|
---
|
|
|
|
## Versioning Strategy
|
|
|
|
### Version 1.0
|
|
- Basic WebSocket support
|
|
- Static messages
|
|
- Message delays
|
|
- Callback support
|
|
- Transformer integration
|
|
|
|
### Version 1.1
|
|
- Subprotocol negotiation
|
|
- Binary message support
|
|
- Auto-close functionality
|
|
- WebSocket metrics
|
|
|
|
### Version 2.0
|
|
- Streaming responses
|
|
- Backpressure handling
|
|
- Message compression
|
|
- Custom close codes
|
|
|
|
---
|
|
|
|
## References
|
|
|
|
- **RFC 6455**: The WebSocket Protocol
|
|
- **RFC 7231**: HTTP Semantics and Content
|
|
- **ASP.NET Core WebSocket Support**: https://docs.microsoft.com/en-us/dotnet/api/system.net.websockets
|
|
- **WireMock.Net Documentation**: https://wiremock.org/docs/dotnet
|
|
|
|
---
|
|
|
|
## Contact & Support
|
|
|
|
For questions or contributions regarding WebSocket support:
|
|
1. Review the comprehensive design documents
|
|
2. Check the implementation templates for code examples
|
|
3. Refer to the best practices guide for patterns
|
|
4. File issues with detailed reproduction steps
|