ln.http/websocket/WebSocket.cs

184 lines
5.9 KiB
C#

using System;
using System.IO;
using System.Threading;
using ln.logging;
using ln.http.exceptions;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace ln.http.websocket
{
public enum WebSocketOpcode : int
{
CONTINUATION = 0x00,
TEXT = 0x01,
BINARY = 0x02,
CLOSE = 0x08,
PING = 0x09,
PONG = 0x0A,
INVALIDOPCODE = -1
}
public enum WebSocketState
{
HANDSHAKE,
OPEN,
CLOSING,
CLOSED,
ERROR
}
public delegate void WebSocketEventDelegate(WebSocket sender,WebSocketEventArgs e);
public class WebSocket
{
public HttpRequest HttpRequest { get; }
public Stream Stream { get; }
public WebSocketState State { get; private set; } = WebSocketState.HANDSHAKE;
public event WebSocketEventDelegate WebSocketEvent;
Thread receiverThread;
public WebSocket(HttpRequest httpRequest)
{
HttpRequest = httpRequest;
Stream = httpRequest.GetConnectionStream();
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(httpRequest);
httpResponse.StatusCode = 101;
httpResponse.StatusMessage = "Switching Protocols";
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, httpResponse);
State = WebSocketState.OPEN;
}
public bool IsAlive => ((receiverThread != null) && receiverThread.IsAlive);
public void Start()
{
if ((receiverThread == null) || !receiverThread.IsAlive)
{
receiverThread = new Thread(() => Run());
receiverThread.Start();
}
}
public void Close()
{
switch (State)
{
case WebSocketState.HANDSHAKE:
case WebSocketState.ERROR:
case WebSocketState.CLOSING:
State = WebSocketState.CLOSED;
Stream.Close();
break;
case WebSocketState.CLOSED:
break;
case WebSocketState.OPEN:
WebSocketFrame closeFrame = new WebSocketFrame(WebSocketOpcode.CLOSE);
lock (Stream)
{
Send(closeFrame);
State = WebSocketState.CLOSING;
}
break;
}
}
public void Run()
{
try
{
//Send("Hello World!");
while (State != WebSocketState.CLOSED)
{
WebSocketFrame webSocketFrame = new WebSocketFrame(Stream);
switch (webSocketFrame.Opcode)
{
case WebSocketOpcode.TEXT:
case WebSocketOpcode.BINARY:
WebSocketEvent(this, new WebSocketEventArgs(webSocketFrame));
break;
case WebSocketOpcode.CLOSE:
if (State == WebSocketState.OPEN)
{
WebSocketFrame closeFrame = new WebSocketFrame(WebSocketOpcode.CLOSE);
closeFrame.FIN = true;
lock (Stream)
{
Send(closeFrame);
State = WebSocketState.CLOSING;
}
}
State = WebSocketState.CLOSED;
Stream.Close();
break;
case WebSocketOpcode.PING:
WebSocketFrame pong = new WebSocketFrame(WebSocketOpcode.PONG);
pong.ApplicationData = webSocketFrame.ApplicationData;
Send(pong);
break;
}
}
} catch (Exception e)
{
Logging.Log(LogLevel.ERROR, "WebSocket caught Exception: {0}", e.ToString());
Logging.Log(e);
}
receiverThread = null;
}
public void Send(WebSocketFrame frame)
{
lock (Stream)
{
if (State == WebSocketState.OPEN)
frame.WriteTo(Stream);
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);
}
}
}