OnClose is called after a close request is received from the client, to directly close the connection use Close()
12{
13
14 private static JsonSerializerOptions jo = new()
15 {
16 MaxDepth = 0
17 };
18
19
22 private readonly Socket socket = req.GetSocket();
24
25 private WebSocketState state = WebSocketState.CLOSED;
26
27 #region Acceptance Requirements
28 Dictionary<string, string> requiredHeaders = [];
29 Dictionary<string, string> requiredParams = [];
30 string bearerToken = "";
31 string oAuth2Token = "";
32 Tuple<string, string>? basicAuth = null;
33 OAuth1_0Information? oAuth1_0Information = null;
34 #endregion
35 private byte[] messageSentOnOpen = [];
36
42 private static string DigestKey(string key)
43 {
44 return Convert.ToBase64String(
45 SHA1.HashData(
46 Encoding.UTF8.GetBytes(key + WebSocketsContants.WS_GUID)
47 ));
48 }
53 private bool Accept()
54 {
55
57 {
58 c?.Debug.WARNING("Not a websocket request, this code should never be reached");
59 res.
SendCode(HTTP_CODES.BAD_REQUEST);
60 return false;
61 }
62
63
65
66 if (!headers.ContainsKey("Sec-WebSocket-Key") && !headers.ContainsKey("Sec-WebSocket-Version"))
67 {
68 c?.Debug.WARNING("Missing Sec-WebSocket-Key or Sec-WebSocket-Version, malformed request");
69 res.
SendCode(HTTP_CODES.BAD_REQUEST);
70 return false;
71 }
72
73
74 if (requiredHeaders.Count > 0)
75 {
76 foreach (var header in requiredHeaders)
77 {
78 if (!headers.TryGetValue(header.Key, out string? value) || value != header.Value)
79 {
80 c?.Debug.WARNING($"Missing required header {header.Key} or value is not correct");
81 res.
SendCode(HTTP_CODES.BAD_REQUEST);
82 return false;
83 }
84 }
85 }
86
87 if (requiredParams.Count > 0)
88 {
89 foreach (var param in requiredParams)
90 {
91 if (!req.
Parameters.TryGetValue(param.Key, out
string? value) || value != param.Value)
92 {
93 c?.Debug.WARNING($"Missing required parameter {param.Key} or value is not correct");
94 res.
SendCode(HTTP_CODES.BAD_REQUEST);
95 return false;
96 }
97 }
98 }
99
100 if (bearerToken != "")
101 {
102 if (!headers.TryGetValue("Authorization", out string? value) || value != $"Bearer {bearerToken}")
103 {
104 c?.Debug.WARNING($"Missing or incorrect Authorization header");
105 res.
SendCode(HTTP_CODES.BAD_REQUEST);
106 return false;
107 }
108 }
109 if (oAuth2Token != "")
110 {
111 if (!headers.TryGetValue("Authorization", out string? value) || value != $"OAuth {oAuth2Token}")
112 {
113 c?.Debug.WARNING($"Missing or incorrect Authorization header");
114 res.
SendCode(HTTP_CODES.BAD_REQUEST);
115 return false;
116 }
117 }
118 if (basicAuth != null)
119 {
120 var bAuth = req.GetBasicAuthInformation();
121 if (bAuth == null || bAuth.Item1 != basicAuth.Item1 || bAuth.Item2 != basicAuth.Item2)
122 {
123 c?.Debug.WARNING($"Missing or incorrect Authorization header");
124 res.
SendCode(HTTP_CODES.BAD_REQUEST);
125 return false;
126 }
127 }
128 if (oAuth1_0Information != null)
129 {
130 var oAuth1 = req.GetOAuth1_0Information();
131 if (oAuth1 == null || oAuth1.Equals(oAuth1))
132 {
133 c?.Debug.WARNING($"Missing or incorrect Authorization header");
134 res.
SendCode(HTTP_CODES.BAD_REQUEST);
135 return false;
136 }
137 }
138
139
140 state = WebSocketState.CONNECTING;
141
142 var clientKey = headers["Sec-WebSocket-Key"];
143 var clientVersion = headers["Sec-WebSocket-Version"];
144
145 var key = DigestKey(clientKey);
146 List<string> response = [
147 "HTTP/1.1 101 Switching Protocols\r\n",
148 "Upgrade: websocket\r\n",
149 "Connection: Upgrade\r\n",
150 "Sec-WebSocket-Accept: " + key + "\r\n",
151 "Sec-WebSocket-Version: " + clientVersion + "\r\n",
152 ];
153
154
155
156 var protocol = headers.GetValueOrDefault("Sec-WebSocket-Protocol", "");
157
158 if (protocol != "")
159 response.Add("Sec-WebSocket-Protocol: " + protocol + "\r\n");
160
161 response.Add("\r\n");
162
163 socket?.Send(Encoding.UTF8.GetBytes(string.Join("", response)));
164
165 state = WebSocketState.OPEN;
166
167
168 if (messageSentOnOpen.Length > 0)
169 {
170 Send(messageSentOnOpen);
171 }
172
173
174 new Task(() =>
175 {
176 OnOpen();
177 }).Start();
178
179 return true;
180 }
181
182 private void MessageLoop()
183 {
184
185 int errorCount = 0;
186 var buffer = new byte[Configuration.KILOBYTE * 32];
187
188
189
190 try
191 {
192 if (state == WebSocketState.OPEN)
193 socket?.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback((IAsyncResult ar) =>
194 {
195 var socket = (Socket?)ar.AsyncState;
196 if (socket == null)
197 {
198 Terminal.DEBUG("socket is null??");
199 errorCount++;
200 return;
201 }
202 int received = 0;
203 try
204 {
205 received = socket.EndReceive(ar);
206 }
207 catch (Exception e)
208 {
209 Terminal.DEBUG("WS Exception " + e.Message);
210 return;
211 }
212 if (received < 2)
213 {
214
215 errorCount++;
216 return;
217 }
218 Frame f = new(buffer[..received]);
219 Opcode opcode = f.GetOpcode();
220
221 switch (f.GetOpcode())
222 {
224 this.Close();
225 state = WebSocketState.CLOSED;
226 return;
227
229 Frame pong = new();
230 pong.SetOpcode(
Opcode.PONG);
231 socket.Send(pong.Build());
232 break;
234 Frame ping = new();
235 ping.SetOpcode(
Opcode.PING);
236 socket.Send(ping.Build());
237 break;
240 {
241 OnMessage(new(f));
242 break;
243 }
244 }
245 MessageLoop();
246 }), socket);
247 }
248 catch (Exception)
249 {
250 state = WebSocketState.CLOSED;
251 socket?.Close();
252 }
253
254 }
255
256
257
258 internal void Process()
259 {
260 var frame = new System.Diagnostics.StackTrace().GetFrame(1);
261 var method = frame?.GetMethod();
262 var rfn = method?.ReflectedType?.Name ?? "";
263 var callerName = method?.Name ?? "";
264 if (rfn != "Server" && callerName != "ProcessRequest")
265 {
266 throw new Exception("This function must be called ONLY by the server process");
267 }
268
269 if (Accept())
270 {
271 new Thread(MessageLoop).Start();
272 }
273 }
274 #region PUBLIC METHODS
279 public void SetMessageSentOnOpen(byte[] data)
280 {
281 messageSentOnOpen = data;
282 }
288 public void Send(byte[] data)
289 {
290 if (state == WebSocketState.OPEN)
291 {
292 Frame f = new();
293 f.SetPayload(data);
294 socket?.Send(f.Build());
295 }
296 else
297 {
298 throw new Exception("WebSocket is not connected");
299 }
300 }
306 public void Send(string message)
307 {
308 if (state == WebSocketState.OPEN)
309 {
310 Frame f = new();
311 f.SetPayload(message);
312 socket?.Send(f.Build());
313
314 f.Dispose();
315 }
316 else
317 {
318 throw new Exception("WebSocket is not connected");
319 }
320 }
321 public void Send(Message msg)
322 {
323 if (state == WebSocketState.OPEN)
324 {
325 Frame f = new();
326 if (msg.GetMessage() != "")
327 f.SetPayload(msg.GetMessage());
328 else if (msg.GetMessageBytes() != "")
329 f.SetPayload(msg.GetMessageBytes());
330 socket?.Send(f.Build());
331
332 f.Dispose();
333
334 }
335 else
336 {
337 throw new Exception("WebSocket is not connected");
338 }
339 }
346 public void Send<T>(T obj, bool includeFields = true)
347 {
348 if (state == WebSocketState.OPEN)
349 {
350 Frame f = new();
351
352 jo.IncludeFields = includeFields;
353 f.SetPayload(JsonSerializer.SerializeToUtf8Bytes(obj, jo));
354 socket?.Send(f.Build());
355
356 f.Dispose();
357 }
358 else
359 {
360 throw new Exception("WebSocket is not connected");
361 }
362 }
367 public void Close()
368 {
369 if (state == WebSocketState.OPEN)
370 {
371 Frame f = new();
372 f.SetOpcode(
Opcode.CLOSE);
373 socket?.Send(f.Build());
374 socket?.Close();
375 f.Dispose();
376 OnClose();
377 }
378 else
379 {
380 throw new Exception("WebSocket is not connected, cannot close");
381 }
382 }
383
393 public void SetConnectionRequirements(
394 Dictionary<string, string> requiredHeaders,
395 Dictionary<string, string> requiredParams,
396 string bearerToken = "",
397 string oAuth2Token = "",
398 Tuple<string, string>? basicAuth = null,
399 OAuth1_0Information? oAuth1_0Information = null
400 )
401 {
402
403 this.requiredHeaders = requiredHeaders;
404 this.requiredParams = requiredParams;
405 this.bearerToken = bearerToken;
406 this.oAuth2Token = oAuth2Token;
407 this.basicAuth = basicAuth;
408 this.oAuth1_0Information = oAuth1_0Information;
409
410 }
415 public WebSocketState GetState()
416 {
417 return state;
418 }
419 #endregion
420 #region EVENTS
425 public virtual void OnMessage(Message msg) { }
429 public virtual void OnOpen() { }
433 public virtual void OnClose() { }
434 #endregion
435}
This class contains all the settings of the server.
Dictionary< string, string > Headers
Return the headers.
bool IsWebSocket()
Returns true if the request is a websocket request.
Dictionary< string, string > Parameters
Return the parameters.
void SendCode(int statusCode)
Send an HTTP Response with no body but with given status code.
Opcode
The opcodes for the WebSocket protocol.