Got at least one data fetching method working; turns out, we can't use a patched LogicStack to get the data
This commit is contained in:
561
Vendor/EmbedIO-3.5.2/WebSockets/Internal/WebSocket.cs
vendored
Normal file
561
Vendor/EmbedIO-3.5.2/WebSockets/Internal/WebSocket.cs
vendored
Normal file
@@ -0,0 +1,561 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Net.WebSockets;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Net.Internal;
|
||||
using Swan;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace EmbedIO.WebSockets.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements the WebSocket interface.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The WebSocket class provides a set of methods and properties for two-way communication using
|
||||
/// the WebSocket protocol (<see href="http://tools.ietf.org/html/rfc6455">RFC 6455</see>).
|
||||
/// </remarks>
|
||||
internal sealed class WebSocket : IWebSocket
|
||||
{
|
||||
public const string SupportedVersion = "13";
|
||||
|
||||
private readonly object _stateSyncRoot = new ();
|
||||
private readonly ConcurrentQueue<MessageEventArgs> _messageEventQueue = new ();
|
||||
private readonly Action _closeConnection;
|
||||
private readonly TimeSpan _waitTime = TimeSpan.FromSeconds(1);
|
||||
|
||||
private volatile WebSocketState _readyState;
|
||||
private AutoResetEvent? _exitReceiving;
|
||||
private FragmentBuffer? _fragmentsBuffer;
|
||||
private volatile bool _inMessage;
|
||||
private AutoResetEvent? _receivePong;
|
||||
private Stream? _stream;
|
||||
|
||||
private WebSocket(HttpConnection connection)
|
||||
{
|
||||
_closeConnection = connection.ForceClose;
|
||||
_stream = connection.Stream;
|
||||
_readyState = WebSocketState.Open;
|
||||
}
|
||||
|
||||
~WebSocket()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the <see cref="WebSocket"/> receives a message.
|
||||
/// </summary>
|
||||
public event EventHandler<MessageEventArgs>? OnMessage;
|
||||
|
||||
/// <inheritdoc />
|
||||
public WebSocketState State => _readyState;
|
||||
|
||||
internal CompressionMethod Compression { get; } = CompressionMethod.None;
|
||||
|
||||
internal bool EmitOnPing { get; set; }
|
||||
|
||||
internal bool InContinuation { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendAsync(byte[] buffer, bool isText, CancellationToken cancellationToken) => SendAsync(buffer, isText ? Opcode.Text : Opcode.Binary, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task CloseAsync(CancellationToken cancellationToken = default) => CloseAsync(CloseStatusCode.Normal, cancellationToken: cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task CloseAsync(
|
||||
CloseStatusCode code = CloseStatusCode.Undefined,
|
||||
string? reason = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
bool CheckParametersForClose()
|
||||
{
|
||||
if (code == CloseStatusCode.NoStatus && !string.IsNullOrEmpty(reason))
|
||||
{
|
||||
"'code' cannot have a reason.".Trace(nameof(WebSocket));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (code == CloseStatusCode.MandatoryExtension)
|
||||
{
|
||||
"'code' cannot be used by a server.".Trace(nameof(WebSocket));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(reason) && Encoding.UTF8.GetBytes(reason).Length > 123)
|
||||
{
|
||||
"The size of 'reason' is greater than the allowable max size.".Trace(nameof(WebSocket));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_readyState != WebSocketState.Open)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (code != CloseStatusCode.Undefined && !CheckParametersForClose())
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (code == CloseStatusCode.NoStatus)
|
||||
{
|
||||
return InternalCloseAsync(cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
var send = !IsOpcodeReserved(code);
|
||||
return InternalCloseAsync(new PayloadData((ushort)code, reason), send, send, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a ping using the WebSocket connection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the <see cref="WebSocket"/> receives a pong to this ping in a time;
|
||||
/// otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public Task<bool> PingAsync() => PingAsync(WebSocketFrame.EmptyPingBytes, _waitTime);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a ping with the specified <paramref name="message"/> using the WebSocket connection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the <see cref="WebSocket"/> receives a pong to this ping in a time;
|
||||
/// otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <param name="message">
|
||||
/// A <see cref="string"/> that represents the message to send.
|
||||
/// </param>
|
||||
public Task<bool> PingAsync(string message)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message))
|
||||
{
|
||||
return PingAsync();
|
||||
}
|
||||
|
||||
var data = Encoding.UTF8.GetBytes(message);
|
||||
|
||||
if (data.Length <= 125)
|
||||
{
|
||||
return PingAsync(WebSocketFrame.CreatePingFrame(data).ToArray(), _waitTime);
|
||||
}
|
||||
|
||||
"A message has greater than the allowable max size.".Error(nameof(PingAsync));
|
||||
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends binary <paramref name="data" /> using the WebSocket connection.
|
||||
/// </summary>
|
||||
/// <param name="data">An array of <see cref="byte" /> that represents the binary data to send.</param>
|
||||
/// <param name="opcode">The opcode.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous of send
|
||||
/// binary data using websocket.
|
||||
/// </returns>
|
||||
#pragma warning disable CA1801 // Unused parameter
|
||||
public async Task SendAsync(byte[] data, Opcode opcode, CancellationToken cancellationToken = default)
|
||||
#pragma warning restore CA1801
|
||||
{
|
||||
if (_readyState != WebSocketState.Open)
|
||||
{
|
||||
throw new WebSocketException(CloseStatusCode.Normal, $"This operation isn\'t available in: {_readyState}");
|
||||
}
|
||||
|
||||
using var stream = new WebSocketStream(data, opcode, Compression);
|
||||
foreach (var frame in stream.GetFrames())
|
||||
{
|
||||
await Send(frame).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
internal static async Task<WebSocket> AcceptAsync(HttpListenerContext httpContext, string acceptedProtocol)
|
||||
{
|
||||
static string CreateResponseKey(string clientKey)
|
||||
{
|
||||
const string Guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
|
||||
var buff = new StringBuilder(clientKey, 64).Append(Guid);
|
||||
#pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms
|
||||
using var sha1 = SHA1.Create();
|
||||
return Convert.ToBase64String(sha1.ComputeHash(Encoding.UTF8.GetBytes(buff.ToString())));
|
||||
#pragma warning restore CA5350 // Do Not Use Weak Cryptographic Algorithms
|
||||
}
|
||||
|
||||
var requestHeaders = httpContext.Request.Headers;
|
||||
|
||||
var webSocketKey = requestHeaders[HttpHeaderNames.SecWebSocketKey];
|
||||
|
||||
if (string.IsNullOrEmpty(webSocketKey))
|
||||
{
|
||||
throw new WebSocketException(CloseStatusCode.ProtocolError, $"Includes no {HttpHeaderNames.SecWebSocketKey} header, or it has an invalid value.");
|
||||
}
|
||||
|
||||
var webSocketVersion = requestHeaders[HttpHeaderNames.SecWebSocketVersion];
|
||||
|
||||
if (webSocketVersion == null || webSocketVersion != SupportedVersion)
|
||||
{
|
||||
throw new WebSocketException(CloseStatusCode.ProtocolError, $"Includes no {HttpHeaderNames.SecWebSocketVersion} header, or it has an invalid value.");
|
||||
}
|
||||
|
||||
var handshakeResponse = new WebSocketHandshakeResponse(httpContext);
|
||||
|
||||
handshakeResponse.Headers[HttpHeaderNames.SecWebSocketAccept] = CreateResponseKey(webSocketKey);
|
||||
|
||||
if (acceptedProtocol.Length > 0)
|
||||
{
|
||||
handshakeResponse.Headers[HttpHeaderNames.SecWebSocketProtocol] = acceptedProtocol;
|
||||
}
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(handshakeResponse.ToString());
|
||||
await httpContext.Connection.Stream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
|
||||
|
||||
// Signal the original response that headers have been sent.
|
||||
httpContext.HttpListenerResponse.HeadersSent = true;
|
||||
|
||||
var socket = new WebSocket(httpContext.Connection);
|
||||
socket.Open();
|
||||
return socket;
|
||||
}
|
||||
|
||||
internal async Task<bool> PingAsync(byte[] frameAsBytes, TimeSpan timeout)
|
||||
{
|
||||
if (_readyState != WebSocketState.Open)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
await _stream.WriteAsync(frameAsBytes, 0, frameAsBytes.Length).ConfigureAwait(false);
|
||||
|
||||
return _receivePong != null && _receivePong.WaitOne(timeout);
|
||||
}
|
||||
|
||||
private static bool IsOpcodeReserved(CloseStatusCode code)
|
||||
=> code == CloseStatusCode.Undefined
|
||||
|| code == CloseStatusCode.NoStatus
|
||||
|| code == CloseStatusCode.Abnormal
|
||||
|| code == CloseStatusCode.TlsHandshakeFailure;
|
||||
|
||||
#pragma warning disable CA1801 // Unused parameter
|
||||
private void Dispose(bool disposing)
|
||||
#pragma warning restore CA1801
|
||||
{
|
||||
try
|
||||
{
|
||||
InternalCloseAsync(new PayloadData((ushort)CloseStatusCode.Away)).Await();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InternalCloseAsync(
|
||||
PayloadData? payloadData = null,
|
||||
bool send = true,
|
||||
bool receive = true,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
lock (_stateSyncRoot)
|
||||
{
|
||||
if (_readyState == WebSocketState.CloseReceived || _readyState == WebSocketState.CloseSent)
|
||||
{
|
||||
"The closing is already in progress.".Trace(nameof(InternalCloseAsync));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_readyState == WebSocketState.Closed)
|
||||
{
|
||||
"The connection has been closed.".Trace(nameof(InternalCloseAsync));
|
||||
return;
|
||||
}
|
||||
|
||||
send = send && _readyState == WebSocketState.Open;
|
||||
receive = receive && send;
|
||||
|
||||
_readyState = WebSocketState.CloseSent;
|
||||
}
|
||||
|
||||
"Begin closing the connection.".Trace(nameof(InternalCloseAsync));
|
||||
|
||||
var bytes = send ? WebSocketFrame.CreateCloseFrame(payloadData).ToArray() : null;
|
||||
await CloseHandshakeAsync(bytes, receive, cancellationToken).ConfigureAwait(false);
|
||||
ReleaseResources();
|
||||
|
||||
"End closing the connection.".Trace(nameof(InternalCloseAsync));
|
||||
|
||||
lock (_stateSyncRoot)
|
||||
{
|
||||
_readyState = WebSocketState.Closed;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CloseHandshakeAsync(
|
||||
byte[]? frameAsBytes,
|
||||
bool receive,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var sent = frameAsBytes != null;
|
||||
|
||||
if (sent)
|
||||
{
|
||||
await _stream.WriteAsync(frameAsBytes, 0, frameAsBytes.Length, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (receive && sent)
|
||||
{
|
||||
_ = _exitReceiving?.WaitOne(_waitTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void Fatal(string message, Exception? exception = null)
|
||||
=> Fatal(message, (exception as WebSocketException)?.Code ?? CloseStatusCode.Abnormal);
|
||||
|
||||
private void Fatal(string message, CloseStatusCode code)
|
||||
=> InternalCloseAsync(new PayloadData((ushort)code, message), !IsOpcodeReserved(code), false).Await();
|
||||
|
||||
private void Message()
|
||||
{
|
||||
if (_inMessage || _messageEventQueue.IsEmpty || _readyState != WebSocketState.Open)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_inMessage = true;
|
||||
|
||||
if (_messageEventQueue.TryDequeue(out var e))
|
||||
{
|
||||
Messages(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void Messages(MessageEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
OnMessage?.Invoke(this, e);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.Log(nameof(WebSocket));
|
||||
}
|
||||
|
||||
if (!_messageEventQueue.TryDequeue(out e) || _readyState != WebSocketState.Open)
|
||||
{
|
||||
_inMessage = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_ = Task.Run(() => Messages(e));
|
||||
}
|
||||
|
||||
private void Open()
|
||||
{
|
||||
_inMessage = true;
|
||||
StartReceiving();
|
||||
|
||||
if (!_messageEventQueue.TryDequeue(out var e) || _readyState != WebSocketState.Open)
|
||||
{
|
||||
_inMessage = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Messages(e);
|
||||
}
|
||||
|
||||
private Task ProcessCloseFrame(WebSocketFrame frame) => InternalCloseAsync(frame.PayloadData, !frame.PayloadData.HasReservedCode, false);
|
||||
|
||||
private async Task ProcessDataFrame(WebSocketFrame frame)
|
||||
{
|
||||
if (frame.IsCompressed)
|
||||
{
|
||||
using var ms = await frame.PayloadData.ApplicationData.CompressAsync(Compression, false, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
_messageEventQueue.Enqueue(new MessageEventArgs(frame.Opcode, ms.ToArray()));
|
||||
}
|
||||
else
|
||||
{
|
||||
_messageEventQueue.Enqueue(new MessageEventArgs(frame));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessFragmentFrame(WebSocketFrame frame)
|
||||
{
|
||||
if (!InContinuation)
|
||||
{
|
||||
// Must process first fragment.
|
||||
if (frame.Opcode == Opcode.Cont)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_fragmentsBuffer = new FragmentBuffer(frame.Opcode, frame.IsCompressed);
|
||||
InContinuation = true;
|
||||
}
|
||||
|
||||
_fragmentsBuffer.AddPayload(frame.PayloadData.ApplicationData);
|
||||
|
||||
if (frame.Fin == Fin.Final)
|
||||
{
|
||||
using (_fragmentsBuffer)
|
||||
{
|
||||
_messageEventQueue.Enqueue(await _fragmentsBuffer.GetMessage(Compression).ConfigureAwait(false));
|
||||
}
|
||||
|
||||
_fragmentsBuffer = null;
|
||||
InContinuation = false;
|
||||
}
|
||||
}
|
||||
|
||||
private Task ProcessPingFrame(WebSocketFrame frame)
|
||||
{
|
||||
if (EmitOnPing)
|
||||
{
|
||||
_messageEventQueue.Enqueue(new MessageEventArgs(frame));
|
||||
}
|
||||
|
||||
return Send(new WebSocketFrame(Opcode.Pong, frame.PayloadData));
|
||||
}
|
||||
|
||||
private void ProcessPongFrame()
|
||||
{
|
||||
_ = _receivePong?.Set();
|
||||
"Received a pong.".Trace(nameof(ProcessPongFrame));
|
||||
}
|
||||
|
||||
private async Task<bool> ProcessReceivedFrame(WebSocketFrame frame)
|
||||
{
|
||||
if (frame.IsFragment)
|
||||
{
|
||||
await ProcessFragmentFrame(frame).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (frame.Opcode)
|
||||
{
|
||||
case Opcode.Text:
|
||||
case Opcode.Binary:
|
||||
await ProcessDataFrame(frame).ConfigureAwait(false);
|
||||
break;
|
||||
case Opcode.Ping:
|
||||
await ProcessPingFrame(frame).ConfigureAwait(false);
|
||||
break;
|
||||
case Opcode.Pong:
|
||||
ProcessPongFrame();
|
||||
break;
|
||||
case Opcode.Close:
|
||||
await ProcessCloseFrame(frame).ConfigureAwait(false);
|
||||
break;
|
||||
default:
|
||||
Fatal($"Unsupported frame received: {frame.PrintToString()}", CloseStatusCode.PolicyViolation);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ReleaseResources()
|
||||
{
|
||||
_closeConnection();
|
||||
_stream = null;
|
||||
|
||||
if (_fragmentsBuffer != null)
|
||||
{
|
||||
_fragmentsBuffer.Dispose();
|
||||
_fragmentsBuffer = null;
|
||||
InContinuation = false;
|
||||
}
|
||||
|
||||
if (_receivePong != null)
|
||||
{
|
||||
_receivePong.Dispose();
|
||||
_receivePong = null;
|
||||
}
|
||||
|
||||
if (_exitReceiving == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_exitReceiving.Dispose();
|
||||
_exitReceiving = null;
|
||||
}
|
||||
|
||||
private Task Send(WebSocketFrame frame)
|
||||
{
|
||||
lock (_stateSyncRoot)
|
||||
{
|
||||
if (_readyState != WebSocketState.Open)
|
||||
{
|
||||
"The sending has been interrupted.".Error(nameof(Send));
|
||||
return Task.Delay(0);
|
||||
}
|
||||
}
|
||||
|
||||
var frameAsBytes = frame.ToArray();
|
||||
return _stream.WriteAsync(frameAsBytes, 0, frameAsBytes.Length);
|
||||
}
|
||||
|
||||
private void StartReceiving()
|
||||
{
|
||||
while (_messageEventQueue.TryDequeue(out _))
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
_exitReceiving = new AutoResetEvent(false);
|
||||
_receivePong = new AutoResetEvent(false);
|
||||
|
||||
var frameStream = new WebSocketFrameStream(_stream);
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
while (_readyState == WebSocketState.Open)
|
||||
{
|
||||
try
|
||||
{
|
||||
var frame = await frameStream.ReadFrameAsync(this).ConfigureAwait(false);
|
||||
|
||||
if (frame == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await ProcessReceivedFrame(frame).ConfigureAwait(false);
|
||||
|
||||
if (!result || _readyState == WebSocketState.Closed)
|
||||
{
|
||||
_ = _exitReceiving?.Set();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_ = Task.Run(Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Fatal("An exception has occurred while receiving.", ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user