internal static async Task <HttpRequest> DoHandShaking(TcpClient clientWssAccepted, NetworkStream clientStream, byte[] wssReceivedBytes) { string wss1stData = Encoding.UTF8.GetString(wssReceivedBytes); HttpRequest firstRequest = null; if (Regex.IsMatch(wss1stData, "^GET", RegexOptions.IgnoreCase)) { firstRequest = await HttpTransform.BuildHttpRequest(wss1stData); WebsocketServerHub.Register(firstRequest.UrlRelative, clientWssAccepted, clientStream); //do handshaking string swk = Regex.Match(wss1stData, "Sec-WebSocket-Key: (.*)").Groups[1].Value.Trim(); string swka = swk + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; byte[] swkaSha1 = System.Security.Cryptography.SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(swka)); string swkaSha1Base64 = Convert.ToBase64String(swkaSha1); byte[] response = Encoding.UTF8.GetBytes( "HTTP/1.1 101 Switching Protocols\r\n" + "Connection: Upgrade\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Accept: " + swkaSha1Base64 + "\r\n\r\n"); await clientStream.WriteAsync(response, 0, response.Length); return(firstRequest); } return(firstRequest); }
internal static async Task ReceiveAndReplyClientMessage(TcpClient clientWssAccepted, NetworkStream clientStream, byte[] wssReceivedBytes, HttpRequest firstRequestOfHandShake) { bool fin = (wssReceivedBytes[0] & 0b10000000) != 0; bool mask = (wssReceivedBytes[1] & 0b10000000) != 0; // must be true, "All messages from the client to the server have this bit set" int opcode = wssReceivedBytes[0] & 0b00001111, // expecting 1 - text message msglen = wssReceivedBytes[1] - 128, // & 0111 1111 offset = 2; if (msglen == 126) { // was ToUInt16(bytes, offset) but the result is incorrect msglen = BitConverter.ToUInt16(new byte[] { wssReceivedBytes[3], wssReceivedBytes[2] }, 0); offset = 4; } else if (msglen == 127) { Console.WriteLine("TODO: msglen == 127, needs qword to store msglen"); // i don't really know the byte order, please edit this // msglen = BitConverter.ToUInt64(new byte[] { bytes[5], bytes[4], bytes[3], bytes[2], bytes[9], bytes[8], bytes[7], bytes[6] }, 0); // offset = 10; } if (msglen == 0) { Console.WriteLine("msglen == 0"); } else if (mask) { byte[] decoded = new byte[msglen]; byte[] masks = new byte[4] { wssReceivedBytes[offset], wssReceivedBytes[offset + 1], wssReceivedBytes[offset + 2], wssReceivedBytes[offset + 3] }; offset += 4; for (int i = 0; i < msglen; ++i) { decoded[i] = (byte)(wssReceivedBytes[offset + i] ^ masks[i % 4]); } string receivedFromClient = Encoding.UTF8.GetString(decoded); var wssResponse = await RoutingHandler.HandleWss(new HttpRequest() { UrlRelative = firstRequestOfHandShake.UrlRelative, Method = "wss", CreatedAt = DateTime.Now, Body = receivedFromClient, RemoteEndPoint = clientWssAccepted.Client.RemoteEndPoint.ToString() }); WebsocketServerHub.Send(clientWssAccepted, clientStream, wssResponse); } else { Console.WriteLine("mask bit not set"); } }
public static void Publish(string urlRelative, IResponse response) { if (_channel.TryGetValue(urlRelative, out BlockingCollection <KeyValuePair <TcpClient, NetworkStream> > clients) && clients != null) { List <Task> tasks = new List <Task>(); foreach (var client in clients) { tasks.Add(Task.Run(() => { if (!client.Key.Client.Connected) { WebsocketServerHub.Remove(urlRelative); return; } WebsocketServerHub.Send(client.Key, client.Value, response); })); } //want to make sure sent all to client //Task.WhenAll(tasks).GetAwaiter().GetResult(); } }
async Task InternalStartAcceptIncommingAsync(TcpListener tcpListener) { //you may want to do with ssl //https://docs.microsoft.com/en-us/dotnet/api/system.net.security.sslstream?redirectedfrom=MSDN&view=netcore-3.1 while (!_isStop) { try { if (_isWss) { //https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_server TcpClient clientWssAccepted = await tcpListener.AcceptTcpClientAsync(); NetworkStream clientStream = clientWssAccepted.GetStream(); HttpRequest wss1stRequestOfHandShake = null; Task twss = Task.Run(async() => { while (!_isStop) { if (!clientWssAccepted.Client.Connected) { if (wss1stRequestOfHandShake != null) { WebsocketServerHub.Remove(wss1stRequestOfHandShake.UrlRelative); } await Shutdown(clientWssAccepted.Client, wss1stRequestOfHandShake); break; } while (!clientStream.DataAvailable) { ; } while (clientWssAccepted.Available < 3) { ; // match against "get" } byte[] wssReceivedBytes = new byte[clientWssAccepted.Available]; await clientStream.ReadAsync(wssReceivedBytes, 0, clientWssAccepted.Available); var handShakeRequest = await WebsocketServerHub.DoHandShaking(clientWssAccepted, clientStream, wssReceivedBytes); if (handShakeRequest != null) { wss1stRequestOfHandShake = handShakeRequest; } await WebsocketServerHub.ReceiveAndReplyClientMessage(clientWssAccepted, clientStream, wssReceivedBytes, wss1stRequestOfHandShake); } }); continue; } //WebHostWorker will try accept its job Socket clientSocket = await tcpListener.AcceptSocketAsync(); //parse request then dispatched by RoutingHandler var t = Task.Run(async() => { Task <HttpRequest> tRequest = ReadByteFromClientSocketAndBuildRequest(clientSocket); HttpRequest request = new HttpRequest(); request.CreatedAt = DateTime.Now; request.RemoteEndPoint = clientSocket.RemoteEndPoint.ToString(); var tempRequest = await tRequest; request.Body = tempRequest.Body; request.Error = tempRequest.Error; request.Header = tempRequest.Header; request.HeadlerCollection = tempRequest.HeadlerCollection; request.HttpVersion = tempRequest.HttpVersion; request.Method = tempRequest.Method; request.QueryParamCollection = tempRequest.QueryParamCollection; request.Url = tempRequest.Url; request.UrlRelative = tempRequest.UrlRelative; request.UrlQueryString = tempRequest.UrlQueryString; //dispatched routing here var processedResult = await RoutingHandler.Handle(request); HttpResponse response = await HttpTransform.BuildHttpResponse(processedResult, request); await SendResponseToClientSocket(clientSocket, request, response); await Shutdown(clientSocket, request); HttpLogger.Log(request); Console.WriteLine($"{request.RemoteEndPoint}@{request.CreatedAt}=>{request.Method}:{request.Url}"); }); } catch (Exception ex) { Console.WriteLine(ex.Message); Console.WriteLine(JsonConvert.SerializeObject(ex)); } finally { await Task.Delay(0); } } }