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.

HTTPManager.cs 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  5. using BestHTTP.Caching;
  6. #endif
  7. using BestHTTP.Extensions;
  8. using BestHTTP.Logger;
  9. using BestHTTP.Statistics;
  10. namespace BestHTTP
  11. {
  12. /// <summary>
  13. ///
  14. /// </summary>
  15. public static class HTTPManager
  16. {
  17. // Static constructor. Setup default values
  18. static HTTPManager()
  19. {
  20. MaxConnectionPerServer = 4;
  21. KeepAliveDefaultValue = true;
  22. MaxPathLength = 255;
  23. MaxConnectionIdleTime = TimeSpan.FromSeconds(20);
  24. #if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR)
  25. IsCookiesEnabled = true;
  26. #endif
  27. CookieJarSize = 10 * 1024 * 1024;
  28. EnablePrivateBrowsing = false;
  29. ConnectTimeout = TimeSpan.FromSeconds(20);
  30. RequestTimeout = TimeSpan.FromSeconds(60);
  31. // Set the default logger mechanism
  32. logger = new BestHTTP.Logger.DefaultLogger();
  33. #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
  34. DefaultCertificateVerifyer = null;
  35. UseAlternateSSLDefaultValue = true;
  36. #endif
  37. }
  38. #region Global Options
  39. /// <summary>
  40. /// The maximum active TCP connections that the client will maintain to a server. Default value is 4. Minimum value is 1.
  41. /// </summary>
  42. public static byte MaxConnectionPerServer
  43. {
  44. get{ return maxConnectionPerServer; }
  45. set
  46. {
  47. if (value <= 0)
  48. throw new ArgumentOutOfRangeException("MaxConnectionPerServer must be greater than 0!");
  49. maxConnectionPerServer = value;
  50. }
  51. }
  52. private static byte maxConnectionPerServer;
  53. /// <summary>
  54. /// Default value of a HTTP request's IsKeepAlive value. Default value is true. If you make rare request to the server it's should be changed to false.
  55. /// </summary>
  56. public static bool KeepAliveDefaultValue { get; set; }
  57. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  58. /// <summary>
  59. /// Set to true, if caching is prohibited.
  60. /// </summary>
  61. public static bool IsCachingDisabled { get; set; }
  62. #endif
  63. /// <summary>
  64. /// How many time must be passed to destroy that connection after a connection finished its last request. Its default value is 20 seconds.
  65. /// </summary>
  66. public static TimeSpan MaxConnectionIdleTime { get; set; }
  67. #if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR)
  68. /// <summary>
  69. /// Set to false to disable all Cookie. It's default value is true.
  70. /// </summary>
  71. public static bool IsCookiesEnabled { get; set; }
  72. #endif
  73. /// <summary>
  74. /// Size of the Cookie Jar in bytes. It's default value is 10485760 (10 MB).
  75. /// </summary>
  76. public static uint CookieJarSize { get; set; }
  77. /// <summary>
  78. /// 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;
  79. /// </summary>
  80. public static bool EnablePrivateBrowsing { get; set; }
  81. /// <summary>
  82. /// 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.
  83. /// </summary>
  84. public static TimeSpan ConnectTimeout { get; set; }
  85. /// <summary>
  86. /// Global, default value of the HTTPRequest's Timeout property. Default value is 60 seconds.
  87. /// </summary>
  88. public static TimeSpan RequestTimeout { get; set; }
  89. #if !((BESTHTTP_DISABLE_CACHING && BESTHTTP_DISABLE_COOKIES) || (UNITY_WEBGL && !UNITY_EDITOR))
  90. /// <summary>
  91. /// By default the plugin will save all cache and cookie data under the path returned by Application.persistentDataPath.
  92. /// You can assign a function to this delegate to return a custom root path to define a new path.
  93. /// <remarks>This delegate will be called on a non Unity thread!</remarks>
  94. /// </summary>
  95. public static System.Func<string> RootCacheFolderProvider { get; set; }
  96. #endif
  97. #if !BESTHTTP_DISABLE_PROXY
  98. /// <summary>
  99. /// The global, default proxy for all HTTPRequests. The HTTPRequest's Proxy still can be changed per-request. Default value is null.
  100. /// </summary>
  101. public static HTTPProxy Proxy { get; set; }
  102. #endif
  103. /// <summary>
  104. /// Heartbeat manager to use less threads in the plugin. The heartbeat updates are called from the OnUpdate function.
  105. /// </summary>
  106. public static HeartbeatManager Heartbeats
  107. {
  108. get
  109. {
  110. if (heartbeats == null)
  111. heartbeats = new HeartbeatManager();
  112. return heartbeats;
  113. }
  114. }
  115. private static HeartbeatManager heartbeats;
  116. /// <summary>
  117. /// A basic BestHTTP.Logger.ILogger implementation to be able to log intelligently additional informations about the plugin's internal mechanism.
  118. /// </summary>
  119. public static BestHTTP.Logger.ILogger Logger
  120. {
  121. get
  122. {
  123. // Make sure that it has a valid logger instance.
  124. if (logger == null)
  125. {
  126. logger = new DefaultLogger();
  127. logger.Level = Loglevels.None;
  128. }
  129. return logger;
  130. }
  131. set { logger = value; }
  132. }
  133. private static BestHTTP.Logger.ILogger logger;
  134. #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
  135. /// <summary>
  136. /// The default ICertificateVerifyer implementation that the plugin will use when the request's UseAlternateSSL property is set to true.
  137. /// </summary>
  138. public static Org.BouncyCastle.Crypto.Tls.ICertificateVerifyer DefaultCertificateVerifyer { get; set; }
  139. /// <summary>
  140. /// The default IClientCredentialsProvider implementation that the plugin will use when the request's UseAlternateSSL property is set to true.
  141. /// </summary>
  142. public static Org.BouncyCastle.Crypto.Tls.IClientCredentialsProvider DefaultClientCredentialsProvider { get; set; }
  143. /// <summary>
  144. /// The default value for the HTTPRequest's UseAlternateSSL property.
  145. /// </summary>
  146. public static bool UseAlternateSSLDefaultValue { get; set; }
  147. #endif
  148. /// <summary>
  149. /// 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
  150. /// exposed here and can be overridden. It's default value is 255.
  151. /// </summary>
  152. internal static int MaxPathLength { get; set; }
  153. #endregion
  154. #region Manager variables
  155. /// <summary>
  156. /// All connection has a reference in this Dictionary untill it's removed completly.
  157. /// </summary>
  158. private static Dictionary<string, List<ConnectionBase>> Connections = new Dictionary<string, List<ConnectionBase>>();
  159. /// <summary>
  160. /// Active connections. These connections all has a request to process.
  161. /// </summary>
  162. private static List<ConnectionBase> ActiveConnections = new List<ConnectionBase>();
  163. /// <summary>
  164. /// Free connections. They can be removed completly after a specified time.
  165. /// </summary>
  166. private static List<ConnectionBase> FreeConnections = new List<ConnectionBase>();
  167. /// <summary>
  168. /// Connections that recycled in the Update loop. If they are not used in the same loop to process a request, they will be transferred to the FreeConnections list.
  169. /// </summary>
  170. private static List<ConnectionBase> RecycledConnections = new List<ConnectionBase>();
  171. /// <summary>
  172. /// List of request that have to wait until there is a free connection to the server.
  173. /// </summary>
  174. private static List<HTTPRequest> RequestQueue = new List<HTTPRequest>();
  175. private static bool IsCallingCallbacks;
  176. internal static System.Object Locker = new System.Object();
  177. #endregion
  178. #region Public Interface
  179. public static void Setup()
  180. {
  181. HTTPUpdateDelegator.CheckInstance();
  182. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  183. HTTPCacheService.CheckSetup();
  184. #endif
  185. #if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR)
  186. Cookies.CookieJar.SetupFolder();
  187. #endif
  188. }
  189. public static HTTPRequest SendRequest(string url, OnRequestFinishedDelegate callback)
  190. {
  191. return SendRequest(new HTTPRequest(new Uri(url), HTTPMethods.Get, callback));
  192. }
  193. public static HTTPRequest SendRequest(string url, HTTPMethods methodType, OnRequestFinishedDelegate callback)
  194. {
  195. return SendRequest(new HTTPRequest(new Uri(url), methodType, callback));
  196. }
  197. public static HTTPRequest SendRequest(string url, HTTPMethods methodType, bool isKeepAlive, OnRequestFinishedDelegate callback)
  198. {
  199. return SendRequest(new HTTPRequest(new Uri(url), methodType, isKeepAlive, callback));
  200. }
  201. public static HTTPRequest SendRequest(string url, HTTPMethods methodType, bool isKeepAlive, bool disableCache, OnRequestFinishedDelegate callback)
  202. {
  203. return SendRequest(new HTTPRequest(new Uri(url), methodType, isKeepAlive, disableCache, callback));
  204. }
  205. public static HTTPRequest SendRequest(HTTPRequest request)
  206. {
  207. lock (Locker)
  208. {
  209. Setup();
  210. if (IsCallingCallbacks)
  211. {
  212. request.State = HTTPRequestStates.Queued;
  213. RequestQueue.Add(request);
  214. }
  215. else
  216. SendRequestImpl(request);
  217. return request;
  218. }
  219. }
  220. public static GeneralStatistics GetGeneralStatistics(StatisticsQueryFlags queryFlags)
  221. {
  222. GeneralStatistics stat = new GeneralStatistics();
  223. stat.QueryFlags = queryFlags;
  224. if ((queryFlags & StatisticsQueryFlags.Connections) != 0)
  225. {
  226. int connections = 0;
  227. foreach(var conn in HTTPManager.Connections)
  228. {
  229. if (conn.Value != null)
  230. connections += conn.Value.Count;
  231. }
  232. #if !BESTHTTP_DISABLE_WEBSOCKET && UNITY_WEBGL && !UNITY_EDITOR
  233. connections += WebSocket.WebSocket.WebSockets.Count;
  234. #endif
  235. stat.Connections = connections;
  236. stat.ActiveConnections = ActiveConnections.Count
  237. #if !BESTHTTP_DISABLE_WEBSOCKET && UNITY_WEBGL && !UNITY_EDITOR
  238. + WebSocket.WebSocket.WebSockets.Count
  239. #endif
  240. ;
  241. stat.FreeConnections = FreeConnections.Count;
  242. stat.RecycledConnections = RecycledConnections.Count;
  243. stat.RequestsInQueue = RequestQueue.Count;
  244. }
  245. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  246. if ((queryFlags & StatisticsQueryFlags.Cache) != 0)
  247. {
  248. stat.CacheEntityCount = HTTPCacheService.GetCacheEntityCount();
  249. stat.CacheSize = HTTPCacheService.GetCacheSize();
  250. }
  251. #endif
  252. #if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR)
  253. if ((queryFlags & StatisticsQueryFlags.Cookies) != 0)
  254. {
  255. List<Cookies.Cookie> cookies = Cookies.CookieJar.GetAll();
  256. stat.CookieCount = cookies.Count;
  257. uint cookiesSize = 0;
  258. for (int i = 0; i < cookies.Count; ++i)
  259. cookiesSize += cookies[i].GuessSize();
  260. stat.CookieJarSize = cookiesSize;
  261. }
  262. #endif
  263. return stat;
  264. }
  265. #endregion
  266. #region Private Functions
  267. private static void SendRequestImpl(HTTPRequest request)
  268. {
  269. ConnectionBase conn = FindOrCreateFreeConnection(request);
  270. if (conn != null)
  271. {
  272. // found a free connection: put it in the ActiveConnection list(they will be checked periodically in the OnUpdate call)
  273. if (ActiveConnections.Find((c) => c == conn) == null)
  274. ActiveConnections.Add(conn);
  275. FreeConnections.Remove(conn);
  276. request.State = HTTPRequestStates.Processing;
  277. request.Prepare();
  278. // then start process the request
  279. conn.Process(request);
  280. }
  281. else
  282. {
  283. // If no free connection found and creation prohibited, we will put back to the queue
  284. request.State = HTTPRequestStates.Queued;
  285. RequestQueue.Add(request);
  286. }
  287. }
  288. private static string GetKeyForRequest(HTTPRequest request)
  289. {
  290. if (request.CurrentUri.IsFile)
  291. return request.CurrentUri.ToString();
  292. // proxyUri + requestUri
  293. // HTTP and HTTPS needs different connections.
  294. return
  295. #if !BESTHTTP_DISABLE_PROXY
  296. (request.Proxy != null ? new UriBuilder(request.Proxy.Address.Scheme, request.Proxy.Address.Host, request.Proxy.Address.Port).Uri.ToString() : string.Empty) +
  297. #endif
  298. new UriBuilder(request.CurrentUri.Scheme, request.CurrentUri.Host, request.CurrentUri.Port).Uri.ToString();
  299. }
  300. /// <summary>
  301. /// Factory method to create a concrete connection object.
  302. /// </summary>
  303. private static ConnectionBase CreateConnection(HTTPRequest request, string serverUrl)
  304. {
  305. if (request.CurrentUri.IsFile)
  306. return new FileConnection(serverUrl);
  307. #if UNITY_WEBGL && !UNITY_EDITOR
  308. return new WebGLConnection(serverUrl);
  309. #else
  310. return new HTTPConnection(serverUrl);
  311. #endif
  312. }
  313. private static ConnectionBase FindOrCreateFreeConnection(HTTPRequest request)
  314. {
  315. ConnectionBase conn = null;
  316. List<ConnectionBase> connections;
  317. string serverUrl = GetKeyForRequest(request);
  318. if (Connections.TryGetValue(serverUrl, out connections))
  319. {
  320. // count active connections
  321. int activeConnections = 0;
  322. for (int i = 0; i < connections.Count; ++i)
  323. if (connections[i].IsActive)
  324. activeConnections++;
  325. if (activeConnections <= MaxConnectionPerServer)
  326. // search for a Free connection
  327. for (int i = 0; i < connections.Count && conn == null; ++i)
  328. {
  329. var tmpConn = connections[i];
  330. if (tmpConn != null &&
  331. tmpConn.IsFree &&
  332. (
  333. #if !BESTHTTP_DISABLE_PROXY
  334. !tmpConn.HasProxy ||
  335. #endif
  336. tmpConn.LastProcessedUri == null ||
  337. tmpConn.LastProcessedUri.Host.Equals(request.CurrentUri.Host, StringComparison.OrdinalIgnoreCase)))
  338. conn = tmpConn;
  339. }
  340. }
  341. else
  342. Connections.Add(serverUrl, connections = new List<ConnectionBase>(MaxConnectionPerServer));
  343. // No free connection found?
  344. if (conn == null)
  345. {
  346. // Max connection reached?
  347. if (connections.Count >= MaxConnectionPerServer)
  348. return null;
  349. // if no, create a new one
  350. connections.Add(conn = CreateConnection(request, serverUrl));
  351. }
  352. return conn;
  353. }
  354. /// <summary>
  355. /// Will return with true when there at least one request that can be processed from the RequestQueue.
  356. /// </summary>
  357. private static bool CanProcessFromQueue()
  358. {
  359. for (int i = 0; i < RequestQueue.Count; ++i)
  360. if (FindOrCreateFreeConnection(RequestQueue[i]) != null)
  361. return true;
  362. return false;
  363. }
  364. private static void RecycleConnection(ConnectionBase conn)
  365. {
  366. conn.Recycle(OnConnectionRecylced);
  367. }
  368. private static void OnConnectionRecylced(ConnectionBase conn)
  369. {
  370. lock (RecycledConnections)
  371. {
  372. RecycledConnections.Add(conn);
  373. }
  374. }
  375. #endregion
  376. #region Internal Helper Functions
  377. /// <summary>
  378. /// Will return the ConnectionBase object that processing the given request.
  379. /// </summary>
  380. internal static ConnectionBase GetConnectionWith(HTTPRequest request)
  381. {
  382. lock (Locker)
  383. {
  384. for (int i = 0; i < ActiveConnections.Count; ++i)
  385. {
  386. var connection = ActiveConnections[i];
  387. if (connection.CurrentRequest == request)
  388. return connection;
  389. }
  390. return null;
  391. }
  392. }
  393. internal static bool RemoveFromQueue(HTTPRequest request)
  394. {
  395. return RequestQueue.Remove(request);
  396. }
  397. #if !((BESTHTTP_DISABLE_CACHING && BESTHTTP_DISABLE_COOKIES) || (UNITY_WEBGL && !UNITY_EDITOR))
  398. /// <summary>
  399. /// Will return where the various caches should be saved.
  400. /// </summary>
  401. internal static string GetRootCacheFolder()
  402. {
  403. try
  404. {
  405. if (RootCacheFolderProvider != null)
  406. return RootCacheFolderProvider();
  407. }
  408. catch(Exception ex)
  409. {
  410. HTTPManager.Logger.Exception("HTTPManager", "GetRootCacheFolder", ex);
  411. }
  412. #if NETFX_CORE
  413. return Windows.Storage.ApplicationData.Current.LocalFolder.Path;
  414. #else
  415. return Application.persistentDataPath;
  416. #endif
  417. }
  418. #endif
  419. #endregion
  420. #region MonoBehaviour Events (Called from HTTPUpdateDelegator)
  421. /// <summary>
  422. /// Update function that should be called regularly from a Unity event(Update, LateUpdate). Callbacks are dispatched from this function.
  423. /// </summary>
  424. public static void OnUpdate()
  425. {
  426. // We will try to acquire a lock. If it fails, we will skip this frame without calling any callback.
  427. if (System.Threading.Monitor.TryEnter(Locker))
  428. {
  429. try
  430. {
  431. IsCallingCallbacks = true;
  432. try
  433. {
  434. for (int i = 0; i < ActiveConnections.Count; ++i)
  435. {
  436. ConnectionBase conn = ActiveConnections[i];
  437. switch (conn.State)
  438. {
  439. case HTTPConnectionStates.Processing:
  440. conn.HandleProgressCallback();
  441. if (conn.CurrentRequest.UseStreaming && conn.CurrentRequest.Response != null && conn.CurrentRequest.Response.HasStreamedFragments())
  442. conn.HandleCallback();
  443. try
  444. {
  445. if (((!conn.CurrentRequest.UseStreaming && conn.CurrentRequest.UploadStream == null) || conn.CurrentRequest.EnableTimoutForStreaming) &&
  446. DateTime.UtcNow - conn.StartTime > conn.CurrentRequest.Timeout)
  447. conn.Abort(HTTPConnectionStates.TimedOut);
  448. }
  449. catch (OverflowException)
  450. {
  451. HTTPManager.Logger.Warning("HTTPManager", "TimeSpan overflow");
  452. }
  453. break;
  454. case HTTPConnectionStates.TimedOut:
  455. // The connection is still in TimedOut state, and if we waited enough time, we will dispatch the
  456. // callback and recycle the connection
  457. try
  458. {
  459. if (DateTime.UtcNow - conn.TimedOutStart > TimeSpan.FromMilliseconds(500))
  460. {
  461. HTTPManager.Logger.Information("HTTPManager", "Hard aborting connection because of a long waiting TimedOut state");
  462. conn.CurrentRequest.Response = null;
  463. conn.CurrentRequest.State = HTTPRequestStates.TimedOut;
  464. conn.HandleCallback();
  465. // this will set the connection's CurrentRequest to null
  466. RecycleConnection(conn);
  467. }
  468. }
  469. catch(OverflowException)
  470. {
  471. HTTPManager.Logger.Warning("HTTPManager", "TimeSpan overflow");
  472. }
  473. break;
  474. case HTTPConnectionStates.Redirected:
  475. // If the server redirected us, we need to find or create a connection to the new server and send out the request again.
  476. SendRequest(conn.CurrentRequest);
  477. RecycleConnection(conn);
  478. break;
  479. case HTTPConnectionStates.WaitForRecycle:
  480. // If it's a streamed request, it's finished now
  481. conn.CurrentRequest.FinishStreaming();
  482. // Call the callback
  483. conn.HandleCallback();
  484. // Then recycle the connection
  485. RecycleConnection(conn);
  486. break;
  487. case HTTPConnectionStates.Upgraded:
  488. // The connection upgraded to an other protocol
  489. conn.HandleCallback();
  490. break;
  491. case HTTPConnectionStates.WaitForProtocolShutdown:
  492. var ws = conn.CurrentRequest.Response as IProtocol;
  493. if (ws != null)
  494. ws.HandleEvents();
  495. if (ws == null || ws.IsClosed)
  496. {
  497. conn.HandleCallback();
  498. // After both sending and receiving a Close message, an endpoint considers the WebSocket connection closed and MUST close the underlying TCP connection.
  499. conn.Dispose();
  500. RecycleConnection(conn);
  501. }
  502. break;
  503. case HTTPConnectionStates.AbortRequested:
  504. // Corner case: we aborted a WebSocket connection
  505. {
  506. ws = conn.CurrentRequest.Response as IProtocol;
  507. if (ws != null)
  508. {
  509. ws.HandleEvents();
  510. if (ws.IsClosed)
  511. {
  512. conn.HandleCallback();
  513. conn.Dispose();
  514. RecycleConnection(conn);
  515. }
  516. }
  517. }
  518. break;
  519. case HTTPConnectionStates.Closed:
  520. // If it's a streamed request, it's finished now
  521. conn.CurrentRequest.FinishStreaming();
  522. // Call the callback
  523. conn.HandleCallback();
  524. // It will remove from the ActiveConnections
  525. RecycleConnection(conn);
  526. break;
  527. case HTTPConnectionStates.Free:
  528. RecycleConnection(conn);
  529. break;
  530. }
  531. }
  532. }
  533. finally
  534. {
  535. IsCallingCallbacks = false;
  536. }
  537. // Just try to grab the lock, if we can't have it we can wait another turn because it isn't
  538. // critical to do it right now.
  539. if (System.Threading.Monitor.TryEnter(RecycledConnections))
  540. try
  541. {
  542. if (RecycledConnections.Count > 0)
  543. {
  544. for (int i = 0; i < RecycledConnections.Count; ++i)
  545. {
  546. var connection = RecycledConnections[i];
  547. // If in a callback made a request that acquired this connection, then we will not remove it from the
  548. // active connections.
  549. if (connection.IsFree)
  550. {
  551. ActiveConnections.Remove(connection);
  552. FreeConnections.Add(connection);
  553. }
  554. }
  555. RecycledConnections.Clear();
  556. }
  557. }
  558. finally
  559. {
  560. System.Threading.Monitor.Exit(RecycledConnections);
  561. }
  562. if (FreeConnections.Count > 0)
  563. for (int i = 0; i < FreeConnections.Count; i++)
  564. {
  565. var connection = FreeConnections[i];
  566. if (connection.IsRemovable)
  567. {
  568. // Remove the connection from the connection reference table
  569. List<ConnectionBase> connections = null;
  570. if (Connections.TryGetValue(connection.ServerAddress, out connections))
  571. connections.Remove(connection);
  572. // Dispose the connection
  573. connection.Dispose();
  574. FreeConnections.RemoveAt(i);
  575. i--;
  576. }
  577. }
  578. if (CanProcessFromQueue())
  579. {
  580. // Sort the queue by priority, only if we have to
  581. if (RequestQueue.Find((req) => req.Priority != 0) != null)
  582. RequestQueue.Sort((req1, req2) => req1.Priority - req2.Priority);
  583. // Create an array from the queue and clear it. When we call the SendRequest while still no room for new connections, the same queue will be rebuilt.
  584. var queue = RequestQueue.ToArray();
  585. RequestQueue.Clear();
  586. for (int i = 0; i < queue.Length; ++i)
  587. SendRequest(queue[i]);
  588. }
  589. }
  590. finally
  591. {
  592. System.Threading.Monitor.Exit(Locker);
  593. }
  594. }
  595. if (heartbeats != null)
  596. heartbeats.Update();
  597. }
  598. public static void OnQuit()
  599. {
  600. lock (Locker)
  601. {
  602. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  603. Caching.HTTPCacheService.SaveLibrary();
  604. #endif
  605. var queue = RequestQueue.ToArray();
  606. RequestQueue.Clear();
  607. foreach (var req in queue)
  608. {
  609. // Swallow any exceptions, we are quitting anyway.
  610. try
  611. {
  612. req.Abort();
  613. }
  614. catch { }
  615. }
  616. // Close all TCP connections when the application is terminating.
  617. foreach (var kvp in Connections)
  618. {
  619. foreach (var conn in kvp.Value)
  620. {
  621. // Swallow any exceptions, we are quitting anyway.
  622. try
  623. {
  624. if (conn.CurrentRequest != null)
  625. conn.CurrentRequest.State = HTTPRequestStates.Aborted;
  626. conn.Abort(HTTPConnectionStates.Closed);
  627. conn.Dispose();
  628. }
  629. catch { }
  630. }
  631. kvp.Value.Clear();
  632. }
  633. Connections.Clear();
  634. OnUpdate();
  635. }
  636. }
  637. #endregion
  638. }
  639. }