feat: integrate WebSocket accept loop into NatsServer and NatsClient
Add WebSocket listener support to NatsServer alongside the existing TCP listener. When WebSocketOptions.Port >= 0, the server binds a second socket, performs HTTP upgrade via WsUpgrade.TryUpgradeAsync, wraps the connection in WsConnection for transparent frame/deframe, and hands it to the standard NatsClient pipeline. Changes: - NatsClient: add IsWebSocket and WsInfo properties - NatsServer: add RunWebSocketAcceptLoopAsync and AcceptWebSocketClientAsync, WS listener lifecycle in StartAsync/ShutdownAsync/Dispose - NatsOptions: change WebSocketOptions.Port default from 0 to -1 (disabled) - WsConnection.ReadAsync: fix premature end-of-stream when ReadFrames returns no payloads by looping until data is available - Add WsIntegration tests (connect, ping, pub/sub over WebSocket) - Add WsConnection masked frame and end-of-stream unit tests
This commit is contained in:
@@ -39,34 +39,37 @@ public sealed class WsConnection : Stream
|
||||
if (_readQueue.Count > 0)
|
||||
return DrainReadQueue(buffer.Span);
|
||||
|
||||
// Read raw bytes from inner stream
|
||||
var rawBuf = new byte[Math.Max(buffer.Length, 4096)];
|
||||
int bytesRead = await _inner.ReadAsync(rawBuf.AsMemory(), ct);
|
||||
if (bytesRead == 0) return 0;
|
||||
|
||||
// Decode frames
|
||||
var payloads = WsReadInfo.ReadFrames(ref _readInfo, new MemoryStream(rawBuf, 0, bytesRead), bytesRead, maxPayload: 1024 * 1024);
|
||||
|
||||
// Collect control frame responses
|
||||
if (_readInfo.PendingControlFrames.Count > 0)
|
||||
while (true)
|
||||
{
|
||||
lock (_writeLock)
|
||||
_pendingControlWrites.AddRange(_readInfo.PendingControlFrames);
|
||||
_readInfo.PendingControlFrames.Clear();
|
||||
// Write pending control frames
|
||||
await FlushControlFramesAsync(ct);
|
||||
// Read raw bytes from inner stream
|
||||
var rawBuf = new byte[Math.Max(buffer.Length, 4096)];
|
||||
int bytesRead = await _inner.ReadAsync(rawBuf.AsMemory(), ct);
|
||||
if (bytesRead == 0) return 0;
|
||||
|
||||
// Decode frames
|
||||
var payloads = WsReadInfo.ReadFrames(ref _readInfo, new MemoryStream(rawBuf, 0, bytesRead), bytesRead, maxPayload: 1024 * 1024);
|
||||
|
||||
// Collect control frame responses
|
||||
if (_readInfo.PendingControlFrames.Count > 0)
|
||||
{
|
||||
lock (_writeLock)
|
||||
_pendingControlWrites.AddRange(_readInfo.PendingControlFrames);
|
||||
_readInfo.PendingControlFrames.Clear();
|
||||
// Write pending control frames
|
||||
await FlushControlFramesAsync(ct);
|
||||
}
|
||||
|
||||
if (_readInfo.CloseReceived)
|
||||
return 0;
|
||||
|
||||
foreach (var payload in payloads)
|
||||
_readQueue.Enqueue(payload);
|
||||
|
||||
// If no payloads were decoded (e.g. only frame headers were read),
|
||||
// continue reading instead of returning 0 which signals end-of-stream
|
||||
if (_readQueue.Count > 0)
|
||||
return DrainReadQueue(buffer.Span);
|
||||
}
|
||||
|
||||
if (_readInfo.CloseReceived)
|
||||
return 0;
|
||||
|
||||
foreach (var payload in payloads)
|
||||
_readQueue.Enqueue(payload);
|
||||
|
||||
if (_readQueue.Count == 0)
|
||||
return 0;
|
||||
|
||||
return DrainReadQueue(buffer.Span);
|
||||
}
|
||||
|
||||
public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken ct = default)
|
||||
|
||||
Reference in New Issue
Block a user