private async Task SendAsyncImpl <T>(WebSocketsFrame.OpCodes opCode, T message, WebSocketsFrame.FrameFlags flags) where T : struct, IMessageWriter { //TODO: come up with a better way of getting ordered access to the socket var writeLock = GetWriteSemaphore(); bool haveLock = writeLock.Wait(0); if (!haveLock) { // try to acquire asynchronously instead, then await writeLock.WaitAsync(); } try { WebSocketServer.WriteStatus(ConnectionType, $"Writing {opCode} message ({message.GetPayloadLength()} bytes)..."); await WebSocketProtocol.WriteAsync(this, opCode, flags, ref message); if (opCode == WebSocketsFrame.OpCodes.Close) { connection.Output.Complete(); } } finally { writeLock.Release(); } }
private async Task SendAsyncImpl <T>(WebSocketsFrame.OpCodes opCode, T message) where T : struct, IMessageWriter { //TODO: come up with a better way of getting ordered access to the socket var writeLock = GetWriteSemaphore(); bool haveLock = writeLock.Wait(0); if (!haveLock) { // try to acquire asynchronously instead, then await writeLock.WaitAsync(); } try { WebSocketServer.WriteStatus($"Writing {opCode} message..."); await WebSocketProtocol.WriteAsync(this, opCode, ref message); if (opCode == WebSocketsFrame.OpCodes.Close) { Close(); } } finally { writeLock.Release(); } }
private async void OnConnection(IChannel connection) { using (connection) { WebSocketConnection socket = null; try { WriteStatus(ConnectionType.Server, "Connected"); WriteStatus(ConnectionType.Server, "Parsing http request..."); var request = await ParseHttpRequest(connection.Input); try { WriteStatus(ConnectionType.Server, "Identifying protocol..."); socket = GetProtocol(connection, ref request); WriteStatus(ConnectionType.Server, $"Protocol: {WebSocketProtocol.Name}"); WriteStatus(ConnectionType.Server, "Authenticating..."); if (!await OnAuthenticateAsync(socket, ref request.Headers)) { throw new InvalidOperationException("Authentication refused"); } WriteStatus(ConnectionType.Server, "Completing handshake..."); await WebSocketProtocol.CompleteServerHandshakeAsync(ref request, socket); } finally { request.Dispose(); // can't use "ref request" or "ref headers" otherwise } WriteStatus(ConnectionType.Server, "Handshake complete hook..."); await OnHandshakeCompleteAsync(socket); connections.TryAdd(socket, socket); WriteStatus(ConnectionType.Server, "Processing incoming frames..."); await socket.ProcessIncomingFramesAsync(this); WriteStatus(ConnectionType.Server, "Exiting..."); await socket.CloseAsync(); } catch (Exception ex) {// meh, bye bye broken connection try { socket?.Dispose(); } catch { } WriteStatus(ConnectionType.Server, ex.StackTrace); WriteStatus(ConnectionType.Server, ex.GetType().Name); WriteStatus(ConnectionType.Server, ex.Message); } finally { WebSocketConnection tmp; if (socket != null) { connections.TryRemove(socket, out tmp); } try { connection.Output.Complete(); } catch { } try { connection.Input.Complete(); } catch { } } } }
internal async Task ProcessIncomingFramesAsync(WebSocketServer server) { while (true) { var buffer = await connection.Input.ReadAsync(); try { if (buffer.IsEmpty && connection.Input.Reading.IsCompleted) { break; // that's all, folks } WebSocketsFrame frame; if (WebSocketProtocol.TryReadFrameHeader(ref buffer, out frame)) { int payloadLength = frame.PayloadLength; // buffer now points to the payload if (connectionType == ConnectionType.Server) { if (!frame.IsMasked) { throw new InvalidOperationException("Client-to-server frames should be masked"); } } else { if (frame.IsMasked) { throw new InvalidOperationException("Server-to-client frames should not be masked"); } } if (frame.IsControlFrame && !frame.IsFinal) { throw new InvalidOperationException("Control frames cannot be fragmented"); } await OnFrameReceivedAsync(ref frame, ref buffer, server); // and finally, progress past the frame if (payloadLength != 0) { buffer = buffer.Slice(payloadLength); } } } finally { connection.Input.Advance(buffer.Start, buffer.End); } } }
public static async Task <WebSocketConnection> ConnectAsync( string location, string protocol = null, string origin = null, Action <HttpRequestHeaders> addHeaders = null, ChannelFactory channelFactory = null) { WebSocketServer.WriteStatus(ConnectionType.Client, $"Connecting to {location}..."); Uri uri; if (!Uri.TryCreate(location, UriKind.Absolute, out uri) || uri.Scheme != "ws") { throw new ArgumentException(nameof(location)); } IPAddress ip; if (!IPAddress.TryParse(uri.Host, out ip)) { throw new NotImplementedException("host must be an IP address at the moment, sorry"); } WebSocketServer.WriteStatus(ConnectionType.Client, $"Opening socket to {ip}:{uri.Port}..."); var socket = await SocketConnection.ConnectAsync(new IPEndPoint(ip, uri.Port), channelFactory); return(await WebSocketProtocol.ClientHandshake(socket, uri, origin, protocol)); }
internal static async Task <WebSocketConnection> ClientHandshake(IChannel channel, Uri uri, string origin, string protocol) { WebSocketServer.WriteStatus(ConnectionType.Client, "Writing client handshake..."); // write the outbound request portion of the handshake var output = channel.Output.Alloc(); output.Write(GET); output.WriteAsciiString(uri.PathAndQuery); output.Write(HTTP_Host); output.WriteAsciiString(uri.Host); output.Write(UpgradeConnectionKey); byte[] challengeKey = new byte[WebSocketProtocol.SecResponseLength]; GetRandomBytes(challengeKey); // we only want the first 16 for entropy, but... meh output.Ensure(WebSocketProtocol.SecRequestLength); int bytesWritten = Base64.Encode(new ReadOnlySpan <byte>(challengeKey, 0, 16), output.Memory); // now cheekily use that data we just wrote the the output buffer // as a source to compute the expected bytes, and store them back // into challengeKey; sneaky! WebSocketProtocol.ComputeReply( output.Memory.Slice(0, WebSocketProtocol.SecRequestLength).Span, challengeKey); output.Advance(bytesWritten); output.Write(CRLF); if (!string.IsNullOrWhiteSpace(origin)) { output.WriteAsciiString("Origin: "); output.WriteAsciiString(origin); output.Write(CRLF); } if (!string.IsNullOrWhiteSpace(protocol)) { output.WriteAsciiString("Sec-WebSocket-Protocol: "); output.WriteAsciiString(protocol); output.Write(CRLF); } output.Write(WebSocketVersion); output.Write(CRLF); // final CRLF to end the HTTP request await output.FlushAsync(); WebSocketServer.WriteStatus(ConnectionType.Client, "Parsing response to client handshake..."); using (var resp = await WebSocketServer.ParseHttpResponse(channel.Input)) { if (!resp.HttpVersion.Equals(ExpectedHttpVersion) || !resp.StatusCode.Equals(ExpectedStatusCode) || !resp.StatusText.Equals(ExpectedStatusText) || !resp.Headers.GetRaw("Upgrade").Equals(ExpectedUpgrade) || !resp.Headers.GetRaw("Connection").Equals(ExpectedConnection)) { throw new InvalidOperationException("Not a web-socket server"); } var accept = resp.Headers.GetRaw("Sec-WebSocket-Accept"); if (!accept.Equals(challengeKey)) { throw new InvalidOperationException("Sec-WebSocket-Accept mismatch"); } protocol = resp.Headers.GetAsciiString("Sec-WebSocket-Protocol"); } var webSocket = new WebSocketConnection(channel, ConnectionType.Client) { Host = uri.Host, RequestLine = uri.OriginalString, Origin = origin, Protocol = protocol }; webSocket.StartProcessingIncomingFrames(); return(webSocket); }