/// <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(); }
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")); }
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); }
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)); }
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); }
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; }
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); }
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); }
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); }
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; }