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