up
This commit is contained in:
768
Assets/BestHTTP/HTTPManager.cs
Normal file
768
Assets/BestHTTP/HTTPManager.cs
Normal file
@@ -0,0 +1,768 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
#if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
using BestHTTP.Caching;
|
||||
#endif
|
||||
|
||||
using BestHTTP.Extensions;
|
||||
using BestHTTP.Logger;
|
||||
using BestHTTP.Statistics;
|
||||
|
||||
namespace BestHTTP
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class HTTPManager
|
||||
{
|
||||
// Static constructor. Setup default values
|
||||
static HTTPManager()
|
||||
{
|
||||
MaxConnectionPerServer = 4;
|
||||
KeepAliveDefaultValue = true;
|
||||
MaxPathLength = 255;
|
||||
MaxConnectionIdleTime = TimeSpan.FromSeconds(20);
|
||||
|
||||
#if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
IsCookiesEnabled = true;
|
||||
#endif
|
||||
|
||||
CookieJarSize = 10 * 1024 * 1024;
|
||||
EnablePrivateBrowsing = false;
|
||||
ConnectTimeout = TimeSpan.FromSeconds(20);
|
||||
RequestTimeout = TimeSpan.FromSeconds(60);
|
||||
|
||||
// Set the default logger mechanism
|
||||
logger = new BestHTTP.Logger.DefaultLogger();
|
||||
|
||||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
DefaultCertificateVerifyer = null;
|
||||
UseAlternateSSLDefaultValue = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
#region Global Options
|
||||
|
||||
/// <summary>
|
||||
/// The maximum active TCP connections that the client will maintain to a server. Default value is 4. Minimum value is 1.
|
||||
/// </summary>
|
||||
public static byte MaxConnectionPerServer
|
||||
{
|
||||
get{ return maxConnectionPerServer; }
|
||||
set
|
||||
{
|
||||
if (value <= 0)
|
||||
throw new ArgumentOutOfRangeException("MaxConnectionPerServer must be greater than 0!");
|
||||
maxConnectionPerServer = value;
|
||||
}
|
||||
}
|
||||
private static byte maxConnectionPerServer;
|
||||
|
||||
/// <summary>
|
||||
/// Default value of a HTTP request's IsKeepAlive value. Default value is true. If you make rare request to the server it's should be changed to false.
|
||||
/// </summary>
|
||||
public static bool KeepAliveDefaultValue { get; set; }
|
||||
|
||||
#if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
/// <summary>
|
||||
/// Set to true, if caching is prohibited.
|
||||
/// </summary>
|
||||
public static bool IsCachingDisabled { get; set; }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// How many time must be passed to destroy that connection after a connection finished its last request. Its default value is 20 seconds.
|
||||
/// </summary>
|
||||
public static TimeSpan MaxConnectionIdleTime { get; set; }
|
||||
|
||||
#if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
/// <summary>
|
||||
/// Set to false to disable all Cookie. It's default value is true.
|
||||
/// </summary>
|
||||
public static bool IsCookiesEnabled { get; set; }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Size of the Cookie Jar in bytes. It's default value is 10485760 (10 MB).
|
||||
/// </summary>
|
||||
public static uint CookieJarSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If this property is set to true, then new cookies treated as session cookies and these cookies are not saved to disk. Its default value is false;
|
||||
/// </summary>
|
||||
public static bool EnablePrivateBrowsing { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Global, default value of the HTTPRequest's ConnectTimeout property. If set to TimeSpan.Zero or lower, no connect timeout logic is executed. Default value is 20 seconds.
|
||||
/// </summary>
|
||||
public static TimeSpan ConnectTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Global, default value of the HTTPRequest's Timeout property. Default value is 60 seconds.
|
||||
/// </summary>
|
||||
public static TimeSpan RequestTimeout { get; set; }
|
||||
|
||||
#if !((BESTHTTP_DISABLE_CACHING && BESTHTTP_DISABLE_COOKIES) || (UNITY_WEBGL && !UNITY_EDITOR))
|
||||
/// <summary>
|
||||
/// By default the plugin will save all cache and cookie data under the path returned by Application.persistentDataPath.
|
||||
/// You can assign a function to this delegate to return a custom root path to define a new path.
|
||||
/// <remarks>This delegate will be called on a non Unity thread!</remarks>
|
||||
/// </summary>
|
||||
public static System.Func<string> RootCacheFolderProvider { get; set; }
|
||||
#endif
|
||||
|
||||
#if !BESTHTTP_DISABLE_PROXY
|
||||
/// <summary>
|
||||
/// The global, default proxy for all HTTPRequests. The HTTPRequest's Proxy still can be changed per-request. Default value is null.
|
||||
/// </summary>
|
||||
public static HTTPProxy Proxy { get; set; }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Heartbeat manager to use less threads in the plugin. The heartbeat updates are called from the OnUpdate function.
|
||||
/// </summary>
|
||||
public static HeartbeatManager Heartbeats
|
||||
{
|
||||
get
|
||||
{
|
||||
if (heartbeats == null)
|
||||
heartbeats = new HeartbeatManager();
|
||||
return heartbeats;
|
||||
}
|
||||
}
|
||||
private static HeartbeatManager heartbeats;
|
||||
|
||||
/// <summary>
|
||||
/// A basic BestHTTP.Logger.ILogger implementation to be able to log intelligently additional informations about the plugin's internal mechanism.
|
||||
/// </summary>
|
||||
public static BestHTTP.Logger.ILogger Logger
|
||||
{
|
||||
get
|
||||
{
|
||||
// Make sure that it has a valid logger instance.
|
||||
if (logger == null)
|
||||
{
|
||||
logger = new DefaultLogger();
|
||||
logger.Level = Loglevels.None;
|
||||
}
|
||||
|
||||
return logger;
|
||||
}
|
||||
|
||||
set { logger = value; }
|
||||
}
|
||||
private static BestHTTP.Logger.ILogger logger;
|
||||
|
||||
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
/// <summary>
|
||||
/// The default ICertificateVerifyer implementation that the plugin will use when the request's UseAlternateSSL property is set to true.
|
||||
/// </summary>
|
||||
public static Org.BouncyCastle.Crypto.Tls.ICertificateVerifyer DefaultCertificateVerifyer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The default IClientCredentialsProvider implementation that the plugin will use when the request's UseAlternateSSL property is set to true.
|
||||
/// </summary>
|
||||
public static Org.BouncyCastle.Crypto.Tls.IClientCredentialsProvider DefaultClientCredentialsProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The default value for the HTTPRequest's UseAlternateSSL property.
|
||||
/// </summary>
|
||||
public static bool UseAlternateSSLDefaultValue { get; set; }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// On most systems the maximum length of a path is around 255 character. If a cache entity's path is longer than this value it doesn't get cached. There no platform independent API to query the exact value on the current system, but it's
|
||||
/// exposed here and can be overridden. It's default value is 255.
|
||||
/// </summary>
|
||||
internal static int MaxPathLength { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Manager variables
|
||||
|
||||
/// <summary>
|
||||
/// All connection has a reference in this Dictionary untill it's removed completly.
|
||||
/// </summary>
|
||||
private static Dictionary<string, List<ConnectionBase>> Connections = new Dictionary<string, List<ConnectionBase>>();
|
||||
|
||||
/// <summary>
|
||||
/// Active connections. These connections all has a request to process.
|
||||
/// </summary>
|
||||
private static List<ConnectionBase> ActiveConnections = new List<ConnectionBase>();
|
||||
|
||||
/// <summary>
|
||||
/// Free connections. They can be removed completly after a specified time.
|
||||
/// </summary>
|
||||
private static List<ConnectionBase> FreeConnections = new List<ConnectionBase>();
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private static List<ConnectionBase> RecycledConnections = new List<ConnectionBase>();
|
||||
|
||||
/// <summary>
|
||||
/// List of request that have to wait until there is a free connection to the server.
|
||||
/// </summary>
|
||||
private static List<HTTPRequest> RequestQueue = new List<HTTPRequest>();
|
||||
private static bool IsCallingCallbacks;
|
||||
|
||||
internal static System.Object Locker = new System.Object();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Interface
|
||||
|
||||
public static void Setup()
|
||||
{
|
||||
HTTPUpdateDelegator.CheckInstance();
|
||||
|
||||
#if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
HTTPCacheService.CheckSetup();
|
||||
#endif
|
||||
|
||||
#if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
Cookies.CookieJar.SetupFolder();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static HTTPRequest SendRequest(string url, OnRequestFinishedDelegate callback)
|
||||
{
|
||||
return SendRequest(new HTTPRequest(new Uri(url), HTTPMethods.Get, callback));
|
||||
}
|
||||
|
||||
public static HTTPRequest SendRequest(string url, HTTPMethods methodType, OnRequestFinishedDelegate callback)
|
||||
{
|
||||
return SendRequest(new HTTPRequest(new Uri(url), methodType, callback));
|
||||
}
|
||||
|
||||
public static HTTPRequest SendRequest(string url, HTTPMethods methodType, bool isKeepAlive, OnRequestFinishedDelegate callback)
|
||||
{
|
||||
return SendRequest(new HTTPRequest(new Uri(url), methodType, isKeepAlive, callback));
|
||||
}
|
||||
|
||||
public static HTTPRequest SendRequest(string url, HTTPMethods methodType, bool isKeepAlive, bool disableCache, OnRequestFinishedDelegate callback)
|
||||
{
|
||||
return SendRequest(new HTTPRequest(new Uri(url), methodType, isKeepAlive, disableCache, callback));
|
||||
}
|
||||
|
||||
public static HTTPRequest SendRequest(HTTPRequest request)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
Setup();
|
||||
|
||||
if (IsCallingCallbacks)
|
||||
{
|
||||
request.State = HTTPRequestStates.Queued;
|
||||
RequestQueue.Add(request);
|
||||
}
|
||||
else
|
||||
SendRequestImpl(request);
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
public static GeneralStatistics GetGeneralStatistics(StatisticsQueryFlags queryFlags)
|
||||
{
|
||||
GeneralStatistics stat = new GeneralStatistics();
|
||||
|
||||
stat.QueryFlags = queryFlags;
|
||||
|
||||
if ((queryFlags & StatisticsQueryFlags.Connections) != 0)
|
||||
{
|
||||
int connections = 0;
|
||||
foreach(var conn in HTTPManager.Connections)
|
||||
{
|
||||
if (conn.Value != null)
|
||||
connections += conn.Value.Count;
|
||||
}
|
||||
|
||||
#if !BESTHTTP_DISABLE_WEBSOCKET && UNITY_WEBGL && !UNITY_EDITOR
|
||||
connections += WebSocket.WebSocket.WebSockets.Count;
|
||||
#endif
|
||||
|
||||
stat.Connections = connections;
|
||||
stat.ActiveConnections = ActiveConnections.Count
|
||||
#if !BESTHTTP_DISABLE_WEBSOCKET && UNITY_WEBGL && !UNITY_EDITOR
|
||||
+ WebSocket.WebSocket.WebSockets.Count
|
||||
#endif
|
||||
;
|
||||
stat.FreeConnections = FreeConnections.Count;
|
||||
stat.RecycledConnections = RecycledConnections.Count;
|
||||
stat.RequestsInQueue = RequestQueue.Count;
|
||||
}
|
||||
|
||||
#if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
if ((queryFlags & StatisticsQueryFlags.Cache) != 0)
|
||||
{
|
||||
stat.CacheEntityCount = HTTPCacheService.GetCacheEntityCount();
|
||||
stat.CacheSize = HTTPCacheService.GetCacheSize();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
if ((queryFlags & StatisticsQueryFlags.Cookies) != 0)
|
||||
{
|
||||
List<Cookies.Cookie> cookies = Cookies.CookieJar.GetAll();
|
||||
stat.CookieCount = cookies.Count;
|
||||
uint cookiesSize = 0;
|
||||
for (int i = 0; i < cookies.Count; ++i)
|
||||
cookiesSize += cookies[i].GuessSize();
|
||||
stat.CookieJarSize = cookiesSize;
|
||||
}
|
||||
#endif
|
||||
|
||||
return stat;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Functions
|
||||
|
||||
private static void SendRequestImpl(HTTPRequest request)
|
||||
{
|
||||
ConnectionBase conn = FindOrCreateFreeConnection(request);
|
||||
|
||||
if (conn != null)
|
||||
{
|
||||
// found a free connection: put it in the ActiveConnection list(they will be checked periodically in the OnUpdate call)
|
||||
if (ActiveConnections.Find((c) => c == conn) == null)
|
||||
ActiveConnections.Add(conn);
|
||||
|
||||
FreeConnections.Remove(conn);
|
||||
|
||||
request.State = HTTPRequestStates.Processing;
|
||||
|
||||
request.Prepare();
|
||||
|
||||
// then start process the request
|
||||
conn.Process(request);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If no free connection found and creation prohibited, we will put back to the queue
|
||||
request.State = HTTPRequestStates.Queued;
|
||||
RequestQueue.Add(request);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetKeyForRequest(HTTPRequest request)
|
||||
{
|
||||
if (request.CurrentUri.IsFile)
|
||||
return request.CurrentUri.ToString();
|
||||
|
||||
// proxyUri + requestUri
|
||||
// HTTP and HTTPS needs different connections.
|
||||
return
|
||||
#if !BESTHTTP_DISABLE_PROXY
|
||||
(request.Proxy != null ? new UriBuilder(request.Proxy.Address.Scheme, request.Proxy.Address.Host, request.Proxy.Address.Port).Uri.ToString() : string.Empty) +
|
||||
#endif
|
||||
new UriBuilder(request.CurrentUri.Scheme, request.CurrentUri.Host, request.CurrentUri.Port).Uri.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory method to create a concrete connection object.
|
||||
/// </summary>
|
||||
private static ConnectionBase CreateConnection(HTTPRequest request, string serverUrl)
|
||||
{
|
||||
if (request.CurrentUri.IsFile)
|
||||
return new FileConnection(serverUrl);
|
||||
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
return new WebGLConnection(serverUrl);
|
||||
#else
|
||||
return new HTTPConnection(serverUrl);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static ConnectionBase FindOrCreateFreeConnection(HTTPRequest request)
|
||||
{
|
||||
ConnectionBase conn = null;
|
||||
List<ConnectionBase> connections;
|
||||
|
||||
string serverUrl = GetKeyForRequest(request);
|
||||
|
||||
if (Connections.TryGetValue(serverUrl, out connections))
|
||||
{
|
||||
// count active connections
|
||||
|
||||
int activeConnections = 0;
|
||||
for (int i = 0; i < connections.Count; ++i)
|
||||
if (connections[i].IsActive)
|
||||
activeConnections++;
|
||||
|
||||
if (activeConnections <= MaxConnectionPerServer)
|
||||
// search for a Free connection
|
||||
for (int i = 0; i < connections.Count && conn == null; ++i)
|
||||
{
|
||||
var tmpConn = connections[i];
|
||||
|
||||
if (tmpConn != null &&
|
||||
tmpConn.IsFree &&
|
||||
(
|
||||
#if !BESTHTTP_DISABLE_PROXY
|
||||
!tmpConn.HasProxy ||
|
||||
#endif
|
||||
tmpConn.LastProcessedUri == null ||
|
||||
tmpConn.LastProcessedUri.Host.Equals(request.CurrentUri.Host, StringComparison.OrdinalIgnoreCase)))
|
||||
conn = tmpConn;
|
||||
}
|
||||
}
|
||||
else
|
||||
Connections.Add(serverUrl, connections = new List<ConnectionBase>(MaxConnectionPerServer));
|
||||
|
||||
// No free connection found?
|
||||
if (conn == null)
|
||||
{
|
||||
// Max connection reached?
|
||||
if (connections.Count >= MaxConnectionPerServer)
|
||||
return null;
|
||||
|
||||
// if no, create a new one
|
||||
connections.Add(conn = CreateConnection(request, serverUrl));
|
||||
}
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will return with true when there at least one request that can be processed from the RequestQueue.
|
||||
/// </summary>
|
||||
private static bool CanProcessFromQueue()
|
||||
{
|
||||
for (int i = 0; i < RequestQueue.Count; ++i)
|
||||
if (FindOrCreateFreeConnection(RequestQueue[i]) != null)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void RecycleConnection(ConnectionBase conn)
|
||||
{
|
||||
conn.Recycle(OnConnectionRecylced);
|
||||
}
|
||||
|
||||
private static void OnConnectionRecylced(ConnectionBase conn)
|
||||
{
|
||||
lock (RecycledConnections)
|
||||
{
|
||||
RecycledConnections.Add(conn);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Helper Functions
|
||||
|
||||
/// <summary>
|
||||
/// Will return the ConnectionBase object that processing the given request.
|
||||
/// </summary>
|
||||
internal static ConnectionBase GetConnectionWith(HTTPRequest request)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
for (int i = 0; i < ActiveConnections.Count; ++i)
|
||||
{
|
||||
var connection = ActiveConnections[i];
|
||||
if (connection.CurrentRequest == request)
|
||||
return connection;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool RemoveFromQueue(HTTPRequest request)
|
||||
{
|
||||
return RequestQueue.Remove(request);
|
||||
}
|
||||
|
||||
#if !((BESTHTTP_DISABLE_CACHING && BESTHTTP_DISABLE_COOKIES) || (UNITY_WEBGL && !UNITY_EDITOR))
|
||||
/// <summary>
|
||||
/// Will return where the various caches should be saved.
|
||||
/// </summary>
|
||||
internal static string GetRootCacheFolder()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (RootCacheFolderProvider != null)
|
||||
return RootCacheFolderProvider();
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("HTTPManager", "GetRootCacheFolder", ex);
|
||||
}
|
||||
|
||||
#if NETFX_CORE
|
||||
return Windows.Storage.ApplicationData.Current.LocalFolder.Path;
|
||||
#else
|
||||
return Application.persistentDataPath;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region MonoBehaviour Events (Called from HTTPUpdateDelegator)
|
||||
|
||||
/// <summary>
|
||||
/// Update function that should be called regularly from a Unity event(Update, LateUpdate). Callbacks are dispatched from this function.
|
||||
/// </summary>
|
||||
public static void OnUpdate()
|
||||
{
|
||||
// We will try to acquire a lock. If it fails, we will skip this frame without calling any callback.
|
||||
if (System.Threading.Monitor.TryEnter(Locker))
|
||||
{
|
||||
try
|
||||
{
|
||||
IsCallingCallbacks = true;
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < ActiveConnections.Count; ++i)
|
||||
{
|
||||
ConnectionBase conn = ActiveConnections[i];
|
||||
|
||||
switch (conn.State)
|
||||
{
|
||||
case HTTPConnectionStates.Processing:
|
||||
conn.HandleProgressCallback();
|
||||
|
||||
if (conn.CurrentRequest.UseStreaming && conn.CurrentRequest.Response != null && conn.CurrentRequest.Response.HasStreamedFragments())
|
||||
conn.HandleCallback();
|
||||
|
||||
try
|
||||
{
|
||||
if (((!conn.CurrentRequest.UseStreaming && conn.CurrentRequest.UploadStream == null) || conn.CurrentRequest.EnableTimoutForStreaming) &&
|
||||
DateTime.UtcNow - conn.StartTime > conn.CurrentRequest.Timeout)
|
||||
conn.Abort(HTTPConnectionStates.TimedOut);
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
HTTPManager.Logger.Warning("HTTPManager", "TimeSpan overflow");
|
||||
}
|
||||
break;
|
||||
|
||||
case HTTPConnectionStates.TimedOut:
|
||||
// The connection is still in TimedOut state, and if we waited enough time, we will dispatch the
|
||||
// callback and recycle the connection
|
||||
try
|
||||
{
|
||||
if (DateTime.UtcNow - conn.TimedOutStart > TimeSpan.FromMilliseconds(500))
|
||||
{
|
||||
HTTPManager.Logger.Information("HTTPManager", "Hard aborting connection because of a long waiting TimedOut state");
|
||||
|
||||
conn.CurrentRequest.Response = null;
|
||||
conn.CurrentRequest.State = HTTPRequestStates.TimedOut;
|
||||
conn.HandleCallback();
|
||||
|
||||
// this will set the connection's CurrentRequest to null
|
||||
RecycleConnection(conn);
|
||||
}
|
||||
}
|
||||
catch(OverflowException)
|
||||
{
|
||||
HTTPManager.Logger.Warning("HTTPManager", "TimeSpan overflow");
|
||||
}
|
||||
break;
|
||||
|
||||
case HTTPConnectionStates.Redirected:
|
||||
// If the server redirected us, we need to find or create a connection to the new server and send out the request again.
|
||||
SendRequest(conn.CurrentRequest);
|
||||
|
||||
RecycleConnection(conn);
|
||||
break;
|
||||
|
||||
case HTTPConnectionStates.WaitForRecycle:
|
||||
// If it's a streamed request, it's finished now
|
||||
conn.CurrentRequest.FinishStreaming();
|
||||
|
||||
// Call the callback
|
||||
conn.HandleCallback();
|
||||
|
||||
// Then recycle the connection
|
||||
RecycleConnection(conn);
|
||||
break;
|
||||
|
||||
case HTTPConnectionStates.Upgraded:
|
||||
// The connection upgraded to an other protocol
|
||||
conn.HandleCallback();
|
||||
break;
|
||||
|
||||
case HTTPConnectionStates.WaitForProtocolShutdown:
|
||||
var ws = conn.CurrentRequest.Response as IProtocol;
|
||||
if (ws != null)
|
||||
ws.HandleEvents();
|
||||
|
||||
if (ws == null || ws.IsClosed)
|
||||
{
|
||||
conn.HandleCallback();
|
||||
|
||||
// After both sending and receiving a Close message, an endpoint considers the WebSocket connection closed and MUST close the underlying TCP connection.
|
||||
conn.Dispose();
|
||||
RecycleConnection(conn);
|
||||
}
|
||||
break;
|
||||
|
||||
case HTTPConnectionStates.AbortRequested:
|
||||
// Corner case: we aborted a WebSocket connection
|
||||
{
|
||||
ws = conn.CurrentRequest.Response as IProtocol;
|
||||
if (ws != null)
|
||||
{
|
||||
ws.HandleEvents();
|
||||
|
||||
if (ws.IsClosed)
|
||||
{
|
||||
conn.HandleCallback();
|
||||
conn.Dispose();
|
||||
|
||||
RecycleConnection(conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case HTTPConnectionStates.Closed:
|
||||
// If it's a streamed request, it's finished now
|
||||
conn.CurrentRequest.FinishStreaming();
|
||||
|
||||
// Call the callback
|
||||
conn.HandleCallback();
|
||||
|
||||
// It will remove from the ActiveConnections
|
||||
RecycleConnection(conn);
|
||||
break;
|
||||
|
||||
case HTTPConnectionStates.Free:
|
||||
RecycleConnection(conn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsCallingCallbacks = false;
|
||||
}
|
||||
|
||||
// Just try to grab the lock, if we can't have it we can wait another turn because it isn't
|
||||
// critical to do it right now.
|
||||
if (System.Threading.Monitor.TryEnter(RecycledConnections))
|
||||
try
|
||||
{
|
||||
if (RecycledConnections.Count > 0)
|
||||
{
|
||||
for (int i = 0; i < RecycledConnections.Count; ++i)
|
||||
{
|
||||
var connection = RecycledConnections[i];
|
||||
// If in a callback made a request that acquired this connection, then we will not remove it from the
|
||||
// active connections.
|
||||
if (connection.IsFree)
|
||||
{
|
||||
ActiveConnections.Remove(connection);
|
||||
FreeConnections.Add(connection);
|
||||
}
|
||||
}
|
||||
RecycledConnections.Clear();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
System.Threading.Monitor.Exit(RecycledConnections);
|
||||
}
|
||||
|
||||
if (FreeConnections.Count > 0)
|
||||
for (int i = 0; i < FreeConnections.Count; i++)
|
||||
{
|
||||
var connection = FreeConnections[i];
|
||||
|
||||
if (connection.IsRemovable)
|
||||
{
|
||||
// Remove the connection from the connection reference table
|
||||
List<ConnectionBase> connections = null;
|
||||
if (Connections.TryGetValue(connection.ServerAddress, out connections))
|
||||
connections.Remove(connection);
|
||||
|
||||
// Dispose the connection
|
||||
connection.Dispose();
|
||||
|
||||
FreeConnections.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (CanProcessFromQueue())
|
||||
{
|
||||
// Sort the queue by priority, only if we have to
|
||||
if (RequestQueue.Find((req) => req.Priority != 0) != null)
|
||||
RequestQueue.Sort((req1, req2) => req1.Priority - req2.Priority);
|
||||
|
||||
// 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.
|
||||
|
||||
var queue = RequestQueue.ToArray();
|
||||
RequestQueue.Clear();
|
||||
|
||||
for (int i = 0; i < queue.Length; ++i)
|
||||
SendRequest(queue[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
System.Threading.Monitor.Exit(Locker);
|
||||
}
|
||||
}
|
||||
|
||||
if (heartbeats != null)
|
||||
heartbeats.Update();
|
||||
}
|
||||
|
||||
public static void OnQuit()
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
#if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
Caching.HTTPCacheService.SaveLibrary();
|
||||
#endif
|
||||
|
||||
var queue = RequestQueue.ToArray();
|
||||
RequestQueue.Clear();
|
||||
foreach (var req in queue)
|
||||
{
|
||||
// Swallow any exceptions, we are quitting anyway.
|
||||
try
|
||||
{
|
||||
req.Abort();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
// Close all TCP connections when the application is terminating.
|
||||
foreach (var kvp in Connections)
|
||||
{
|
||||
foreach (var conn in kvp.Value)
|
||||
{
|
||||
// Swallow any exceptions, we are quitting anyway.
|
||||
try
|
||||
{
|
||||
if (conn.CurrentRequest != null)
|
||||
conn.CurrentRequest.State = HTTPRequestStates.Aborted;
|
||||
conn.Abort(HTTPConnectionStates.Closed);
|
||||
conn.Dispose();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
kvp.Value.Clear();
|
||||
}
|
||||
Connections.Clear();
|
||||
|
||||
OnUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user