提交Unity 联机Pro

This commit is contained in:
PC-20230316NUNE\Administrator
2024-08-17 14:27:18 +08:00
parent f00193b000
commit 894100ae37
7448 changed files with 854473 additions and 0 deletions

View File

@@ -0,0 +1,364 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_WEBSOCKET
using System;
using BestHTTP.Connections;
using BestHTTP.Extensions;
using BestHTTP.PlatformSupport.Memory;
using BestHTTP.WebSocket.Frames;
namespace BestHTTP.WebSocket
{
internal sealed class OverHTTP1 : WebSocketBaseImplementation
{
public override bool IsOpen => webSocket != null && !webSocket.IsClosed;
public override int BufferedAmount => webSocket.BufferedAmount;
public override int Latency => this.webSocket.Latency;
public override DateTime LastMessageReceived => this.webSocket.lastMessage;
/// <summary>
/// Indicates whether we sent out the connection request to the server.
/// </summary>
private bool requestSent;
/// <summary>
/// The internal WebSocketResponse object
/// </summary>
private WebSocketResponse webSocket;
public OverHTTP1(WebSocket parent, Uri uri, string origin, string protocol) : base(parent, uri, origin, protocol)
{
string scheme = HTTPProtocolFactory.IsSecureProtocol(uri) ? "wss" : "ws";
int port = uri.Port != -1 ? uri.Port : (scheme.Equals("wss", StringComparison.OrdinalIgnoreCase) ? 443 : 80);
// Somehow if i use the UriBuilder it's not the same as if the uri is constructed from a string...
//uri = new UriBuilder(uri.Scheme, uri.Host, uri.Scheme.Equals("wss", StringComparison.OrdinalIgnoreCase) ? 443 : 80, uri.PathAndQuery).Uri;
base.Uri = new Uri(scheme + "://" + uri.Host + ":" + port + uri.GetRequestPathAndQueryURL());
}
protected override void CreateInternalRequest()
{
if (this._internalRequest != null)
return;
this._internalRequest = new HTTPRequest(base.Uri, OnInternalRequestCallback);
this._internalRequest.Context.Add("WebSocket", this.Parent.Context);
// Called when the regular GET request is successfully upgraded to WebSocket
this._internalRequest.OnUpgraded = OnInternalRequestUpgraded;
//http://tools.ietf.org/html/rfc6455#section-4
// The request MUST contain an |Upgrade| header field whose value MUST include the "websocket" keyword.
this._internalRequest.SetHeader("Upgrade", "websocket");
// The request MUST contain a |Connection| header field whose value MUST include the "Upgrade" token.
this._internalRequest.SetHeader("Connection", "Upgrade");
// The request MUST include a header field with the name |Sec-WebSocket-Key|. The value of this header field MUST be a nonce consisting of a
// randomly selected 16-byte value that has been base64-encoded (see Section 4 of [RFC4648]). The nonce MUST be selected randomly for each connection.
this._internalRequest.SetHeader("Sec-WebSocket-Key", WebSocket.GetSecKey(new object[] { this, InternalRequest, base.Uri, new object() }));
// The request MUST include a header field with the name |Origin| [RFC6454] if the request is coming from a browser client.
// If the connection is from a non-browser client, the request MAY include this header field if the semantics of that client match the use-case described here for browser clients.
// More on Origin Considerations: http://tools.ietf.org/html/rfc6455#section-10.2
if (!string.IsNullOrEmpty(Origin))
this._internalRequest.SetHeader("Origin", Origin);
// The request MUST include a header field with the name |Sec-WebSocket-Version|. The value of this header field MUST be 13.
this._internalRequest.SetHeader("Sec-WebSocket-Version", "13");
if (!string.IsNullOrEmpty(Protocol))
this._internalRequest.SetHeader("Sec-WebSocket-Protocol", Protocol);
// Disable caching
this._internalRequest.SetHeader("Cache-Control", "no-cache");
this._internalRequest.SetHeader("Pragma", "no-cache");
#if !BESTHTTP_DISABLE_CACHING
this._internalRequest.DisableCache = true;
#endif
#if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR)
this._internalRequest.Proxy = this.Parent.GetProxy(this.Uri);
#endif
this._internalRequest.OnBeforeRedirection += InternalRequest_OnBeforeRedirection;
if (this.Parent.OnInternalRequestCreated != null)
{
try
{
this.Parent.OnInternalRequestCreated(this.Parent, this._internalRequest);
}
catch (Exception ex)
{
HTTPManager.Logger.Exception("OverHTTP1", "CreateInternalRequest", ex, this.Parent.Context);
}
}
}
private bool InternalRequest_OnBeforeRedirection(HTTPRequest originalRequest, HTTPResponse response, Uri redirectUri)
{
// We have to re-select/reset the implementation in the parent Websocket, as the redirected request might gets served over a HTTP/2 connection!
this.Parent.SelectImplementation(redirectUri, originalRequest.GetFirstHeaderValue("Origin"), originalRequest.GetFirstHeaderValue("Sec-WebSocket-Protocol"))
.StartOpen();
originalRequest.Callback = null;
return false;
}
public override void StartClose(UInt16 code, string message)
{
if (this.State == WebSocketStates.Connecting)
{
if (this.InternalRequest != null)
this.InternalRequest.Abort();
this.State = WebSocketStates.Closed;
if (this.Parent.OnClosed != null)
this.Parent.OnClosed(this.Parent, (ushort)WebSocketStausCodes.NoStatusCode, string.Empty);
}
else
{
this.State = WebSocketStates.Closing;
webSocket.Close(code, message);
}
}
public override void StartOpen()
{
if (requestSent)
throw new InvalidOperationException("Open already called! You can't reuse this WebSocket instance!");
if (this.Parent.Extensions != null)
{
try
{
for (int i = 0; i < this.Parent.Extensions.Length; ++i)
{
var ext = this.Parent.Extensions[i];
if (ext != null)
ext.AddNegotiation(InternalRequest);
}
}
catch (Exception ex)
{
HTTPManager.Logger.Exception("OverHTTP1", "Open", ex, this.Parent.Context);
}
}
InternalRequest.Send();
requestSent = true;
this.State = WebSocketStates.Connecting;
}
private void OnInternalRequestCallback(HTTPRequest req, HTTPResponse resp)
{
string reason = string.Empty;
switch (req.State)
{
case HTTPRequestStates.Finished:
HTTPManager.Logger.Information("OverHTTP1", string.Format("Request finished. Status Code: {0} Message: {1}", resp.StatusCode.ToString(), resp.Message), this.Parent.Context);
if (resp.StatusCode == 101)
{
// The request upgraded successfully.
return;
}
else
reason = string.Format("Request Finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}",
resp.StatusCode,
resp.Message,
resp.DataAsText);
break;
// The request finished with an unexpected error. The request's Exception property may contain more info about the error.
case HTTPRequestStates.Error:
reason = "Request Finished with Error! " + (req.Exception != null ? ("Exception: " + req.Exception.Message + req.Exception.StackTrace) : string.Empty);
break;
// The request aborted, initiated by the user.
case HTTPRequestStates.Aborted:
reason = "Request Aborted!";
break;
// Connecting to the server is timed out.
case HTTPRequestStates.ConnectionTimedOut:
reason = "Connection Timed Out!";
break;
// The request didn't finished in the given time.
case HTTPRequestStates.TimedOut:
reason = "Processing the request Timed Out!";
break;
default:
return;
}
if (this.State != WebSocketStates.Connecting || !string.IsNullOrEmpty(reason))
{
if (this.Parent.OnError != null)
this.Parent.OnError(this.Parent, reason);
else if (!HTTPManager.IsQuitting)
HTTPManager.Logger.Error("OverHTTP1", reason, this.Parent.Context);
}
else if (this.Parent.OnClosed != null)
this.Parent.OnClosed(this.Parent, (ushort)WebSocketStausCodes.NormalClosure, "Closed while opening");
this.State = WebSocketStates.Closed;
if (!req.IsKeepAlive && resp != null && resp is WebSocketResponse)
(resp as WebSocketResponse).CloseStream();
}
private void OnInternalRequestUpgraded(HTTPRequest req, HTTPResponse resp)
{
HTTPManager.Logger.Information("OverHTTP1", "Internal request upgraded!", this.Parent.Context);
webSocket = resp as WebSocketResponse;
if (webSocket == null)
{
if (this.Parent.OnError != null)
{
string reason = string.Empty;
if (req.Exception != null)
reason = req.Exception.Message + " " + req.Exception.StackTrace;
this.Parent.OnError(this.Parent, reason);
}
this.State = WebSocketStates.Closed;
return;
}
// If Close called while we connected
if (this.State == WebSocketStates.Closed)
{
webSocket.CloseStream();
return;
}
if (!resp.HasHeader("sec-websocket-accept"))
{
this.State = WebSocketStates.Closed;
webSocket.CloseStream();
if (this.Parent.OnError != null)
this.Parent.OnError(this.Parent, "No Sec-Websocket-Accept header is sent by the server!");
return;
}
webSocket.WebSocket = this.Parent;
if (this.Parent.Extensions != null)
{
for (int i = 0; i < this.Parent.Extensions.Length; ++i)
{
var ext = this.Parent.Extensions[i];
try
{
if (ext != null && !ext.ParseNegotiation(webSocket))
this.Parent.Extensions[i] = null; // Keep extensions only that successfully negotiated
}
catch (Exception ex)
{
HTTPManager.Logger.Exception("OverHTTP1", "ParseNegotiation", ex, this.Parent.Context);
// Do not try to use a defective extension in the future
this.Parent.Extensions[i] = null;
}
}
}
this.State = WebSocketStates.Open;
if (this.Parent.OnOpen != null)
{
try
{
this.Parent.OnOpen(this.Parent);
}
catch (Exception ex)
{
HTTPManager.Logger.Exception("OverHTTP1", "OnOpen", ex, this.Parent.Context);
}
}
webSocket.OnText = (ws, msg) =>
{
if (this.Parent.OnMessage != null)
this.Parent.OnMessage(this.Parent, msg);
};
webSocket.OnBinaryNoAlloc = (ws, frame) =>
{
if (this.Parent.OnBinary != null)
{
var bin = new byte[frame.Count];
Array.Copy(frame.Data, 0, bin, 0, frame.Count);
this.Parent.OnBinary(this.Parent, bin);
}
if (this.Parent.OnBinaryNoAlloc != null)
this.Parent.OnBinaryNoAlloc(this.Parent, frame);
};
webSocket.OnClosed = (ws, code, msg) =>
{
this.State = WebSocketStates.Closed;
if (this.Parent.OnClosed != null)
this.Parent.OnClosed(this.Parent, code, msg);
};
if (this.Parent.OnIncompleteFrame != null)
webSocket.OnIncompleteFrame = (ws, frame) =>
{
if (this.Parent.OnIncompleteFrame != null)
this.Parent.OnIncompleteFrame(this.Parent, frame);
};
if (this.Parent.StartPingThread)
webSocket.StartPinging(Math.Max(this.Parent.PingFrequency, 100));
webSocket.StartReceive();
}
public override void Send(string message)
{
webSocket.Send(message);
}
public override void Send(byte[] buffer)
{
webSocket.Send(buffer);
}
public override void Send(byte[] buffer, ulong offset, ulong count)
{
webSocket.Send(buffer, offset, count);
}
public override void SendAsBinary(BufferSegment data)
{
webSocket.Send(WebSocketFrameTypes.Binary, data);
}
public override void SendAsText(BufferSegment data)
{
webSocket.Send(WebSocketFrameTypes.Text, data);
}
public override void Send(WebSocketFrame frame)
{
webSocket.Send(frame);
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 042433932184b3747ab2c9857b763ef7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,728 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2 && !BESTHTTP_DISABLE_WEBSOCKET
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Text;
using BestHTTP.Connections.HTTP2;
using BestHTTP.Extensions;
using BestHTTP.Logger;
using BestHTTP.PlatformSupport.Memory;
using BestHTTP.WebSocket.Frames;
namespace BestHTTP.WebSocket
{
/// <summary>
/// Implements RFC 8441 (https://tools.ietf.org/html/rfc8441) to use Websocket over HTTP/2
/// </summary>
public sealed class OverHTTP2 : WebSocketBaseImplementation, IHeartbeat
{
public override int BufferedAmount { get => this._bufferedAmount; }
internal volatile int _bufferedAmount;
public override bool IsOpen => this.State == WebSocketStates.Open;
public override int Latency { get { return this.Parent.StartPingThread ? base.Latency : (int)this.http2Handler.Latency; } }
private List<WebSocketFrameReader> IncompleteFrames = new List<WebSocketFrameReader>();
private HTTP2Handler http2Handler;
/// <summary>
/// True if we sent out a Close message to the server
/// </summary>
internal volatile bool closeSent;
/// <summary>
/// When we sent out the last ping.
/// </summary>
private DateTime lastPing = DateTime.MinValue;
private bool waitingForPong = false;
/// <summary>
/// A circular buffer to store the last N rtt times calculated by the pong messages.
/// </summary>
private CircularBuffer<int> rtts = new CircularBuffer<int>(WebSocketResponse.RTTBufferCapacity);
private PeekableIncomingSegmentStream incomingSegmentStream = new PeekableIncomingSegmentStream();
private ConcurrentQueue<WebSocketFrameReader> CompletedFrames = new ConcurrentQueue<WebSocketFrameReader>();
internal ConcurrentQueue<WebSocketFrame> frames = new ConcurrentQueue<WebSocketFrame>();
public OverHTTP2(WebSocket parent, Uri uri, string origin, string protocol) : base(parent, uri, origin, protocol)
{
// use https scheme so it will be served over HTTP/2. Thre request's Tag will be set to this class' instance so HTTP2Handler will know it has to create a HTTP2WebSocketStream instance to
// process the request.
string scheme = "https";
int port = uri.Port != -1 ? uri.Port : 443;
base.Uri = new Uri(scheme + "://" + uri.Host + ":" + port + uri.GetRequestPathAndQueryURL());
}
internal void SetHTTP2Handler(HTTP2Handler handler) => this.http2Handler = handler;
protected override void CreateInternalRequest()
{
HTTPManager.Logger.Verbose("OverHTTP2", "CreateInternalRequest", this.Parent.Context);
base._internalRequest = new HTTPRequest(base.Uri, HTTPMethods.Connect, OnInternalRequestCallback);
base._internalRequest.Context.Add("WebSocket", this.Parent.Context);
base._internalRequest.SetHeader(":protocol", "websocket");
// The request MUST include a header field with the name |Sec-WebSocket-Key|. The value of this header field MUST be a nonce consisting of a
// randomly selected 16-byte value that has been base64-encoded (see Section 4 of [RFC4648]). The nonce MUST be selected randomly for each connection.
base._internalRequest.SetHeader("sec-webSocket-key", WebSocket.GetSecKey(new object[] { this, InternalRequest, base.Uri, new object() }));
// The request MUST include a header field with the name |Origin| [RFC6454] if the request is coming from a browser client.
// If the connection is from a non-browser client, the request MAY include this header field if the semantics of that client match the use-case described here for browser clients.
// More on Origin Considerations: http://tools.ietf.org/html/rfc6455#section-10.2
if (!string.IsNullOrEmpty(base.Origin))
base._internalRequest.SetHeader("origin", base.Origin);
// The request MUST include a header field with the name |Sec-WebSocket-Version|. The value of this header field MUST be 13.
base._internalRequest.SetHeader("sec-webSocket-version", "13");
if (!string.IsNullOrEmpty(base.Protocol))
base._internalRequest.SetHeader("sec-webSocket-protocol", base.Protocol);
// Disable caching
base._internalRequest.SetHeader("cache-control", "no-cache");
base._internalRequest.SetHeader("pragma", "no-cache");
#if !BESTHTTP_DISABLE_CACHING
base._internalRequest.DisableCache = true;
#endif
base._internalRequest.OnHeadersReceived += OnHeadersReceived;
// set a fake upload stream, so HPACKEncoder will not set the END_STREAM flag
base._internalRequest.UploadStream = new MemoryStream(0);
base._internalRequest.UseUploadStreamLength = false;
this.LastMessageReceived = DateTime.Now;
base._internalRequest.Tag = this;
if (this.Parent.OnInternalRequestCreated != null)
{
try
{
this.Parent.OnInternalRequestCreated(this.Parent, base._internalRequest);
}
catch (Exception ex)
{
HTTPManager.Logger.Exception("OverHTTP2", "CreateInternalRequest", ex, this.Parent.Context);
}
}
}
private void OnHeadersReceived(HTTPRequest req, HTTPResponse resp, Dictionary<string, List<string>> newHeaders)
{
HTTPManager.Logger.Verbose("OverHTTP2", $"OnHeadersReceived - StatusCode: {resp?.StatusCode}", this.Parent.Context);
if (resp != null && resp.StatusCode == 200)
{
if (this.Parent.Extensions != null)
{
for (int i = 0; i < this.Parent.Extensions.Length; ++i)
{
var ext = this.Parent.Extensions[i];
try
{
if (ext != null && !ext.ParseNegotiation(resp))
this.Parent.Extensions[i] = null; // Keep extensions only that successfully negotiated
}
catch (Exception ex)
{
HTTPManager.Logger.Exception("OverHTTP2", "ParseNegotiation", ex, this.Parent.Context);
// Do not try to use a defective extension in the future
this.Parent.Extensions[i] = null;
}
}
}
this.State = WebSocketStates.Open;
if (this.Parent.OnOpen != null)
{
try
{
this.Parent.OnOpen(this.Parent);
}
catch (Exception ex)
{
HTTPManager.Logger.Exception("OverHTTP2", "OnOpen", ex, this.Parent.Context);
}
}
if (this.Parent.StartPingThread)
{
this.LastMessageReceived = DateTime.Now;
SendPing();
}
}
else
req.Abort();
}
private static bool CanReadFullFrame(PeekableIncomingSegmentStream stream)
{
if (stream.Length < 2)
return false;
stream.BeginPeek();
if (stream.PeekByte() == -1)
return false;
int maskAndLength = stream.PeekByte();
if (maskAndLength == -1)
return false;
// The second byte is the Mask Bit and the length of the payload data
var HasMask = (maskAndLength & 0x80) != 0;
// if 0-125, that is the payload length.
var Length = (UInt64)(maskAndLength & 127);
// If 126, the following 2 bytes interpreted as a 16-bit unsigned integer are the payload length.
if (Length == 126)
{
byte[] rawLen = BufferPool.Get(2, true);
for (int i = 0; i < 2; i++)
{
int data = stream.PeekByte();
if (data < 0)
return false;
rawLen[i] = (byte)data;
}
if (BitConverter.IsLittleEndian)
Array.Reverse(rawLen, 0, 2);
Length = (UInt64)BitConverter.ToUInt16(rawLen, 0);
BufferPool.Release(rawLen);
}
else if (Length == 127)
{
// If 127, the following 8 bytes interpreted as a 64-bit unsigned integer (the
// most significant bit MUST be 0) are the payload length.
byte[] rawLen = BufferPool.Get(8, true);
for (int i = 0; i < 8; i++)
{
int data = stream.PeekByte();
if (data < 0)
return false;
rawLen[i] = (byte)data;
}
if (BitConverter.IsLittleEndian)
Array.Reverse(rawLen, 0, 8);
Length = (UInt64)BitConverter.ToUInt64(rawLen, 0);
BufferPool.Release(rawLen);
}
// Header + Mask&Length
Length += 2;
// 4 bytes for Mask if present
if (HasMask)
Length += 4;
return stream.Length >= (long)Length;
}
internal void OnReadThread(BufferSegment buffer)
{
this.LastMessageReceived = DateTime.Now;
this.incomingSegmentStream.Write(buffer);
while (CanReadFullFrame(this.incomingSegmentStream))
{
WebSocketFrameReader frame = new WebSocketFrameReader();
frame.Read(this.incomingSegmentStream);
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
HTTPManager.Logger.Verbose("OverHTTP2", "Frame received: " + frame.ToString(), this.Parent.Context);
if (!frame.IsFinal)
{
if (this.Parent.OnIncompleteFrame == null)
IncompleteFrames.Add(frame);
else
CompletedFrames.Enqueue(frame);
continue;
}
switch (frame.Type)
{
// For a complete documentation and rules on fragmentation see http://tools.ietf.org/html/rfc6455#section-5.4
// A fragmented Frame's last fragment's opcode is 0 (Continuation) and the FIN bit is set to 1.
case WebSocketFrameTypes.Continuation:
// Do an assemble pass only if OnFragment is not set. Otherwise put it in the CompletedFrames, we will handle it in the HandleEvent phase.
if (this.Parent.OnIncompleteFrame == null)
{
frame.Assemble(IncompleteFrames);
// Remove all incomplete frames
IncompleteFrames.Clear();
// Control frames themselves MUST NOT be fragmented. So, its a normal text or binary frame. Go, handle it as usual.
goto case WebSocketFrameTypes.Binary;
}
else
{
CompletedFrames.Enqueue(frame);
}
break;
case WebSocketFrameTypes.Text:
case WebSocketFrameTypes.Binary:
frame.DecodeWithExtensions(this.Parent);
CompletedFrames.Enqueue(frame);
break;
// Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in response, unless it already received a Close frame.
case WebSocketFrameTypes.Ping:
if (!closeSent && this.State != WebSocketStates.Closed)
{
// copy data set to true here, as the frame's data is released back to the pool after the switch
Send(new WebSocketFrame(this.Parent, WebSocketFrameTypes.Pong, frame.Data, true, true, true));
}
break;
case WebSocketFrameTypes.Pong:
// https://tools.ietf.org/html/rfc6455#section-5.5
// A Pong frame MAY be sent unsolicited. This serves as a
// unidirectional heartbeat. A response to an unsolicited Pong frame is
// not expected.
if (!waitingForPong)
break;
waitingForPong = false;
// the difference between the current time and the time when the ping message is sent
TimeSpan diff = DateTime.Now - lastPing;
// add it to the buffer
this.rtts.Add((int)diff.TotalMilliseconds);
// and calculate the new latency
base.Latency = CalculateLatency();
break;
// If an endpoint receives a Close frame and did not previously send a Close frame, the endpoint MUST send a Close frame in response.
case WebSocketFrameTypes.ConnectionClose:
HTTPManager.Logger.Information("OverHTTP2", "ConnectionClose packet received!", this.Parent.Context);
CompletedFrames.Enqueue(frame);
if (!closeSent)
Send(new WebSocketFrame(this.Parent, WebSocketFrameTypes.ConnectionClose, BufferSegment.Empty));
this.State = WebSocketStates.Closed;
break;
}
}
}
private void OnInternalRequestCallback(HTTPRequest req, HTTPResponse resp)
{
HTTPManager.Logger.Verbose("OverHTTP2", $"OnInternalRequestCallback - this.State: {this.State}", this.Parent.Context);
// If it's already closed, all events are called too.
if (this.State == WebSocketStates.Closed)
return;
if (this.State == WebSocketStates.Connecting && HTTPManager.HTTP2Settings.WebSocketOverHTTP2Settings.EnableImplementationFallback)
{
this.Parent.FallbackToHTTP1();
return;
}
string reason = string.Empty;
switch (req.State)
{
case HTTPRequestStates.Finished:
HTTPManager.Logger.Information("OverHTTP2", string.Format("Request finished. Status Code: {0} Message: {1}", resp.StatusCode.ToString(), resp.Message), this.Parent.Context);
if (resp.StatusCode == 101)
{
// The request upgraded successfully.
return;
}
else
reason = string.Format("Request Finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}",
resp.StatusCode,
resp.Message,
resp.DataAsText);
break;
// The request finished with an unexpected error. The request's Exception property may contain more info about the error.
case HTTPRequestStates.Error:
reason = "Request Finished with Error! " + (req.Exception != null ? ("Exception: " + req.Exception.Message + req.Exception.StackTrace) : string.Empty);
break;
// The request aborted, initiated by the user.
case HTTPRequestStates.Aborted:
reason = "Request Aborted!";
break;
// Connecting to the server is timed out.
case HTTPRequestStates.ConnectionTimedOut:
reason = "Connection Timed Out!";
break;
// The request didn't finished in the given time.
case HTTPRequestStates.TimedOut:
reason = "Processing the request Timed Out!";
break;
default:
return;
}
if (this.State != WebSocketStates.Connecting || !string.IsNullOrEmpty(reason))
{
if (this.Parent.OnError != null)
{
try
{
this.Parent.OnError(this.Parent, reason);
}
catch (Exception ex)
{
HTTPManager.Logger.Exception("OverHTTP2", "OnError", ex, this.Parent.Context);
}
}
else if (!HTTPManager.IsQuitting)
HTTPManager.Logger.Error("OverHTTP2", reason, this.Parent.Context);
}
else if (this.Parent.OnClosed != null)
{
try
{
this.Parent.OnClosed(this.Parent, (ushort)WebSocketStausCodes.NormalClosure, "Closed while opening");
}
catch (Exception ex)
{
HTTPManager.Logger.Exception("OverHTTP2", "OnClosed", ex, this.Parent.Context);
}
}
this.State = WebSocketStates.Closed;
}
public override void StartOpen()
{
HTTPManager.Logger.Verbose("OverHTTP2", "StartOpen", this.Parent.Context);
if (this.Parent.Extensions != null)
{
try
{
for (int i = 0; i < this.Parent.Extensions.Length; ++i)
{
var ext = this.Parent.Extensions[i];
if (ext != null)
ext.AddNegotiation(base.InternalRequest);
}
}
catch (Exception ex)
{
HTTPManager.Logger.Exception("OverHTTP2", "Open", ex, this.Parent.Context);
}
}
base.InternalRequest.Send();
HTTPManager.Heartbeats.Subscribe(this);
this.State = WebSocketStates.Connecting;
}
public override void StartClose(ushort code, string message)
{
HTTPManager.Logger.Verbose("OverHTTP2", "StartClose", this.Parent.Context);
if (this.State == WebSocketStates.Connecting)
{
if (this.InternalRequest != null)
this.InternalRequest.Abort();
this.State = WebSocketStates.Closed;
if (this.Parent.OnClosed != null)
this.Parent.OnClosed(this.Parent, code, message);
}
else
{
Send(new WebSocketFrame(this.Parent, WebSocketFrameTypes.ConnectionClose, WebSocket.EncodeCloseData(code, message), true, false, false));
this.State = WebSocketStates.Closing;
}
}
public override void Send(string message)
{
if (message == null)
throw new ArgumentNullException("message must not be null!");
int count = System.Text.Encoding.UTF8.GetByteCount(message);
byte[] data = BufferPool.Get(count, true);
System.Text.Encoding.UTF8.GetBytes(message, 0, message.Length, data, 0);
SendAsText(data.AsBuffer(0, count));
}
public override void Send(byte[] buffer)
{
if (buffer == null)
throw new ArgumentNullException("data must not be null!");
Send(new WebSocketFrame(this.Parent, WebSocketFrameTypes.Binary, new BufferSegment(buffer, 0, buffer.Length)));
}
public override void Send(byte[] data, ulong offset, ulong count)
{
if (data == null)
throw new ArgumentNullException("data must not be null!");
if (offset + count > (ulong)data.Length)
throw new ArgumentOutOfRangeException("offset + count >= data.Length");
Send(new WebSocketFrame(this.Parent, WebSocketFrameTypes.Binary, new BufferSegment(data, (int)offset, (int)count), true, true));
}
public override void Send(WebSocketFrame frame)
{
if (this.State == WebSocketStates.Closed || closeSent)
return;
this.frames.Enqueue(frame);
this.http2Handler.SignalRunnerThread();
this._bufferedAmount += frame.Data.Count;
if (frame.Type == WebSocketFrameTypes.ConnectionClose)
this.closeSent = true;
}
public override void SendAsBinary(BufferSegment data)
{
Send(WebSocketFrameTypes.Binary, data);
}
public override void SendAsText(BufferSegment data)
{
Send(WebSocketFrameTypes.Text, data);
}
private void Send(WebSocketFrameTypes type, BufferSegment data)
{
Send(new WebSocketFrame(this.Parent, type, data, true, true, false));
}
private int CalculateLatency()
{
if (this.rtts.Count == 0)
return 0;
int sumLatency = 0;
for (int i = 0; i < this.rtts.Count; ++i)
sumLatency += this.rtts[i];
return sumLatency / this.rtts.Count;
}
internal void PreReadCallback()
{
if (this.Parent.StartPingThread)
{
DateTime now = DateTime.Now;
if (!waitingForPong && now - LastMessageReceived >= TimeSpan.FromMilliseconds(this.Parent.PingFrequency))
SendPing();
if (waitingForPong && now - lastPing > this.Parent.CloseAfterNoMessage)
{
if (this.State != WebSocketStates.Closed)
{
HTTPManager.Logger.Warning("OverHTTP2",
string.Format("No message received in the given time! Closing WebSocket. LastPing: {0}, PingFrequency: {1}, Close After: {2}, Now: {3}",
this.lastPing, TimeSpan.FromMilliseconds(this.Parent.PingFrequency), this.Parent.CloseAfterNoMessage, now), this.Parent.Context);
CloseWithError("No message received in the given time!");
}
}
}
}
public void OnHeartbeatUpdate(TimeSpan dif)
{
DateTime now = DateTime.Now;
switch (this.State)
{
case WebSocketStates.Connecting:
if (now - this.InternalRequest.Timing.Start >= this.Parent.CloseAfterNoMessage)
{
if (HTTPManager.HTTP2Settings.WebSocketOverHTTP2Settings.EnableImplementationFallback)
{
this.State = WebSocketStates.Closed;
this.InternalRequest.OnHeadersReceived = null;
this.InternalRequest.Callback = null;
this.Parent.FallbackToHTTP1();
}
else
{
CloseWithError("WebSocket Over HTTP/2 Implementation failed to connect in the given time!");
}
}
break;
default:
while (CompletedFrames.TryDequeue(out var frame))
{
// Bugs in the clients shouldn't interrupt the code, so we need to try-catch and ignore any exception occurring here
try
{
switch (frame.Type)
{
case WebSocketFrameTypes.Continuation:
if (HTTPManager.Logger.Level == Loglevels.All)
HTTPManager.Logger.Verbose("OverHTTP2", "HandleEvents - OnIncompleteFrame", this.Parent.Context);
if (this.Parent.OnIncompleteFrame != null)
this.Parent.OnIncompleteFrame(this.Parent, frame);
break;
case WebSocketFrameTypes.Text:
// Any not Final frame is handled as a fragment
if (!frame.IsFinal)
goto case WebSocketFrameTypes.Continuation;
if (HTTPManager.Logger.Level == Loglevels.All)
HTTPManager.Logger.Verbose("OverHTTP2", $"HandleEvents - OnText(\"{frame.DataAsText}\")", this.Parent.Context);
if (this.Parent.OnMessage != null)
this.Parent.OnMessage(this.Parent, frame.DataAsText);
break;
case WebSocketFrameTypes.Binary:
// Any not Final frame is handled as a fragment
if (!frame.IsFinal)
goto case WebSocketFrameTypes.Continuation;
if (HTTPManager.Logger.Level == Loglevels.All)
HTTPManager.Logger.Verbose("OverHTTP2", $"HandleEvents - OnBinary({frame.Data})", this.Parent.Context);
if (this.Parent.OnBinary != null)
{
var data = new byte[frame.Data.Count];
Array.Copy(frame.Data.Data, frame.Data.Offset, data, 0, frame.Data.Count);
this.Parent.OnBinary(this.Parent, data);
}
if (this.Parent.OnBinaryNoAlloc != null)
this.Parent.OnBinaryNoAlloc(this.Parent, frame.Data);
break;
case WebSocketFrameTypes.ConnectionClose:
HTTPManager.Logger.Verbose("OverHTTP2", "HandleEvents - Calling OnClosed", this.Parent.Context);
if (this.Parent.OnClosed != null)
{
try
{
UInt16 statusCode = 0;
string msg = string.Empty;
// If we received any data, we will get the status code and the message from it
if (/*CloseFrame != null && */ frame.Data != BufferSegment.Empty && frame.Data.Count >= 2)
{
if (BitConverter.IsLittleEndian)
Array.Reverse(frame.Data.Data, frame.Data.Offset, 2);
statusCode = BitConverter.ToUInt16(frame.Data.Data, frame.Data.Offset);
if (frame.Data.Count > 2)
msg = Encoding.UTF8.GetString(frame.Data.Data, frame.Data.Offset + 2, frame.Data.Count - 2);
frame.ReleaseData();
}
this.Parent.OnClosed(this.Parent, statusCode, msg);
this.Parent.OnClosed = null;
}
catch (Exception ex)
{
HTTPManager.Logger.Exception("OverHTTP2", "HandleEvents - OnClosed", ex, this.Parent.Context);
}
}
HTTPManager.Heartbeats.Unsubscribe(this);
break;
}
}
catch (Exception ex)
{
HTTPManager.Logger.Exception("OverHTTP2", string.Format("HandleEvents({0})", frame.ToString()), ex, this.Parent.Context);
}
finally
{
frame.ReleaseData();
}
}
break;
}
}
/// <summary>
/// Next interaction relative to *now*.
/// </summary>
public TimeSpan GetNextInteraction()
{
if (waitingForPong)
return TimeSpan.MaxValue;
return (LastMessageReceived + TimeSpan.FromMilliseconds(this.Parent.PingFrequency)) - DateTime.Now;
}
private void SendPing()
{
HTTPManager.Logger.Information("OverHTTP2", "Sending Ping frame, waiting for a pong...", this.Parent.Context);
lastPing = DateTime.Now;
waitingForPong = true;
Send(new WebSocketFrame(this.Parent, WebSocketFrameTypes.Ping, BufferSegment.Empty));
}
private void CloseWithError(string message)
{
HTTPManager.Logger.Verbose("OverHTTP2", $"CloseWithError(\"{message}\")", this.Parent.Context);
this.State = WebSocketStates.Closed;
if (this.Parent.OnError != null)
{
try
{
this.Parent.OnError(this.Parent, message);
}
catch (Exception ex)
{
HTTPManager.Logger.Exception("OverHTTP2", "OnError", ex, this.Parent.Context);
}
}
this.InternalRequest.Abort();
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aadf012d28ce0f3458e08598d3b745f6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a9b2365dee16ce748bf7849f48d85410
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,60 @@
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2 && !BESTHTTP_DISABLE_WEBSOCKET
using System;
using System.Collections.Generic;
using System.IO;
using BestHTTP.Extensions;
using BestHTTP.PlatformSupport.Memory;
namespace BestHTTP.WebSocket.Implementations.Utils
{
public sealed class LockedBufferSegmenStream : BufferSegmentStream
{
public bool IsClosed { get; private set; }
public override int Read(byte[] buffer, int offset, int count)
{
lock (base.bufferList)
{
if (this.IsClosed && base.bufferList.Count == 0)
return 0;
int sumReadCount = base.Read(buffer, offset, count);
return sumReadCount == 0 ? -1 : sumReadCount;
}
}
public override void Write(BufferSegment bufferSegment)
{
lock (base.bufferList)
{
if (this.IsClosed)
return;
base.Write(bufferSegment);
}
}
public override void Reset()
{
lock (base.bufferList)
{
base.Reset();
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
Reset();
}
public override void Close()
{
this.IsClosed = true;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 311f1dc3057a76e45964b3b63c4a5e79
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,270 @@
#if (UNITY_WEBGL && !UNITY_EDITOR) && !BESTHTTP_DISABLE_WEBSOCKET
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using BestHTTP.PlatformSupport.Memory;
namespace BestHTTP.WebSocket
{
delegate void OnWebGLWebSocketOpenDelegate(uint id);
delegate void OnWebGLWebSocketTextDelegate(uint id, string text);
delegate void OnWebGLWebSocketBinaryDelegate(uint id, IntPtr pBuffer, int length);
delegate void OnWebGLWebSocketErrorDelegate(uint id, string error);
delegate void OnWebGLWebSocketCloseDelegate(uint id, int code, string reason);
internal sealed class WebGLBrowser : WebSocketBaseImplementation
{
public override WebSocketStates State => ImplementationId != 0 ? WS_GetState(ImplementationId) : WebSocketStates.Unknown;
public override bool IsOpen => ImplementationId != 0 && WS_GetState(ImplementationId) == WebSocketStates.Open;
public override int BufferedAmount => WS_GetBufferedAmount(ImplementationId);
internal static Dictionary<uint, WebSocket> WebSockets = new Dictionary<uint, WebSocket>();
private uint ImplementationId;
public WebGLBrowser(WebSocket parent, Uri uri, string origin, string protocol) : base(parent, uri, origin, protocol)
{
}
public override void StartOpen()
{
try
{
ImplementationId = WS_Create(this.Uri.OriginalString, this.Protocol, OnOpenCallback, OnTextCallback, OnBinaryCallback, OnErrorCallback, OnCloseCallback);
WebSockets.Add(ImplementationId, this.Parent);
}
catch(Exception ex)
{
HTTPManager.Logger.Exception("WebSocket", "Open", ex, this.Parent.Context);
}
}
public override void StartClose(UInt16 code, string message)
{
WS_Close(this.ImplementationId, code, message);
}
public override void Send(string message)
{
var count = System.Text.Encoding.UTF8.GetByteCount(message);
var buffer = BufferPool.Get(count, true);
System.Text.Encoding.UTF8.GetBytes(message, 0, message.Length, buffer, 0);
WS_Send_String(this.ImplementationId, buffer, 0, count);
BufferPool.Release(buffer);
}
public override void Send(byte[] buffer)
{
WS_Send_Binary(this.ImplementationId, buffer, 0, buffer.Length);
}
public override void Send(byte[] buffer, ulong offset, ulong count)
{
WS_Send_Binary(this.ImplementationId, buffer, (int)offset, (int)count);
}
public override void SendAsBinary(BufferSegment data)
{
WS_Send_Binary(this.ImplementationId, data.Data, data.Offset, data.Count);
BufferPool.Release(data);
}
public override void SendAsText(BufferSegment data)
{
WS_Send_String(this.ImplementationId, data.Data, data.Offset, data.Count);
BufferPool.Release(data);
}
[DllImport("__Internal")]
static extern uint WS_Create(string url, string protocol, OnWebGLWebSocketOpenDelegate onOpen, OnWebGLWebSocketTextDelegate onText, OnWebGLWebSocketBinaryDelegate onBinary, OnWebGLWebSocketErrorDelegate onError, OnWebGLWebSocketCloseDelegate onClose);
[DllImport("__Internal")]
static extern WebSocketStates WS_GetState(uint id);
[DllImport("__Internal")]
static extern int WS_GetBufferedAmount(uint id);
[DllImport("__Internal")]
static extern int WS_Send_String(uint id, byte[] strData, int pos, int length);
[DllImport("__Internal")]
static extern int WS_Send_Binary(uint id, byte[] buffer, int pos, int length);
[DllImport("__Internal")]
static extern void WS_Close(uint id, ushort code, string reason);
[DllImport("__Internal")]
static extern void WS_Release(uint id);
[AOT.MonoPInvokeCallback(typeof(OnWebGLWebSocketOpenDelegate))]
static void OnOpenCallback(uint id)
{
WebSocket ws;
if (WebSockets.TryGetValue(id, out ws))
{
if (ws.OnOpen != null)
{
try
{
ws.OnOpen(ws);
}
catch(Exception ex)
{
HTTPManager.Logger.Exception("WebSocket", "OnOpen", ex, ws.Context);
}
}
}
else
HTTPManager.Logger.Warning("WebSocket", "OnOpenCallback - No WebSocket found for id: " + id.ToString(), ws.Context);
}
[AOT.MonoPInvokeCallback(typeof(OnWebGLWebSocketTextDelegate))]
static void OnTextCallback(uint id, string text)
{
WebSocket ws;
if (WebSockets.TryGetValue(id, out ws))
{
if (ws.OnMessage != null)
{
try
{
ws.OnMessage(ws, text);
}
catch (Exception ex)
{
HTTPManager.Logger.Exception("WebSocket", "OnMessage", ex, ws.Context);
}
}
}
else
HTTPManager.Logger.Warning("WebSocket", "OnTextCallback - No WebSocket found for id: " + id.ToString());
}
[AOT.MonoPInvokeCallback(typeof(OnWebGLWebSocketBinaryDelegate))]
static void OnBinaryCallback(uint id, IntPtr pBuffer, int length)
{
WebSocket ws;
if (WebSockets.TryGetValue(id, out ws))
{
if (ws.OnBinary != null)
{
try
{
byte[] buffer = new byte[length];
// Copy data from the 'unmanaged' memory to managed memory. Buffer will be reclaimed by the GC.
Marshal.Copy(pBuffer, buffer, 0, length);
ws.OnBinary(ws, buffer);
}
catch (Exception ex)
{
HTTPManager.Logger.Exception("WebSocket", "OnBinary", ex, ws.Context);
}
}
if (ws.OnBinaryNoAlloc != null)
{
try
{
byte[] buffer = BufferPool.Get(length, true);
// We still have to do a copy here, but at least the buffer will be released back to the pool.
// Copy data from the 'unmanaged' memory to managed memory. Buffer will be reclaimed by the GC.
Marshal.Copy(pBuffer, buffer, 0, length);
ws.OnBinaryNoAlloc(ws, new BufferSegment(buffer, 0, length));
BufferPool.Release(buffer);
}
catch (Exception ex)
{
HTTPManager.Logger.Exception("WebSocket", "OnBinary", ex, ws.Context);
}
}
}
else
HTTPManager.Logger.Warning("WebSocket", "OnBinaryCallback - No WebSocket found for id: " + id.ToString());
}
[AOT.MonoPInvokeCallback(typeof(OnWebGLWebSocketErrorDelegate))]
static void OnErrorCallback(uint id, string error)
{
WebSocket ws;
if (WebSockets.TryGetValue(id, out ws))
{
WebSockets.Remove(id);
if (ws.OnError != null)
{
try
{
ws.OnError(ws, error);
}
catch (Exception ex)
{
HTTPManager.Logger.Exception("WebSocket", "OnError", ex, ws.Context);
}
}
}
else
HTTPManager.Logger.Warning("WebSocket", "OnErrorCallback - No WebSocket found for id: " + id.ToString());
try
{
WS_Release(id);
}
catch(Exception ex)
{
HTTPManager.Logger.Exception("WebSocket", "WS_Release", ex);
}
}
[AOT.MonoPInvokeCallback(typeof(OnWebGLWebSocketCloseDelegate))]
static void OnCloseCallback(uint id, int code, string reason)
{
// To match non-webgl behavior, we have to treat this client-side generated message as an error
if (code == (int)WebSocketStausCodes.ClosedAbnormally)
{
OnErrorCallback(id, "Abnormal disconnection.");
return;
}
WebSocket ws;
if (WebSockets.TryGetValue(id, out ws))
{
WebSockets.Remove(id);
if (ws.OnClosed != null)
{
try
{
ws.OnClosed(ws, (ushort)code, reason);
}
catch (Exception ex)
{
HTTPManager.Logger.Exception("WebSocket", "OnClosed", ex, ws.Context);
}
}
}
else
HTTPManager.Logger.Warning("WebSocket", "OnCloseCallback - No WebSocket found for id: " + id.ToString());
try
{
WS_Release(id);
}
catch(Exception ex)
{
HTTPManager.Logger.Exception("WebSocket", "WS_Release", ex);
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2f992398667c95b4f8694562f4bd37e5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,98 @@
#if !BESTHTTP_DISABLE_WEBSOCKET
using System;
using BestHTTP.PlatformSupport.Memory;
#if !UNITY_WEBGL || UNITY_EDITOR
using BestHTTP.WebSocket.Frames;
#endif
namespace BestHTTP.WebSocket
{
/// <summary>
/// States of the underlying implementation's state.
/// </summary>
public enum WebSocketStates : byte
{
Connecting = 0,
Open = 1,
Closing = 2,
Closed = 3,
Unknown
};
public delegate void OnWebSocketOpenDelegate(WebSocket webSocket);
public delegate void OnWebSocketMessageDelegate(WebSocket webSocket, string message);
public delegate void OnWebSocketBinaryDelegate(WebSocket webSocket, byte[] data);
public delegate void OnWebSocketBinaryNoAllocDelegate(WebSocket webSocket, BufferSegment data);
public delegate void OnWebSocketClosedDelegate(WebSocket webSocket, UInt16 code, string message);
public delegate void OnWebSocketErrorDelegate(WebSocket webSocket, string reason);
#if !UNITY_WEBGL || UNITY_EDITOR
public delegate void OnWebSocketIncompleteFrameDelegate(WebSocket webSocket, WebSocketFrameReader frame);
#endif
public abstract class WebSocketBaseImplementation
{
public virtual WebSocketStates State { get; protected set; }
public virtual bool IsOpen { get; protected set; }
public virtual int BufferedAmount { get; protected set; }
#if !UNITY_WEBGL || UNITY_EDITOR
public HTTPRequest InternalRequest
{
get
{
if (this._internalRequest == null)
CreateInternalRequest();
return this._internalRequest;
}
}
protected HTTPRequest _internalRequest;
public virtual int Latency { get; protected set; }
public virtual DateTime LastMessageReceived { get; protected set; }
#endif
public WebSocket Parent { get; }
public Uri Uri { get; protected set; }
public string Origin { get; }
public string Protocol { get; }
public WebSocketBaseImplementation(WebSocket parent, Uri uri, string origin, string protocol)
{
this.Parent = parent;
this.Uri = uri;
this.Origin = origin;
this.Protocol = protocol;
#if !UNITY_WEBGL || UNITY_EDITOR
this.LastMessageReceived = DateTime.MinValue;
// Set up some default values.
this.Parent.PingFrequency = 10_000;
this.Parent.CloseAfterNoMessage = TimeSpan.FromSeconds(2);
#endif
}
public abstract void StartOpen();
public abstract void StartClose(UInt16 code, string message);
public abstract void Send(string message);
public abstract void Send(byte[] buffer);
public abstract void Send(byte[] buffer, ulong offset, ulong count);
public abstract void SendAsBinary(BufferSegment data);
public abstract void SendAsText(BufferSegment data);
#if !UNITY_WEBGL || UNITY_EDITOR
protected abstract void CreateInternalRequest();
/// <summary>
/// It will send the given frame to the server.
/// </summary>
public abstract void Send(WebSocketFrame frame);
#endif
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ba79e3bbef722f24881197bc223ded5c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: