public void SendPong()
 {
     using (EneterTrace.Entering())
     {
         SendFrame(maskingKey => WebSocketFormatter.EncodePongFrame(maskingKey, null));
     }
 }
        public void CloseConnection()
        {
            using (EneterTrace.Entering())
            {
                using (ThreadLock.Lock(myConnectionManipulatorLock))
                {
                    myStopReceivingRequestedFlag = true;
                    myMessageInSendProgress      = EMessageInSendProgress.None;

                    if (myTcpClient != null)
                    {
                        // Try to send the frame closing the communication.
                        if (myClientStream != null)
                        {
                            try
                            {
                                // If it was not disconnected by the client then try to send the message the connection was closed.
                                if (myIsListeningToResponses)
                                {
                                    // Generate the masking key.
                                    byte[] aCloseFrame = WebSocketFormatter.EncodeCloseFrame(null, 1000);
                                    myClientStream.Write(aCloseFrame, 0, aCloseFrame.Length);
                                }
                            }
                            catch (Exception err)
                            {
                                EneterTrace.Warning(TracedObject + ErrorHandler.FailedToCloseConnection, err);
                            }

                            myClientStream.Close();
                        }

                        try
                        {
                            myTcpClient.Close();
                        }
                        catch (Exception err)
                        {
                            EneterTrace.Warning(TracedObject + "failed to close Tcp connection.", err);
                        }

                        myTcpClient = null;
                    }

                    myReceivedMessages.UnblockProcessingThreads();
                }
            }
        }
예제 #3
0
        private void ValidateOpenConnectionResponse(Match responseRegEx, byte[] webSocketKey)
        {
            using (EneterTrace.Entering())
            {
                if (!responseRegEx.Success)
                {
                    string anErrorMessage = TracedObject + ErrorHandler.FailedToOpenConnection + " The http response was not recognized.";
                    EneterTrace.Error(anErrorMessage);
                    throw new InvalidOperationException(anErrorMessage);
                }

                // Check required header fields.
                IDictionary <string, string> aHeaderFields = WebSocketFormatter.GetHttpHeaderFields(responseRegEx);

                string aSecurityAccept;
                aHeaderFields.TryGetValue("Sec-WebSocket-Accept", out aSecurityAccept);

                // If some required header field is missing or has incorrect value.
                if (!aHeaderFields.ContainsKey("Upgrade") ||
                    !aHeaderFields.ContainsKey("Connection") ||
                    string.IsNullOrEmpty(aSecurityAccept))
                {
                    string anErrorMessage = TracedObject + ErrorHandler.FailedToOpenConnection + " A required header field was missing.";
                    EneterTrace.Error(anErrorMessage);
                    throw new InvalidOperationException(anErrorMessage);
                }

                // Check the value of websocket accept.
                string aWebSocketKeyBase64   = Convert.ToBase64String(webSocketKey);
                string aCalculatedAcceptance = WebSocketFormatter.EncryptWebSocketKey(aWebSocketKeyBase64);
                if (aCalculatedAcceptance != aSecurityAccept)
                {
                    string anErrorMessage = TracedObject + ErrorHandler.FailedToOpenConnection + " Sec-WebSocket-Accept has incorrect value.";
                    EneterTrace.Error(anErrorMessage);
                    throw new InvalidOperationException(anErrorMessage);
                }
            }
        }
        public void DoRequestListening()
        {
            using (EneterTrace.Entering())
            {
                myIsListeningToResponses = true;
                ushort aCloseCode = 0;

                try
                {
                    DynamicStream aContinuousMessageStream = null;

                    while (!myStopReceivingRequestedFlag)
                    {
                        // Decode the incoming message.
                        WebSocketFrame aFrame = WebSocketFormatter.DecodeFrame(myClientStream);

                        if (!myStopReceivingRequestedFlag && aFrame != null)
                        {
                            // Frames from server must be unmasked.
                            // According the protocol, If the frame was NOT masked, the server must close connection with the client.
                            if (aFrame.MaskFlag == false)
                            {
                                throw new InvalidOperationException(TracedObject + "received unmasked frame from the client. Frames from client shall be masked.");
                            }

                            // Process the frame.
                            if (aFrame.FrameType == EFrameType.Ping)
                            {
                                // Response 'pong'. The response responses same data as received in the 'ping'.
                                SendFrame(maskingKey => WebSocketFormatter.EncodePongFrame(maskingKey, aFrame.Message));
                            }
                            else if (aFrame.FrameType == EFrameType.Close)
                            {
                                EneterTrace.Debug(TracedObject + "received the close frame.");
                                break;
                            }
                            else if (aFrame.FrameType == EFrameType.Pong)
                            {
                                Notify(PongReceived);
                            }
                            // If a new message starts.
                            else if (aFrame.FrameType == EFrameType.Binary || aFrame.FrameType == EFrameType.Text)
                            {
                                // If a previous message is not finished then the new message is not expected -> protocol error.
                                if (aContinuousMessageStream != null)
                                {
                                    EneterTrace.Warning(TracedObject + "detected unexpected new message. (previous message was not finished)");

                                    // Protocol error close code.
                                    aCloseCode = 1002;
                                    break;
                                }

                                WebSocketMessage aReceivedMessage = null;

                                // If the message does not come in multiple frames then optimize the performance
                                // and use MemoryStream instead of DynamicStream.
                                if (aFrame.IsFinal)
                                {
                                    MemoryStream aMessageStream = new MemoryStream(aFrame.Message);
                                    aReceivedMessage = new WebSocketMessage(aFrame.FrameType == EFrameType.Text, aMessageStream);
                                }
                                else
                                // if the message is split to several frames then use DynamicStream so that writing of incoming
                                // frames and reading of already received data can run in parallel.
                                {
                                    // Create stream where the message data will be writen.
                                    aContinuousMessageStream = new DynamicStream();
                                    aContinuousMessageStream.WriteWithoutCopying(aFrame.Message, 0, aFrame.Message.Length);
                                    aReceivedMessage = new WebSocketMessage(aFrame.FrameType == EFrameType.Text, aContinuousMessageStream);
                                }

                                // Put received message to the queue.
                                myReceivedMessages.EnqueueMessage(aReceivedMessage);
                            }
                            // If a message continues. (I.e. message is split into more fragments.)
                            else if (aFrame.FrameType == EFrameType.Continuation)
                            {
                                // If the message does not exist then continuing frame does not have any sense -> protocol error.
                                if (aContinuousMessageStream == null)
                                {
                                    EneterTrace.Warning(TracedObject + "detected unexpected continuing of a message. (none message was started before)");

                                    // Protocol error close code.
                                    aCloseCode = 1002;
                                    break;
                                }

                                aContinuousMessageStream.WriteWithoutCopying(aFrame.Message, 0, aFrame.Message.Length);

                                // If this is the final frame.
                                if (aFrame.IsFinal)
                                {
                                    aContinuousMessageStream.IsBlockingMode = false;
                                    aContinuousMessageStream = null;
                                }
                            }
                        }

                        // If disconnected
                        if (aFrame == null)// || !myTcpClient.Client.Poll(0, SelectMode.SelectWrite))
                        {
                            //EneterTrace.Warning(TracedObject + "detected the TCP connection is not available. The connection will be closed.");
                            break;
                        }
                    }
                }
                catch (IOException)
                {
                    // Ignore this exception. It is often thrown when the connection was closed.
                    // Do not thrace this because the tracing degradates the performance in this case.
                }
                catch (Exception err)
                {
                    EneterTrace.Error(TracedObject + ErrorHandler.FailedInListeningLoop, err);
                }

                // If the connection is being closed due to a protocol error.
                if (aCloseCode > 1000)
                {
                    // Try to send the close message.
                    try
                    {
                        byte[] aCloseMessage = WebSocketFormatter.EncodeCloseFrame(null, aCloseCode);
                        myClientStream.Write(aCloseMessage, 0, aCloseMessage.Length);
                    }
                    catch
                    {
                    }
                }

                myIsListeningToResponses = false;

                myReceivedMessages.UnblockProcessingThreads();

                // Notify the listening to messages stoped.
                Notify(ConnectionClosed);
            }
        }
        public void SendMessage(object data, bool isFinal)
        {
            using (EneterTrace.Entering())
            {
                using (ThreadLock.Lock(myConnectionManipulatorLock))
                {
                    // If there is no message that was not finalized yet then send the binary or text data frame.
                    if (myMessageInSendProgress == EMessageInSendProgress.None)
                    {
                        if (data is byte[])
                        {
                            SendFrame(maskingKey => WebSocketFormatter.EncodeBinaryMessageFrame(isFinal, maskingKey, (byte[])data));

                            if (isFinal == false)
                            {
                                myMessageInSendProgress = EMessageInSendProgress.Binary;
                            }
                        }
                        else if (data is string)
                        {
                            SendFrame(maskingKey => WebSocketFormatter.EncodeTextMessageFrame(isFinal, maskingKey, (string)data));

                            if (isFinal == false)
                            {
                                myMessageInSendProgress = EMessageInSendProgress.Text;
                            }
                        }
                        else
                        {
                            string anErrorMessage = TracedObject + "failed to send the message because input parameter data is not byte[] or string.";
                            EneterTrace.Error(anErrorMessage);
                            throw new ArgumentException(anErrorMessage);
                        }
                    }
                    // If there is a binary message that was sent only partialy - was not finalized yet.
                    else if (myMessageInSendProgress == EMessageInSendProgress.Binary)
                    {
                        if (data is byte[])
                        {
                            SendFrame(maskingKey => WebSocketFormatter.EncodeContinuationMessageFrame(isFinal, maskingKey, (byte[])data));

                            if (isFinal == true)
                            {
                                myMessageInSendProgress = EMessageInSendProgress.None;
                            }
                        }
                        else
                        {
                            string anErrorMessage = TracedObject + "failed to send the continuation binary message because input parameter data was not byte[].";
                            EneterTrace.Error(anErrorMessage);
                            throw new ArgumentException(anErrorMessage);
                        }
                    }
                    // If there is a text message that was sent only partialy - was not finalized yet.
                    else
                    {
                        if (data is string)
                        {
                            SendFrame(maskingKey => WebSocketFormatter.EncodeContinuationMessageFrame(isFinal, maskingKey, (string)data));

                            if (isFinal == true)
                            {
                                myMessageInSendProgress = EMessageInSendProgress.None;
                            }
                        }
                        else
                        {
                            string anErrorMessage = TracedObject + "failed to send the continuation text message because input parameter data was not string.";
                            EneterTrace.Error(anErrorMessage);
                            throw new ArgumentException(anErrorMessage);
                        }
                    }
                }
            }
        }
예제 #6
0
        private void ClearConnection(bool sendCloseMessageFlag)
        {
            using (EneterTrace.Entering())
            {
                using (ThreadLock.Lock(myConnectionManipulatorLock))
                {
                    myStopReceivingRequestedFlag = true;
                    myMessageInSendProgress      = EMessageInSendProgress.None;

                    if (myTcpClient != null)
                    {
                        // Try to send the frame closing the communication.
                        if (myClientStream != null)
                        {
                            try
                            {
                                if (sendCloseMessageFlag)
                                {
                                    // Generate the masking key.
                                    byte[] aMaskingKey = GetMaskingKey();
                                    byte[] aCloseFrame = WebSocketFormatter.EncodeCloseFrame(aMaskingKey, 1000);
                                    myClientStream.Write(aCloseFrame, 0, aCloseFrame.Length);
                                }
                            }
                            catch (Exception err)
                            {
                                EneterTrace.Warning(TracedObject + ErrorHandler.FailedToCloseConnection, err);
                            }

                            myClientStream.Close();
                        }

                        try
                        {
                            myTcpClient.Close();
                        }
                        catch (Exception err)
                        {
                            EneterTrace.Warning(TracedObject + "failed to close Tcp connection.", err);
                        }

                        myTcpClient = null;
                    }


                    if (myResponseReceiverThread != null && myResponseReceiverThread.ThreadState != ThreadState.Unstarted)
                    {
                        if (!myResponseReceiverThread.Join(3000))
                        {
                            EneterTrace.Warning(TracedObject + ErrorHandler.FailedToStopThreadId + myResponseReceiverThread.ManagedThreadId);

                            try
                            {
                                myResponseReceiverThread.Abort();
                            }
                            catch (Exception err)
                            {
                                EneterTrace.Warning(TracedObject + ErrorHandler.FailedToAbortThread, err);
                            }
                        }
                    }
                    myResponseReceiverThread = null;

                    try
                    {
                        myMessageProcessingThread.UnregisterMessageHandler();
                    }
                    catch (Exception err)
                    {
                        EneterTrace.Warning(TracedObject + ErrorHandler.FailedToUnregisterMessageHandler, err);
                    }

                    // Reset the responsibility for starting of threads looping for response messages.
                    myResponsibleForActivatingListening = EResponseListeningResponsible.OpenConnection;
                }
            }
        }
예제 #7
0
        /// <summary>
        /// Opens connection to the websocket server.
        /// </summary>
        public void OpenConnection()
        {
            using (EneterTrace.Entering())
            {
                using (ThreadLock.Lock(myConnectionManipulatorLock))
                {
                    if (IsConnected)
                    {
                        string aMessage = TracedObject + ErrorHandler.IsAlreadyConnected;
                        EneterTrace.Error(aMessage);
                        throw new InvalidOperationException(aMessage);
                    }

                    // If it is needed clear after previous connection
                    if (myTcpClient != null)
                    {
                        try
                        {
                            ClearConnection(false);
                        }
                        catch
                        {
                            // We tried to clean after the previous connection. The exception can be ignored.
                        }
                    }

                    try
                    {
                        myStopReceivingRequestedFlag = false;

                        // Generate the key for this connection.
                        byte[] aWebsocketKey = new byte[16];
                        myGenerator.NextBytes(aWebsocketKey);
                        string aKey64baseEncoded = Convert.ToBase64String(aWebsocketKey);
                        HeaderFields["Sec-WebSocket-Key"] = aKey64baseEncoded;

                        // Send HTTP request to open the websocket communication.
                        byte[] anOpenRequest = WebSocketFormatter.EncodeOpenConnectionHttpRequest(Uri.AbsolutePath + Uri.Query, HeaderFields);

                        // Open TCP connection.
                        AddressFamily anAddressFamily = (Uri.HostNameType == UriHostNameType.IPv6) ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork;

                        myTcpClient         = new TcpClient(anAddressFamily);
                        myTcpClient.NoDelay = true;

#if !NET35
                        if (ResponseReceivingPort > 0)
                        {
                            IPAddress aDummyIpAddress = anAddressFamily == AddressFamily.InterNetworkV6 ? IPAddress.IPv6None : IPAddress.None;
                            myTcpClient.Client.Bind(new IPEndPoint(aDummyIpAddress, ResponseReceivingPort));
                        }
#endif

                        myTcpClient.SendTimeout    = SendTimeout;
                        myTcpClient.ReceiveTimeout = ReceiveTimeout;


                        // Note: TcpClient and Socket do not have a possibility to set the connection timeout.
                        //       There it must be workerounded a little bit.
                        Exception        anException = null;
                        ManualResetEvent aConnectionCompletedEvent = new ManualResetEvent(false);
                        EneterThreadPool.QueueUserWorkItem(() =>
                        {
                            try
                            {
                                // This call also resolves the host name.
                                myTcpClient.Connect(Uri.Host, Uri.Port);
                            }
                            catch (Exception err)
                            {
                                anException = err;
                            }
                            aConnectionCompletedEvent.Set();
                        });
                        if (!aConnectionCompletedEvent.WaitOne(ConnectTimeout))
                        {
                            throw new TimeoutException(TracedObject + "failed to open connection within " + ConnectTimeout + " ms.");
                        }
                        if (anException != null)
                        {
                            throw anException;
                        }

                        // If SSL then authentication is performed and security stream is provided.
                        myClientStream = mySecurityFactory.CreateSecurityStreamAndAuthenticate(myTcpClient.GetStream());

                        // Send HTTP request opening the connection.
                        myClientStream.Write(anOpenRequest, 0, anOpenRequest.Length);

                        // Get HTTP response and check if the communication was open.
                        Match anHttpResponseRegEx = WebSocketFormatter.DecodeOpenConnectionHttpResponse(myClientStream);

                        ValidateOpenConnectionResponse(anHttpResponseRegEx, aWebsocketKey);

                        // If somebody is subscribed to receive some response messages then
                        // the bidirectional communication is needed and the listening thread must be activated.
                        if (IsResponseSubscribed)
                        {
                            ActivateResponseListening();
                        }
                        else
                        {
                            // Nobody is subscribed so delegate the responsibility to start listening threads
                            // to the point when somebody subscribes to receive some response messages like
                            // CloseConnection, Pong, MessageReceived.
                            myResponsibleForActivatingListening = EResponseListeningResponsible.EventSubscription;
                        }

                        // Notify opening the websocket connection.
                        // Note: the notification is executed from a different thread.
                        Notify(ConnectionOpened);
                    }
                    catch (Exception err)
                    {
                        try
                        {
                            ClearConnection(false);
                        }
                        catch
                        {
                        }

                        EneterTrace.Error(TracedObject + ErrorHandler.FailedToOpenConnection, err);
                        throw;
                    }
                }
            }
        }
        // Handles TCP connection.
        // It parses the HTTP request of the websocket to get the requested path.
        // Then it searches the matching PathListeners and calls it to handle the connection.
        protected override void HandleConnection(TcpClient tcpClient)
        {
            using (EneterTrace.Entering())
            {
                try
                {
                    // Get the data stream.
                    // Note: If SSL then perform the authentication and provide the stream encoding/decoding data.
                    Stream aDataStream = SecurityFactory.CreateSecurityStreamAndAuthenticate(tcpClient.GetStream());

                    // Receive open websocket communication request.
                    Match anHttpOpenConnectionRegEx = WebSocketFormatter.DecodeOpenConnectionHttpRequest(aDataStream);
                    if (!anHttpOpenConnectionRegEx.Success)
                    {
                        EneterTrace.Warning(TracedObject + "failed to receive open websocket connection request. (incorrect http request)");
                        byte[] aCloseConnectionResponse = WebSocketFormatter.EncodeCloseFrame(null, 400);
                        aDataStream.Write(aCloseConnectionResponse, 0, aCloseConnectionResponse.Length);

                        return;
                    }


                    // Get http header fields.
                    IDictionary <string, string> aHeaderFields = WebSocketFormatter.GetHttpHeaderFields(anHttpOpenConnectionRegEx);

                    string aSecurityKey;
                    aHeaderFields.TryGetValue("Sec-WebSocket-Key", out aSecurityKey);

                    // If some required header field is missing or has incorrect value.
                    if (!aHeaderFields.ContainsKey("Upgrade") ||
                        !aHeaderFields.ContainsKey("Connection") ||
                        string.IsNullOrEmpty(aSecurityKey))
                    {
                        EneterTrace.Warning(TracedObject + "failed to receive open websocket connection request. (missing or incorrect header field)");
                        byte[] aCloseConnectionResponse = WebSocketFormatter.EncodeCloseFrame(null, 400);
                        aDataStream.Write(aCloseConnectionResponse, 0, aCloseConnectionResponse.Length);

                        return;
                    }

                    // Get the path to identify the end-point.
                    string anIncomingPath = anHttpOpenConnectionRegEx.Groups["path"].Value;
                    if (string.IsNullOrEmpty(anIncomingPath))
                    {
                        EneterTrace.Warning(TracedObject + "failed to process Websocket request because the path is null or empty string.");
                        byte[] aCloseConnectionResponse = WebSocketFormatter.EncodeCloseFrame(null, 400);
                        aDataStream.Write(aCloseConnectionResponse, 0, aCloseConnectionResponse.Length);

                        return;
                    }

                    // if the incoming path is the whole uri then extract the absolute path.
                    Uri anIncomingUri;
                    Uri.TryCreate(anIncomingPath, UriKind.Absolute, out anIncomingUri);
                    string anAbsolutePath = (anIncomingUri != null) ? anIncomingUri.AbsolutePath : anIncomingPath;

                    // Get handler for that path.
                    Uri aHandlerUri;
                    Action <IWebSocketClientContext> aPathHandler;
                    using (ThreadLock.Lock(myHandlers))
                    {
                        KeyValuePair <Uri, object> aPair = myHandlers.FirstOrDefault(x => x.Key.AbsolutePath == anAbsolutePath);
                        aPathHandler = aPair.Value as Action <IWebSocketClientContext>;
                        aHandlerUri  = aPair.Key;
                    }

                    // If the listener does not exist.
                    if (aPathHandler == null)
                    {
                        EneterTrace.Warning(TracedObject + "does not listen to " + anIncomingPath);
                        byte[] aCloseConnectionResponse = WebSocketFormatter.EncodeCloseFrame(null, 404);
                        aDataStream.Write(aCloseConnectionResponse, 0, aCloseConnectionResponse.Length);

                        return;
                    }

                    // Response that the connection is accepted.
                    byte[] anOpenConnectionResponse = WebSocketFormatter.EncodeOpenConnectionHttpResponse(aSecurityKey);
                    aDataStream.Write(anOpenConnectionResponse, 0, anOpenConnectionResponse.Length);


                    // Create the context for conecting client.
                    string aRequestUriStr = aHandlerUri.Scheme + "://" + aHandlerUri.Host + ":" + Address.Port + anAbsolutePath + anHttpOpenConnectionRegEx.Groups["query"].Value;
                    Uri    aRequestUri    = new Uri(aRequestUriStr);
                    WebSocketClientContext aClientContext = new WebSocketClientContext(aRequestUri, aHeaderFields, tcpClient, aDataStream);


                    // Call path handler in a another thread.
                    EneterThreadPool.QueueUserWorkItem(() =>
                    {
                        try
                        {
                            aPathHandler(aClientContext);
                        }
                        catch (Exception err)
                        {
                            EneterTrace.Warning(TracedObject + ErrorHandler.DetectedException, err);
                        }
                    });


                    // Start listening loop in the client context.
                    // The loop will read websocket messages from the underlying tcp connection.
                    // Note: User is responsible to call WebSocketClientContext.CloseConnection() to stop this loop
                    //       or the service must close the conneciton.
                    aClientContext.DoRequestListening();
                }
                catch (Exception err)
                {
                    EneterTrace.Error(TracedObject + "failed to process TCP connection.", err);
                }
            }
        }