| File: System\Net\Managed\WebSockets\HttpWebSocket.Managed.cs | Web Access |
| Project: src\runtime\src\libraries\System.Net.HttpListener\src\System.Net.HttpListener.csproj (System.Net.HttpListener) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Threading; using System.Threading.Tasks; namespace System.Net.WebSockets { internal static partial class HttpWebSocket { private const string SupportedVersion = "13"; internal static async Task<HttpListenerWebSocketContext> AcceptWebSocketAsyncCore(HttpListenerContext context, string? subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval) { ValidateOptions(subProtocol, receiveBufferSize, MinSendBufferSize, keepAliveInterval); // get property will create a new response if one doesn't exist. HttpListenerResponse response = context.Response; HttpListenerRequest request = context.Request; ValidateWebSocketHeaders(context); string? secWebSocketVersion = request.Headers[HttpKnownHeaderNames.SecWebSocketVersion]; // Optional for non-browser client string? origin = request.Headers[HttpKnownHeaderNames.Origin]; string[]? secWebSocketProtocols = null; string outgoingSecWebSocketProtocolString; bool shouldSendSecWebSocketProtocolHeader = ProcessWebSocketProtocolHeader( request.Headers[HttpKnownHeaderNames.SecWebSocketProtocol], subProtocol, out outgoingSecWebSocketProtocolString); if (shouldSendSecWebSocketProtocolHeader) { secWebSocketProtocols = new string[] { outgoingSecWebSocketProtocolString }; response.Headers.Add(HttpKnownHeaderNames.SecWebSocketProtocol, outgoingSecWebSocketProtocolString); } // negotiate the websocket key return value string? secWebSocketKey = request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; string secWebSocketAccept = HttpWebSocket.GetSecWebSocketAcceptString(secWebSocketKey); response.Headers.Add(HttpKnownHeaderNames.Connection, HttpKnownHeaderNames.Upgrade); response.Headers.Add(HttpKnownHeaderNames.Upgrade, WebSocketUpgradeToken); response.Headers.Add(HttpKnownHeaderNames.SecWebSocketAccept, secWebSocketAccept); response.StatusCode = (int)HttpStatusCode.SwitchingProtocols; // HTTP 101 response.StatusDescription = HttpStatusDescription.Get(HttpStatusCode.SwitchingProtocols)!; HttpResponseStream responseStream = (response.OutputStream as HttpResponseStream)!; // Send websocket handshake headers await responseStream.WriteWebSocketHandshakeHeadersAsync().ConfigureAwait(false); WebSocket rawWebSocket = WebSocket.CreateFromStream(context.Connection.ConnectedStream, isServer: true, subProtocol, keepAliveInterval); // Wrap the raw websocket so its Dispose() automatically closes the connection. // This is important as common and recommended usage is to just Dispose the HttpListenerWebSocketContext.WebSocket // and without it connection would not be cleaned up causing memory leaks and other issues. HttpListenerWrappedWebSocket webSocket = new(rawWebSocket, context); HttpListenerWebSocketContext webSocketContext = new HttpListenerWebSocketContext( request.Url!, request.Headers, request.Cookies, context.User!, request.IsAuthenticated, request.IsLocal, request.IsSecureConnection, origin!, secWebSocketProtocols ?? Array.Empty<string>(), secWebSocketVersion!, secWebSocketKey!, webSocket); return webSocketContext; } private const bool WebSocketsSupported = true; /// <summary> /// Wraps the real WebSocket so that when Dispose() is called, /// we also call context.Response.Close(), triggering UnregisterContext. /// </summary> private sealed class HttpListenerWrappedWebSocket : WebSocket { private readonly WebSocket _inner; private readonly HttpListenerContext _context; private bool _disposed; internal HttpListenerWrappedWebSocket(WebSocket inner, HttpListenerContext context) { _inner = inner; _context = context; } public override WebSocketCloseStatus? CloseStatus => _inner.CloseStatus; public override string? CloseStatusDescription => _inner.CloseStatusDescription; public override WebSocketState State => _inner.State; public override string? SubProtocol => _inner.SubProtocol; public override void Abort() => _inner.Abort(); public override Task CloseAsync( WebSocketCloseStatus closeStatus, string? statusDescription, CancellationToken cancellationToken) => _inner.CloseAsync(closeStatus, statusDescription, cancellationToken); public override Task CloseOutputAsync( WebSocketCloseStatus closeStatus, string? statusDescription, CancellationToken cancellationToken) => _inner.CloseOutputAsync(closeStatus, statusDescription, cancellationToken); public override void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (_disposed) return; _disposed = true; if (disposing) { // Dispose the underlying raw WebSocket _inner.Dispose(); // Ensure we remove the context from the HttpListener's tracking dictionary // by forcibly calling Response.Close(). This closes the underlying connection // and also calls UnregisterContext(context), preventing stale HttpListenerContext objects causing memory leak. _context.Response.Close(); } else { // Technically we shouldn't be doing the following work when disposing == false, // as the following work relies on other finalizable objects. // But given we have little choice: if someone drops the websocket without // disposing of it we need to close the context to prevent memory leaks. try { _context.Response.Close(); } catch { // We are doing best effort here to handle the case where the user does not properly Dispose the WebSocket. // If we fail to close the reponse, we are not going to throw an exception and possibly crash. // We are just going to ignore it and let the GC do its best. } } } ~HttpListenerWrappedWebSocket() { Dispose(false); } public override Task<WebSocketReceiveResult> ReceiveAsync( ArraySegment<byte> buffer, CancellationToken cancellationToken) => _inner.ReceiveAsync(buffer, cancellationToken); public override Task SendAsync( ArraySegment<byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) => _inner.SendAsync(buffer, messageType, endOfMessage, cancellationToken); } } }