Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Commit c852bdc

Browse files
Avoid zero-byte send in WebSockets (#3326)
1 parent dde73d0 commit c852bdc

File tree

2 files changed

+42
-5
lines changed

2 files changed

+42
-5
lines changed

src/Common/WebSocketExtensions.cs

+11-5
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,28 @@ public static ValueTask SendAsync(this WebSocket webSocket, ReadOnlySequence<byt
3939
private static async ValueTask SendMultiSegmentAsync(WebSocket webSocket, ReadOnlySequence<byte> buffer, WebSocketMessageType webSocketMessageType, CancellationToken cancellationToken = default)
4040
{
4141
var position = buffer.Start;
42+
// Get a segment before the loop so we can be one segment behind while writing
43+
// This allows us to do a non-zero byte write for the endOfMessage = true send
44+
buffer.TryGet(ref position, out var prevSegment);
4245
while (buffer.TryGet(ref position, out var segment))
4346
{
4447
#if NETCOREAPP3_0
45-
await webSocket.SendAsync(segment, webSocketMessageType, endOfMessage: false, cancellationToken);
48+
await webSocket.SendAsync(prevSegment, webSocketMessageType, endOfMessage: false, cancellationToken);
4649
#else
47-
var isArray = MemoryMarshal.TryGetArray(segment, out var arraySegment);
50+
var isArray = MemoryMarshal.TryGetArray(prevSegment, out var arraySegment);
4851
Debug.Assert(isArray);
4952
await webSocket.SendAsync(arraySegment, webSocketMessageType, endOfMessage: false, cancellationToken);
5053
#endif
54+
prevSegment = segment;
5155
}
5256

53-
// Empty end of message frame
57+
// End of message frame
5458
#if NETCOREAPP3_0
55-
await webSocket.SendAsync(Memory<byte>.Empty, webSocketMessageType, endOfMessage: true, cancellationToken);
59+
await webSocket.SendAsync(prevSegment, webSocketMessageType, endOfMessage: true, cancellationToken);
5660
#else
57-
await webSocket.SendAsync(new ArraySegment<byte>(Array.Empty<byte>()), webSocketMessageType, endOfMessage: true, cancellationToken);
61+
var isArrayEnd = MemoryMarshal.TryGetArray(prevSegment, out var arraySegmentEnd);
62+
Debug.Assert(isArrayEnd);
63+
await webSocket.SendAsync(arraySegmentEnd, webSocketMessageType, endOfMessage: true, cancellationToken);
5864
#endif
5965
}
6066
}

test/Microsoft.AspNetCore.Http.Connections.Tests/WebSocketsTests.cs

+31
Original file line numberDiff line numberDiff line change
@@ -396,5 +396,36 @@ public async Task SubProtocolSelectorIsUsedToSelectSubProtocol()
396396
}
397397
}
398398
}
399+
400+
[Fact]
401+
public async Task MultiSegmentSendWillNotSendEmptyEndOfMessageFrame()
402+
{
403+
using (var feature = new TestWebSocketConnectionFeature())
404+
{
405+
var serverSocket = await feature.AcceptAsync();
406+
var sequence = ReadOnlySequenceFactory.CreateSegments(new byte[] { 1 }, new byte[] { 15 });
407+
Assert.False(sequence.IsSingleSegment);
408+
409+
await serverSocket.SendAsync(sequence, WebSocketMessageType.Text);
410+
411+
// Run the client socket
412+
var client = feature.Client.ExecuteAndCaptureFramesAsync();
413+
414+
await serverSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", default);
415+
416+
var messages = await client.OrTimeout();
417+
Assert.Equal(2, messages.Received.Count);
418+
419+
// First message: 1 byte, endOfMessage false
420+
Assert.Single(messages.Received[0].Buffer);
421+
Assert.Equal(1, messages.Received[0].Buffer[0]);
422+
Assert.False(messages.Received[0].EndOfMessage);
423+
424+
// Second message: 1 byte, endOfMessage true
425+
Assert.Single(messages.Received[1].Buffer);
426+
Assert.Equal(15, messages.Received[1].Buffer[0]);
427+
Assert.True(messages.Received[1].EndOfMessage);
428+
}
429+
}
399430
}
400431
}

0 commit comments

Comments
 (0)