HttpServerBoxed 0.0.11 alpha
A simple http server for C# and .NET
Loading...
Searching...
No Matches
HSB.Components.WebSockets Namespace Reference

Classes

class  Frame
 
class  Message
 

Enumerations

enum  Opcode {
  CONTINUATION = 0 , TEXT = 1 , BINARY = 2 , CLOSE = 8 ,
  PING = 9 , PONG = 10
}
 The opcodes for the WebSocket protocol. More...
 

Functions

class WebSocket (Request req, Response res, Configuration? c=null)
 

Enumeration Type Documentation

◆ Opcode

The opcodes for the WebSocket protocol.

opcodes 3-7 are reserved for further non-control frames

Definition at line 7 of file Opcodes.cs.

7 {
8 CONTINUATION = 0,
9 TEXT = 1,
10 BINARY = 2,
11 CLOSE = 8,
12 PING = 9,
13 PONG = 10
14}

Function Documentation

◆ WebSocket()

class HSB.Components.WebSockets.WebSocket ( Request  req,
Response  res,
Configuration c = null 
)

Returns the Base64 string of the SecWebSocketKey+WS_GUID

Parameters
key
Returns

Accept the ws connection request

Returns

Sets a message to be sent to the client when the connection is open

Parameters
data

Sends a message to the client when the connection is open

Parameters
data
Exceptions
ExceptionIf connection is not opened

Sends a message to the client when the connection is open

Parameters
message
Exceptions
Exception

Sends an object to the client as a json string

Parameters
obj
includeFields
Exceptions
Exception

Closes the websocket connection

Exceptions
Exception

Set requirements to accept the connection request

Parameters
requiredHeaders
requiredParams
bearerToken
oAuth2Token
basicAuth
oAuth1_0Information

Returns the current state of the websocket

Returns

OnMessage is called when a message is received from the client

Parameters
msg

OnOpen is called after a connection request is received from the client

OnClose is called after a close request is received from the client, to directly close the connection use Close()

Definition at line 11 of file WebSocket.cs.

12{
13
14 private static JsonSerializerOptions jo = new()
15 {
16 MaxDepth = 0
17 };
18
19 //todo -> add multiframe support
20 protected Response res = res;
21 protected Request req = req;
22 private readonly Socket socket = req.GetSocket();
23 protected Configuration? c = c;
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
56 if (!req.IsWebSocket()) //not a websocket request, we cannot do anything here
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
64 var headers = req.Headers;
65 //those two values are required, if they are missing -> 400
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 //check if request contains all the required headers
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); //is this correct?
82 return false;
83 }
84 }
85 }
86 //same for parameters
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); //is this correct?
95 return false;
96 }
97 }
98 }
99 //if an authentication method is set, check if it is correct
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); //is this correct?
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); //is this correct?
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); //is this correct?
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); //is this correct?
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 //to-do add protocol support via decorator
155
156 var protocol = headers.GetValueOrDefault("Sec-WebSocket-Protocol", "");
157
158 if (protocol != "") //if the protocol is not empty, add it to the response
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 //todo add error loop detection
185 int errorCount = 0;
186 var buffer = new byte[Configuration.KILOBYTE * 32];
187
188 // while (state == WebSocketState.OPEN && errorCount < 10)
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 //Terminal.DEBUG("wrong data length?? -> " + received);
215 errorCount++;
216 return;
217 }
218 Frame f = new(buffer[..received]);
219 Opcode opcode = f.GetOpcode();
220
221 switch (f.GetOpcode())
222 {
223 case Opcode.CLOSE:
224 this.Close();
225 state = WebSocketState.CLOSED;
226 return;
227 //todo -> check Ping e Pong correctness
228 case Opcode.PING:
229 Frame pong = new();
230 pong.SetOpcode(Opcode.PONG);
231 socket.Send(pong.Build());
232 break;
233 case Opcode.PONG:
234 Frame ping = new();
235 ping.SetOpcode(Opcode.PING);
236 socket.Send(ping.Build());
237 break;
238 case Opcode.TEXT:
239 case Opcode.BINARY:
240 {
241 OnMessage(new(f));
242 break;
243 }
244 }
245 MessageLoop(); //speriamo che lo stack regga
246 }), socket);
247 }
248 catch (Exception)
249 {
250 state = WebSocketState.CLOSED;
251 socket?.Close();
252 }
253 // }
254 }
255
256
257 //this is should be use only by HSB/Server.cs or HSB/Configuration.cs
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 //destroy frame
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 //destroy frame
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 //serialize object to json, with fields
352 jo.IncludeFields = includeFields;
353 f.SetPayload(JsonSerializer.SerializeToUtf8Bytes(obj, jo));
354 socket?.Send(f.Build());
355 //destroy frame
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 //todo -> accept only one auth type
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.
Definition Request.cs:287
bool IsWebSocket()
Returns true if the request is a websocket request.
Definition Request.cs:318
Dictionary< string, string > Parameters
Return the parameters.
Definition Request.cs:295
void SendCode(int statusCode)
Send an HTTP Response with no body but with given status code.
Definition Response.cs:198
Opcode
The opcodes for the WebSocket protocol.
Definition WSOpcodes.cs:8