using Best.HTTP.Caching;
using Best.HTTP.Cookies;
using Best.HTTP.Hosts.Connections;
using Best.HTTP.Hosts.Settings;
using Best.HTTP.HostSetting;
using Best.HTTP.Request.Authentication;
using Best.HTTP.Request.Timings;
using Best.HTTP.Shared.Extensions;
using Best.HTTP.Shared.Logger;
using Best.HTTP.Shared.PlatformSupport.Memory;
using Best.HTTP.Shared.PlatformSupport.Text;
using Best.HTTP.Shared.PlatformSupport.Threading;
using System;
using System.IO;
namespace Best.HTTP.Shared
{
public enum ShutdownTypes
{
Running,
Gentle,
Immediate
}
public delegate void OnSetupFinishedDelegate();
///
/// Global entry point to access and manage main services of the plugin.
///
[Best.HTTP.Shared.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstructionAttribute]
public static partial class HTTPManager
{
///
/// Static constructor. Setup default values.
///
static HTTPManager()
{
PerHostSettings.Add("*", new HostSettings());
// Set the default logger mechanism
logger = new Best.HTTP.Shared.Logger.ThreadedLogger();
IOService = new Best.HTTP.Shared.PlatformSupport.FileSystem.DefaultIOService();
#if !UNITY_WEBGL || UNITY_EDITOR
ProxyDetector = new HTTP.Proxies.Autodetect.ProxyDetector();
#endif
UserAgent = $"com.Tivadar.Best.HTTP v{typeof(HTTPManager)?.Assembly?.GetName()?.Version}/Unity {UnityEngine.Application.unityVersion}";
}
///
/// Delegate for the setup finished event.
///
public static OnSetupFinishedDelegate OnSetupFinished;
///
/// Instance of the per-host settings manager.
///
public static HostSettingsManager PerHostSettings { get; private set; } = new HostSettingsManager();
///
/// Cached DateTime value for cases where high resolution isn't needed.
///
/// Warning!! It must be used only on the main update thread!
public static DateTime CurrentFrameDateTime { get; private set; } = DateTime.Now;
///
/// By default the plugin will save all cache and cookie data under the path returned by Application.persistentDataPath.
/// You can assign a function to this delegate to return a custom root path to define a new path.
/// This delegate will be called on a non Unity thread!
///
public static Func RootSaveFolderProvider { get; set; }
#if !UNITY_WEBGL || UNITY_EDITOR
public static HTTP.Proxies.Autodetect.ProxyDetector ProxyDetector {
get => _proxyDetector;
set {
_proxyDetector?.Detach();
_proxyDetector = value;
}
}
private static HTTP.Proxies.Autodetect.ProxyDetector _proxyDetector;
#endif
///
/// The global, default proxy for all HTTPRequests. The HTTPRequest's Proxy still can be changed per-request. Default value is null.
///
public static HTTP.Proxies.Proxy Proxy { get; set; }
///
/// Heartbeat manager to use less threads in the plugin. The heartbeat updates are called from the OnUpdate function.
///
public static HeartbeatManager Heartbeats
{
get
{
if (heartbeats == null)
heartbeats = new HeartbeatManager();
return heartbeats;
}
}
private static HeartbeatManager heartbeats;
///
/// A basic Best.HTTP.Logger.ILogger implementation to be able to log intelligently additional informations about the plugin's internal mechanism.
///
public static Best.HTTP.Shared.Logger.ILogger Logger
{
get
{
// Make sure that it has a valid logger instance.
if (logger == null)
{
logger = new ThreadedLogger();
logger.Level = Loglevels.None;
}
return logger;
}
set { logger = value; }
}
private static Best.HTTP.Shared.Logger.ILogger logger;
///
/// An IIOService implementation to handle filesystem operations.
///
public static Best.HTTP.Shared.PlatformSupport.FileSystem.IIOService IOService;
///
/// User-agent string that will be sent with each requests.
///
public static string UserAgent;
///
/// It's true if the application is quitting and the plugin is shutting down itself.
///
public static bool IsQuitting { get { return _isQuitting; } private set { _isQuitting = value; } }
private static volatile bool _isQuitting;
public static string RootFolderName = "com.Tivadar.Best.HTTP.v3";
///
/// The local content cache, maintained by the plugin. When set to a non-null value, Maintain called immediately on the cache.
///
public static HTTPCache LocalCache
{
get => _httpCache;
set
{
_httpCache?.Dispose();
(_httpCache = value)?.Maintain(contentLength: 0, deleteLockedEntries: true, context: null);
}
}
private static HTTPCache _httpCache;
private static bool IsSetupCalled;
///
/// Initializes the HTTPManager with default settings. This method should be called on Unity's main thread before using the HTTP plugin. By default it gets called by .
///
public static void Setup()
{
if (IsSetupCalled)
return;
IsSetupCalled = true;
IsQuitting = false;
HTTPManager.Logger.Information("HTTPManager", "Setup called! UserAgent: " + UserAgent);
HTTPUpdateDelegator.CheckInstance();
if (LocalCache == null)
{
// this will trigger a maintain call too.
LocalCache = new HTTPCache(new HTTPCacheOptions());
}
CookieJar.SetupFolder();
CookieJar.Load();
try
{
OnSetupFinished?.Invoke();
}
catch(Exception ex)
{
HTTPManager.logger.Exception(nameof(HTTPManager), "OnSetupFinished", ex, null);
}
}
internal static HTTPRequest SendRequest(HTTPRequest request)
{
if (!IsSetupCalled)
Setup();
if (request.IsCancellationRequested || IsQuitting)
return request;
if (!request.DownloadSettings.DisableCache)
{
#if !UNITY_WEBGL || UNITY_EDITOR
ThreadedRunner.RunShortLiving((request) =>
{
#endif
var hash = HTTPCache.CalculateHash(request.MethodType, request.CurrentUri);
if (LocalCache.CanServeWithoutValidation(hash, ErrorTypeForValidation.None, request.Context))
LocalCache.Redirect(request, hash);
//RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(request, HTTPRequestStates.Queued, null));
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(request, RequestEvents.QueuedResend));
#if !UNITY_WEBGL || UNITY_EDITOR
}, request);
#endif
}
else
{
//RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(request, HTTPRequestStates.Queued, null));
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(request, RequestEvents.QueuedResend));
}
return request;
}
///
/// Will return where the various caches should be saved.
///
public static string GetRootSaveFolder()
{
try
{
if (RootSaveFolderProvider != null)
return Path.Combine(RootSaveFolderProvider(), RootFolderName);
}
catch (Exception ex)
{
HTTPManager.Logger.Exception(nameof(HTTPManager), nameof(GetRootSaveFolder), ex);
}
#if UNITY_SWITCH && !UNITY_EDITOR
throw new NotSupportedException(UnityEngine.Application.platform.ToString());
#else
return Path.Combine(UnityEngine.Application.persistentDataPath, RootFolderName);
#endif
}
#if UNITY_EDITOR
[UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)]
public static void ResetSetup()
{
IsSetupCalled = false;
Profiler.Network.NetworkStatsCollector.ResetNetworkStats();
#if !UNITY_WEBGL || UNITY_EDITOR
PlatformSupport.Network.DNS.Cache.DNSCache.Clear();
#endif
DigestStore.Clear();
PerHostSettings.Clear();
PerHostSettings.Add("*", new HostSettings());
LocalCache = null;
HTTPManager.Logger.Information("HTTPManager", "Reset called!");
}
#endif
///
/// Updates the HTTPManager. This method should be called regularly from a Unity event (e.g., Update, LateUpdate).
/// It processes various events and callbacks and manages internal tasks.
///
public static void OnUpdate()
{
try
{
CurrentFrameDateTime = DateTime.Now;
using (new Unity.Profiling.ProfilerMarker(nameof(TimingEventHelper)).Auto())
TimingEventHelper.ProcessQueue();
using (new Unity.Profiling.ProfilerMarker(nameof(RequestEventHelper)).Auto())
RequestEventHelper.ProcessQueue();
using (new Unity.Profiling.ProfilerMarker(nameof(ConnectionEventHelper)).Auto())
ConnectionEventHelper.ProcessQueue();
if (heartbeats != null)
{
using (new Unity.Profiling.ProfilerMarker(nameof(HeartbeatManager)).Auto())
heartbeats.Update();
}
using (new Unity.Profiling.ProfilerMarker(nameof(BufferPool)).Auto())
BufferPool.Maintain();
using (new Unity.Profiling.ProfilerMarker(nameof(StringBuilderPool)).Auto())
StringBuilderPool.Maintain();
#if BESTHTTP_PROFILE && UNITY_2021_2_OR_NEWER
using (new Unity.Profiling.ProfilerMarker("Profile").Auto())
{
// Sent
{
long newNetworkBytesSent = Profiler.Network.NetworkStatsCollector.TotalNetworkBytesSent;
var diff = newNetworkBytesSent - _lastNetworkBytesSent;
_lastNetworkBytesSent = newNetworkBytesSent;
Profiler.Network.NetworkStats.SentSinceLastFrame.Value = diff;
Profiler.Network.NetworkStats.SentTotal.Value = newNetworkBytesSent;
Profiler.Network.NetworkStats.BufferedToSend.Value = Profiler.Network.NetworkStatsCollector.BufferedToSend;
}
// Received
{
long newNetworkBytesReceived = Profiler.Network.NetworkStatsCollector.TotalNetworkBytesReceived;
var diff = newNetworkBytesReceived - _lastNetworkBytesReceived;
_lastNetworkBytesReceived = newNetworkBytesReceived;
Profiler.Network.NetworkStats.ReceivedSinceLastFrame.Value = diff;
Profiler.Network.NetworkStats.ReceivedTotal.Value = newNetworkBytesReceived;
Profiler.Network.NetworkStats.ReceivedAndUnprocessed.Value = Profiler.Network.NetworkStatsCollector.ReceivedAndUnprocessed;
}
// Open/Total connections
Profiler.Network.NetworkStats.OpenConnectionsCounter.Value = Profiler.Network.NetworkStatsCollector.OpenConnections;
Profiler.Network.NetworkStats.TotalConnectionsCounter.Value = Profiler.Network.NetworkStatsCollector.TotalConnections;
// Memory stats
BufferPool.GetStatistics(ref bufferPoolStats);
Profiler.Memory.MemoryStats.Borrowed.Value = bufferPoolStats.Borrowed;
Profiler.Memory.MemoryStats.Pooled.Value = bufferPoolStats.PoolSize;
Profiler.Memory.MemoryStats.CacheHits.Value = bufferPoolStats.GetBuffers;
Profiler.Memory.MemoryStats.ArrayAllocations.Value = bufferPoolStats.ArrayAllocations;
}
#endif
}
catch (Exception ex)
{
HTTPManager.logger.Exception(nameof(HTTPManager), nameof(OnUpdate), ex);
}
}
#if BESTHTTP_PROFILE && UNITY_2021_2_OR_NEWER
private static long _lastNetworkBytesSent = 0;
private static long _lastNetworkBytesReceived = 0;
private static BufferPoolStats bufferPoolStats = default;
#endif
///
/// Shuts down the HTTPManager and performs cleanup operations. This method should be called when the application is quitting.
///
public static void OnQuit()
{
HTTPManager.Logger.Information("HTTPManager", "OnQuit called!");
IsQuitting = true;
AbortAll();
CookieJar.Persist();
OnUpdate();
HostManager.Clear();
Heartbeats.Clear();
DigestStore.Clear();
}
///
/// Aborts all ongoing HTTP requests and performs an immediate shutdown of the HTTPManager.
///
public static void AbortAll()
{
HTTPManager.Logger.Information("HTTPManager", "AbortAll called!");
// This is an immediate shutdown request!
RequestEventHelper.Clear();
ConnectionEventHelper.Clear();
HostManager.Shutdown();
}
}
}