Skip to content

Commit ef6b6d5

Browse files
IDisposableMartyIXCarnaViire
authored
Add additional close statuses in ManagedWebSocket (#83827)
* Allow non RFC close statuses in ManagedWebSocket Add ServiceRestart (1012), TryAgainLater (1013), and BadGateway (1014) to the list of `WebSocketCloseStatus` values and allow them to be used as valid WebSocket close statuses so we don't reject the close and discard the status description by adding them to the private `IsValueCloseStatus` method switch statement declaring them as valid `true`. These codes are documented [here as IANA registered codes](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code) as valid server-initiated close reasons. Fixes Issue #82602 * Cleanup comment format Co-authored-by: MartyIX <[email protected]> * Rename test data to CloseStatuses Co-authored-by: Natalia Kondratyeva <[email protected]> * Rename test method to follow naming convention. Co-authored-by: Natalia Kondratyeva <[email protected]> * Addressed PR feedback Finished rename of CloseStatuses test data Renamed `closeStatusDescription` to `serverMessage` Send hello message and close status then await both responses and check they are as expected. This necessitated switching to the `ReceiveAsync` that accepts an `ArraySegment`. Explicitly typed `var`s Inlined helper methods (for clarity) * Rename local for per PR feedback Swapping back to a distinct and more appropriately named variable for the `closeStatusDescription` Co-authored-by: Natalia Kondratyeva <[email protected]> * Label the boolean for isServer flag Co-authored-by: Natalia Kondratyeva <[email protected]> * Use better local variable name Renamed local `serverMessage` back to `closeStatusDescription` per PR feedback. Co-authored-by: Natalia Kondratyeva <[email protected]> * Address PR and rebase to main Rebased to current main, updated the commit messages and added the remaining changes to address the PR comments. --------- Co-authored-by: MartyIX <[email protected]> Co-authored-by: Natalia Kondratyeva <[email protected]>
1 parent 958b1e0 commit ef6b6d5

File tree

4 files changed

+97
-0
lines changed

4 files changed

+97
-0
lines changed

src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,6 +1059,9 @@ private static bool IsValidCloseStatus(WebSocketCloseStatus closeStatus)
10591059
case WebSocketCloseStatus.NormalClosure:
10601060
case WebSocketCloseStatus.PolicyViolation:
10611061
case WebSocketCloseStatus.ProtocolError:
1062+
case (WebSocketCloseStatus)1012: // ServiceRestart
1063+
case (WebSocketCloseStatus)1013: // TryAgainLater
1064+
case (WebSocketCloseStatus)1014: // BadGateway
10621065
return true;
10631066

10641067
default:

src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/WebSocketCloseStatus.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ public enum WebSocketCloseStatus
1818
MessageTooBig = 1009,
1919
MandatoryExtension = 1010,
2020
InternalServerError = 1011
21+
// non-RFC IANA registered status codes that we allow as valid closing status
22+
// ServiceRestart = 1012, // indicates that the server / service is restarting.
23+
// TryAgainLater = 1013, // indicates that a temporary server condition forced blocking the client's request.
24+
// BadGateway = 1014 // indicates that the server acting as gateway received an invalid response
2125
// TLSHandshakeFailed = 1015, // 1015 is reserved and should never be used by user
2226

2327
// 0 - 999 Status codes in the range 0-999 are not used.

src/libraries/System.Net.WebSockets/tests/System.Net.WebSockets.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<RdXmlFile Include="default.rd.xml" />
77
</ItemGroup>
88
<ItemGroup>
9+
<Compile Include="WebSocketCloseTests.cs" />
910
<Compile Include="WebSocketTests.cs" />
1011
<Compile Include="WebSocketExceptionTests.cs" />
1112
<Compile Include="WebSocketReceiveResultTests.cs" />
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
using System.Text;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Xunit;
9+
10+
namespace System.Net.WebSockets.Tests
11+
{
12+
public class WebSocketCloseTests
13+
{
14+
private readonly CancellationTokenSource? _cancellation;
15+
16+
public WebSocketCloseTests()
17+
{
18+
if (!Debugger.IsAttached)
19+
{
20+
_cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(5));
21+
}
22+
}
23+
24+
public CancellationToken CancellationToken => _cancellation?.Token ?? default;
25+
26+
public static object[][] CloseStatuses = {
27+
new object[] { WebSocketCloseStatus.EndpointUnavailable },
28+
new object[] { WebSocketCloseStatus.InternalServerError },
29+
new object[] { WebSocketCloseStatus.InvalidMessageType},
30+
new object[] { WebSocketCloseStatus.InvalidPayloadData },
31+
new object[] { WebSocketCloseStatus.MandatoryExtension },
32+
new object[] { WebSocketCloseStatus.MessageTooBig },
33+
new object[] { WebSocketCloseStatus.NormalClosure },
34+
new object[] { WebSocketCloseStatus.PolicyViolation },
35+
new object[] { WebSocketCloseStatus.ProtocolError },
36+
new object[] { (WebSocketCloseStatus)1012 }, // ServiceRestart indicates that the server / service is restarting.
37+
new object[] { (WebSocketCloseStatus)1013 }, // TryAgainLater indicates that a temporary server condition forced blocking the client's request.
38+
new object[] { (WebSocketCloseStatus)1014 }, // BadGateway indicates that the server acting as gateway received an invalid response
39+
};
40+
41+
[Theory]
42+
[MemberData(nameof(CloseStatuses))]
43+
public void WebSocketReceiveResult_WebSocketCloseStatus_Roundtrip(WebSocketCloseStatus closeStatus)
44+
{
45+
string closeStatusDescription = "closeStatus " + closeStatus.ToString();
46+
WebSocketReceiveResult wsrr = new WebSocketReceiveResult(42, WebSocketMessageType.Close, endOfMessage: true, closeStatus, closeStatusDescription);
47+
Assert.Equal(42, wsrr.Count);
48+
Assert.Equal(closeStatus, wsrr.CloseStatus);
49+
Assert.Equal(closeStatusDescription, wsrr.CloseStatusDescription);
50+
}
51+
52+
[Theory]
53+
[MemberData(nameof(CloseStatuses))]
54+
public async Task ReceiveAsync_ValidCloseStatus_Success(WebSocketCloseStatus closeStatus)
55+
{
56+
byte[] receiveBuffer = new byte[1024];
57+
WebSocketTestStream stream = new();
58+
Encoding encoding = Encoding.UTF8;
59+
60+
using (WebSocket server = WebSocket.CreateFromStream(stream, isServer: true, subProtocol: null, TimeSpan.FromSeconds(3)))
61+
using (WebSocket client = WebSocket.CreateFromStream(stream.Remote, isServer: false, subProtocol: null, TimeSpan.FromSeconds(3)))
62+
{
63+
Assert.NotNull(server);
64+
Assert.NotNull(client);
65+
66+
// send something
67+
string hello = "Testing " + closeStatus.ToString();
68+
byte[] sendBytes = encoding.GetBytes(hello);
69+
await server.SendAsync(sendBytes.AsMemory(), WebSocketMessageType.Text, WebSocketMessageFlags.None, CancellationToken);
70+
71+
// and then server-side close with the test status
72+
string closeStatusDescription = "CloseStatus " + closeStatus.ToString();
73+
await server.CloseOutputAsync(closeStatus, closeStatusDescription, CancellationToken);
74+
75+
// get the hello from the client (after the close message was sent)
76+
WebSocketReceiveResult result = await client.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken);
77+
Assert.Equal(WebSocketMessageType.Text, result.MessageType);
78+
string response = encoding.GetString(receiveBuffer.AsSpan(0, result.Count));
79+
Assert.Equal(hello, response);
80+
81+
// now look for the expected close status
82+
WebSocketReceiveResult closing = await client.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken);
83+
Assert.Equal(WebSocketMessageType.Close, closing.MessageType);
84+
Assert.Equal(closeStatus, closing.CloseStatus);
85+
Assert.Equal(closeStatusDescription, closing.CloseStatusDescription);
86+
}
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)