This commit is contained in:
2026-06-15 18:18:16 +08:00
parent 97c9fba14e
commit 2b9f134e5f
4164 changed files with 386922 additions and 79 deletions

View File

@@ -0,0 +1,332 @@
#if UNITY_WEBGL && !UNITY_EDITOR
using System;
using System.IO;
using System.Threading;
using Best.HTTP.Caching;
using Best.HTTP.Hosts.Connections.File;
using Best.HTTP.Hosts.Connections.HTTP1;
using Best.HTTP.HostSetting;
using Best.HTTP.Request.Authentication;
using Best.HTTP.Request.Timings;
using Best.HTTP.Shared;
using Best.HTTP.Shared.PlatformSupport.Memory;
using Best.HTTP.Shared.Streams;
namespace Best.HTTP.Hosts.Connections.WebGL
{
class PeekableIncomingSegmentContentProviderStream : PeekableContentProviderStream
{
private int peek_listIdx;
private int peek_pos;
public override void BeginPeek()
{
peek_listIdx = 0;
peek_pos = base.bufferList.Count > 0 ? base.bufferList[0].Offset : 0;
}
public override int PeekByte()
{
if (base.bufferList.Count == 0)
return -1;
var segment = base.bufferList[this.peek_listIdx];
if (peek_pos >= segment.Offset + segment.Count)
{
if (base.bufferList.Count <= this.peek_listIdx + 1)
return -1;
segment = base.bufferList[++this.peek_listIdx];
this.peek_pos = segment.Offset;
}
return segment.Data[this.peek_pos++];
}
}
internal sealed class WebGLXHRConnection : ConnectionBase
{
public PeekableContentProviderStream ContentProvider { get; private set; }
int NativeId;
PeekableHTTP1Response _response;
public WebGLXHRConnection(HostKey hostKey)
: base(hostKey, false)
{
WebGLXHRNativeInterface.XHR_SetLoglevel((byte)HTTPManager.Logger.Level);
}
public override void Shutdown(ShutdownTypes type)
{
base.Shutdown(type);
WebGLXHRNativeInterface.XHR_Abort(this.NativeId);
}
protected override void ThreadFunc()
{
// XmlHttpRequest setup
CurrentRequest.Prepare();
Credentials credentials = null;// CurrentRequest.Authenticator?.Credentials;
this.NativeId = WebGLXHRNativeInterface.XHR_Create(HTTPRequest.MethodNames[(byte)CurrentRequest.MethodType],
CurrentRequest.CurrentUri.OriginalString,
credentials?.UserName, credentials?.Password, CurrentRequest.WithCredentials ? 1 : 0);
WebGLXHRNativeConnectionLayer.Add(NativeId, this);
CurrentRequest.EnumerateHeaders((header, values) =>
{
if (!header.Equals("Content-Length"))
for (int i = 0; i < values.Count; ++i)
WebGLXHRNativeInterface.XHR_SetRequestHeader(NativeId, header, values[i]);
}, /*callBeforeSendCallback:*/ true);
WebGLXHRNativeConnectionLayer.SetupHandlers(NativeId, CurrentRequest);
WebGLXHRNativeInterface.XHR_SetTimeout(NativeId, (uint)(CurrentRequest.TimeoutSettings.ConnectTimeout.TotalMilliseconds + CurrentRequest.TimeoutSettings.Timeout.TotalMilliseconds));
Stream upStream = CurrentRequest.UploadSettings.UploadStream;
byte[] body = null;
int length = 0;
bool releaseBodyBuffer = false;
if (upStream != null)
{
var internalBuffer = BufferPool.Get(upStream.Length > 0 ? upStream.Length : CurrentRequest.UploadSettings.UploadChunkSize, true);
using (BufferPoolMemoryStream ms = new BufferPoolMemoryStream(internalBuffer, 0, internalBuffer.Length, true, true, false, true))
{
var buffer = BufferPool.Get(CurrentRequest.UploadSettings.UploadChunkSize, true);
int readCount = -1;
while ((readCount = upStream.Read(buffer, 0, buffer.Length)) > 0)
ms.Write(buffer, 0, readCount);
BufferPool.Release(buffer);
length = (int)ms.Position;
body = ms.GetBuffer();
releaseBodyBuffer = true;
}
}
if (this._response == null)
this.CurrentRequest.Response = this._response = new PeekableHTTP1Response(this.CurrentRequest, false, null);
this.ContentProvider = new PeekableIncomingSegmentContentProviderStream();
WebGLXHRNativeInterface.XHR_Send(NativeId, body, length);
if (releaseBodyBuffer)
BufferPool.Release(body);
this.CurrentRequest.TimeoutSettings.QueuedAt = DateTime.MinValue;
this.CurrentRequest.TimeoutSettings.ProcessingStarted = DateTime.UtcNow;
this.CurrentRequest.OnCancellationRequested += OnCancellationRequested;
}
#region Callback Implementations
private void OnCancellationRequested(HTTPRequest req)
{
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(WebGLXHRConnection), $"{this.NativeId} - OnCancellationRequested()", this.Context);
Interlocked.Exchange(ref this._response, null);
req.OnCancellationRequested -= OnCancellationRequested;
WebGLXHRNativeInterface.XHR_Abort(this.NativeId);
}
internal void OnBuffer(BufferSegment buffer)
{
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(WebGLXHRConnection), $"{this.NativeId} - OnBuffer({buffer})", this.Context);
try
{
if (this.CurrentRequest.TimeoutSettings.IsTimedOut(DateTime.UtcNow))
throw new TimeoutException();
if (this.CurrentRequest.IsCancellationRequested)
throw new Exception("Cancellation requested!");
this.ContentProvider?.Write(buffer);
this._response?.ProcessPeekable(this.ContentProvider);
}
catch (Exception e)
{
BufferPool.Release(buffer);
if (this.ShutdownType == ShutdownTypes.Immediate)
return;
FinishedProcessing(e);
}
// After an exception, this._response will be null!
if (this._response != null && this._response.ReadState == PeekableHTTP1Response.PeekableReadState.Finished)
FinishedProcessing(null);
}
internal void OnError(string error)
{
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(WebGLXHRConnection), $"{this.NativeId} - OnError({error})", this.Context);
FinishedProcessing(new Exception(error));
}
internal void OnResponse(BufferSegment payload)
{
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(WebGLXHRConnection), $"{this.NativeId} - OnResponse({payload})", this.Context);
this._response?.DownStream?.EmergencyIncreaseMaxBuffered();
OnBuffer(payload);
}
void FinishedProcessing(Exception ex)
{
// Warning: FinishedProcessing might be called from different threads in parallel:
// - send thread triggered by a write failure
// - read thread oncontent/OnError/OnConnectionClosed
var resp = Interlocked.Exchange(ref this._response, null);
if (resp == null)
return;
HTTPManager.Logger.Verbose(nameof(WebGLXHRConnection), $"{nameof(FinishedProcessing)}({resp}, {ex})", this.Context);
// Unset the consumer, we no longer expect another OnContent call until further notice.
this.ContentProvider?.Unbind();
this.ContentProvider?.Dispose();
this.ContentProvider = null;
var req = this.CurrentRequest;
req.OnCancellationRequested -= OnCancellationRequested;
bool resendRequest = false;
HTTPRequestStates requestState = HTTPRequestStates.Finished;
HTTPConnectionStates connectionState = HTTPConnectionStates.Recycle;
Exception error = ex;
if (error != null)
{
// Timeout is a non-retryable error
if (ex is TimeoutException)
{
error = null;
requestState = HTTPRequestStates.TimedOut;
}
else if (ex is HTTPCacheAcquireLockException)
{
error = null;
resendRequest = true;
}
else
{
if (req.RetrySettings.Retries < req.RetrySettings.MaxRetries)
{
req.RetrySettings.Retries++;
error = null;
resendRequest = true;
}
else
{
requestState = HTTPRequestStates.Error;
}
}
// Any exception means that the connection is in an unknown state, we shouldn't try to reuse it.
connectionState = HTTPConnectionStates.Closed;
resp.Dispose();
}
else
{
// After HandleResponse connectionState can have the following values:
// - Processing: nothing interesting, caller side can decide what happens with the connection (recycle connection).
// - Closed: server sent an connection: close header.
// - ClosedResendRequest: in this case resendRequest is true, and the connection must not be reused.
// In this case we can send only one ConnectionEvent to handle both case and avoid concurrency issues.
KeepAliveHeader keepAlive = null;
error = ConnectionHelper.HandleResponse(req, out resendRequest, out connectionState, ref keepAlive, this.Context);
connectionState = HTTPConnectionStates.Recycle;
if (error != null)
requestState = HTTPRequestStates.Error;
else if (!resendRequest && resp.IsUpgraded)
requestState = HTTPRequestStates.Processing;
}
req.Timing.StartNext(TimingEventNames.Queued);
HTTPManager.Logger.Verbose(nameof(WebGLXHRConnection), $"{nameof(FinishedProcessing)} final decision. ResendRequest: {resendRequest}, RequestState: {requestState}, ConnectionState: {connectionState}", this.Context);
// If HandleResponse returned with ClosedResendRequest or there were an error and we can retry the request
if (connectionState == HTTPConnectionStates.ClosedResendRequest || (resendRequest && connectionState == HTTPConnectionStates.Closed))
{
ConnectionHelper.ResendRequestAndCloseConnection(this, req);
}
else if (resendRequest && requestState == HTTPRequestStates.Finished)
{
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(req, RequestEvents.Resend));
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, connectionState));
}
else
{
// Otherwise set the request's then the connection's state
ConnectionHelper.EnqueueEvents(this, connectionState, req, requestState, error);
}
}
internal void OnDownloadProgress(int down, int total) => RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.CurrentRequest, RequestEvents.DownloadProgress, down, total));
internal void OnUploadProgress(int up, int total) => RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.CurrentRequest, RequestEvents.UploadProgress, up, total));
internal void OnTimeout()
{
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(WebGLXHRConnection), $"{this.NativeId} - OnTimeout", this.Context);
CurrentRequest.Response = null;
CurrentRequest.State = HTTPRequestStates.TimedOut;
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
}
internal void OnAborted()
{
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(WebGLXHRConnection), $"{this.NativeId} - OnAborted", this.Context);
CurrentRequest.Response = null;
CurrentRequest.State = HTTPRequestStates.Aborted;
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
WebGLXHRNativeConnectionLayer.Remove(NativeId);
WebGLXHRNativeInterface.XHR_Release(NativeId);
this.ContentProvider?.Dispose();
this.ContentProvider = null;
}
}
#endregion
}
}
#endif

View File

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

View File

@@ -0,0 +1,153 @@
#if UNITY_WEBGL && !UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Best.HTTP.Shared;
using Best.HTTP.Shared.Extensions;
using Best.HTTP.Shared.PlatformSupport.Memory;
namespace Best.HTTP.Hosts.Connections.WebGL
{
internal static class WebGLXHRNativeConnectionLayer
{
static Dictionary<int, WebGLXHRConnection> Connections = new Dictionary<int, WebGLXHRConnection>(1);
public static void Add(int nativeId, WebGLXHRConnection connection) => Connections.Add(nativeId, connection);
public static void Remove(int nativeId) => Connections.Remove(nativeId);
public static void SetupHandlers(int nativeId, HTTPRequest request)
{
WebGLXHRNativeInterface.XHR_SetResponseHandler(nativeId, OnResponse, OnError, OnTimeout, OnAborted, OnBufferCallback, OnAllocArray);
// Setting OnUploadProgress result in an addEventListener("progress", ...) call making the request non-simple.
// https://forum.unity.com/threads/best-http-released.200006/page-49#post-3696220
WebGLXHRNativeInterface.XHR_SetProgressHandler(nativeId,
request.DownloadSettings.OnDownloadProgress == null ? (OnWebGLXHRProgressDelegate)null : OnDownloadProgress,
request.UploadSettings.OnUploadProgress == null ? (OnWebGLXHRProgressDelegate)null : OnUploadProgress);
}
[AOT.MonoPInvokeCallback(typeof(OnWebGLXHRAllocArray))]
static unsafe IntPtr OnAllocArray(int nativeId, int length)
{
byte[] buffer = BufferPool.Get(length, true);
fixed (byte* ptr = buffer)
{
var p = (IntPtr)ptr;
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose(nameof(WebGLXHRConnection), $"({p}) <= OnAllocArray({nativeId}, {length})");
return p;
}
}
[AOT.MonoPInvokeCallback(typeof(OnWebGLXHRRequestHandlerDelegate))]
static void OnResponse(int nativeId, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 2)] byte[] pBuffer, int length)
{
WebGLXHRConnection conn = null;
if (!Connections.TryGetValue(nativeId, out conn))
{
HTTPManager.Logger.Error("WebGLConnection", $"OnResponse({nativeId}, {pBuffer}, {length}): No WebGL connection found for nativeId({nativeId})!");
return;
}
if (HTTPManager.Logger.IsDiagnostic)
HTTPManager.Logger.Verbose("WebGLConnection", $"OnResponse({nativeId}, {pBuffer}, {length})", conn.Context);
BufferSegment payload = BufferSegment.Empty;
if (length > 0)
payload = pBuffer.AsBuffer(length);
conn.OnResponse(payload);
}
[AOT.MonoPInvokeCallback(typeof(OnWebGLXHRBufferDelegate))]
public static void OnBufferCallback(int nativeId, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 2)] byte[] pBuffer, int length)
{
WebGLXHRConnection conn = null;
if (!Connections.TryGetValue(nativeId, out conn))
{
HTTPManager.Logger.Error("WebGLConnection - OnBufferCallback", "No WebGL connection found for nativeId: " + nativeId.ToString());
return;
}
BufferSegment payload = BufferSegment.Empty;
if (length > 0)
payload = pBuffer.AsBuffer(length);
conn.OnBuffer(payload);
}
[AOT.MonoPInvokeCallback(typeof(OnWebGLXHRProgressDelegate))]
static void OnDownloadProgress(int nativeId, int downloaded, int total)
{
WebGLXHRConnection conn = null;
if (!Connections.TryGetValue(nativeId, out conn))
{
HTTPManager.Logger.Error("WebGLConnection - OnDownloadProgress", "No WebGL connection found for nativeId: " + nativeId.ToString());
return;
}
HTTPManager.Logger.Information(nativeId + " OnDownloadProgress", downloaded.ToString() + " / " + total.ToString(), conn.Context);
conn.OnDownloadProgress(downloaded, total);
}
[AOT.MonoPInvokeCallback(typeof(OnWebGLXHRProgressDelegate))]
static void OnUploadProgress(int nativeId, int uploaded, int total)
{
WebGLXHRConnection conn = null;
if (!Connections.TryGetValue(nativeId, out conn))
{
HTTPManager.Logger.Error("WebGLConnection - OnUploadProgress", "No WebGL connection found for nativeId: " + nativeId.ToString());
return;
}
HTTPManager.Logger.Information(nativeId + " OnUploadProgress", uploaded.ToString() + " / " + total.ToString(), conn.Context);
conn.OnUploadProgress(uploaded, total);
}
[AOT.MonoPInvokeCallback(typeof(OnWebGLXHRErrorDelegate))]
static void OnError(int nativeId, string error)
{
WebGLXHRConnection conn = null;
if (!Connections.TryGetValue(nativeId, out conn))
{
HTTPManager.Logger.Error("WebGLConnection - OnError", "No WebGL connection found for nativeId: " + nativeId.ToString() + " Error: " + error);
return;
}
conn.OnError(error);
}
[AOT.MonoPInvokeCallback(typeof(OnWebGLXHRTimeoutDelegate))]
static void OnTimeout(int nativeId)
{
WebGLXHRConnection conn = null;
if (!Connections.TryGetValue(nativeId, out conn))
{
HTTPManager.Logger.Error("WebGLConnection - OnTimeout", "No WebGL connection found for nativeId: " + nativeId.ToString());
return;
}
conn.OnTimeout();
}
[AOT.MonoPInvokeCallback(typeof(OnWebGLXHRAbortedDelegate))]
static void OnAborted(int nativeId)
{
WebGLXHRConnection conn = null;
if (!Connections.TryGetValue(nativeId, out conn))
{
HTTPManager.Logger.Error("WebGLConnection - OnAborted", "No WebGL connection found for nativeId: " + nativeId.ToString());
return;
}
conn.OnAborted();
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,54 @@
#if UNITY_WEBGL && !UNITY_EDITOR
using System;
using System.Runtime.InteropServices;
namespace Best.HTTP.Hosts.Connections.WebGL
{
delegate void OnWebGLXHRRequestHandlerDelegate(int nativeId, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 2)] byte[] pBuffer, int length);
delegate void OnWebGLXHRBufferDelegate(int nativeId, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 2)] byte[] pBuffer, int length);
delegate void OnWebGLXHRProgressDelegate(int nativeId, int downloaded, int total);
delegate void OnWebGLXHRErrorDelegate(int nativeId, string error);
delegate void OnWebGLXHRTimeoutDelegate(int nativeId);
delegate void OnWebGLXHRAbortedDelegate(int nativeId);
delegate IntPtr OnWebGLXHRAllocArray(int nativeId, int size);
internal static class WebGLXHRNativeInterface
{
[DllImport("__Internal")]
public static extern int XHR_Create(string method, string url, string userName, string passwd, int withCredentials);
/// <summary>
/// Is an unsigned long representing the number of milliseconds a request can take before automatically being terminated. A value of 0 (which is the default) means there is no timeout.
/// </summary>
[DllImport("__Internal")]
public static extern void XHR_SetTimeout(int nativeId, uint timeout);
[DllImport("__Internal")]
public static extern void XHR_SetRequestHeader(int nativeId, string header, string value);
[DllImport("__Internal")]
public static extern void XHR_SetResponseHandler(int nativeId,
OnWebGLXHRRequestHandlerDelegate onresponse,
OnWebGLXHRErrorDelegate onerror,
OnWebGLXHRTimeoutDelegate ontimeout,
OnWebGLXHRAbortedDelegate onabort,
OnWebGLXHRBufferDelegate onbuffer,
OnWebGLXHRAllocArray onallocArray);
[DllImport("__Internal")]
public static extern void XHR_SetProgressHandler(int nativeId, OnWebGLXHRProgressDelegate onDownloadProgress, OnWebGLXHRProgressDelegate onUploadProgress);
[DllImport("__Internal")]
public static extern void XHR_Send(int nativeId, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 2)] byte[] body, int length);
[DllImport("__Internal")]
public static extern void XHR_Abort(int nativeId);
[DllImport("__Internal")]
public static extern void XHR_Release(int nativeId);
[DllImport("__Internal")]
public static extern void XHR_SetLoglevel(int logLevel);
}
}
#endif

View File

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