using System; using System.Collections.Generic; #if !BESTHTTP_DISABLE_CACHING using BestHTTP.Caching; #endif using BestHTTP.Core; using BestHTTP.Extensions; using BestHTTP.Logger; using BestHTTP.PlatformSupport.Memory; using BestHTTP.PlatformSupport.Text; using Unity.Profiling; #if !BESTHTTP_DISABLE_COOKIES using BestHTTP.Cookies; #endif using BestHTTP.Connections; namespace BestHTTP { public enum ShutdownTypes { Running, Gentle, Immediate } #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR) public delegate Connections.TLS.AbstractTls13Client TlsClientFactoryDelegate(HTTPRequest request, List protocols); #endif public delegate System.Security.Cryptography.X509Certificates.X509Certificate ClientCertificateSelector(HTTPRequest request, string targetHost, System.Security.Cryptography.X509Certificates.X509CertificateCollection localCertificates, System.Security.Cryptography.X509Certificates.X509Certificate remoteCertificate, string[] acceptableIssuers); /// /// /// [BestHTTP.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstructionAttribute] public static partial class HTTPManager { // Static constructor. Setup default values static HTTPManager() { MaxConnectionPerServer = 6; KeepAliveDefaultValue = true; MaxPathLength = 255; MaxConnectionIdleTime = TimeSpan.FromSeconds(20); #if !BESTHTTP_DISABLE_COOKIES #if UNITY_WEBGL && !UNITY_EDITOR // Under webgl when IsCookiesEnabled is true, it will set the withCredentials flag for the XmlHTTPRequest // and that's different from the default behavior. // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials IsCookiesEnabled = false; #else IsCookiesEnabled = true; #endif #endif CookieJarSize = 10 * 1024 * 1024; EnablePrivateBrowsing = false; ConnectTimeout = TimeSpan.FromSeconds(20); RequestTimeout = TimeSpan.FromSeconds(60); // Set the default logger mechanism logger = new BestHTTP.Logger.ThreadedLogger(); #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR) UseAlternateSSLDefaultValue = true; #endif #if NETFX_CORE IOService = new PlatformSupport.FileSystem.NETFXCOREIOService(); #else IOService = new PlatformSupport.FileSystem.DefaultIOService(); #endif #if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR) ProxyDetector = new Proxies.Autodetect.ProxyDetector(); #endif } #if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2 /// /// HTTP/2 settings /// public static Connections.HTTP2.HTTP2PluginSettings HTTP2Settings = new Connections.HTTP2.HTTP2PluginSettings(); #endif #region Global Options /// /// The maximum active TCP connections that the client will maintain to a server. Default value is 6. Minimum value is 1. /// public static byte MaxConnectionPerServer { get{ return maxConnectionPerServer; } set { if (value <= 0) throw new ArgumentOutOfRangeException("MaxConnectionPerServer must be greater than 0!"); bool isGrowing = value > maxConnectionPerServer; maxConnectionPerServer = value; // If the allowed connections per server is growing, go through all hosts and try to send out queueud requests. if (isGrowing) HostManager.TryToSendQueuedRequests(); } } private static byte maxConnectionPerServer; /// /// Default value of a HTTP request's IsKeepAlive value. Default value is true. If you make rare request to the server it should be changed to false. /// public static bool KeepAliveDefaultValue { get; set; } #if !BESTHTTP_DISABLE_CACHING /// /// Set to true, if caching is prohibited. /// public static bool IsCachingDisabled { get; set; } #endif /// /// How many time must be passed to destroy that connection after a connection finished its last request. Its default value is 20 seconds. /// public static TimeSpan MaxConnectionIdleTime { get; set; } #if !BESTHTTP_DISABLE_COOKIES /// /// Set to false to disable all Cookie. It's default value is true. /// public static bool IsCookiesEnabled { get; set; } #endif /// /// Size of the Cookie Jar in bytes. It's default value is 10485760 (10 MB). /// public static uint CookieJarSize { get; set; } /// /// If this property is set to true, then new cookies treated as session cookies and these cookies are not saved to disk. Its default value is false; /// public static bool EnablePrivateBrowsing { get; set; } /// /// Global, default value of the HTTPRequest's ConnectTimeout property. If set to TimeSpan.Zero or lower, no connect timeout logic is executed. Default value is 20 seconds. /// public static TimeSpan ConnectTimeout { get; set; } /// /// Global, default value of the HTTPRequest's Timeout property. Default value is 60 seconds. /// public static TimeSpan RequestTimeout { get; set; } /// /// 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 System.Func RootCacheFolderProvider { get; set; } #if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR) public static Proxies.Autodetect.ProxyDetector ProxyDetector { get => _proxyDetector; set { _proxyDetector?.Detach(); _proxyDetector = value; } } private static Proxies.Autodetect.ProxyDetector _proxyDetector; /// /// The global, default proxy for all HTTPRequests. The HTTPRequest's Proxy still can be changed per-request. Default value is null. /// public static Proxy Proxy { get; set; } #endif /// /// 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 BestHTTP.Logger.ILogger implementation to be able to log intelligently additional informations about the plugin's internal mechanism. /// public static BestHTTP.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 BestHTTP.Logger.ILogger logger; #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR) public static TlsClientFactoryDelegate TlsClientFactory; public static Connections.TLS.AbstractTls13Client DefaultTlsClientFactory(HTTPRequest request, List protocols) { // http://tools.ietf.org/html/rfc3546#section-3.1 // -It is RECOMMENDED that clients include an extension of type "server_name" in the client hello whenever they locate a server by a supported name type. // -Literal IPv4 and IPv6 addresses are not permitted in "HostName". // User-defined list has a higher priority List hostNames = null; // If there's no user defined one and the host isn't an IP address, add the default one if (!request.CurrentUri.IsHostIsAnIPAddress()) { hostNames = new List(1); hostNames.Add(new SecureProtocol.Org.BouncyCastle.Tls.ServerName(0, System.Text.Encoding.UTF8.GetBytes(request.CurrentUri.Host))); } return new Connections.TLS.DefaultTls13Client(request, hostNames, protocols); } /// /// The default value for the HTTPRequest's UseAlternateSSL property. /// public static bool UseAlternateSSLDefaultValue { get; set; } #endif #if !NETFX_CORE public static Func DefaultCertificationValidator; public static ClientCertificateSelector ClientCertificationProvider; #endif /// /// TCP Client's send buffer size. /// public static int? SendBufferSize; /// /// TCP Client's receive buffer size. /// public static int? ReceiveBufferSize; /// /// An IIOService implementation to handle filesystem operations. /// public static PlatformSupport.FileSystem.IIOService IOService; /// /// On most systems the maximum length of a path is around 255 character. If a cache entity's path is longer than this value it doesn't get cached. There no platform independent API to query the exact value on the current system, but it's /// exposed here and can be overridden. It's default value is 255. /// internal static int MaxPathLength { get; set; } /// /// User-agent string that will be sent with each requests. /// public static string UserAgent = "BestHTTP/2 v2.8.4"; /// /// 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; #endregion #region Manager variables private static bool IsSetupCalled; #endregion #region Public Interface public static void Setup() { if (IsSetupCalled) return; IsSetupCalled = true; IsQuitting = false; HTTPManager.Logger.Information("HTTPManager", "Setup called! UserAgent: " + UserAgent); HTTPUpdateDelegator.CheckInstance(); #if !BESTHTTP_DISABLE_CACHING HTTPCacheService.CheckSetup(); #endif #if !BESTHTTP_DISABLE_COOKIES Cookies.CookieJar.SetupFolder(); Cookies.CookieJar.Load(); #endif HostManager.Load(); } public static HTTPRequest SendRequest(string url, OnRequestFinishedDelegate callback) { return SendRequest(new HTTPRequest(new Uri(url), HTTPMethods.Get, callback)); } public static HTTPRequest SendRequest(string url, HTTPMethods methodType, OnRequestFinishedDelegate callback) { return SendRequest(new HTTPRequest(new Uri(url), methodType, callback)); } public static HTTPRequest SendRequest(string url, HTTPMethods methodType, bool isKeepAlive, OnRequestFinishedDelegate callback) { return SendRequest(new HTTPRequest(new Uri(url), methodType, isKeepAlive, callback)); } public static HTTPRequest SendRequest(string url, HTTPMethods methodType, bool isKeepAlive, bool disableCache, OnRequestFinishedDelegate callback) { return SendRequest(new HTTPRequest(new Uri(url), methodType, isKeepAlive, disableCache, callback)); } public static HTTPRequest SendRequest(HTTPRequest request) { if (!IsSetupCalled) Setup(); if (request.IsCancellationRequested || IsQuitting) return request; #if !BESTHTTP_DISABLE_CACHING // If possible load the full response from cache. if (Caching.HTTPCacheService.IsCachedEntityExpiresInTheFuture(request)) { DateTime started = DateTime.Now; PlatformSupport.Threading.ThreadedRunner.RunShortLiving((req) => { if (Connections.ConnectionHelper.TryLoadAllFromCache("HTTPManager", req, req.Context)) { req.Timing.Add("Full Cache Load", DateTime.Now - started); req.State = HTTPRequestStates.Finished; } else { // If for some reason it couldn't load we place back the request to the queue. request.State = HTTPRequestStates.Queued; RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(request, RequestEvents.Resend)); } }, request); } else #endif { request.State = HTTPRequestStates.Queued; RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(request, RequestEvents.Resend)); } return request; } #endregion #region Internal Helper Functions /// /// Will return where the various caches should be saved. /// public static string GetRootCacheFolder() { try { if (RootCacheFolderProvider != null) return RootCacheFolderProvider(); } catch(Exception ex) { HTTPManager.Logger.Exception("HTTPManager", "GetRootCacheFolder", ex); } #if NETFX_CORE return Windows.Storage.ApplicationData.Current.LocalFolder.Path; #else return UnityEngine.Application.persistentDataPath; #endif } #if UNITY_EDITOR #if UNITY_2019_3_OR_NEWER [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)] #endif public static void ResetSetup() { IsSetupCalled = false; BufferedReadNetworkStream.ResetNetworkStats(); HTTPManager.Logger.Information("HTTPManager", "Reset called!"); } #endif #endregion #region MonoBehaviour Events (Called from HTTPUpdateDelegator) /// /// Update function that should be called regularly from a Unity event(Update, LateUpdate). Callbacks are dispatched from this function. /// public static void OnUpdate() { using (var _ = new ProfilerMarker(nameof(RequestEventHelper)).Auto()) RequestEventHelper.ProcessQueue(); using (var _ = new ProfilerMarker(nameof(ConnectionEventHelper)).Auto()) ConnectionEventHelper.ProcessQueue(); using (var _ = new ProfilerMarker(nameof(ProtocolEventHelper)).Auto()) ProtocolEventHelper.ProcessQueue(); using (var _ = new ProfilerMarker(nameof(PluginEventHelper)).Auto()) PluginEventHelper.ProcessQueue(); using (var _ = new ProfilerMarker(nameof(Timer)).Auto()) BestHTTP.Extensions.Timer.Process(); if (heartbeats != null) { using (var _ = new ProfilerMarker(nameof(HeartbeatManager)).Auto()) heartbeats.Update(); } using (var _ = new ProfilerMarker(nameof(BufferPool)).Auto()) BufferPool.Maintain(); using (var _ = new ProfilerMarker(nameof(StringBuilderPool)).Auto()) StringBuilderPool.Maintain(); } public static void OnQuit() { HTTPManager.Logger.Information("HTTPManager", "OnQuit called!"); IsQuitting = true; AbortAll(); #if !BESTHTTP_DISABLE_CACHING HTTPCacheService.SaveLibrary(); #endif #if !BESTHTTP_DISABLE_COOKIES CookieJar.Persist(); #endif OnUpdate(); HostManager.Clear(); Heartbeats.Clear(); } public static void AbortAll() { HTTPManager.Logger.Information("HTTPManager", "AbortAll called!"); // This is an immediate shutdown request! RequestEventHelper.Clear(); ConnectionEventHelper.Clear(); PluginEventHelper.Clear(); ProtocolEventHelper.Clear(); HostManager.Shutdown(); ProtocolEventHelper.CancelActiveProtocols(); } #endregion } }