You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
461 lines
16 KiB
461 lines
16 KiB
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; |
|
|
|
#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<SecureProtocol.Org.BouncyCastle.Tls.ProtocolName> 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); |
|
|
|
/// <summary> |
|
/// |
|
/// </summary> |
|
[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(); |
|
//#elif UNITY_WEBGL && !UNITY_EDITOR |
|
// IOService = new PlatformSupport.FileSystem.WebGLIOService(); |
|
#else |
|
IOService = new PlatformSupport.FileSystem.DefaultIOService(); |
|
#endif |
|
} |
|
|
|
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2 |
|
/// <summary> |
|
/// HTTP/2 settings |
|
/// </summary> |
|
public static Connections.HTTP2.HTTP2PluginSettings HTTP2Settings = new Connections.HTTP2.HTTP2PluginSettings(); |
|
#endif |
|
|
|
#region Global Options |
|
|
|
/// <summary> |
|
/// The maximum active TCP connections that the client will maintain to a server. Default value is 6. Minimum value is 1. |
|
/// </summary> |
|
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; |
|
|
|
/// <summary> |
|
/// 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. |
|
/// </summary> |
|
public static bool KeepAliveDefaultValue { get; set; } |
|
|
|
#if !BESTHTTP_DISABLE_CACHING |
|
/// <summary> |
|
/// Set to true, if caching is prohibited. |
|
/// </summary> |
|
public static bool IsCachingDisabled { get; set; } |
|
#endif |
|
|
|
/// <summary> |
|
/// How many time must be passed to destroy that connection after a connection finished its last request. Its default value is 20 seconds. |
|
/// </summary> |
|
public static TimeSpan MaxConnectionIdleTime { get; set; } |
|
|
|
#if !BESTHTTP_DISABLE_COOKIES |
|
/// <summary> |
|
/// Set to false to disable all Cookie. It's default value is true. |
|
/// </summary> |
|
public static bool IsCookiesEnabled { get; set; } |
|
#endif |
|
|
|
/// <summary> |
|
/// Size of the Cookie Jar in bytes. It's default value is 10485760 (10 MB). |
|
/// </summary> |
|
public static uint CookieJarSize { get; set; } |
|
|
|
/// <summary> |
|
/// 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; |
|
/// </summary> |
|
public static bool EnablePrivateBrowsing { get; set; } |
|
|
|
/// <summary> |
|
/// 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. |
|
/// </summary> |
|
public static TimeSpan ConnectTimeout { get; set; } |
|
|
|
/// <summary> |
|
/// Global, default value of the HTTPRequest's Timeout property. Default value is 60 seconds. |
|
/// </summary> |
|
public static TimeSpan RequestTimeout { get; set; } |
|
|
|
/// <summary> |
|
/// 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. |
|
/// <remarks>This delegate will be called on a non Unity thread!</remarks> |
|
/// </summary> |
|
public static System.Func<string> RootCacheFolderProvider { get; set; } |
|
|
|
#if !BESTHTTP_DISABLE_PROXY |
|
/// <summary> |
|
/// The global, default proxy for all HTTPRequests. The HTTPRequest's Proxy still can be changed per-request. Default value is null. |
|
/// </summary> |
|
public static Proxy Proxy { get; set; } |
|
#endif |
|
|
|
/// <summary> |
|
/// Heartbeat manager to use less threads in the plugin. The heartbeat updates are called from the OnUpdate function. |
|
/// </summary> |
|
public static HeartbeatManager Heartbeats |
|
{ |
|
get |
|
{ |
|
if (heartbeats == null) |
|
heartbeats = new HeartbeatManager(); |
|
return heartbeats; |
|
} |
|
} |
|
private static HeartbeatManager heartbeats; |
|
|
|
/// <summary> |
|
/// A basic BestHTTP.Logger.ILogger implementation to be able to log intelligently additional informations about the plugin's internal mechanism. |
|
/// </summary> |
|
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<SecureProtocol.Org.BouncyCastle.Tls.ProtocolName> 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<SecureProtocol.Org.BouncyCastle.Tls.ServerName> 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<SecureProtocol.Org.BouncyCastle.Tls.ServerName>(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); |
|
} |
|
|
|
/// <summary> |
|
/// The default value for the HTTPRequest's UseAlternateSSL property. |
|
/// </summary> |
|
public static bool UseAlternateSSLDefaultValue { get; set; } |
|
#endif |
|
|
|
#if !NETFX_CORE |
|
public static Func<HTTPRequest, System.Security.Cryptography.X509Certificates.X509Certificate, System.Security.Cryptography.X509Certificates.X509Chain, System.Net.Security.SslPolicyErrors, bool> DefaultCertificationValidator; |
|
public static ClientCertificateSelector ClientCertificationProvider; |
|
#endif |
|
|
|
/// <summary> |
|
/// TCP Client's send buffer size. |
|
/// </summary> |
|
public static int? SendBufferSize; |
|
|
|
/// <summary> |
|
/// TCP Client's receive buffer size. |
|
/// </summary> |
|
public static int? ReceiveBufferSize; |
|
|
|
/// <summary> |
|
/// An IIOService implementation to handle filesystem operations. |
|
/// </summary> |
|
public static PlatformSupport.FileSystem.IIOService IOService; |
|
|
|
/// <summary> |
|
/// 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. |
|
/// </summary> |
|
internal static int MaxPathLength { get; set; } |
|
|
|
/// <summary> |
|
/// User-agent string that will be sent with each requests. |
|
/// </summary> |
|
public static string UserAgent = "BestHTTP/2 v2.7.0"; |
|
|
|
/// <summary> |
|
/// It's true if the application is quitting and the plugin is shutting down itself. |
|
/// </summary> |
|
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<HTTPRequest>((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 |
|
|
|
/// <summary> |
|
/// Will return where the various caches should be saved. |
|
/// </summary> |
|
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) |
|
|
|
/// <summary> |
|
/// Update function that should be called regularly from a Unity event(Update, LateUpdate). Callbacks are dispatched from this function. |
|
/// </summary> |
|
public static void OnUpdate() |
|
{ |
|
RequestEventHelper.ProcessQueue(); |
|
ConnectionEventHelper.ProcessQueue(); |
|
ProtocolEventHelper.ProcessQueue(); |
|
PluginEventHelper.ProcessQueue(); |
|
|
|
BestHTTP.Extensions.Timer.Process(); |
|
|
|
if (heartbeats != null) |
|
heartbeats.Update(); |
|
|
|
BufferPool.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 |
|
} |
|
}
|
|
|