MTSC  1.0.5
Build TCP servers out of modules with handlers for communication, exception and logging.
WebsocketHandler.cs
1 using MTSC.Common.Http;
4 using System;
5 using System.Collections.Concurrent;
6 using System.Collections.Generic;
7 using System.Security.Cryptography;
8 using System.Text;
9 
10 namespace MTSC.Server.Handlers
11 {
12  public class WebsocketHandler : IHandler
13  {
14  private static string WebsocketHeaderAcceptKey = "Sec-WebSocket-Accept";
15  private static string WebsocketHeaderKey = "Sec-WebSocket-Key";
16  private static string WebsocketProtocolKey = "Sec-WebSocket-Protocol";
17  private static string WebsocketProtocolVersionKey = "Sec-WebSocket-Version";
18  private static string GlobalUniqueIdentifier = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
19  private static SHA1 sha1Provider = SHA1.Create();
20  private static RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
21  public enum SocketState
22  {
23  Initial,
24  Handshaking,
25  Established,
26  Closed
27  }
28  #region Fields
29  public ConcurrentDictionary<ClientData, SocketState> webSockets = new ConcurrentDictionary<ClientData, SocketState>();
30  ConcurrentQueue<Tuple<ClientData,WebsocketMessage>> messageQueue = new ConcurrentQueue<Tuple<ClientData, WebsocketMessage>>();
31  List<IWebsocketModule> websocketModules = new List<IWebsocketModule>();
32  #endregion
33  #region Public Methods
40  {
41  this.websocketModules.Add(module);
42  return this;
43  }
48  public void QueueMessage(ClientData client, byte[] message)
49  {
50  WebsocketMessage sendMessage = new WebsocketMessage();
51  sendMessage.Data = message;
52  sendMessage.FIN = true;
53  rng.GetBytes(sendMessage.Mask);
54  sendMessage.Opcode = WebsocketMessage.Opcodes.Text;
55  messageQueue.Enqueue(new Tuple<ClientData, WebsocketMessage>(client, sendMessage));
56  }
62  public void QueueMessage(ClientData client, WebsocketMessage message)
63  {
64  messageQueue.Enqueue(new Tuple<ClientData, WebsocketMessage>(client, message));
65  }
70  public void CloseConnection(ClientData client)
71  {
72  WebsocketMessage websocketMessage = new WebsocketMessage();
73  websocketMessage.FIN = true;
74  websocketMessage.Opcode = WebsocketMessage.Opcodes.Close;
75  websocketMessage.Masked = false;
76  QueueMessage(client, websocketMessage);
77  }
78  #endregion
79  #region Handler Implementation
80  void IHandler.ClientRemoved(Server server, ClientData client)
81  {
82  SocketState state = SocketState.Initial;
83  while (webSockets.ContainsKey(client))
84  {
85  webSockets.TryRemove(client, out state);
86  }
87  }
88 
89  bool IHandler.HandleClient(Server server, ClientData client)
90  {
91  webSockets[client] = SocketState.Initial;
92  return false;
93  }
94 
95  bool IHandler.HandleReceivedMessage(Server server, ClientData client, Message message)
96  {
97  if (webSockets[client] == SocketState.Initial)
98  {
99  HttpMessage request = new HttpMessage();
100  request.ParseRequest(message.MessageBytes);
101  if(request.Method == HttpMessage.MethodEnum.Get && request.ContainsHeader(HttpMessage.GeneralHeadersEnum.Connection) &&
102  request[HttpMessage.GeneralHeadersEnum.Connection].ToLower() == "upgrade" && request.ContainsHeader(WebsocketProtocolVersionKey) &&
103  request[WebsocketProtocolVersionKey] == "13")
104  {
105  /*
106  * Prepare the handshake string.
107  */
108  string base64Key = request[WebsocketHeaderKey];
109  base64Key = base64Key.Trim();
110  string handshakeKey = base64Key + GlobalUniqueIdentifier;
111  string returnBase64Key = Convert.ToBase64String(sha1Provider.ComputeHash(Encoding.UTF8.GetBytes(handshakeKey)));
112 
113  /*
114  * Prepare the response.
115  */
116  HttpMessage response = new HttpMessage();
117  response.StatusCode = HttpMessage.StatusCodes.SwitchingProtocols;
118  response[HttpMessage.GeneralHeadersEnum.Upgrade] = "websocket";
119  response[HttpMessage.GeneralHeadersEnum.Connection] = "Upgrade";
120  response[WebsocketHeaderAcceptKey] = returnBase64Key;
121  server.QueueMessage(client, response.BuildResponse(true));
122  webSockets[client] = SocketState.Established;
123  server.LogDebug("Websocket initialized " + client.TcpClient.Client.RemoteEndPoint.ToString());
124  foreach (IWebsocketModule websocketModule in websocketModules)
125  {
126  websocketModule.ConnectionInitialized(server, this, client);
127  }
128  return true;
129  }
130  }
131  else if(webSockets[client] == SocketState.Established)
132  {
133  WebsocketMessage receivedMessage = new WebsocketMessage(message.MessageBytes);
134  if (receivedMessage.Opcode == WebsocketMessage.Opcodes.Close)
135  {
136  foreach (IWebsocketModule websocketModule in websocketModules)
137  {
138  websocketModule.ConnectionClosed(server, this, client);
139  }
140  client.ToBeRemoved = true;
141  SocketState outSocketState = SocketState.Closed;
142  while (webSockets.ContainsKey(client))
143  {
144  webSockets.TryRemove(client, out outSocketState);
145  }
146  }
147  else
148  {
149  foreach (IWebsocketModule websocketModule in websocketModules)
150  {
151  if (websocketModule.HandleReceivedMessage(server, this, client, receivedMessage))
152  {
153  break;
154  }
155  }
156  }
157  return true;
158  }
159  return false;
160  }
161 
162  bool IHandler.HandleSendMessage(Server server, ClientData client, ref Message message)
163  {
164  return false;
165  }
166 
167  bool IHandler.PreHandleReceivedMessage(Server server, ClientData client, ref Message message)
168  {
169  return false;
170  }
171 
172  void IHandler.Tick(Server server)
173  {
174  while (messageQueue.Count > 0)
175  {
176  Tuple<ClientData, WebsocketMessage> tuple = null;
177  if (messageQueue.TryDequeue(out tuple))
178  {
179  server.QueueMessage(tuple.Item1, tuple.Item2.GetMessageBytes());
180  if(tuple.Item2.Opcode == WebsocketMessage.Opcodes.Close)
181  {
182  foreach (IWebsocketModule websocketModule in websocketModules)
183  {
184  websocketModule.ConnectionClosed(server, this, tuple.Item1);
185  }
186  tuple.Item1.ToBeRemoved = true;
187  SocketState outSocketState = SocketState.Closed;
188  while (webSockets.ContainsKey(tuple.Item1))
189  {
190  webSockets.TryRemove(tuple.Item1, out outSocketState);
191  }
192  }
193  }
194  }
195  }
196  #endregion
197  }
198 }
Interface for communication handlers.
Definition: IHandler.cs:11
bool HandleReceivedMessage(Server.Server server, WebsocketHandler handler, ClientData client, WebsocketMessage receivedMessage)
Handle a received message.
void QueueMessage(ClientData client, WebsocketMessage message)
Send a message to the client.
WebsocketHandler AddWebsocketHandler(IWebsocketModule module)
Add a webSocket module onto the server.
void ConnectionClosed(Server.Server server, WebsocketHandler handler, ClientData client)
Called when a connection has been closed.
Structure containing client information.
Definition: Server.cs:402
Class containing the bytes of a websocket received message.
void ClientRemoved(Server server, ClientData client)
Handles the removal of a client from the server.
Basic server class to handle TCP connections.
Definition: Server.cs:20
byte [] BuildResponse(bool includeContentLengthHeader)
Build the response bytes based on the message contents.
Definition: HttpMessage.cs:372
bool ContainsHeader(ResponseHeadersEnum header)
Check if the message contains a header.
Definition: HttpMessage.cs:500
void CloseConnection(ClientData client)
Signals that the connetion is closing.
Definition: Client.cs:14
void QueueMessage(ClientData client, byte[] message)
Send a message to the client.
void ParseRequest(byte[] requestBytes)
Parse the received bytes and populate the message contents.
Definition: HttpMessage.cs:267
void ConnectionInitialized(Server.Server server, WebsocketHandler handler, ClientData client)
Called when a connection has been initialized.