HTTPManager.cs 32 KB

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