示例#1
0
        public static async Task HandleWebSocketRequestAsync(HttpContext context)
        {
            void BadRequest(string message)
            {
                context.Response.StatusCode  = StatusCodes.Status400BadRequest;
                context.Response.ContentType = "text/plain; charset=utf-8";
                using (var sw = new System.IO.StreamWriter(context.Response.Body)) {
                    sw.WriteLine(message);
                }
            }

            //
            // Make sure we get a good ID
            //
            if (!context.Request.Query.TryGetValue("id", out var idValues))
            {
                BadRequest("Missing `id`");
                return;
            }

            var id = idValues.LastOrDefault();

            if (id == null || id.Length != 32)
            {
                BadRequest("Invalid `id`");
                return;
            }

            //
            // Clear old sessions
            //
            var toClear = pendingSessions.Where(x => (DateTime.UtcNow - x.Value.CreateTimeUtc) > SessionTimeout).ToList();

            foreach (var c in toClear)
            {
                pendingSessions.TryRemove(c.Key, out var _);
            }

            //
            // Find the pending session
            //
            if (!pendingSessions.TryRemove(id, out var activeSession))
            {
                BadRequest("Unknown `id`");
                return;
            }

            //
            // Set the element's dimensions
            //
            if (!context.Request.Query.TryGetValue("w", out var wValues) || wValues.Count < 1)
            {
                BadRequest("Missing `w`");
                return;
            }
            if (!context.Request.Query.TryGetValue("h", out var hValues) || hValues.Count < 1)
            {
                BadRequest("Missing `h`");
                return;
            }
            var icult = System.Globalization.CultureInfo.InvariantCulture;

            if (!double.TryParse(wValues.Last(), System.Globalization.NumberStyles.Any, icult, out var w))
            {
                w = 640;
            }
            if (!double.TryParse(hValues.Last(), System.Globalization.NumberStyles.Any, icult, out var h))
            {
                h = 480;
            }

            //
            // OK, Run
            //
            var token = CancellationToken.None;

            System.Net.WebSockets.WebSocket webSocket = null;

            void Error(string m, Exception e) => activeSession?.Logger?.LogWarning(e, m);

            //
            // Create a new session and let it handle everything from here
            //
            try {
                webSocket = await context.WebSockets.AcceptWebSocketAsync("Goui").ConfigureAwait(false);

                var session = new Goui.WebSocketSession(webSocket, activeSession.Element, activeSession.DisposeElementAfterSession, w, h, Error, token);
                await session.RunAsync().ConfigureAwait(false);
            }
            catch (System.Net.WebSockets.WebSocketException ex) when(ex.WebSocketErrorCode == System.Net.WebSockets.WebSocketError.ConnectionClosedPrematurely)
            {
                // The remote party closed the WebSocket connection without completing the close handshake.
            }
            catch (Exception ex) {
                context.Abort();
                activeSession?.Logger?.LogWarning(ex, "Web socket session failed");
            }
            finally {
                webSocket?.Dispose();
            }
        }
示例#2
0
        /// <summary>
        /// Invoked when this handler is determined to be the best suited to handle the supplied connection.
        /// </summary>
        /// <param name="context">
        /// The HTTP context.
        /// </param>
        /// <returns>
        /// The handling task.
        /// </returns>
        public override async Task Handle(HttpContext context)
        {
            ClientWebSocket wsServer = null;

            System.Net.WebSockets.WebSocket wsClient = null;

            try
            {
                // First we need the URL for this connection, since it's been requested to be
                // upgraded to a websocket.

                var connFeature = context.Features.Get <IHttpRequestFeature>();

                string fullUrl = string.Empty;

                if (connFeature != null && connFeature.RawTarget != null && !string.IsNullOrEmpty(connFeature.RawTarget) && !(string.IsNullOrWhiteSpace(connFeature.RawTarget)))
                {
                    fullUrl = $"{context.Request.Scheme}://{context.Request.Host}{connFeature.RawTarget}";
                }
                else
                {
                    fullUrl = $"{context.Request.Scheme}://{context.Request.Host}{context.Request.Path}{context.Request.QueryString}";
                }

                // Need to replate the scheme with appropriate websocket scheme.
                if (fullUrl.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
                {
                    fullUrl = "ws://" + fullUrl.Substring(7);
                }
                else if (fullUrl.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
                {
                    fullUrl = "wss://" + fullUrl.Substring(8);
                }

                // Next we need to try and parse the URL as a URI, because the websocket client
                // requires this for connecting upstream.

                if (!Uri.TryCreate(fullUrl, UriKind.RelativeOrAbsolute, out Uri wsUri))
                {
                    LoggerProxy.Default.Error("Failed to parse websocket URI.");
                    return;
                }

                // Create the websocket that's going to connect to the remote server.
                wsServer = new ClientWebSocket();
                wsServer.Options.Cookies = new System.Net.CookieContainer();
                //wsServer.Options.SetBuffer((int)ushort.MaxValue * 16, (int)ushort.MaxValue * 16);

                foreach (var proto in context.WebSockets.WebSocketRequestedProtocols)
                {
                    wsServer.Options.AddSubProtocol(proto);
                }

                foreach (var hdr in context.Request.Headers)
                {
                    if (!ForbiddenWsHeaders.IsForbidden(hdr.Key))
                    {
                        try
                        {
                            wsServer.Options.SetRequestHeader(hdr.Key, hdr.Value.ToString());
                        }
                        catch (Exception hdrException)
                        {
                            LoggerProxy.Default.Error(hdrException);
                        }
                    }
                }

                foreach (var cookie in context.Request.Cookies)
                {
                    try
                    {
                        wsServer.Options.Cookies.Add(new Uri(fullUrl, UriKind.Absolute), new System.Net.Cookie(cookie.Key, System.Net.WebUtility.UrlEncode(cookie.Value)));
                    }
                    catch (Exception e)
                    {
                        LoggerProxy.Default.Error("Error while attempting to add websocket cookie.");
                        LoggerProxy.Default.Error(e);
                    }
                }

                if (context.Connection.ClientCertificate != null)
                {
                    wsServer.Options.ClientCertificates = new System.Security.Cryptography.X509Certificates.X509CertificateCollection(new[] { context.Connection.ClientCertificate.ToV2Certificate() });
                }

                // Connect the server websocket to the upstream, remote webserver.
                await wsServer.ConnectAsync(wsUri, context.RequestAborted);

                foreach (string key in wsServer.ResponseHeaders)
                {
                    if (!ForbiddenWsHeaders.IsForbidden(key))
                    {
                        try
                        {
                            var value = wsServer.ResponseHeaders[key];
                            context.Response.Headers[key] = wsServer.ResponseHeaders[key];
                        }
                        catch (Exception hdrException)
                        {
                            LoggerProxy.Default.Error(hdrException);
                        }
                    }
                }

                // Create, via acceptor, the client websocket. This is the local machine's websocket.
                wsClient = await context.WebSockets.AcceptWebSocketAsync(wsServer.SubProtocol ?? null);

                // Match the HTTP version of the client on the upstream request. We don't want to
                // transparently pass around headers that are wrong for the client's HTTP version.
                Version upstreamReqVersionMatch = null;

                Match match = s_httpVerRegex.Match(context.Request.Protocol);
                if (match != null && match.Success)
                {
                    upstreamReqVersionMatch = Version.Parse(match.Value);
                }

                var msgNfo = new HttpMessageInfo
                {
                    Url             = wsUri,
                    Method          = new HttpMethod(context.Request.Method),
                    IsEncrypted     = context.Request.IsHttps,
                    Headers         = context.Request.Headers.ToNameValueCollection(),
                    HttpVersion     = upstreamReqVersionMatch ?? new Version(1, 0),
                    MessageProtocol = MessageProtocol.WebSocket,
                    MessageType     = MessageType.Request,
                    RemoteAddress   = context.Connection.RemoteIpAddress,
                    RemotePort      = (ushort)context.Connection.RemotePort,
                    LocalAddress    = context.Connection.LocalIpAddress,
                    LocalPort       = (ushort)context.Connection.LocalPort
                };

                _configuration.NewHttpMessageHandler?.Invoke(msgNfo);

                switch (msgNfo.ProxyNextAction)
                {
                case ProxyNextAction.DropConnection:
                {
                    await wsClient.CloseAsync(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);

                    return;
                }
                }

                var serverMessageInfo = new HttpMessageInfo
                {
                    Url             = wsUri,
                    MessageId       = msgNfo.MessageId,
                    Method          = new HttpMethod(context.Request.Method),
                    IsEncrypted     = context.Request.IsHttps,
                    Headers         = context.Request.Headers.ToNameValueCollection(),
                    HttpVersion     = upstreamReqVersionMatch ?? new Version(1, 0),
                    MessageProtocol = MessageProtocol.WebSocket,
                    MessageType     = MessageType.Response,
                    RemoteAddress   = context.Connection.RemoteIpAddress,
                    RemotePort      = (ushort)context.Connection.RemotePort,
                    LocalAddress    = context.Connection.LocalIpAddress,
                    LocalPort       = (ushort)context.Connection.LocalPort
                };

                var clientMessageInfo = new HttpMessageInfo
                {
                    Url             = wsUri,
                    MessageId       = msgNfo.MessageId,
                    IsEncrypted     = context.Request.IsHttps,
                    Headers         = context.Request.Headers.ToNameValueCollection(),
                    HttpVersion     = upstreamReqVersionMatch ?? new Version(1, 0),
                    MessageProtocol = MessageProtocol.WebSocket,
                    MessageType     = MessageType.Request,
                    RemoteAddress   = context.Connection.RemoteIpAddress,
                    RemotePort      = (ushort)context.Connection.RemotePort,
                    LocalAddress    = context.Connection.LocalIpAddress,
                    LocalPort       = (ushort)context.Connection.LocalPort
                };

                bool inspect = true;

                switch (msgNfo.ProxyNextAction)
                {
                case ProxyNextAction.AllowAndIgnoreContent:
                case ProxyNextAction.AllowAndIgnoreContentAndResponse:
                {
                    inspect = false;
                }
                break;
                }

                // Spawn an async task that will poll the remote server for data in a loop, and then
                // write any data it gets to the client websocket.
                var serverTask = Task.Run(async() =>
                {
                    System.Net.WebSockets.WebSocketReceiveResult serverResult = null;
                    var serverBuffer = new byte[1024 * 4];
                    try
                    {
                        bool looping = true;

                        serverResult = await wsServer.ReceiveAsync(new ArraySegment <byte>(serverBuffer), context.RequestAborted);

                        while (looping && !serverResult.CloseStatus.HasValue && !context.RequestAborted.IsCancellationRequested)
                        {
                            if (inspect)
                            {
                                serverMessageInfo.Body = new Memory <byte>(serverBuffer, 0, serverResult.Count);

                                switch (serverResult.MessageType)
                                {
                                case System.Net.WebSockets.WebSocketMessageType.Binary:
                                    {
                                        serverMessageInfo.BodyContentType = s_octetStreamContentType;
                                    }
                                    break;

                                case System.Net.WebSockets.WebSocketMessageType.Text:
                                    {
                                        serverMessageInfo.BodyContentType = s_plainTextContentType;
                                    }
                                    break;
                                }

                                _configuration.HttpMessageWholeBodyInspectionHandler?.Invoke(serverMessageInfo);
                            }

                            switch (serverMessageInfo.ProxyNextAction)
                            {
                            case ProxyNextAction.DropConnection:
                                {
                                    looping = false;
                                }
                                break;

                            default:
                                {
                                    await wsClient.SendAsync(new ArraySegment <byte>(serverBuffer, 0, serverResult.Count), serverResult.MessageType, serverResult.EndOfMessage, context.RequestAborted);

                                    if (!wsClient.CloseStatus.HasValue)
                                    {
                                        serverResult = await wsServer.ReceiveAsync(new ArraySegment <byte>(serverBuffer), context.RequestAborted);
                                        continue;
                                    }
                                }
                                break;
                            }

                            looping = false;
                        }

                        await wsClient.CloseAsync(serverResult.CloseStatus.Value, serverResult.CloseStatusDescription, context.RequestAborted);
                    }
                    catch (Exception err)
                    {
                        LoggerProxy.Default.Error(err);
                        try
                        {
                            var closeStatus  = serverResult?.CloseStatus ?? System.Net.WebSockets.WebSocketCloseStatus.NormalClosure;
                            var closeMessage = serverResult?.CloseStatusDescription ?? string.Empty;

                            await wsClient.CloseAsync(closeStatus, closeMessage, context.RequestAborted);
                        }
                        catch { }
                    }
                });

                // Spawn an async task that will poll the local client websocket, in a loop, and then
                // write any data it gets to the remote server websocket.
                var clientTask = Task.Run(async() =>
                {
                    System.Net.WebSockets.WebSocketReceiveResult clientResult = null;
                    var clientBuffer = new byte[1024 * 4];
                    try
                    {
                        bool looping = true;

                        clientResult = await wsClient.ReceiveAsync(new ArraySegment <byte>(clientBuffer), context.RequestAborted);

                        while (looping && !clientResult.CloseStatus.HasValue && !context.RequestAborted.IsCancellationRequested)
                        {
                            if (inspect)
                            {
                                clientMessageInfo.Body = new Memory <byte>(clientBuffer, 0, clientResult.Count);

                                switch (clientResult.MessageType)
                                {
                                case System.Net.WebSockets.WebSocketMessageType.Binary:
                                    {
                                        clientMessageInfo.BodyContentType = s_octetStreamContentType;
                                    }
                                    break;

                                case System.Net.WebSockets.WebSocketMessageType.Text:
                                    {
                                        clientMessageInfo.BodyContentType = s_plainTextContentType;
                                    }
                                    break;
                                }

                                _configuration.HttpMessageWholeBodyInspectionHandler?.Invoke(clientMessageInfo);
                            }

                            switch (clientMessageInfo.ProxyNextAction)
                            {
                            case ProxyNextAction.DropConnection:
                                {
                                    looping = false;
                                }
                                break;

                            default:
                                {
                                    await wsServer.SendAsync(new ArraySegment <byte>(clientBuffer, 0, clientResult.Count), clientResult.MessageType, clientResult.EndOfMessage, context.RequestAborted);

                                    if (!wsServer.CloseStatus.HasValue)
                                    {
                                        clientResult = await wsClient.ReceiveAsync(new ArraySegment <byte>(clientBuffer), context.RequestAborted);
                                        continue;
                                    }
                                }
                                break;
                            }

                            looping = false;
                        }

                        await wsServer.CloseAsync(clientResult.CloseStatus.Value, clientResult.CloseStatusDescription, context.RequestAborted);
                    }
                    catch (Exception err)
                    {
                        LoggerProxy.Default.Error(err);
                        try
                        {
                            var closeStatus  = clientResult?.CloseStatus ?? System.Net.WebSockets.WebSocketCloseStatus.NormalClosure;
                            var closeMessage = clientResult?.CloseStatusDescription ?? string.Empty;

                            await wsServer.CloseAsync(closeStatus, closeMessage, context.RequestAborted);
                        }
                        catch { }
                    }
                });

                // Above, we have created a bridge between the local and remote websocket. Wait for
                // both associated tasks to complete.
                await Task.WhenAll(serverTask, clientTask);
            }
            catch (Exception wshe)
            {
                LoggerProxy.Default.Error(wshe);
            }
            finally
            {
                if (wsClient != null)
                {
                    wsClient.Dispose();
                    wsClient = null;
                }

                if (wsServer != null)
                {
                    wsServer.Dispose();
                    wsServer = null;
                }
            }
        }
 protected override void Dispose(bool disposing)
 {
     cts.Cancel();
     webSocket.Dispose();
     base.Dispose(disposing);
 }
        public override async Task Handle(HttpContext context)
        {
            ClientWebSocket wsServer = null;

            System.Net.WebSockets.WebSocket wsClient = null;

            try
            {
                // First we need the URL for this connection, since it's been requested to be upgraded to
                // a websocket.
                var fullUrl = Microsoft.AspNetCore.Http.Extensions.UriHelper.GetDisplayUrl(context.Request);

                // Need to replate the scheme with appropriate websocket scheme.
                if (fullUrl.StartsWith("http://"))
                {
                    fullUrl = "ws://" + fullUrl.Substring(7);
                }
                else if (fullUrl.StartsWith("https://"))
                {
                    fullUrl = "wss://" + fullUrl.Substring(8);
                }

                // Next we need to try and parse the URL as a URI, because the websocket client requires
                // this for connecting upstream.

                if (!Uri.TryCreate(fullUrl, UriKind.RelativeOrAbsolute, out Uri wsUri))
                {
                    LoggerProxy.Default.Error("Failed to parse websocket URI.");
                    return;
                }

                // Create the websocket that's going to connect to the remote server.
                wsServer = new ClientWebSocket();
                wsServer.Options.Cookies = new System.Net.CookieContainer();
                wsServer.Options.SetBuffer((int)ushort.MaxValue * 16, (int)ushort.MaxValue * 16);

                foreach (var cookie in context.Request.Cookies)
                {
                    try
                    {
                        wsServer.Options.Cookies.Add(new Uri(fullUrl, UriKind.Absolute), new System.Net.Cookie(cookie.Key, System.Net.WebUtility.UrlEncode(cookie.Value)));
                    }
                    catch (Exception e)
                    {
                        LoggerProxy.Default.Error("Error while attempting to add websocket cookie.");
                        LoggerProxy.Default.Error(e);
                    }
                }

                if (context.Connection.ClientCertificate != null)
                {
                    wsServer.Options.ClientCertificates = new System.Security.Cryptography.X509Certificates.X509CertificateCollection(new[] { context.Connection.ClientCertificate.ToV2Certificate() });
                }

                var reqHeaderBuilder = new StringBuilder();
                foreach (var hdr in context.Request.Headers)
                {
                    if (!ForbiddenWsHeaders.IsForbidden(hdr.Key))
                    {
                        reqHeaderBuilder.AppendFormat("{0}: {1}\r\n", hdr.Key, hdr.Value.ToString());

                        try
                        {
                            wsServer.Options.SetRequestHeader(hdr.Key, hdr.Value.ToString());
                            Console.WriteLine("Set Header: {0} ::: {1}", hdr.Key, hdr.Value.ToString());
                        }
                        catch (Exception hdrException)
                        {
                            Console.WriteLine("Failed Header: {0} ::: {1}", hdr.Key, hdr.Value.ToString());
                            LoggerProxy.Default.Error(hdrException);
                        }
                    }
                }

                reqHeaderBuilder.Append("\r\n");

                LoggerProxy.Default.Info(string.Format("Connecting websocket to {0}", wsUri.AbsoluteUri));

                // Connect the server websocket to the upstream, remote webserver.
                await wsServer.ConnectAsync(wsUri, context.RequestAborted);

                LoggerProxy.Default.Info(String.Format("Connected websocket to {0}", wsUri.AbsoluteUri));

                // Create, via acceptor, the client websocket. This is the local machine's websocket.
                wsClient = await context.WebSockets.AcceptWebSocketAsync(wsServer.SubProtocol ?? null);

                ProxyNextAction nxtAction = ProxyNextAction.AllowAndIgnoreContentAndResponse;
                string          customResponseContentType = string.Empty;
                byte[]          customResponse            = null;
                m_msgBeginCb?.Invoke(wsUri, reqHeaderBuilder.ToString(), null, context.Request.IsHttps ? MessageType.SecureWebSocket : MessageType.WebSocket, MessageDirection.Request, out nxtAction, out customResponseContentType, out customResponse);

                switch (nxtAction)
                {
                case ProxyNextAction.DropConnection:
                {
                    if (customResponse != null)
                    {
                    }

                    await wsClient.CloseAsync(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);

                    return;
                }
                }

                // Spawn an async task that will poll the remote server for data in a loop, and then
                // write any data it gets to the client websocket.
                var serverTask = Task.Run(async() =>
                {
                    System.Net.WebSockets.WebSocketReceiveResult serverStatus = null;
                    var serverBuffer = new byte[1024 * 4];
                    try
                    {
                        bool looping = true;

                        serverStatus = await wsServer.ReceiveAsync(new ArraySegment <byte>(serverBuffer), context.RequestAborted);

                        while (looping && !serverStatus.CloseStatus.HasValue && !context.RequestAborted.IsCancellationRequested)
                        {
                            await wsClient.SendAsync(new ArraySegment <byte>(serverBuffer, 0, serverStatus.Count), serverStatus.MessageType, serverStatus.EndOfMessage, context.RequestAborted);

                            if (!wsClient.CloseStatus.HasValue)
                            {
                                serverStatus = await wsServer.ReceiveAsync(new ArraySegment <byte>(serverBuffer), context.RequestAborted);
                                continue;
                            }

                            looping = false;
                        }

                        await wsClient.CloseAsync(serverStatus.CloseStatus.Value, serverStatus.CloseStatusDescription, context.RequestAborted);
                    }
                    catch
                    {
                        try
                        {
                            var closeStatus  = serverStatus?.CloseStatus ?? System.Net.WebSockets.WebSocketCloseStatus.NormalClosure;
                            var closeMessage = serverStatus?.CloseStatusDescription ?? string.Empty;

                            await wsClient.CloseAsync(closeStatus, closeMessage, context.RequestAborted);
                        }
                        catch { }
                    }
                });

                // Spawn an async task that will poll the local client websocket, in a loop, and then
                // write any data it gets to the remote server websocket.
                var clientTask = Task.Run(async() =>
                {
                    System.Net.WebSockets.WebSocketReceiveResult clientResult = null;
                    var clientBuffer = new byte[1024 * 4];
                    try
                    {
                        bool looping = true;

                        clientResult = await wsClient.ReceiveAsync(new ArraySegment <byte>(clientBuffer), context.RequestAborted);

                        while (looping && !clientResult.CloseStatus.HasValue && !context.RequestAborted.IsCancellationRequested)
                        {
                            await wsServer.SendAsync(new ArraySegment <byte>(clientBuffer, 0, clientResult.Count), clientResult.MessageType, clientResult.EndOfMessage, context.RequestAborted);

                            if (!wsServer.CloseStatus.HasValue)
                            {
                                clientResult = await wsClient.ReceiveAsync(new ArraySegment <byte>(clientBuffer), context.RequestAborted);
                                continue;
                            }

                            looping = false;
                        }

                        await wsServer.CloseAsync(clientResult.CloseStatus.Value, clientResult.CloseStatusDescription, context.RequestAborted);
                    }
                    catch
                    {
                        try
                        {
                            var closeStatus  = clientResult?.CloseStatus ?? System.Net.WebSockets.WebSocketCloseStatus.NormalClosure;
                            var closeMessage = clientResult?.CloseStatusDescription ?? string.Empty;

                            await wsServer.CloseAsync(closeStatus, closeMessage, context.RequestAborted);
                        }
                        catch { }
                    }
                });

                // Above, we have created a bridge between the local and remote websocket. Wait for both
                // associated tasks to complete.
                await Task.WhenAll(serverTask, clientTask);
            }
            catch (Exception wshe)
            {
                if (wshe is System.Net.WebSockets.WebSocketException)
                {
                    var cast = wshe as System.Net.WebSockets.WebSocketException;

                    Console.WriteLine(cast.WebSocketErrorCode);
                    if (cast.Data != null)
                    {
                        foreach (KeyValuePair <object, object> kvp in cast.Data)
                        {
                            Console.WriteLine("{0} ::: {1}", kvp.Key, kvp.Value);
                        }
                    }
                }
                LoggerProxy.Default.Error(wshe);
            }
            finally
            {
                if (wsClient != null)
                {
                    wsClient.Dispose();
                    wsClient = null;
                }

                if (wsServer != null)
                {
                    wsServer.Dispose();
                    wsServer = null;
                }
            }
        }
        /// <summary>
        /// Invoked when this handler is determined to be the best suited to handle the supplied connection.
        /// </summary>
        /// <param name="context">
        /// The HTTP context.
        /// </param>
        /// <returns>
        /// The handling task.
        /// </returns>
        public override async Task Handle(HttpContext context)
        {
            ClientWebSocket wsServer = null;

            System.Net.WebSockets.WebSocket wsClient = null;

            DiagnosticsWebSession diagSession = new DiagnosticsWebSession();

            try
            {
                // First we need the URL for this connection, since it's been requested to be upgraded to
                // a websocket.
                var fullUrl = Microsoft.AspNetCore.Http.Extensions.UriHelper.GetDisplayUrl(context.Request);

                // Need to replate the scheme with appropriate websocket scheme.
                if (fullUrl.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
                {
                    fullUrl = "ws://" + fullUrl.Substring(7);
                }
                else if (fullUrl.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
                {
                    fullUrl = "wss://" + fullUrl.Substring(8);
                }

                diagSession.ClientRequestUri = fullUrl;

                // Next we need to try and parse the URL as a URI, because the websocket client requires
                // this for connecting upstream.

                if (!Uri.TryCreate(fullUrl, UriKind.RelativeOrAbsolute, out Uri wsUri))
                {
                    LoggerProxy.Default.Error("Failed to parse websocket URI.");
                    return;
                }

                // Create the websocket that's going to connect to the remote server.
                wsServer = new ClientWebSocket();
                wsServer.Options.Cookies = new System.Net.CookieContainer();
                wsServer.Options.SetBuffer((int)ushort.MaxValue * 16, (int)ushort.MaxValue * 16);

                foreach (var cookie in context.Request.Cookies)
                {
                    try
                    {
                        wsServer.Options.Cookies.Add(new Uri(fullUrl, UriKind.Absolute), new System.Net.Cookie(cookie.Key, System.Net.WebUtility.UrlEncode(cookie.Value)));
                    }
                    catch (Exception e)
                    {
                        LoggerProxy.Default.Error("Error while attempting to add websocket cookie.");
                        LoggerProxy.Default.Error(e);
                    }
                }

                if (context.Connection.ClientCertificate != null)
                {
                    wsServer.Options.ClientCertificates = new System.Security.Cryptography.X509Certificates.X509CertificateCollection(new[] { context.Connection.ClientCertificate.ToV2Certificate() });
                }

                if (Collector.IsDiagnosticsEnabled)
                {
                    var diagHeaderBuilder = new StringBuilder();
                    foreach (var hdr in context.Request.Headers)
                    {
                        diagHeaderBuilder.AppendFormat($"{hdr.Key}: {hdr.Value.ToString()}\r\n");
                    }

                    diagSession.ClientRequestHeaders = diagHeaderBuilder.ToString();
                }

                var reqHeaderBuilder = new StringBuilder();
                foreach (var hdr in context.Request.Headers)
                {
                    if (!ForbiddenWsHeaders.IsForbidden(hdr.Key))
                    {
                        reqHeaderBuilder.AppendFormat("{0}: {1}\r\n", hdr.Key, hdr.Value.ToString());

                        try
                        {
                            wsServer.Options.SetRequestHeader(hdr.Key, hdr.Value.ToString());
                        }
                        catch (Exception hdrException)
                        {
                            LoggerProxy.Default.Error(hdrException);
                        }
                    }
                }

                reqHeaderBuilder.Append("\r\n");

                diagSession.ServerRequestHeaders = reqHeaderBuilder.ToString();

                // Connect the server websocket to the upstream, remote webserver.
                await wsServer.ConnectAsync(wsUri, context.RequestAborted);

                // Create, via acceptor, the client websocket. This is the local machine's websocket.
                wsClient = await context.WebSockets.AcceptWebSocketAsync(wsServer.SubProtocol ?? null);

                var msgNfo = new HttpMessageInfo
                {
                    Url             = wsUri,
                    IsEncrypted     = context.Request.IsHttps,
                    Headers         = context.Request.Headers.ToNameValueCollection(),
                    MessageProtocol = MessageProtocol.WebSocket,
                    MessageType     = MessageType.Request,
                    RemoteAddress   = context.Connection.RemoteIpAddress,
                    RemotePort      = (ushort)context.Connection.RemotePort,
                    LocalAddress    = context.Connection.LocalIpAddress,
                    LocalPort       = (ushort)context.Connection.LocalPort
                };

                _newMessageCb?.Invoke(msgNfo);

                switch (msgNfo.ProxyNextAction)
                {
                case ProxyNextAction.DropConnection:
                {
                    await wsClient.CloseAsync(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);

                    return;
                }
                }

                // Spawn an async task that will poll the remote server for data in a loop, and then
                // write any data it gets to the client websocket.
                var serverTask = Task.Run(async() =>
                {
                    System.Net.WebSockets.WebSocketReceiveResult serverStatus = null;
                    var serverBuffer = new byte[1024 * 4];
                    try
                    {
                        bool looping = true;

                        serverStatus = await wsServer.ReceiveAsync(new ArraySegment <byte>(serverBuffer), context.RequestAborted);

                        while (looping && !serverStatus.CloseStatus.HasValue && !context.RequestAborted.IsCancellationRequested)
                        {
                            await wsClient.SendAsync(new ArraySegment <byte>(serverBuffer, 0, serverStatus.Count), serverStatus.MessageType, serverStatus.EndOfMessage, context.RequestAborted);

                            if (!wsClient.CloseStatus.HasValue)
                            {
                                serverStatus = await wsServer.ReceiveAsync(new ArraySegment <byte>(serverBuffer), context.RequestAborted);
                                continue;
                            }

                            looping = false;
                        }

                        await wsClient.CloseAsync(serverStatus.CloseStatus.Value, serverStatus.CloseStatusDescription, context.RequestAborted);
                    }
                    catch
                    {
                        try
                        {
                            var closeStatus  = serverStatus?.CloseStatus ?? System.Net.WebSockets.WebSocketCloseStatus.NormalClosure;
                            var closeMessage = serverStatus?.CloseStatusDescription ?? string.Empty;

                            await wsClient.CloseAsync(closeStatus, closeMessage, context.RequestAborted);
                        }
                        catch { }
                    }
                });

                // Spawn an async task that will poll the local client websocket, in a loop, and then
                // write any data it gets to the remote server websocket.
                var clientTask = Task.Run(async() =>
                {
                    System.Net.WebSockets.WebSocketReceiveResult clientResult = null;
                    var clientBuffer = new byte[1024 * 4];
                    try
                    {
                        bool looping = true;

                        clientResult = await wsClient.ReceiveAsync(new ArraySegment <byte>(clientBuffer), context.RequestAborted);

                        while (looping && !clientResult.CloseStatus.HasValue && !context.RequestAborted.IsCancellationRequested)
                        {
                            await wsServer.SendAsync(new ArraySegment <byte>(clientBuffer, 0, clientResult.Count), clientResult.MessageType, clientResult.EndOfMessage, context.RequestAborted);

                            if (!wsServer.CloseStatus.HasValue)
                            {
                                clientResult = await wsClient.ReceiveAsync(new ArraySegment <byte>(clientBuffer), context.RequestAborted);
                                continue;
                            }

                            looping = false;
                        }

                        await wsServer.CloseAsync(clientResult.CloseStatus.Value, clientResult.CloseStatusDescription, context.RequestAborted);
                    }
                    catch
                    {
                        try
                        {
                            var closeStatus  = clientResult?.CloseStatus ?? System.Net.WebSockets.WebSocketCloseStatus.NormalClosure;
                            var closeMessage = clientResult?.CloseStatusDescription ?? string.Empty;

                            await wsServer.CloseAsync(closeStatus, closeMessage, context.RequestAborted);
                        }
                        catch { }
                    }
                });

                // Above, we have created a bridge between the local and remote websocket. Wait for both
                // associated tasks to complete.
                await Task.WhenAll(serverTask, clientTask);
            }
            catch (Exception wshe)
            {
                LoggerProxy.Default.Error(wshe);
            }
            finally
            {
                Collector.ReportSession(diagSession);

                if (wsClient != null)
                {
                    wsClient.Dispose();
                    wsClient = null;
                }

                if (wsServer != null)
                {
                    wsServer.Dispose();
                    wsServer = null;
                }
            }
        }