HTTPManager.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802
  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 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. #if !NETFX_CORE && !UNITY_WP8
  149. public static Func<HTTPRequest, System.Security.Cryptography.X509Certificates.X509Certificate, System.Security.Cryptography.X509Certificates.X509Chain, bool> DefaultCertificationValidator { get; set; }
  150. #endif
  151. /// <summary>
  152. /// Setting this option to true, the processing connection will set the TCP NoDelay option to send out data as soon as it can.
  153. /// </summary>
  154. public static bool TryToMinimizeTCPLatency = false;
  155. public static int SendBufferSize = 65 * 1024;
  156. public static int ReceiveBufferSize = 65 * 1024;
  157. /// <summary>
  158. /// 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
  159. /// exposed here and can be overridden. It's default value is 255.
  160. /// </summary>
  161. internal static int MaxPathLength { get; set; }
  162. #endregion
  163. #region Manager variables
  164. /// <summary>
  165. /// All connection has a reference in this Dictionary until it's removed completely.
  166. /// </summary>
  167. private static Dictionary<string, List<ConnectionBase>> Connections = new Dictionary<string, List<ConnectionBase>>();
  168. /// <summary>
  169. /// Active connections. These connections all has a request to process.
  170. /// </summary>
  171. private static List<ConnectionBase> ActiveConnections = new List<ConnectionBase>();
  172. /// <summary>
  173. /// Free connections. They can be removed completely after a specified time.
  174. /// </summary>
  175. private static List<ConnectionBase> FreeConnections = new List<ConnectionBase>();
  176. /// <summary>
  177. /// 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.
  178. /// </summary>
  179. private static List<ConnectionBase> RecycledConnections = new List<ConnectionBase>();
  180. /// <summary>
  181. /// List of request that have to wait until there is a free connection to the server.
  182. /// </summary>
  183. private static List<HTTPRequest> RequestQueue = new List<HTTPRequest>();
  184. private static bool IsCallingCallbacks;
  185. internal static System.Object Locker = new System.Object();
  186. internal static bool IsQuitting { get; private set; }
  187. #endregion
  188. #region Public Interface
  189. public static void Setup()
  190. {
  191. HTTPUpdateDelegator.CheckInstance();
  192. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  193. HTTPCacheService.CheckSetup();
  194. #endif
  195. #if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR)
  196. Cookies.CookieJar.SetupFolder();
  197. #endif
  198. }
  199. public static HTTPRequest SendRequest(string url, OnRequestFinishedDelegate callback)
  200. {
  201. return SendRequest(new HTTPRequest(new Uri(url), HTTPMethods.Get, callback));
  202. }
  203. public static HTTPRequest SendRequest(string url, HTTPMethods methodType, OnRequestFinishedDelegate callback)
  204. {
  205. return SendRequest(new HTTPRequest(new Uri(url), methodType, callback));
  206. }
  207. public static HTTPRequest SendRequest(string url, HTTPMethods methodType, bool isKeepAlive, OnRequestFinishedDelegate callback)
  208. {
  209. return SendRequest(new HTTPRequest(new Uri(url), methodType, isKeepAlive, callback));
  210. }
  211. public static HTTPRequest SendRequest(string url, HTTPMethods methodType, bool isKeepAlive, bool disableCache, OnRequestFinishedDelegate callback)
  212. {
  213. return SendRequest(new HTTPRequest(new Uri(url), methodType, isKeepAlive, disableCache, callback));
  214. }
  215. public static HTTPRequest SendRequest(HTTPRequest request)
  216. {
  217. lock (Locker)
  218. {
  219. Setup();
  220. if (IsCallingCallbacks)
  221. {
  222. request.State = HTTPRequestStates.Queued;
  223. RequestQueue.Add(request);
  224. }
  225. else
  226. SendRequestImpl(request);
  227. return request;
  228. }
  229. }
  230. public static GeneralStatistics GetGeneralStatistics(StatisticsQueryFlags queryFlags)
  231. {
  232. GeneralStatistics stat = new GeneralStatistics();
  233. stat.QueryFlags = queryFlags;
  234. if ((queryFlags & StatisticsQueryFlags.Connections) != 0)
  235. {
  236. int connections = 0;
  237. foreach(var conn in HTTPManager.Connections)
  238. {
  239. if (conn.Value != null)
  240. connections += conn.Value.Count;
  241. }
  242. #if !BESTHTTP_DISABLE_WEBSOCKET && UNITY_WEBGL && !UNITY_EDITOR
  243. connections += WebSocket.WebSocket.WebSockets.Count;
  244. #endif
  245. stat.Connections = connections;
  246. stat.ActiveConnections = ActiveConnections.Count
  247. #if !BESTHTTP_DISABLE_WEBSOCKET && UNITY_WEBGL && !UNITY_EDITOR
  248. + WebSocket.WebSocket.WebSockets.Count
  249. #endif
  250. ;
  251. stat.FreeConnections = FreeConnections.Count;
  252. stat.RecycledConnections = RecycledConnections.Count;
  253. stat.RequestsInQueue = RequestQueue.Count;
  254. }
  255. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  256. if ((queryFlags & StatisticsQueryFlags.Cache) != 0)
  257. {
  258. stat.CacheEntityCount = HTTPCacheService.GetCacheEntityCount();
  259. stat.CacheSize = HTTPCacheService.GetCacheSize();
  260. }
  261. #endif
  262. #if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR)
  263. if ((queryFlags & StatisticsQueryFlags.Cookies) != 0)
  264. {
  265. List<Cookies.Cookie> cookies = Cookies.CookieJar.GetAll();
  266. stat.CookieCount = cookies.Count;
  267. uint cookiesSize = 0;
  268. for (int i = 0; i < cookies.Count; ++i)
  269. cookiesSize += cookies[i].GuessSize();
  270. stat.CookieJarSize = cookiesSize;
  271. }
  272. #endif
  273. return stat;
  274. }
  275. #endregion
  276. #region Private Functions
  277. private static void SendRequestImpl(HTTPRequest request)
  278. {
  279. ConnectionBase conn = FindOrCreateFreeConnection(request);
  280. if (conn != null)
  281. {
  282. // found a free connection: put it in the ActiveConnection list(they will be checked periodically in the OnUpdate call)
  283. if (ActiveConnections.Find((c) => c == conn) == null)
  284. ActiveConnections.Add(conn);
  285. FreeConnections.Remove(conn);
  286. request.State = HTTPRequestStates.Processing;
  287. request.Prepare();
  288. // then start process the request
  289. conn.Process(request);
  290. }
  291. else
  292. {
  293. // If no free connection found and creation prohibited, we will put back to the queue
  294. request.State = HTTPRequestStates.Queued;
  295. RequestQueue.Add(request);
  296. }
  297. }
  298. private static string GetKeyForRequest(HTTPRequest request)
  299. {
  300. if (request.CurrentUri.IsFile)
  301. return request.CurrentUri.ToString();
  302. // proxyUri + requestUri
  303. // HTTP and HTTPS needs different connections.
  304. return
  305. #if !BESTHTTP_DISABLE_PROXY
  306. (request.Proxy != null ? new UriBuilder(request.Proxy.Address.Scheme, request.Proxy.Address.Host, request.Proxy.Address.Port).Uri.ToString() : string.Empty) +
  307. #endif
  308. new UriBuilder(request.CurrentUri.Scheme, request.CurrentUri.Host, request.CurrentUri.Port).Uri.ToString();
  309. }
  310. /// <summary>
  311. /// Factory method to create a concrete connection object.
  312. /// </summary>
  313. private static ConnectionBase CreateConnection(HTTPRequest request, string serverUrl)
  314. {
  315. if (request.CurrentUri.IsFile && Application.platform != RuntimePlatform.WebGLPlayer)
  316. return new FileConnection(serverUrl);
  317. #if UNITY_WEBGL && !UNITY_EDITOR
  318. return new WebGLConnection(serverUrl);
  319. #else
  320. return new HTTPConnection(serverUrl);
  321. #endif
  322. }
  323. private static ConnectionBase FindOrCreateFreeConnection(HTTPRequest request)
  324. {
  325. ConnectionBase conn = null;
  326. List<ConnectionBase> connections;
  327. string serverUrl = GetKeyForRequest(request);
  328. if (Connections.TryGetValue(serverUrl, out connections))
  329. {
  330. // count active connections
  331. int activeConnections = 0;
  332. for (int i = 0; i < connections.Count; ++i)
  333. if (connections[i].IsActive)
  334. activeConnections++;
  335. if (activeConnections <= MaxConnectionPerServer)
  336. // search for a Free connection
  337. for (int i = 0; i < connections.Count && conn == null; ++i)
  338. {
  339. var tmpConn = connections[i];
  340. if (tmpConn != null &&
  341. tmpConn.IsFree &&
  342. (
  343. #if !BESTHTTP_DISABLE_PROXY
  344. !tmpConn.HasProxy ||
  345. #endif
  346. tmpConn.LastProcessedUri == null ||
  347. tmpConn.LastProcessedUri.Host.Equals(request.CurrentUri.Host, StringComparison.OrdinalIgnoreCase)))
  348. conn = tmpConn;
  349. }
  350. }
  351. else
  352. Connections.Add(serverUrl, connections = new List<ConnectionBase>(MaxConnectionPerServer));
  353. // No free connection found?
  354. if (conn == null)
  355. {
  356. // Max connection reached?
  357. if (connections.Count >= MaxConnectionPerServer)
  358. return null;
  359. // if no, create a new one
  360. connections.Add(conn = CreateConnection(request, serverUrl));
  361. }
  362. return conn;
  363. }
  364. /// <summary>
  365. /// Will return with true when there at least one request that can be processed from the RequestQueue.
  366. /// </summary>
  367. private static bool CanProcessFromQueue()
  368. {
  369. for (int i = 0; i < RequestQueue.Count; ++i)
  370. if (FindOrCreateFreeConnection(RequestQueue[i]) != null)
  371. return true;
  372. return false;
  373. }
  374. private static void RecycleConnection(ConnectionBase conn)
  375. {
  376. conn.Recycle(OnConnectionRecylced);
  377. }
  378. private static void OnConnectionRecylced(ConnectionBase conn)
  379. {
  380. lock (RecycledConnections)
  381. {
  382. RecycledConnections.Add(conn);
  383. }
  384. }
  385. #endregion
  386. #region Internal Helper Functions
  387. /// <summary>
  388. /// Will return the ConnectionBase object that processing the given request.
  389. /// </summary>
  390. internal static ConnectionBase GetConnectionWith(HTTPRequest request)
  391. {
  392. lock (Locker)
  393. {
  394. for (int i = 0; i < ActiveConnections.Count; ++i)
  395. {
  396. var connection = ActiveConnections[i];
  397. if (connection.CurrentRequest == request)
  398. return connection;
  399. }
  400. return null;
  401. }
  402. }
  403. internal static bool RemoveFromQueue(HTTPRequest request)
  404. {
  405. return RequestQueue.Remove(request);
  406. }
  407. #if !((BESTHTTP_DISABLE_CACHING && BESTHTTP_DISABLE_COOKIES) || (UNITY_WEBGL && !UNITY_EDITOR))
  408. /// <summary>
  409. /// Will return where the various caches should be saved.
  410. /// </summary>
  411. internal static string GetRootCacheFolder()
  412. {
  413. try
  414. {
  415. if (RootCacheFolderProvider != null)
  416. return RootCacheFolderProvider();
  417. }
  418. catch(Exception ex)
  419. {
  420. HTTPManager.Logger.Exception("HTTPManager", "GetRootCacheFolder", ex);
  421. }
  422. #if NETFX_CORE
  423. return Windows.Storage.ApplicationData.Current.LocalFolder.Path;
  424. #else
  425. return Application.persistentDataPath;
  426. #endif
  427. }
  428. #endif
  429. #endregion
  430. #region MonoBehaviour Events (Called from HTTPUpdateDelegator)
  431. /// <summary>
  432. /// Update function that should be called regularly from a Unity event(Update, LateUpdate). Callbacks are dispatched from this function.
  433. /// </summary>
  434. public static void OnUpdate()
  435. {
  436. // We will try to acquire a lock. If it fails, we will skip this frame without calling any callback.
  437. if (System.Threading.Monitor.TryEnter(Locker))
  438. {
  439. try
  440. {
  441. IsCallingCallbacks = true;
  442. try
  443. {
  444. for (int i = 0; i < ActiveConnections.Count; ++i)
  445. {
  446. ConnectionBase conn = ActiveConnections[i];
  447. switch (conn.State)
  448. {
  449. case HTTPConnectionStates.Processing:
  450. conn.HandleProgressCallback();
  451. if (conn.CurrentRequest.UseStreaming && conn.CurrentRequest.Response != null && conn.CurrentRequest.Response.HasStreamedFragments())
  452. conn.HandleCallback();
  453. try
  454. {
  455. if (((!conn.CurrentRequest.UseStreaming && conn.CurrentRequest.UploadStream == null) || conn.CurrentRequest.EnableTimoutForStreaming) &&
  456. DateTime.UtcNow - conn.StartTime > conn.CurrentRequest.Timeout)
  457. conn.Abort(HTTPConnectionStates.TimedOut);
  458. }
  459. catch (OverflowException)
  460. {
  461. HTTPManager.Logger.Warning("HTTPManager", "TimeSpan overflow");
  462. }
  463. break;
  464. case HTTPConnectionStates.TimedOut:
  465. // The connection is still in TimedOut state, and if we waited enough time, we will dispatch the
  466. // callback and recycle the connection
  467. try
  468. {
  469. if (DateTime.UtcNow - conn.TimedOutStart > TimeSpan.FromMilliseconds(500))
  470. {
  471. HTTPManager.Logger.Information("HTTPManager", "Hard aborting connection because of a long waiting TimedOut state");
  472. conn.CurrentRequest.Response = null;
  473. conn.CurrentRequest.State = HTTPRequestStates.TimedOut;
  474. conn.HandleCallback();
  475. // this will set the connection's CurrentRequest to null
  476. RecycleConnection(conn);
  477. }
  478. }
  479. catch(OverflowException)
  480. {
  481. HTTPManager.Logger.Warning("HTTPManager", "TimeSpan overflow");
  482. }
  483. break;
  484. case HTTPConnectionStates.Redirected:
  485. // If the server redirected us, we need to find or create a connection to the new server and send out the request again.
  486. SendRequest(conn.CurrentRequest);
  487. RecycleConnection(conn);
  488. break;
  489. case HTTPConnectionStates.WaitForRecycle:
  490. // If it's a streamed request, it's finished now
  491. conn.CurrentRequest.FinishStreaming();
  492. // Call the callback
  493. conn.HandleCallback();
  494. // Then recycle the connection
  495. RecycleConnection(conn);
  496. break;
  497. case HTTPConnectionStates.Upgraded:
  498. // The connection upgraded to an other protocol
  499. conn.HandleCallback();
  500. break;
  501. case HTTPConnectionStates.WaitForProtocolShutdown:
  502. var ws = conn.CurrentRequest.Response as IProtocol;
  503. if (ws != null)
  504. ws.HandleEvents();
  505. if (ws == null || ws.IsClosed)
  506. {
  507. conn.HandleCallback();
  508. // After both sending and receiving a Close message, an endpoint considers the WebSocket connection closed and MUST close the underlying TCP connection.
  509. conn.Dispose();
  510. RecycleConnection(conn);
  511. }
  512. break;
  513. case HTTPConnectionStates.AbortRequested:
  514. // Corner case: we aborted a WebSocket connection
  515. {
  516. ws = conn.CurrentRequest.Response as IProtocol;
  517. if (ws != null)
  518. {
  519. ws.HandleEvents();
  520. if (ws.IsClosed)
  521. {
  522. conn.HandleCallback();
  523. conn.Dispose();
  524. RecycleConnection(conn);
  525. }
  526. }
  527. }
  528. break;
  529. case HTTPConnectionStates.Closed:
  530. // If it's a streamed request, it's finished now
  531. conn.CurrentRequest.FinishStreaming();
  532. // Call the callback
  533. conn.HandleCallback();
  534. // It will remove from the ActiveConnections
  535. RecycleConnection(conn);
  536. break;
  537. case HTTPConnectionStates.Free:
  538. RecycleConnection(conn);
  539. break;
  540. }
  541. }
  542. }
  543. finally
  544. {
  545. IsCallingCallbacks = false;
  546. }
  547. // Just try to grab the lock, if we can't have it we can wait another turn because it isn't
  548. // critical to do it right now.
  549. if (System.Threading.Monitor.TryEnter(RecycledConnections))
  550. try
  551. {
  552. if (RecycledConnections.Count > 0)
  553. {
  554. for (int i = 0; i < RecycledConnections.Count; ++i)
  555. {
  556. var connection = RecycledConnections[i];
  557. // If in a callback made a request that acquired this connection, then we will not remove it from the
  558. // active connections.
  559. if (connection.IsFree)
  560. {
  561. ActiveConnections.Remove(connection);
  562. FreeConnections.Add(connection);
  563. }
  564. }
  565. RecycledConnections.Clear();
  566. }
  567. }
  568. finally
  569. {
  570. System.Threading.Monitor.Exit(RecycledConnections);
  571. }
  572. if (FreeConnections.Count > 0)
  573. for (int i = 0; i < FreeConnections.Count; i++)
  574. {
  575. var connection = FreeConnections[i];
  576. if (connection.IsRemovable)
  577. {
  578. // Remove the connection from the connection reference table
  579. List<ConnectionBase> connections = null;
  580. if (Connections.TryGetValue(connection.ServerAddress, out connections))
  581. connections.Remove(connection);
  582. // Dispose the connection
  583. connection.Dispose();
  584. FreeConnections.RemoveAt(i);
  585. i--;
  586. }
  587. }
  588. if (CanProcessFromQueue())
  589. {
  590. // Sort the queue by priority, only if we have to
  591. if (RequestQueue.Find((req) => req.Priority != 0) != null)
  592. RequestQueue.Sort((req1, req2) => req1.Priority - req2.Priority);
  593. // 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.
  594. var queue = RequestQueue.ToArray();
  595. RequestQueue.Clear();
  596. for (int i = 0; i < queue.Length; ++i)
  597. SendRequest(queue[i]);
  598. }
  599. }
  600. finally
  601. {
  602. System.Threading.Monitor.Exit(Locker);
  603. }
  604. }
  605. if (heartbeats != null)
  606. heartbeats.Update();
  607. }
  608. public static void OnQuit()
  609. {
  610. lock (Locker)
  611. {
  612. IsQuitting = true;
  613. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  614. Caching.HTTPCacheService.SaveLibrary();
  615. #endif
  616. #if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR)
  617. Cookies.CookieJar.Persist();
  618. #endif
  619. AbortAll(true);
  620. OnUpdate();
  621. }
  622. }
  623. public static void AbortAll(bool allowCallbacks = false)
  624. {
  625. lock (Locker)
  626. {
  627. var queue = RequestQueue.ToArray();
  628. RequestQueue.Clear();
  629. foreach (var req in queue)
  630. {
  631. // Swallow any exceptions, we are quitting anyway.
  632. try
  633. {
  634. if (!allowCallbacks)
  635. req.Callback = null;
  636. req.Abort();
  637. }
  638. catch { }
  639. }
  640. // Close all TCP connections when the application is terminating.
  641. foreach (var kvp in Connections)
  642. {
  643. foreach (var conn in kvp.Value)
  644. {
  645. // Swallow any exceptions, we are quitting anyway.
  646. try
  647. {
  648. if (conn.CurrentRequest != null)
  649. {
  650. if (!allowCallbacks)
  651. conn.CurrentRequest.Callback = null;
  652. conn.CurrentRequest.State = HTTPRequestStates.Aborted;
  653. }
  654. conn.Abort(HTTPConnectionStates.Closed);
  655. conn.Dispose();
  656. }
  657. catch { }
  658. }
  659. kvp.Value.Clear();
  660. }
  661. Connections.Clear();
  662. }
  663. }
  664. #endregion
  665. }
  666. }