/// <summary>
        /// Creates a new <see cref="RoRamu.WebSocket.Client.WebSocket4Net.WebSocket4NetConnection" /> object.
        /// </summary>
        /// <param name="connectionInfo">Information about how to connect to the websocket server.</param>
        public WebSocket4NetConnection(WebSocketConnectionInfo connectionInfo)
        {
            if (connectionInfo == null)
            {
                throw new ArgumentNullException(nameof(connectionInfo));
            }
            if (!Uri.TryCreate(connectionInfo.RemoteEndpoint, UriKind.Absolute, out Uri endpoint))
            {
                throw new ArgumentException("The provided websocket endpoint is not a well-formed URI string", nameof(connectionInfo));
            }
            if (!WebSocketSchemes.Contains(endpoint.Scheme))
            {
                throw new ArgumentException($"The endpoint scheme must be one of the following: {FormattedWebSocketSchemesList}", nameof(connectionInfo));
            }

            this._socket = new WebSocket4NetImpl.WebSocket(
                uri: connectionInfo.RemoteEndpoint,
                customHeaderItems: new List <KeyValuePair <string, string> >(connectionInfo.Headers),
                cookies: new List <KeyValuePair <string, string> >(connectionInfo.Cookies));

            this._socket.MessageReceived += async(sender, messageArgs) => await this.OnMessage?.Invoke(messageArgs.Message);

            this._socket.DataReceived += async(sender, dataArgs) => await this.OnMessage?.Invoke(dataArgs.Data.DecodeToString());

            this._socket.Error += async(sender, errorArgs) => await this.OnError?.Invoke(errorArgs.Exception);

            this._socket.Closed += async(sender, args) => await this.OnClose?.Invoke();

            this._socket.Opened += async(sender, args) => await this.OnOpen?.Invoke();
        }
Example #2
0
        public void ShouldProvideAdditionalHeaders()
        {
            const string origin                = "http://blah.com/path/to/page";
            const string host                  = "blah.com";
            const string subprotocol           = "Submarine!";
            const string username              = "******";
            const string secret                = "Secret";
            const string clientIp              = "127.0.0.1";
            const string cookies               = "chocolate=yummy; oatmeal=alsoyummy";
            const int    clientPort            = 0;
            const string negotiatedSubProtocol = "Negotiated";

            var request =
                new WebSocketHttpRequest {
                Headers =
                {
                    { "Origin",                 origin      },
                    { "Host",                   host        },
                    { "Sec-WebSocket-Protocol", subprotocol },
                    { "Username",               username    },
                    { "Cookie",                 cookies     }
                }
            };

            var info = WebSocketConnectionInfo.Create(request, clientIp, clientPort, negotiatedSubProtocol);

            var    headers       = info.Headers;
            string usernameValue = null;

            Assert.IsNotNull(headers);
            Assert.AreEqual(5, headers.Count);
            Assert.True(headers.TryGetValue("Username", out usernameValue));
            Assert.True(usernameValue.Equals(username));
            Assert.True(headers.ContainsKey("Cookie"));
        }
Example #3
0
        public void ShouldReadHeadersFromRequest()
        {
            const string origin      = "http://blah.com/path/to/page";
            const string host        = "blah.com";
            const string subprotocol = "Submarine!";
            const string path        = "/path/to/page";
            const string clientIp    = "127.0.0.1";
            const int    clientPort  = 0;

            var request =
                new WebSocketHttpRequest
            {
                Headers =
                {
                    { "Origin",                 origin      },
                    { "Host",                   host        },
                    { "Sec-WebSocket-Protocol", subprotocol }
                },
                Path = path
            };
            var info = WebSocketConnectionInfo.Create(request, clientIp, clientPort);

            Assert.AreEqual(origin, info.Origin);
            Assert.AreEqual(host, info.Host);
            Assert.AreEqual(subprotocol, info.SubProtocol);
            Assert.AreEqual(path, info.Path);
            Assert.AreEqual(clientIp, info.ClientIpAddress);
        }
Example #4
0
        public void ShouldHaveId()
        {
            var request = new WebSocketHttpRequest();
            var info    = WebSocketConnectionInfo.Create(request, null, 1);

            Assert.AreNotEqual(default(Guid), info.Id);
        }
        public virtual Task Start(IConnection connection, string connectionData, CancellationToken disconnectToken)
        {
            if (connection == null)
            {
                throw new ArgumentNullException("connection");
            }

            _initializeHandler = new TransportInitializationHandler(connection.TransportConnectTimeout, disconnectToken);

            // Tie into the OnFailure event so that we can stop the transport silently.
            _initializeHandler.OnFailure += () =>
            {
                Dispose();
            };

            _disconnectToken = disconnectToken;
            _connectionInfo  = new WebSocketConnectionInfo(connection, connectionData);

            // We don't need to await this task
            PerformConnect().ContinueWith(task =>
            {
                if (task.IsFaulted)
                {
                    _initializeHandler.Fail(task.Exception);
                }
                else if (task.IsCanceled)
                {
                    _initializeHandler.Fail();
                }
            },
                                          TaskContinuationOptions.NotOnRanToCompletion);

            return(_initializeHandler.Task);
        }
 /// <inheritdoc />
 protected override WebSocketReceiverController <TContractImplementation> CreateController(
     string connectionId,
     WebSocketConnectionInfo connectionInfo,
     IWebSocketConnection connection)
 {
     return(new WebSocketReceiverController <TContractImplementation>(connectionId, connection, this.ReceiverImplementation));
 }
Example #7
0
 protected override TestServiceController CreateController(
     string connectionId,
     WebSocketConnectionInfo connectionInfo,
     IWebSocketConnection connection)
 {
     return(new TestServiceController(connectionId, connection));
 }
        public void ShouldReadHeadersFromRequest()
        {
            const string origin     = "http://blah.com/path/to/page";
            const string host       = "blah.com";
            const string path       = "/path/to/page";
            var          clientIp   = IPAddress.Parse("127.0.0.1");
            const int    clientPort = 0;

            var request =
                new WebSocketHttpRequest
            {
                Headers =
                {
                    { "Origin", origin },
                    { "Host",   host   },
                },
                Path = path
            };
            var info = WebSocketConnectionInfo.Create(request, clientIp, clientPort);

            Assert.AreEqual(origin, info.Origin);
            Assert.AreEqual(host, info.Host);
            Assert.AreEqual(path, info.Path);
            Assert.AreEqual(clientIp, info.ClientIpAddress);
        }
Example #9
0
        public Task Start(IConnection connection, string data, CancellationToken disconnectToken)
        {
            _startTcs = new TaskCompletionSource<object>();
            _disconnectToken = disconnectToken;
            _connectionInfo = new WebSocketConnectionInfo(connection, data);

            // We don't need to await this task
            PerformConnect().ContinueWithNotComplete(_startTcs);

            return _startTcs.Task;
        }
Example #10
0
        public Task Start(IConnection connection, string data, CancellationToken disconnectToken)
        {
            _startTcs        = new TaskCompletionSource <object>();
            _disconnectToken = disconnectToken;
            _connectionInfo  = new WebSocketConnectionInfo(connection, data);

            // We don't need to await this task
            PerformConnect().ContinueWithNotComplete(_startTcs);

            return(_startTcs.Task);
        }
Example #11
0
        public void ShouldReadSecWebSocketOrigin()
        {
            const string origin  = "http://example.com/myPath";
            var          request =
                new WebSocketHttpRequest {
                Headers = { { "Sec-WebSocket-Origin", origin } }
            };
            var info = WebSocketConnectionInfo.Create(request, null, 1, null);

            Assert.AreEqual(origin, info.Origin);
        }
        /// <summary>
        /// The logic used to handle a new incoming connection.
        /// </summary>
        /// <param name="socket">
        /// Represents the underlying websocket implementation's connection object.
        /// </param>
        /// <param name="connectionInfo">
        /// Information about the connection that was made by the client during the handshake.
        /// </param>
        private void OnOpen(WebSocketUnderlyingConnection socket, WebSocketConnectionInfo connectionInfo)
        {
            if (socket == null)
            {
                throw new ArgumentNullException(nameof(socket));
            }
            if (connectionInfo == null)
            {
                throw new ArgumentNullException(nameof(connectionInfo));
            }

            Task.Run(async() =>
            {
                // Create the proxy for this connection
                WebSocketClientProxy proxy = await this.CreateProxy(socket, connectionInfo);

                // Set the proxy for this connection
                this._connections.AddOrUpdate(
                    key: proxy.Id,
                    addValue: proxy,
                    updateValueFactory: (id, oldProxy) =>
                {
                    // Close the old connection and swallow any exceptions
                    this.Logger?.Log(LogLevel.Info, $"Closing duplicate connection to client '{id}'.");
                    oldProxy.Close().ContinueWith(closeTask =>
                    {
                        this.Logger?.Log(LogLevel.Warning, $"Failed to close duplicate connection to client '{id}'.", closeTask.Exception);
                    }, TaskContinuationOptions.OnlyOnFaulted);

                    return(proxy);
                });
            }).ContinueWith(createProxyTask =>
            {
                this.Logger?.Log(LogLevel.Error, "Failed to create client connection proxy.", createProxyTask.Exception);

                // Get the user-safe exceptions
                IEnumerable <UserSafeWebSocketException> userSafeExceptions = createProxyTask?.Exception?.InnerExceptions
                                                                              ?.Where(e => e is UserSafeWebSocketException)
                                                                              ?.Select(e => e as UserSafeWebSocketException);

                // Send the user-safe exceptions to the client
                if (userSafeExceptions != null && userSafeExceptions.Any())
                {
                    var errorMessage = new ErrorResponse(new AggregateException("Failed to connect", userSafeExceptions), requestId: null);
                }

                // Close the connection
                socket.Close().ContinueWith(closeTask =>
                {
                    this.Logger?.Log(LogLevel.Warning, $"Could not close the connection for which proxy creation failed.", closeTask.Exception);
                }, TaskContinuationOptions.OnlyOnFaulted);
            }, TaskContinuationOptions.OnlyOnFaulted);
        }
Example #13
0
        public void ShouldParseCookies()
        {
            const string cookie  = "chocolate=tasty; cabbage=not so much";
            var          request =
                new WebSocketHttpRequest {
                Headers = { { "Cookie", cookie } }
            };

            var info = WebSocketConnectionInfo.Create(request, null, 1, null);

            Assert.AreEqual(info.Cookies["chocolate"], "tasty");
            Assert.AreEqual(info.Cookies["cabbage"], "not so much");
        }
 /// <summary>
 /// Creates a new instance of a controller, which defines the behavior of the service for a connection.
 /// </summary>
 /// <param name="connectionId">The connection's ID.</param>
 /// <param name="connectionInfo">Information about the connection.</param>
 /// <param name="connection">
 /// Actions available for interacting with this websocket connection.
 /// </param>
 /// <returns>A controller.</returns>
 protected abstract TController CreateController(
     string connectionId,
     WebSocketConnectionInfo connectionInfo,
     IWebSocketConnection connection);
 /// <summary>
 /// Generates a unique string to represent a connection (duplicate connections will be terminated automatically).
 /// </summary>
 /// <param name="connectionInfo">Information about the connection.</param>
 /// <returns>A connection ID.</returns>
 protected virtual string GenerateConnectionId(WebSocketConnectionInfo connectionInfo)
 {
     return(Guid.NewGuid().ToString());
 }
        /// <summary>
        /// Creates a new instance of <see cref="RoRamu.WebSocket.Service.WebSocketClientProxy" /> to
        /// represent a new connection.
        /// </summary>
        /// <param name="socket">
        /// The <see cref="RoRamu.WebSocket.WebSocketUnderlyingConnection" /> which represents the underlying
        /// implementation of a websocket connection.
        /// </param>
        /// <param name="connectionInfo">
        /// Information about the connection that was made by the client during the handshake.  For
        /// example, this can be used to authenticate a client based on request headers, or assign
        /// different behaviors to the connection based on the path used in the URL.
        /// </param>
        /// <returns>
        /// The new <see cref="RoRamu.WebSocket.WebSocketUnderlyingConnection" /> representing the connection.
        /// </returns>
        private async Task <WebSocketClientProxy> CreateProxy(WebSocketUnderlyingConnection socket, WebSocketConnectionInfo connectionInfo)
        {
            // Generate the connection ID
            string connectionId;

            try
            {
                connectionId = this.GenerateConnectionId(connectionInfo) ?? throw new ArgumentNullException(nameof(connectionId));
            }
            catch
            {
                this.Logger?.Log(LogLevel.Error, $"Failed to generate connectionId.", connectionInfo);
                throw;
            }

            // Create the proxy
            WebSocketClientProxy proxy = new WebSocketClientProxy(
                connectionId,
                socket,
                connection => this.CreateController(connectionId, connectionInfo, connection));

            // Attach the event handlers
            socket.OnClose   = proxy.OnCloseInternal;
            socket.OnError   = proxy.OnErrorInternal;
            socket.OnMessage = proxy.OnMessageInternal;

            // Run the "OnOpen()" method
            await proxy.OnOpenInternal();

            return(proxy);
        }
Example #17
0
 public TestClient(WebSocketConnectionInfo connectionInfo) : base(new WebSocket4NetConnection(connectionInfo), CreateWebSocketController)
 {
 }
        public virtual Task Start(IConnection connection, string connectionData, CancellationToken disconnectToken)
        {
            if (connection == null)
            {
                throw new ArgumentNullException("connection");
            }

            _initializeHandler = new TransportInitializationHandler(connection.TransportConnectTimeout, disconnectToken);

            // Tie into the OnFailure event so that we can stop the transport silently.
            _initializeHandler.OnFailure += () =>
            {
                Dispose();
            };

            _disconnectToken = disconnectToken;
            _connectionInfo = new WebSocketConnectionInfo(connection, connectionData);

            // We don't need to await this task
            PerformConnect().ContinueWith(task =>
            {
                if (task.IsFaulted)
                {
                    _initializeHandler.Fail(task.Exception);
                }
                else if (task.IsCanceled)
                {
                    _initializeHandler.Fail();
                }
            },
            TaskContinuationOptions.NotOnRanToCompletion);

            return _initializeHandler.Task;
        }