using System;
using System.Net.Sockets;
using System.Text;
namespace NetCoreServer
{
///
/// WebSocket secure session
///
/// WebSocket secure session is used to read and write data from the connected WebSocket client. Thread-safe.
public class WssSession : HttpsSession, IWebSocket
{
internal readonly WebSocket WebSocket;
///
/// Initialize a new WebSocket session
///
/// WebSocket server
public WssSession(WssServer server) : base(server) { WebSocket = new WebSocket(this); }
// WebSocket connection methods
public virtual bool Close() => Close(0, Span.Empty);
public virtual bool Close(int status) => Close(status, Span.Empty);
public virtual bool Close(int status, string text) => Close(status, Encoding.UTF8.GetBytes(text));
public virtual bool Close(int status, ReadOnlySpan text) => Close(status, Encoding.UTF8.GetBytes(text.ToArray()));
public virtual bool Close(int status, byte[] buffer) => Close(status, buffer.AsSpan());
public virtual bool Close(int status, byte[] buffer, long offset, long size) => Close(status, buffer.AsSpan((int)offset, (int)size));
public virtual bool Close(int status, ReadOnlySpan buffer) { SendCloseAsync(status, buffer); base.Disconnect(); return true; }
#region WebSocket send text methods
public long SendText(string text) => SendText(Encoding.UTF8.GetBytes(text));
public long SendText(ReadOnlySpan text) => SendText(Encoding.UTF8.GetBytes(text.ToArray()));
public long SendText(byte[] buffer) => SendText(buffer.AsSpan());
public long SendText(byte[] buffer, long offset, long size) => SendText(buffer.AsSpan((int)offset, (int)size));
public long SendText(ReadOnlySpan buffer)
{
lock (WebSocket.WsSendLock)
{
WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_TEXT, false, buffer);
return base.Send(WebSocket.WsSendBuffer.AsSpan());
}
}
public bool SendTextAsync(string text) => SendTextAsync(Encoding.UTF8.GetBytes(text));
public bool SendTextAsync(ReadOnlySpan text) => SendTextAsync(Encoding.UTF8.GetBytes(text.ToArray()));
public bool SendTextAsync(byte[] buffer) => SendTextAsync(buffer.AsSpan());
public bool SendTextAsync(byte[] buffer, long offset, long size) => SendTextAsync(buffer.AsSpan((int)offset, (int)size));
public bool SendTextAsync(ReadOnlySpan buffer)
{
lock (WebSocket.WsSendLock)
{
WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_TEXT, false, buffer);
return base.SendAsync(WebSocket.WsSendBuffer.AsSpan());
}
}
#endregion
#region WebSocket send binary methods
public long SendBinary(string text) => SendBinary(Encoding.UTF8.GetBytes(text));
public long SendBinary(ReadOnlySpan text) => SendBinary(Encoding.UTF8.GetBytes(text.ToArray()));
public long SendBinary(byte[] buffer) => SendBinary(buffer.AsSpan());
public long SendBinary(byte[] buffer, long offset, long size) => SendBinary(buffer.AsSpan((int)offset, (int)size));
public long SendBinary(ReadOnlySpan buffer)
{
lock (WebSocket.WsSendLock)
{
WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_BINARY, false, buffer);
return base.Send(WebSocket.WsSendBuffer.AsSpan());
}
}
public bool SendBinaryAsync(string text) => SendBinaryAsync(Encoding.UTF8.GetBytes(text));
public bool SendBinaryAsync(ReadOnlySpan text) => SendBinaryAsync(Encoding.UTF8.GetBytes(text.ToArray()));
public bool SendBinaryAsync(byte[] buffer) => SendBinaryAsync(buffer.AsSpan());
public bool SendBinaryAsync(byte[] buffer, long offset, long size) => SendBinaryAsync(buffer.AsSpan((int)offset, (int)size));
public bool SendBinaryAsync(ReadOnlySpan buffer)
{
lock (WebSocket.WsSendLock)
{
WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_BINARY, false, buffer);
return base.SendAsync(WebSocket.WsSendBuffer.AsSpan());
}
}
#endregion
#region WebSocket send close methods
public long SendClose(int status, string text) => SendClose(status, Encoding.UTF8.GetBytes(text));
public long SendClose(int status, ReadOnlySpan text) => SendClose(status, Encoding.UTF8.GetBytes(text.ToArray()));
public long SendClose(int status, byte[] buffer) => SendClose(status, buffer.AsSpan());
public long SendClose(int status, byte[] buffer, long offset, long size) => SendClose(status, buffer.AsSpan((int)offset, (int)size));
public long SendClose(int status, ReadOnlySpan buffer)
{
lock (WebSocket.WsSendLock)
{
WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_CLOSE, false, buffer, status);
return base.Send(WebSocket.WsSendBuffer.AsSpan());
}
}
public bool SendCloseAsync(int status, string text) => SendCloseAsync(status, Encoding.UTF8.GetBytes(text));
public bool SendCloseAsync(int status, ReadOnlySpan text) => SendCloseAsync(status, Encoding.UTF8.GetBytes(text.ToArray()));
public bool SendCloseAsync(int status, byte[] buffer) => SendCloseAsync(status, buffer.AsSpan());
public bool SendCloseAsync(int status, byte[] buffer, long offset, long size) => SendCloseAsync(status, buffer.AsSpan((int)offset, (int)size));
public bool SendCloseAsync(int status, ReadOnlySpan buffer)
{
lock (WebSocket.WsSendLock)
{
WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_CLOSE, false, buffer, status);
return base.SendAsync(WebSocket.WsSendBuffer.AsSpan());
}
}
#endregion
#region WebSocket send ping methods
public long SendPing(string text) => SendPing(Encoding.UTF8.GetBytes(text));
public long SendPing(ReadOnlySpan text) => SendPing(Encoding.UTF8.GetBytes(text.ToArray()));
public long SendPing(byte[] buffer) => SendPing(buffer.AsSpan());
public long SendPing(byte[] buffer, long offset, long size) => SendPing(buffer.AsSpan((int)offset, (int)size));
public long SendPing(ReadOnlySpan buffer)
{
lock (WebSocket.WsSendLock)
{
WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_PING, false, buffer);
return base.Send(WebSocket.WsSendBuffer.AsSpan());
}
}
public bool SendPingAsync(string text) => SendPingAsync(Encoding.UTF8.GetBytes(text));
public bool SendPingAsync(ReadOnlySpan text) => SendPingAsync(Encoding.UTF8.GetBytes(text.ToArray()));
public bool SendPingAsync(byte[] buffer) => SendPingAsync(buffer.AsSpan());
public bool SendPingAsync(byte[] buffer, long offset, long size) => SendPingAsync(buffer.AsSpan((int)offset, (int)size));
public bool SendPingAsync(ReadOnlySpan buffer)
{
lock (WebSocket.WsSendLock)
{
WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_PING, false, buffer);
return base.SendAsync(WebSocket.WsSendBuffer.AsSpan());
}
}
#endregion
#region WebSocket send pong methods
public long SendPong(string text) => SendPong(Encoding.UTF8.GetBytes(text));
public long SendPong(ReadOnlySpan text) => SendPong(Encoding.UTF8.GetBytes(text.ToArray()));
public long SendPong(byte[] buffer) => SendPong(buffer.AsSpan());
public long SendPong(byte[] buffer, long offset, long size) => SendPong(buffer.AsSpan((int)offset, (int)size));
public long SendPong(ReadOnlySpan buffer)
{
lock (WebSocket.WsSendLock)
{
WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_PONG, false, buffer);
return base.Send(WebSocket.WsSendBuffer.AsSpan());
}
}
public bool SendPongAsync(string text) => SendPongAsync(Encoding.UTF8.GetBytes(text));
public bool SendPongAsync(ReadOnlySpan text) => SendPongAsync(Encoding.UTF8.GetBytes(text.ToArray()));
public bool SendPongAsync(byte[] buffer) => SendPongAsync(buffer.AsSpan());
public bool SendPongAsync(byte[] buffer, long offset, long size) => SendPongAsync(buffer.AsSpan((int)offset, (int)size));
public bool SendPongAsync(ReadOnlySpan buffer)
{
lock (WebSocket.WsSendLock)
{
WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_PONG, false, buffer);
return base.SendAsync(WebSocket.WsSendBuffer.AsSpan());
}
}
#endregion
#region WebSocket receive methods
public string ReceiveText()
{
Buffer result = new Buffer();
if (!WebSocket.WsHandshaked)
return result.ExtractString(0, result.Data.Length);
Buffer cache = new Buffer();
// Receive WebSocket frame data
while (!WebSocket.WsFinalReceived)
{
while (!WebSocket.WsFrameReceived)
{
long required = WebSocket.RequiredReceiveFrameSize();
cache.Resize(required);
long received = base.Receive(cache.Data, 0, required);
if (received != required)
return result.ExtractString(0, result.Data.Length);
WebSocket.PrepareReceiveFrame(cache.Data, 0, received);
}
if (!WebSocket.WsFinalReceived)
WebSocket.PrepareReceiveFrame(null, 0, 0);
}
// Copy WebSocket frame data
result.Append(WebSocket.WsReceiveFinalBuffer);
WebSocket.PrepareReceiveFrame(null, 0, 0);
return result.ExtractString(0, result.Data.Length);
}
public Buffer ReceiveBinary()
{
Buffer result = new Buffer();
if (!WebSocket.WsHandshaked)
return result;
Buffer cache = new Buffer();
// Receive WebSocket frame data
while (!WebSocket.WsFinalReceived)
{
while (!WebSocket.WsFrameReceived)
{
long required = WebSocket.RequiredReceiveFrameSize();
cache.Resize(required);
long received = base.Receive(cache.Data, 0, required);
if (received != required)
return result;
WebSocket.PrepareReceiveFrame(cache.Data, 0, received);
}
if (!WebSocket.WsFinalReceived)
WebSocket.PrepareReceiveFrame(null, 0, 0);
}
// Copy WebSocket frame data
result.Append(WebSocket.WsReceiveFinalBuffer);
WebSocket.PrepareReceiveFrame(null, 0, 0);
return result;
}
#endregion
#region Session handlers
protected override void OnDisconnecting()
{
if (WebSocket.WsHandshaked)
OnWsDisconnecting();
}
protected override void OnDisconnected()
{
// Disconnect WebSocket
if (WebSocket.WsHandshaked)
{
WebSocket.WsHandshaked = false;
OnWsDisconnected();
}
// Reset WebSocket upgrade HTTP request and response
Request.Clear();
Response.Clear();
// Clear WebSocket send/receive buffers
WebSocket.ClearWsBuffers();
// Initialize new WebSocket random nonce
WebSocket.InitWsNonce();
}
protected override void OnReceived(byte[] buffer, long offset, long size)
{
// Check for WebSocket handshaked status
if (WebSocket.WsHandshaked)
{
// Prepare receive frame
WebSocket.PrepareReceiveFrame(buffer, offset, size);
return;
}
base.OnReceived(buffer, offset, size);
}
protected override void OnReceivedRequestHeader(HttpRequest request)
{
// Check for WebSocket handshaked status
if (WebSocket.WsHandshaked)
return;
// Try to perform WebSocket upgrade
if (!WebSocket.PerformServerUpgrade(request, Response))
{
base.OnReceivedRequestHeader(request);
return;
}
}
protected override void OnReceivedRequest(HttpRequest request)
{
// Check for WebSocket handshaked status
if (WebSocket.WsHandshaked)
{
// Prepare receive frame from the remaining request body
var body = Request.Body;
var data = Encoding.UTF8.GetBytes(body);
WebSocket.PrepareReceiveFrame(data, 0, data.Length);
return;
}
base.OnReceivedRequest(request);
}
protected override void OnReceivedRequestError(HttpRequest request, string error)
{
// Check for WebSocket handshaked status
if (WebSocket.WsHandshaked)
{
OnError(new SocketError());
return;
}
base.OnReceivedRequestError(request, error);
}
#endregion
#region Web socket handlers
public virtual void OnWsConnecting(HttpRequest request) {}
public virtual void OnWsConnected(HttpResponse response) {}
public virtual bool OnWsConnecting(HttpRequest request, HttpResponse response) { return true; }
public virtual void OnWsConnected(HttpRequest request) {}
public virtual void OnWsDisconnecting() {}
public virtual void OnWsDisconnected() {}
public virtual void OnWsReceived(byte[] buffer, long offset, long size) {}
public virtual void OnWsClose(byte[] buffer, long offset, long size, int status = 1000) { Close(); }
public virtual void OnWsPing(byte[] buffer, long offset, long size) { SendPongAsync(buffer, offset, size); }
public virtual void OnWsPong(byte[] buffer, long offset, long size) {}
public virtual void OnWsError(string error) { OnError(SocketError.SocketError); }
public virtual void OnWsError(SocketError error) { OnError(error); }
public void SendUpgrade(HttpResponse response) { SendResponseAsync(response); }
#endregion
}
}