From a2f114216cc5ef43f480b56ed3fdd831e9c5a5b8 Mon Sep 17 00:00:00 2001 From: Harald Wolff Date: Fri, 18 Dec 2020 09:17:36 +0100 Subject: [PATCH] Added WebSocketResponse --- ln.http/websocket/WebSocketResponse.cs | 190 +++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 ln.http/websocket/WebSocketResponse.cs diff --git a/ln.http/websocket/WebSocketResponse.cs b/ln.http/websocket/WebSocketResponse.cs new file mode 100644 index 0000000..1d6575b --- /dev/null +++ b/ln.http/websocket/WebSocketResponse.cs @@ -0,0 +1,190 @@ +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(); + } + } +}