HTTPManager.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. using System;
  2. using System.Collections.Generic;
  3. #if !BESTHTTP_DISABLE_CACHING
  4. using BestHTTP.Caching;
  5. #endif
  6. using BestHTTP.Core;
  7. using BestHTTP.Extensions;
  8. using BestHTTP.Logger;
  9. using BestHTTP.PlatformSupport.Memory;
  10. using BestHTTP.PlatformSupport.Text;
  11. using Unity.Profiling;
  12. #if !BESTHTTP_DISABLE_COOKIES
  13. using BestHTTP.Cookies;
  14. #endif
  15. using BestHTTP.Connections;
  16. namespace BestHTTP
  17. {
  18. public enum ShutdownTypes
  19. {
  20. Running,
  21. Gentle,
  22. Immediate
  23. }
  24. #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
  25. public delegate Connections.TLS.AbstractTls13Client TlsClientFactoryDelegate(HTTPRequest request, List<SecureProtocol.Org.BouncyCastle.Tls.ProtocolName> protocols);
  26. #endif
  27. 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);
  28. /// <summary>
  29. ///
  30. /// </summary>
  31. [BestHTTP.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstructionAttribute]
  32. public static partial class HTTPManager
  33. {
  34. // Static constructor. Setup default values
  35. static HTTPManager()
  36. {
  37. MaxConnectionPerServer = 6;
  38. KeepAliveDefaultValue = true;
  39. MaxPathLength = 255;
  40. MaxConnectionIdleTime = TimeSpan.FromSeconds(20);
  41. #if !BESTHTTP_DISABLE_COOKIES
  42. #if UNITY_WEBGL && !UNITY_EDITOR
  43. // Under webgl when IsCookiesEnabled is true, it will set the withCredentials flag for the XmlHTTPRequest
  44. // and that's different from the default behavior.
  45. // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials
  46. IsCookiesEnabled = false;
  47. #else
  48. IsCookiesEnabled = true;
  49. #endif
  50. #endif
  51. CookieJarSize = 10 * 1024 * 1024;
  52. EnablePrivateBrowsing = false;
  53. ConnectTimeout = TimeSpan.FromSeconds(20);
  54. RequestTimeout = TimeSpan.FromSeconds(60);
  55. // Set the default logger mechanism
  56. logger = new BestHTTP.Logger.ThreadedLogger();
  57. #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
  58. UseAlternateSSLDefaultValue = true;
  59. #endif
  60. #if NETFX_CORE
  61. IOService = new PlatformSupport.FileSystem.NETFXCOREIOService();
  62. #else
  63. IOService = new PlatformSupport.FileSystem.DefaultIOService();
  64. #endif
  65. #if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR)
  66. ProxyDetector = new Proxies.Autodetect.ProxyDetector();
  67. #endif
  68. }
  69. #if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
  70. /// <summary>
  71. /// HTTP/2 settings
  72. /// </summary>
  73. public static Connections.HTTP2.HTTP2PluginSettings HTTP2Settings = new Connections.HTTP2.HTTP2PluginSettings();
  74. #endif
  75. #region Global Options
  76. /// <summary>
  77. /// The maximum active TCP connections that the client will maintain to a server. Default value is 6. Minimum value is 1.
  78. /// </summary>
  79. public static byte MaxConnectionPerServer
  80. {
  81. get{ return maxConnectionPerServer; }
  82. set
  83. {
  84. if (value <= 0)
  85. throw new ArgumentOutOfRangeException("MaxConnectionPerServer must be greater than 0!");
  86. bool isGrowing = value > maxConnectionPerServer;
  87. maxConnectionPerServer = value;
  88. // If the allowed connections per server is growing, go through all hosts and try to send out queueud requests.
  89. if (isGrowing)
  90. HostManager.TryToSendQueuedRequests();
  91. }
  92. }
  93. private static byte maxConnectionPerServer;
  94. /// <summary>
  95. /// 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.
  96. /// </summary>
  97. public static bool KeepAliveDefaultValue { get; set; }
  98. #if !BESTHTTP_DISABLE_CACHING
  99. /// <summary>
  100. /// Set to true, if caching is prohibited.
  101. /// </summary>
  102. public static bool IsCachingDisabled { get; set; }
  103. #endif
  104. /// <summary>
  105. /// How many time must be passed to destroy that connection after a connection finished its last request. Its default value is 20 seconds.
  106. /// </summary>
  107. public static TimeSpan MaxConnectionIdleTime { get; set; }
  108. #if !BESTHTTP_DISABLE_COOKIES
  109. /// <summary>
  110. /// Set to false to disable all Cookie. It's default value is true.
  111. /// </summary>
  112. public static bool IsCookiesEnabled { get; set; }
  113. #endif
  114. /// <summary>
  115. /// Size of the Cookie Jar in bytes. It's default value is 10485760 (10 MB).
  116. /// </summary>
  117. public static uint CookieJarSize { get; set; }
  118. /// <summary>
  119. /// 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;
  120. /// </summary>
  121. public static bool EnablePrivateBrowsing { get; set; }
  122. /// <summary>
  123. /// 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.
  124. /// </summary>
  125. public static TimeSpan ConnectTimeout { get; set; }
  126. /// <summary>
  127. /// Global, default value of the HTTPRequest's Timeout property. Default value is 60 seconds.
  128. /// </summary>
  129. public static TimeSpan RequestTimeout { get; set; }
  130. /// <summary>
  131. /// By default the plugin will save all cache and cookie data under the path returned by Application.persistentDataPath.
  132. /// You can assign a function to this delegate to return a custom root path to define a new path.
  133. /// <remarks>This delegate will be called on a non Unity thread!</remarks>
  134. /// </summary>
  135. public static System.Func<string> RootCacheFolderProvider { get; set; }
  136. #if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR)
  137. public static Proxies.Autodetect.ProxyDetector ProxyDetector {
  138. get => _proxyDetector;
  139. set {
  140. _proxyDetector?.Detach();
  141. _proxyDetector = value;
  142. }
  143. }
  144. private static Proxies.Autodetect.ProxyDetector _proxyDetector;
  145. /// <summary>
  146. /// The global, default proxy for all HTTPRequests. The HTTPRequest's Proxy still can be changed per-request. Default value is null.
  147. /// </summary>
  148. public static Proxy Proxy { get; set; }
  149. #endif
  150. /// <summary>
  151. /// Heartbeat manager to use less threads in the plugin. The heartbeat updates are called from the OnUpdate function.
  152. /// </summary>
  153. public static HeartbeatManager Heartbeats
  154. {
  155. get
  156. {
  157. if (heartbeats == null)
  158. heartbeats = new HeartbeatManager();
  159. return heartbeats;
  160. }
  161. }
  162. private static HeartbeatManager heartbeats;
  163. /// <summary>
  164. /// A basic BestHTTP.Logger.ILogger implementation to be able to log intelligently additional informations about the plugin's internal mechanism.
  165. /// </summary>
  166. public static BestHTTP.Logger.ILogger Logger
  167. {
  168. get
  169. {
  170. // Make sure that it has a valid logger instance.
  171. if (logger == null)
  172. {
  173. logger = new ThreadedLogger();
  174. logger.Level = Loglevels.None;
  175. }
  176. return logger;
  177. }
  178. set { logger = value; }
  179. }
  180. private static BestHTTP.Logger.ILogger logger;
  181. #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
  182. public static TlsClientFactoryDelegate TlsClientFactory;
  183. public static Connections.TLS.AbstractTls13Client DefaultTlsClientFactory(HTTPRequest request, List<SecureProtocol.Org.BouncyCastle.Tls.ProtocolName> protocols)
  184. {
  185. // http://tools.ietf.org/html/rfc3546#section-3.1
  186. // -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.
  187. // -Literal IPv4 and IPv6 addresses are not permitted in "HostName".
  188. // User-defined list has a higher priority
  189. List<SecureProtocol.Org.BouncyCastle.Tls.ServerName> hostNames = null;
  190. // If there's no user defined one and the host isn't an IP address, add the default one
  191. if (!request.CurrentUri.IsHostIsAnIPAddress())
  192. {
  193. hostNames = new List<SecureProtocol.Org.BouncyCastle.Tls.ServerName>(1);
  194. hostNames.Add(new SecureProtocol.Org.BouncyCastle.Tls.ServerName(0, System.Text.Encoding.UTF8.GetBytes(request.CurrentUri.Host)));
  195. }
  196. return new Connections.TLS.DefaultTls13Client(request, hostNames, protocols);
  197. }
  198. /// <summary>
  199. /// The default value for the HTTPRequest's UseAlternateSSL property.
  200. /// </summary>
  201. public static bool UseAlternateSSLDefaultValue { get; set; }
  202. #endif
  203. #if !NETFX_CORE
  204. public static Func<HTTPRequest, System.Security.Cryptography.X509Certificates.X509Certificate, System.Security.Cryptography.X509Certificates.X509Chain, System.Net.Security.SslPolicyErrors, bool> DefaultCertificationValidator;
  205. public static ClientCertificateSelector ClientCertificationProvider;
  206. #endif
  207. /// <summary>
  208. /// TCP Client's send buffer size.
  209. /// </summary>
  210. public static int? SendBufferSize;
  211. /// <summary>
  212. /// TCP Client's receive buffer size.
  213. /// </summary>
  214. public static int? ReceiveBufferSize;
  215. /// <summary>
  216. /// An IIOService implementation to handle filesystem operations.
  217. /// </summary>
  218. public static PlatformSupport.FileSystem.IIOService IOService;
  219. /// <summary>
  220. /// 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
  221. /// exposed here and can be overridden. It's default value is 255.
  222. /// </summary>
  223. internal static int MaxPathLength { get; set; }
  224. /// <summary>
  225. /// User-agent string that will be sent with each requests.
  226. /// </summary>
  227. public static string UserAgent = "BestHTTP/2 v2.8.5";
  228. /// <summary>
  229. /// It's true if the application is quitting and the plugin is shutting down itself.
  230. /// </summary>
  231. public static bool IsQuitting { get { return _isQuitting; } private set { _isQuitting = value; } }
  232. private static volatile bool _isQuitting;
  233. #endregion
  234. #region Manager variables
  235. private static bool IsSetupCalled;
  236. #endregion
  237. #region Public Interface
  238. public static void Setup()
  239. {
  240. if (IsSetupCalled)
  241. return;
  242. IsSetupCalled = true;
  243. IsQuitting = false;
  244. HTTPManager.Logger.Information("HTTPManager", "Setup called! UserAgent: " + UserAgent);
  245. HTTPUpdateDelegator.CheckInstance();
  246. #if !BESTHTTP_DISABLE_CACHING
  247. HTTPCacheService.CheckSetup();
  248. #endif
  249. #if !BESTHTTP_DISABLE_COOKIES
  250. Cookies.CookieJar.SetupFolder();
  251. Cookies.CookieJar.Load();
  252. #endif
  253. HostManager.Load();
  254. }
  255. public static HTTPRequest SendRequest(string url, OnRequestFinishedDelegate callback)
  256. {
  257. return SendRequest(new HTTPRequest(new Uri(url), HTTPMethods.Get, callback));
  258. }
  259. public static HTTPRequest SendRequest(string url, HTTPMethods methodType, OnRequestFinishedDelegate callback)
  260. {
  261. return SendRequest(new HTTPRequest(new Uri(url), methodType, callback));
  262. }
  263. public static HTTPRequest SendRequest(string url, HTTPMethods methodType, bool isKeepAlive, OnRequestFinishedDelegate callback)
  264. {
  265. return SendRequest(new HTTPRequest(new Uri(url), methodType, isKeepAlive, callback));
  266. }
  267. public static HTTPRequest SendRequest(string url, HTTPMethods methodType, bool isKeepAlive, bool disableCache, OnRequestFinishedDelegate callback)
  268. {
  269. return SendRequest(new HTTPRequest(new Uri(url), methodType, isKeepAlive, disableCache, callback));
  270. }
  271. public static HTTPRequest SendRequest(HTTPRequest request)
  272. {
  273. if (!IsSetupCalled)
  274. Setup();
  275. if (request.IsCancellationRequested || IsQuitting)
  276. return request;
  277. #if !BESTHTTP_DISABLE_CACHING
  278. // If possible load the full response from cache.
  279. if (Caching.HTTPCacheService.IsCachedEntityExpiresInTheFuture(request))
  280. {
  281. DateTime started = DateTime.Now;
  282. PlatformSupport.Threading.ThreadedRunner.RunShortLiving<HTTPRequest>((req) =>
  283. {
  284. if (Connections.ConnectionHelper.TryLoadAllFromCache("HTTPManager", req, req.Context))
  285. {
  286. req.Timing.Add("Full Cache Load", DateTime.Now - started);
  287. req.State = HTTPRequestStates.Finished;
  288. }
  289. else
  290. {
  291. // If for some reason it couldn't load we place back the request to the queue.
  292. request.State = HTTPRequestStates.Queued;
  293. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(request, RequestEvents.Resend));
  294. }
  295. }, request);
  296. }
  297. else
  298. #endif
  299. {
  300. request.State = HTTPRequestStates.Queued;
  301. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(request, RequestEvents.Resend));
  302. }
  303. return request;
  304. }
  305. #endregion
  306. #region Internal Helper Functions
  307. /// <summary>
  308. /// Will return where the various caches should be saved.
  309. /// </summary>
  310. public static string GetRootCacheFolder()
  311. {
  312. try
  313. {
  314. if (RootCacheFolderProvider != null)
  315. return RootCacheFolderProvider();
  316. }
  317. catch(Exception ex)
  318. {
  319. HTTPManager.Logger.Exception("HTTPManager", "GetRootCacheFolder", ex);
  320. }
  321. #if NETFX_CORE
  322. return Windows.Storage.ApplicationData.Current.LocalFolder.Path;
  323. #else
  324. return UnityEngine.Application.persistentDataPath;
  325. #endif
  326. }
  327. #if UNITY_EDITOR
  328. #if UNITY_2019_3_OR_NEWER
  329. [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)]
  330. #endif
  331. public static void ResetSetup()
  332. {
  333. IsSetupCalled = false;
  334. BufferedReadNetworkStream.ResetNetworkStats();
  335. HTTPManager.Logger.Information("HTTPManager", "Reset called!");
  336. }
  337. #endif
  338. #endregion
  339. #region MonoBehaviour Events (Called from HTTPUpdateDelegator)
  340. /// <summary>
  341. /// Update function that should be called regularly from a Unity event(Update, LateUpdate). Callbacks are dispatched from this function.
  342. /// </summary>
  343. public static void OnUpdate()
  344. {
  345. using (var _ = new ProfilerMarker(nameof(RequestEventHelper)).Auto())
  346. RequestEventHelper.ProcessQueue();
  347. using (var _ = new ProfilerMarker(nameof(ConnectionEventHelper)).Auto())
  348. ConnectionEventHelper.ProcessQueue();
  349. using (var _ = new ProfilerMarker(nameof(ProtocolEventHelper)).Auto())
  350. ProtocolEventHelper.ProcessQueue();
  351. using (var _ = new ProfilerMarker(nameof(PluginEventHelper)).Auto())
  352. PluginEventHelper.ProcessQueue();
  353. using (var _ = new ProfilerMarker(nameof(Timer)).Auto())
  354. BestHTTP.Extensions.Timer.Process();
  355. if (heartbeats != null)
  356. {
  357. using (var _ = new ProfilerMarker(nameof(HeartbeatManager)).Auto())
  358. heartbeats.Update();
  359. }
  360. using (var _ = new ProfilerMarker(nameof(BufferPool)).Auto())
  361. BufferPool.Maintain();
  362. using (var _ = new ProfilerMarker(nameof(StringBuilderPool)).Auto())
  363. StringBuilderPool.Maintain();
  364. }
  365. public static void OnQuit()
  366. {
  367. HTTPManager.Logger.Information("HTTPManager", "OnQuit called!");
  368. IsQuitting = true;
  369. AbortAll();
  370. #if !BESTHTTP_DISABLE_CACHING
  371. HTTPCacheService.SaveLibrary();
  372. #endif
  373. #if !BESTHTTP_DISABLE_COOKIES
  374. CookieJar.Persist();
  375. #endif
  376. OnUpdate();
  377. HostManager.Clear();
  378. Heartbeats.Clear();
  379. }
  380. public static void AbortAll()
  381. {
  382. HTTPManager.Logger.Information("HTTPManager", "AbortAll called!");
  383. // This is an immediate shutdown request!
  384. RequestEventHelper.Clear();
  385. ConnectionEventHelper.Clear();
  386. PluginEventHelper.Clear();
  387. ProtocolEventHelper.Clear();
  388. HostManager.Shutdown();
  389. ProtocolEventHelper.CancelActiveProtocols();
  390. }
  391. #endregion
  392. }
  393. }