internal static async Task <HttpListenerWebSocketContext> AcceptWebSocketAsyncCore(HttpListenerContext context, string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval, ArraySegment <byte>?internalBuffer = null) { 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 webSocket = WebSocket.CreateFromStream(context.Connection.ConnectedStream, isServer: true, subProtocol, keepAliveInterval); HttpListenerWebSocketContext webSocketContext = new HttpListenerWebSocketContext( request.Url, request.Headers, request.Cookies, context.User, request.IsAuthenticated, request.IsLocal, request.IsSecureConnection, origin, secWebSocketProtocols != null ? secWebSocketProtocols : Array.Empty <string>(), secWebSocketVersion, secWebSocketKey, webSocket); return(webSocketContext); }
public async Task ConnectAsync(Uri uri, HttpMessageInvoker?invoker, CancellationToken cancellationToken, ClientWebSocketOptions options) { bool disposeHandler = false; invoker ??= new HttpMessageInvoker(SetupHandler(options, out disposeHandler)); HttpResponseMessage?response = null; bool disposeResponse = false; bool tryDowngrade = false; try { while (true) { try { HttpRequestMessage request; if (!tryDowngrade && options.HttpVersion >= HttpVersion.Version20 || (options.HttpVersion == HttpVersion.Version11 && options.HttpVersionPolicy == HttpVersionPolicy.RequestVersionOrHigher)) { if (options.HttpVersion > HttpVersion.Version20 && options.HttpVersionPolicy != HttpVersionPolicy.RequestVersionOrLower) { throw new WebSocketException(WebSocketError.UnsupportedProtocol); } request = new HttpRequestMessage(HttpMethod.Connect, uri) { Version = HttpVersion.Version20 }; tryDowngrade = true; } else if (tryDowngrade || options.HttpVersion == HttpVersion.Version11) { request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = HttpVersion.Version11 }; tryDowngrade = false; } else { throw new WebSocketException(WebSocketError.UnsupportedProtocol); } if (options._requestHeaders?.Count > 0) // use field to avoid lazily initializing the collection { foreach (string key in options.RequestHeaders) { request.Headers.TryAddWithoutValidation(key, options.RequestHeaders[key]); } } string?secValue = AddWebSocketHeaders(request, options); // Issue the request. CancellationTokenSource?linkedCancellation; CancellationTokenSource externalAndAbortCancellation; if (cancellationToken.CanBeCanceled) // avoid allocating linked source if external token is not cancelable { linkedCancellation = externalAndAbortCancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _abortSource.Token); } else { linkedCancellation = null; externalAndAbortCancellation = _abortSource; } using (linkedCancellation) { response = await invoker.SendAsync(request, externalAndAbortCancellation.Token).ConfigureAwait(false); externalAndAbortCancellation.Token.ThrowIfCancellationRequested(); // poll in case sends/receives in request/response didn't observe cancellation } ValidateResponse(response, secValue, options); break; } catch (HttpRequestException ex) when ((ex.Data.Contains("SETTINGS_ENABLE_CONNECT_PROTOCOL") || ex.Data.Contains("HTTP2_ENABLED")) && tryDowngrade && (options.HttpVersion == HttpVersion.Version11 || options.HttpVersionPolicy == HttpVersionPolicy.RequestVersionOrLower)) { } } // The SecWebSocketProtocol header is optional. We should only get it with a non-empty value if we requested subprotocols, // and then it must only be one of the ones we requested. If we got a subprotocol other than one we requested (or if we // already got one in a previous header), fail. Otherwise, track which one we got. string?subprotocol = null; if (response.Headers.TryGetValues(HttpKnownHeaderNames.SecWebSocketProtocol, out IEnumerable <string>?subprotocolEnumerableValues)) { Debug.Assert(subprotocolEnumerableValues is string[]); string[] subprotocolArray = (string[])subprotocolEnumerableValues; if (subprotocolArray.Length > 0 && !string.IsNullOrEmpty(subprotocolArray[0])) { if (options._requestedSubProtocols is not null) { foreach (string requestedProtocol in options._requestedSubProtocols) { if (requestedProtocol.Equals(subprotocolArray[0], StringComparison.OrdinalIgnoreCase)) { subprotocol = requestedProtocol; break; } } } if (subprotocol == null) { throw new WebSocketException( WebSocketError.UnsupportedProtocol, SR.Format(SR.net_WebSockets_AcceptUnsupportedProtocol, string.Join(", ", options.RequestedSubProtocols), string.Join(", ", subprotocolArray))); } } } // Because deflate options are negotiated we need a new object WebSocketDeflateOptions?negotiatedDeflateOptions = null; if (options.DangerousDeflateOptions is not null && response.Headers.TryGetValues(HttpKnownHeaderNames.SecWebSocketExtensions, out IEnumerable <string>?extensions)) { foreach (ReadOnlySpan <char> extension in extensions) { if (extension.TrimStart().StartsWith(ClientWebSocketDeflateConstants.Extension)) { negotiatedDeflateOptions = ParseDeflateOptions(extension, options.DangerousDeflateOptions); break; } } } // Get the response stream and wrap it in a web socket. Stream connectedStream = response.Content.ReadAsStream(); Debug.Assert(connectedStream.CanWrite); Debug.Assert(connectedStream.CanRead); WebSocket = WebSocket.CreateFromStream(connectedStream, new WebSocketCreationOptions { IsServer = false, SubProtocol = subprotocol, KeepAliveInterval = options.KeepAliveInterval, DangerousDeflateOptions = negotiatedDeflateOptions }); _negotiatedDeflateOptions = negotiatedDeflateOptions; } catch (Exception exc) { if (_state < WebSocketState.Closed) { _state = WebSocketState.Closed; } Abort(); disposeResponse = true; if (exc is WebSocketException || (exc is OperationCanceledException && cancellationToken.IsCancellationRequested)) { throw; } throw new WebSocketException(WebSocketError.Faulted, SR.net_webstatus_ConnectFailure, exc); } finally { if (response is not null) { if (options.CollectHttpResponseDetails) { HttpStatusCode = response.StatusCode; HttpResponseHeaders = new HttpResponseHeadersReadOnlyCollection(response.Headers); } if (disposeResponse) { response.Dispose(); } } // Disposing the handler will not affect any active stream wrapped in the WebSocket. if (disposeHandler) { invoker?.Dispose(); } } }