Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[browser][MT] WebSocket thread affinity & server initiated close #96618

Merged
merged 12 commits into from
Jan 16, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,15 @@ internal static partial class WebSocketValidate
internal const int MaxDeflateWindowBits = 15;

internal const int MaxControlFramePayloadLength = 123;
#if TARGET_BROWSER
private const int ValidCloseStatusCodesFrom = 3000;
private const int ValidCloseStatusCodesTo = 4999;
#else
private const int CloseStatusCodeAbort = 1006;
private const int CloseStatusCodeFailedTLSHandshake = 1015;
private const int InvalidCloseStatusCodesFrom = 0;
private const int InvalidCloseStatusCodesTo = 999;
#endif

// [0x21, 0x7E] except separators "()<>@,;:\\\"/[]?={} ".
private static readonly SearchValues<char> s_validSubprotocolChars =
Expand Down Expand Up @@ -84,11 +89,15 @@ internal static void ValidateCloseStatus(WebSocketCloseStatus closeStatus, strin
}

int closeStatusCode = (int)closeStatus;

#if TARGET_BROWSER
// as defined in https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#code
if (closeStatus != WebSocketCloseStatus.NormalClosure && (closeStatusCode < ValidCloseStatusCodesFrom || closeStatusCode > ValidCloseStatusCodesTo))
#else
if ((closeStatusCode >= InvalidCloseStatusCodesFrom &&
closeStatusCode <= InvalidCloseStatusCodesTo) ||
closeStatusCode == CloseStatusCodeAbort ||
closeStatusCode == CloseStatusCodeFailedTLSHandshake)
#endif
{
// CloseStatus 1006 means Aborted - this will never appear on the wire and is reflected by calling WebSocket.Abort
throw new ArgumentException(SR.Format(SR.net_WebSockets_InvalidCloseStatusCode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ public static async Task InvokeAsync(HttpContext context)

if (context.Request.QueryString.HasValue && context.Request.QueryString.Value.Contains("delay10sec"))
{
Thread.Sleep(10000);
await Task.Delay(10000);
}
else if (context.Request.QueryString.HasValue && context.Request.QueryString.Value.Contains("delay20sec"))
{
Thread.Sleep(20000);
await Task.Delay(20000);
}

try
Expand Down Expand Up @@ -124,14 +124,15 @@ await socket.CloseAsync(
}

bool sendMessage = false;
string receivedMessage = null;
if (receiveResult.MessageType == WebSocketMessageType.Text)
{
string receivedMessage = Encoding.UTF8.GetString(receiveBuffer, 0, offset);
receivedMessage = Encoding.UTF8.GetString(receiveBuffer, 0, offset);
if (receivedMessage == ".close")
{
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, receivedMessage, CancellationToken.None);
}
if (receivedMessage == ".shutdown")
else if (receivedMessage == ".shutdown")
{
await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, receivedMessage, CancellationToken.None);
}
Expand Down Expand Up @@ -161,6 +162,14 @@ await socket.SendAsync(
!replyWithPartialMessages,
CancellationToken.None);
}
if (receivedMessage == ".closeafter")
{
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, receivedMessage, CancellationToken.None);
}
else if (receivedMessage == ".shutdownafter")
{
await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, receivedMessage, CancellationToken.None);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,26 @@ internal static partial class BrowserInterop
return protocol;
}

public static WebSocketCloseStatus? GetCloseStatus(JSObject? webSocket)
{
if (webSocket == null || webSocket.IsDisposed)
return null;
if (!webSocket.HasProperty("close_status"))
{
return null;
}
int status = webSocket.GetPropertyAsInt32("close_status");
return (WebSocketCloseStatus)status;
}

public static string? GetCloseStatusDescription(JSObject? webSocket)
{
if (webSocket == null || webSocket.IsDisposed)
return null;
string? description = webSocket.GetPropertyAsString("close_status_description");
return description;
}

public static int GetReadyState(JSObject? webSocket)
{
if (webSocket == null || webSocket.IsDisposed) return -1;
Expand All @@ -28,16 +48,14 @@ public static int GetReadyState(JSObject? webSocket)
public static partial JSObject WebSocketCreate(
string uri,
string?[]? subProtocols,
IntPtr responseStatusPtr,
[JSMarshalAs<JSType.Function<JSType.Number, JSType.String>>] Action<int, string> onClosed);
IntPtr responseStatusPtr);

public static unsafe JSObject UnsafeCreate(
string uri,
string?[]? subProtocols,
MemoryHandle responseHandle,
[JSMarshalAs<JSType.Function<JSType.Number, JSType.String>>] Action<int, string> onClosed)
MemoryHandle responseHandle)
{
return WebSocketCreate(uri, subProtocols, (IntPtr)responseHandle.Pointer, onClosed);
return WebSocketCreate(uri, subProtocols, (IntPtr)responseHandle.Pointer);
}

[JSImport("INTERNAL.ws_wasm_open")]
Expand All @@ -52,19 +70,9 @@ public static partial Task WebSocketOpen(
int messageType,
bool endOfMessage);

public static unsafe Task? UnsafeSendSync(JSObject jsWs, ArraySegment<byte> buffer, WebSocketMessageType messageType, bool endOfMessage)
public static unsafe Task? UnsafeSend(JSObject jsWs, MemoryHandle pinBuffer, int length, WebSocketMessageType messageType, bool endOfMessage)
{
if (buffer.Count == 0)
{
return WebSocketSend(jsWs, IntPtr.Zero, 0, (int)messageType, endOfMessage);
}

var span = buffer.AsSpan();
// we can do this because the bytes in the buffer are always consumed synchronously (not later with Task resolution)
fixed (void* spanPtr = span)
{
return WebSocketSend(jsWs, (IntPtr)spanPtr, buffer.Count, (int)messageType, endOfMessage);
}
return WebSocketSend(jsWs, (IntPtr)pinBuffer.Pointer, length, (int)messageType, endOfMessage);
}

[JSImport("INTERNAL.ws_wasm_receive")]
Expand All @@ -73,7 +81,7 @@ public static partial Task WebSocketOpen(
IntPtr bufferPtr,
int bufferLength);

public static unsafe Task? ReceiveUnsafeSync(JSObject jsWs, MemoryHandle pinBuffer, int length)
public static unsafe Task? ReceiveUnsafe(JSObject jsWs, MemoryHandle pinBuffer, int length)
{
return WebSocketReceive(jsWs, (IntPtr)pinBuffer.Pointer, length);
}
Expand Down
Loading
Loading