Beispiel #1
0
            // Does all the real work of making a connection.  Currently, this blocks on the initial connection.
            // I'd rather there be a cleaner interface for this, where the Task itself is being polled and the state changes over when it's done.
            private async Task DoConnection()
            {
                if (_status != Status.ReadyToConnect)
                {
                    throw new Exception("Not in status=ReadyToConnect.");
                }

                _lastErrorMsg = string.Empty;
                Uri uri = new Uri(_connectUrl);                  // I think this can throw exceptions for bad formatting?

                // Creates a websocket connection and lets you start sending or receiving messages on separate threads.
                ClientWebSocket wsClient = null;

                try
                {
                    wsClient = new ClientWebSocket();
                    using (CancellationTokenSource connectTimeout = new CancellationTokenSource(_connectTimeoutMS))
                    {
                        // Apply all the headers that were passed in.
                        foreach (KeyValuePair <string, string> kvp in _connectHeaders)
                        {
                            wsClient.Options.SetRequestHeader(kvp.Key, kvp.Value);
                        }

                        _status = Status.Connecting;
                        await wsClient.ConnectAsync(uri, connectTimeout.Token).ConfigureAwait(false);
                    }

                    _status = Status.Connected;
                    _rgws   = new RGWebSocket(OnRecvTextMsg, OnRecvBinaryMsg, OnDisconnect, _logCb, uri.ToString(), wsClient);
                    _logCb?.Invoke($"UWS Connected to {_connectUrl}", 1);
                }
                catch (AggregateException age)
                {
                    if (age.InnerException is OperationCanceledException)
                    {
                        _lastErrorMsg = "Connection timed out.";
                        _logCb?.Invoke(_lastErrorMsg, 0);
                    }
                    else if (age.InnerException is WebSocketException)
                    {
                        _lastErrorMsg = ((WebSocketException)age.InnerException).Message;
                        _logCb?.Invoke(_lastErrorMsg, 0);
                    }
                    else
                    {
                        _lastErrorMsg = age.Message;
                        _logCb?.Invoke(_lastErrorMsg, 0);
                    }
                    wsClient?.Dispose();                      // cleanup
                    _status = Status.Disconnected;
                }
                catch (Exception e)
                {
                    _lastErrorMsg = e.Message;
                    _logCb?.Invoke(_lastErrorMsg, 0);
                    wsClient?.Dispose();                      // cleanup
                    _status = Status.Disconnected;
                }
            }
Beispiel #2
0
 //-------------------
 // Privates.  These calls occur on non-main-threads, so messages get queued up and you POLL them out in the Receive call above on the main thread.
 private Task OnReceiveText(RGWebSocket rgws, string msg)
 {
     _incomingMessages.Add(new wsMessage()
     {
         stringMsg = msg, binMsg = null
     });
     _logger($"UWS Recv {msg.Length} bytes txt", 3);
     return(Task.CompletedTask);
 }
Beispiel #3
0
 // This callback holds the reference to PooledArray, so it must be decremented to free it (eventually) after it's consumed.
 private Task OnReceiveBinary(RGWebSocket rgws, PooledArray msg)
 {
     msg.IncRef();                  // bump the refcount since we aren't done with it yet, and RGWebSocket can decrement it without freeing the buffer
     _incomingMessages.Add(new wsMessage()
     {
         stringMsg = string.Empty, binMsg = msg
     });
     _logger($"UWS Recv {msg.Length} bytes bin", 3);
     return(Task.CompletedTask);
 }
Beispiel #4
0
            // We capture the callback so we can manage the websocket set internally.
            private void OnDisconnection(RGWebSocket rgws)
            {
                RGWebSocket ws;

                if (_websockets.TryRemove(rgws._uniqueId, out ws))
                {
                    _disconnected.TryAdd(rgws._uniqueId, rgws);
                    _disconnectionCount.Release();
                    _onDisconnect(rgws);                      // let the caller know it's disconnected now
                }
            }
Beispiel #5
0
 // A simple blocking way to make sure this is all torn down.
 public void Shutdown()
 {
     if (_rgws != null)
     {
         _rgws.Shutdown();
         _rgws.Dispose();
         _rgws = null;
     }
     _logger("UWS shutdown.", 1);
     _status = Status.ReadyToConnect;
 }
Beispiel #6
0
 // Add this websocket to the list of those we need to remove and unblock the cleanup thread
 private void OnDisconnection(RGWebSocket rgws)
 {
     _logger($"{rgws._displayId} OnDisconnection call.", 3);
     _disconnected.Add(rgws);               // finally, put it on the list of things to be disposed of
     _cleanupSocket.Set();                  // Let the Cleanup process know there's something to do
 }
Beispiel #7
0
            //-------------------
            // Task: when a connection is requested, depending on whether it's an HTTP request or WebSocket request, do different things.
            private async Task HandleConnection(HttpListenerContext httpContext)
            {
                // Allow debugging to actually happen, where you have unlimited time to check things without breaking a connection.  -1 means don't cancel over time.
                int timeoutMS = Debugger.IsAttached ? -1 : _connectionMS;

                if (httpContext.Request.IsWebSocketRequest)
                {
                    // Kick off an async task to upgrade the web socket and do send/recv messaging, but fail if it takes more than a second to finish.
                    try
                    {
                        _logger("WebSocketServer.HandleConnection - websocket detected.  Upgrading connection.", 2);
                        using (CancellationTokenSource upgradeTimeout = new CancellationTokenSource(timeoutMS))
                        {
                            HttpListenerWebSocketContext webSocketContext = await Task.Run(async() => { return(await httpContext.AcceptWebSocketAsync(null, _idleSeconds).ConfigureAwait(false)); }, upgradeTimeout.Token);

                            _logger("WebSocketServer.HandleConnection - websocket detected.  Upgraded.", 3);

                            // Note, we hook our own OnDisconnect before proxying it on to the ConnectionManager.  Note, due to heavy congestion and C# scheduling, it's entirely possible that this is already a closed socket, and is immediately flagged for destruction.
                            RGWebSocket rgws = new RGWebSocket(httpContext, _connectionManager.OnReceiveText, _connectionManager.OnReceiveBinary, OnDisconnection, _logger, httpContext.Request.RemoteEndPoint.ToString(), webSocketContext.WebSocket);
                            _websockets.TryAdd(rgws._uniqueId, rgws);
                            await _connectionManager.OnConnection(rgws).ConfigureAwait(false);

                            _logger($"WebSocketServer.HandleConnection - websocket detected.  Upgrade completed. {rgws._displayId}", 3);
                        }
                    }
                    catch (OperationCanceledException ex)                      // timeout
                    {
                        _logger($"WebSocketServer.HandleConnection - websocket upgrade timeout {ex.Message}", 0);
                        httpContext.Response.StatusCode = 500;
                        httpContext.Response.Close();       // this breaks the connection, otherwise it may linger forever
                    }
                    catch (Exception ex)                    // anything else
                    {
                        _logger($"WebSocketServer.HandleConnection - websocket upgrade exception {ex.Message}", 0);
                        httpContext.Response.StatusCode = 500;
                        httpContext.Response.Close();                          // this breaks the connection, otherwise it may linger forever
                    }
                }
                else                  // let the application specify what the HTTP response is, but we do the async write here to free up the app to do other things
                {
                    try
                    {
                        _logger($"WebSocketServer.HandleConnection - normal http request {httpContext.Request.RawUrl}", 3);
                        using (CancellationTokenSource responseTimeout = new CancellationTokenSource(timeoutMS))
                        {
                            // Remember to set httpContext.Response.StatusCode, httpContext.Response.ContentLength64, and httpContenxtResponse.OutputStream
                            await _httpRequestCallback(httpContext).ConfigureAwait(false);
                        }
                    }
                    catch (OperationCanceledException ex)                      // timeout
                    {
                        _logger($"WebSocketServer.HandleConnection - websocket upgrade timeout {ex.Message}", 0);
                        httpContext.Response.StatusCode = 500;
//						httpContext.Response.Abort();  // this breaks the connection, otherwise it may linger forever
                    }
                    catch (Exception ex)                     // anything else
                    {
                        _logger($"WebSocketServer.HandleConnection - http callback handler exception {ex.Message}", 0);
                        httpContext.Response.StatusCode = 500;
//						httpContext.Response.Abort();  // this breaks the connection, otherwise it may linger forever
                    }
                    finally
                    {
                        httpContext.Response.Close();                          // This frees all the memory associated with this connection.
                    }
                }
            }
Beispiel #8
0
 // Forcibly disposes the RGWS
 public void Dispose()
 {
     _rgws?.Dispose();
     _rgws = null;
 }
Beispiel #9
0
 // At this point, it's a done deal.  Both Recv and Send are completed, nothing to synchronize.  This is called by Send after Recv is finished.
 private void OnDisconnect(RGWebSocket rgws)
 {
     _logCb?.Invoke("UWS Disconnected.", 1);
     _status = Status.Disconnected;
 }
Beispiel #10
0
 private void OnRecvBinaryMsg(RGWebSocket rgws, byte[] msg)
 {
     _incomingMessages.Enqueue(new Tuple <string, byte[]>(string.Empty, msg));
     _logCb?.Invoke($"UWS Recv {msg.Length} bytes bin", 2);
 }
Beispiel #11
0
 //-------------------
 // Privates.  These calls occur on non-main-threads, so messages get queued up and you POLL them out in the Receive call above on the main thread.
 private void OnRecvTextMsg(RGWebSocket rgws, string msg)
 {
     _incomingMessages.Enqueue(new Tuple <string, byte[]>(msg, null));
     _logCb?.Invoke($"UWS Recv {msg.Length} bytes txt", 2);
 }
Beispiel #12
0
            //-------------------
            // Task: when a connection is requested, depending on whether it's an HTTP request or WebSocket request, do different things.
            private async Task HandleConnection(Task <HttpListenerContext> listenerContext)
            {
                HttpListenerContext httpContext = listenerContext.Result;

                if (httpContext.Request.IsWebSocketRequest)
                {
                    // Kick off an async task to upgrade the web socket and do send/recv messaging, but fail if it takes more than a second to finish.
                    try
                    {
                        _logger?.Invoke("WebSocketServer.HandleConnection - websocket detected.  Upgrading connection.", 1);
                        using (CancellationTokenSource upgradeTimeout = new CancellationTokenSource(_connectionMS))
                        {
                            HttpListenerWebSocketContext webSocketContext = await Task.Run(async() => { return(await httpContext.AcceptWebSocketAsync(null).ConfigureAwait(false)); }, upgradeTimeout.Token);

                            _logger?.Invoke("WebSocketServer.HandleConnection - websocket detected.  Upgraded.", 1);

                            RGWebSocket rgws = new RGWebSocket(_onReceiveMsgText, _onReceiveMsgBinary, OnDisconnection, _logger, httpContext.Request.RemoteEndPoint.ToString(), webSocketContext.WebSocket, 25);
                            _websockets.TryAdd(rgws._uniqueId, rgws);
                            _websocketConnection(rgws);
                        }
                    }
                    catch (OperationCanceledException)                      // timeout
                    {
                        _logger?.Invoke("WebSocketServer.HandleConnection - websocket upgrade timeout", 1);
                        httpContext.Response.StatusCode = 500;
                        httpContext.Response.Close();
                    }
                    catch                     // anything else
                    {
                        _logger?.Invoke("WebSocketServer.HandleConnection - websocket upgrade exception", 1);
                        httpContext.Response.StatusCode = 500;
                        httpContext.Response.Close();
                    }
                }
                else                  // let the application specify what the HTTP response is, but we do the async write here to free up the app to do other things
                {
                    try
                    {
                        _logger?.Invoke("WebSocketServer.HandleConnection - normal http request", 1);
                        using (CancellationTokenSource responseTimeout = new CancellationTokenSource(_connectionMS))
                        {
                            byte[] buffer = null;
                            httpContext.Response.StatusCode      = _httpRequestCallback(httpContext, out buffer);
                            httpContext.Response.ContentLength64 = buffer.Length;
                            await httpContext.Response.OutputStream.WriteAsync(buffer, 0, buffer.Length, responseTimeout.Token);
                        }
                    }
                    catch (OperationCanceledException)                      // timeout
                    {
                        _logger?.Invoke("WebSocketServer.HandleConnection - http response timeout", 1);
                        httpContext.Response.StatusCode = 500;
                    }
                    catch                     // anything else
                    {
                        _logger?.Invoke("WebSocketServer.HandleConnection - http callback handler exception", 1);
                        httpContext.Response.StatusCode = 500;
                    }
                    finally
                    {
                        httpContext.Response.Close();
                    }
                }
            }
Beispiel #13
0
 // At this point, it's a done deal.  Both Recv and Send are completed, nothing to synchronize.  This is called at the bottom of the Send thread after Recv is completed.
 // However, it is possible that the Recv/Send threads shutdown before the RGWS constructor is even finished
 private void OnDisconnect(RGWebSocket rgws)
 {
     _logger("UWS Disconnected.", 1);
     _status = Status.Disconnected;
     _disconnectCallback?.Invoke(this);                  // This callback needs to NOT modify any tracking structures, because it may be called as early as DURING the RGWS constructor.  Just set flags
 }