using System; using System.IO; using ln.logging; using ln.http.exceptions; using System.Security.Cryptography; using System.Text; using ln.type; namespace ln.http.websocket { public delegate void WebSocketStateChanged(WebSocketResponse sender, WebSocketState newState); public delegate void WebSocketReceivedText(WebSocketResponse sender, string text); public delegate void WebSocketReceivedBytes(WebSocketResponse sender, byte[] bytes); public class WebSocketResponse : HttpResponse { public event WebSocketStateChanged OnWebSocketStateChanged; public event WebSocketReceivedText OnWebSocketReceivedText; public event WebSocketReceivedBytes OnWebSocketReceivedBytes; public Stream Stream { get; private set; } WebSocketState state = WebSocketState.HANDSHAKE; public WebSocketState State { get => state; private set { if (state != value) { state = value; OnWebSocketStateChanged?.Invoke(this, value); } } } public WebSocketResponse() {} public void Close() { lock (this) { switch (State) { case WebSocketState.HANDSHAKE: case WebSocketState.ERROR: case WebSocketState.CLOSING: State = WebSocketState.CLOSED; break; case WebSocketState.CLOSED: break; case WebSocketState.OPEN: WebSocketFrame closeFrame = new WebSocketFrame(WebSocketOpcode.CLOSE); Send(closeFrame); State = WebSocketState.CLOSING; break; } } } public void Run() { try { while (State != WebSocketState.CLOSED) { WebSocketFrame webSocketFrame = new WebSocketFrame(Stream); if (webSocketFrame.Opcode != WebSocketOpcode.TEXT) Logging.Log(LogLevel.DEBUG, "ws.opcode: {0}", webSocketFrame.Opcode); switch (webSocketFrame.Opcode) { case WebSocketOpcode.TEXT: Received(Encoding.UTF8.GetString(webSocketFrame.ApplicationData)); break; case WebSocketOpcode.BINARY: Received(webSocketFrame.ApplicationData); break; case WebSocketOpcode.CLOSE: if (State == WebSocketState.OPEN) { WebSocketFrame closeFrame = new WebSocketFrame(WebSocketOpcode.CLOSE); closeFrame.FIN = true; lock (this) { Send(closeFrame); State = WebSocketState.CLOSING; } } State = WebSocketState.CLOSED; break; case WebSocketOpcode.PING: WebSocketFrame pong = new WebSocketFrame(WebSocketOpcode.PONG); pong.ApplicationData = webSocketFrame.ApplicationData; Send(pong); break; } } } catch (IOException) { State = WebSocketState.ERROR; Logging.Log(LogLevel.DEBUG, "WebSocket connection was dropped unexpected"); Close(); } catch (Exception e) { Logging.Log(LogLevel.ERROR, "WebSocket caught Exception: {0}", e.ToString()); Logging.Log(e); } finally { State = WebSocketState.CLOSED; } } public virtual void Received(string textMessage) => OnWebSocketReceivedText?.Invoke(this, textMessage); public virtual void Received(byte[] binaryMessage) => OnWebSocketReceivedBytes?.Invoke(this, binaryMessage); public void Send(WebSocketFrame frame) { if (Stream == null) return; lock (this) { if (State == WebSocketState.OPEN) { try { frame.WriteTo(Stream); } catch (IOException) { if (State != WebSocketState.ERROR) { Logging.Log(LogLevel.ERROR, "WebSocket.Send(): Websocket connection was dropped unexpected"); State = WebSocketState.ERROR; Close(); } } } else throw new IOException("WebSocket is not open"); } } public void Send(string textMessage) { WebSocketFrame webSocketFrame = new WebSocketFrame(textMessage); Send(webSocketFrame); } public void Send(byte[] binaryMessage) { WebSocketFrame webSocketFrame = new WebSocketFrame(binaryMessage); Send(webSocketFrame); } public override void SendResponse(Stream stream, HttpRequest httpRequest) { lock (this) { Stream = stream; if ((!httpRequest.GetRequestHeader("upgrade", "").Contains("websocket")) && (!httpRequest.GetRequestHeader("connection", "").Contains("Upgrade"))) throw new HttpException(400, "This resource is a websocket endpoint only"); if (!httpRequest.GetRequestHeader("Sec-WebSocket-Version", "").Equals("13")) throw new HttpException(400, "Unsupported Protocol Version (WebSocket)"); String wsKey = httpRequest.GetRequestHeader("Sec-WebSocket-Key"); HttpResponse httpResponse = new HttpResponse(HttpStatusCode.SwitchingProtocols); httpResponse.AddHeader("upgrade", "websocket"); httpResponse.AddHeader("connection", "Upgrade"); httpResponse.AddHeader("Sec-WebSocket-Version", "13"); string acceptKey = String.Format("{0}258EAFA5-E914-47DA-95CA-C5AB0DC85B11", httpRequest.GetRequestHeader("Sec-WebSocket-Key")); httpResponse.AddHeader( "Sec-Websocket-Accept", Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(acceptKey))) ); HTTPServer.SendResponse(Stream, httpRequest, httpResponse); //HTTPServerConnection.Current.Value.AbortRequested += (connection) => Close(); State = WebSocketState.OPEN; } Run(); } } }