491 lines
18 KiB
C#
491 lines
18 KiB
C#
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
|
|
#if !BESTHTTP_DISABLE_CACHING
|
|
|
|
using BestHTTP.Caching;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
using BestHTTP.Core;
|
|
|
|
using BestHTTP.Extensions;
|
|
|
|
using BestHTTP.Logger;
|
|
|
|
using BestHTTP.PlatformSupport.Memory;
|
|
|
|
using BestHTTP.PlatformSupport.Text;
|
|
|
|
using Unity.Profiling;
|
|
|
|
|
|
|
|
#if !BESTHTTP_DISABLE_COOKIES
|
|
|
|
using BestHTTP.Cookies;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
using BestHTTP.Connections;
|
|
|
|
|
|
|
|
namespace BestHTTP
|
|
|
|
{
|
|
|
|
public enum ShutdownTypes
|
|
|
|
{
|
|
|
|
Running,
|
|
|
|
Gentle,
|
|
|
|
Immediate
|
|
|
|
}
|
|
|
|
|
|
|
|
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
|
|
|
public delegate Connections.TLS.AbstractTls13Client TlsClientFactoryDelegate(HTTPRequest request, List<SecureProtocol.Org.BouncyCastle.Tls.ProtocolName> protocols);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
public delegate System.Security.Cryptography.X509Certificates.X509Certificate ClientCertificateSelector(HTTPRequest request, string targetHost, System.Security.Cryptography.X509Certificates.X509CertificateCollection localCertificates, System.Security.Cryptography.X509Certificates.X509Certificate remoteCertificate, string[] acceptableIssuers);
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
///
|
|
|
|
/// </summary>
|
|
|
|
[BestHTTP.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstructionAttribute]
|
|
|
|
public static partial class HTTPManager
|
|
|
|
{
|
|
|
|
// Static constructor. Setup default values
|
|
|
|
static HTTPManager()
|
|
|
|
{
|
|
|
|
MaxConnectionPerServer = 6;
|
|
|
|
KeepAliveDefaultValue = true;
|
|
|
|
MaxPathLength = 255;
|
|
|
|
MaxConnectionIdleTime = TimeSpan.FromSeconds(20);
|
|
|
|
|
|
|
|
#if !BESTHTTP_DISABLE_COOKIES
|
|
|
|
#if UNITY_WEBGL && !UNITY_EDITOR
|
|
|
|
// Under webgl when IsCookiesEnabled is true, it will set the withCredentials flag for the XmlHTTPRequest
|
|
|
|
// and that's different from the default behavior.
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials
|
|
|
|
IsCookiesEnabled = false;
|
|
|
|
#else
|
|
|
|
IsCookiesEnabled = true;
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
CookieJarSize = 10 * 1024 * 1024;
|
|
|
|
EnablePrivateBrowsing = false;
|
|
|
|
ConnectTimeout = TimeSpan.FromSeconds(20);
|
|
|
|
RequestTimeout = TimeSpan.FromSeconds(60);
|
|
|
|
|
|
|
|
// Set the default logger mechanism
|
|
|
|
logger = new BestHTTP.Logger.ThreadedLogger();
|
|
|
|
|
|
|
|
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
|
|
|
UseAlternateSSLDefaultValue = true;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if NETFX_CORE
|
|
|
|
IOService = new PlatformSupport.FileSystem.NETFXCOREIOService();
|
|
|
|
#else
|
|
|
|
IOService = new PlatformSupport.FileSystem.DefaultIOService();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR)
|
|
|
|
ProxyDetector = new Proxies.Autodetect.ProxyDetector();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
|
|
|
|
/// <summary>
|
|
|
|
/// HTTP/2 settings
|
|
|
|
/// </summary>
|
|
|
|
public static Connections.HTTP2.HTTP2PluginSettings HTTP2Settings = new Connections.HTTP2.HTTP2PluginSettings();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#region Global Options
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The maximum active TCP connections that the client will maintain to a server. Default value is 6. 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!");
|
|
|
|
|
|
|
|
bool isGrowing = value > maxConnectionPerServer;
|
|
|
|
maxConnectionPerServer = value;
|
|
|
|
|
|
|
|
// If the allowed connections per server is growing, go through all hosts and try to send out queueud requests.
|
|
|
|
if (isGrowing)
|
|
|
|
HostManager.TryToSendQueuedRequests();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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 should be changed to false.
|
|
|
|
/// </summary>
|
|
|
|
public static bool KeepAliveDefaultValue { get; set; }
|
|
|
|
|
|
|
|
#if !BESTHTTP_DISABLE_CACHING
|
|
|
|
/// <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
|
|
|
|
/// <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; }
|
|
|
|
|
|
|
|
/// <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; }
|
|
|
|
|
|
|
|
#if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR)
|
|
|
|
|
|
|
|
public static Proxies.Autodetect.ProxyDetector ProxyDetector {
|
|
|
|
get => _proxyDetector;
|
|
|
|
set {
|
|
|
|
_proxyDetector?.Detach();
|
|
|
|
_proxyDetector = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
private static Proxies.Autodetect.ProxyDetector _proxyDetector;
|
|
|
|
|
|
|
|
/// <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 Proxy 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 ThreadedLogger();
|
|
|
|
logger.Level = Loglevels.None;
|
|
|
|
}
|
|
|
|
|
|
|
|
return logger;
|
|
|
|
}
|
|
|
|
|
|
|
|
set { logger = value; }
|
|
|
|
}
|
|
|
|
private static BestHTTP.Logger.ILogger logger;
|
|
|
|
|
|
|
|
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
|
|
|
|
|
|
|
public static TlsClientFactoryDelegate TlsClientFactory;
|
|
|
|
|
|
|
|
public static Connections.TLS.AbstractTls13Client DefaultTlsClientFactory(HTTPRequest request, List<SecureProtocol.Org.BouncyCastle.Tls.ProtocolName> protocols)
|
|
|
|
{
|
|
|
|
// http://tools.ietf.org/html/rfc3546#section-3.1
|
|
|
|
// -It is RECOMMENDED that clients include an extension of type "server_name" in the client hello whenever they locate a server by a supported name type.
|
|
|
|
// -Literal IPv4 and IPv6 addresses are not permitted in "HostName".
|
|
|
|
|
|
|
|
// User-defined list has a higher priority
|
|
|
|
List<SecureProtocol.Org.BouncyCastle.Tls.ServerName> hostNames = null;
|
|
|
|
|
|
|
|
// If there's no user defined one and the host isn't an IP address, add the default one
|
|
|
|
if (!request.CurrentUri.IsHostIsAnIPAddress())
|
|
|
|
{
|
|
|
|
hostNames = new List<SecureProtocol.Org.BouncyCastle.Tls.ServerName>(1);
|
|
|
|
hostNames.Add(new SecureProtocol.Org.BouncyCastle.Tls.ServerName(0, System.Text.Encoding.UTF8.GetBytes(request.CurrentUri.Host)));
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Connections.TLS.DefaultTls13Client(request, hostNames, protocols);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The default value for the HTTPRequest's UseAlternateSSL property.
|
|
|
|
/// </summary>
|
|
|
|
public static bool UseAlternateSSLDefaultValue { get; set; }
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if !NETFX_CORE
|
|
|
|
public static Func<HTTPRequest, System.Security.Cryptography.X509Certificates.X509Certificate, System.Security.Cryptography.X509Certificates.X509Chain, System.Net.Security.SslPolicyErrors, bool> DefaultCertificationValidator;
|
|
|
|
public static ClientCertificateSelector ClientCertificationProvider;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// TCP Client's send buffer size.
|
|
|
|
/// </summary>
|
|
|
|
public static int? SendBufferSize;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// TCP Client's receive buffer size.
|
|
|
|
/// </summary>
|
|
|
|
public static int? ReceiveBufferSize;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// An IIOService implementation to handle filesystem operations.
|
|
|
|
/// </summary>
|
|
|
|
public static PlatformSupport.FileSystem.IIOService IOService;
|
|
|
|
|
|
|
|
/// <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; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// User-agent string that will be sent with each requests.
|
|
|
|
/// </summary>
|
|
|
|
public static string UserAgent = "BestHTTP/2 v2.8.4";
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// It's true if the application is quitting and the plugin is shutting down itself.
|
|
|
|
/// </summary>
|
|
|
|
public static bool IsQuitting { get { return _isQuitting; } private set { _isQuitting = value; } }
|
|
|
|
private static volatile bool _isQuitting;
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Manager variables
|
|
|
|
|
|
|
|
private static bool IsSetupCalled;
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Public Interface
|
|
|
|
|
|
|
|
public static void Setup()
|
|
|
|
{
|
|
|
|
if (IsSetupCalled)
|
|
|
|
return;
|
|
|
|
IsSetupCalled = true;
|
|
|
|
IsQuitting = false;
|
|
|
|
|
|
|
|
HTTPManager.Logger.Information("HTTPManager", "Setup called! UserAgent: " + UserAgent);
|
|
|
|
|
|
|
|
HTTPUpdateDelegator.CheckInstance();
|
|
|
|
|
|
|
|
#if !BESTHTTP_DISABLE_CACHING
|
|
|
|
HTTPCacheService.CheckSetup();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if !BESTHTTP_DISABLE_COOKIES
|
|
|
|
Cookies.CookieJar.SetupFolder();
|
|
|
|
Cookies.CookieJar.Load();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
HostManager.Load();
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
if (!IsSetupCalled)
|
|
|
|
Setup();
|
|
|
|
|
|
|
|
if (request.IsCancellationRequested || IsQuitting)
|
|
|
|
return request;
|
|
|
|
|
|
|
|
#if !BESTHTTP_DISABLE_CACHING
|
|
|
|
// If possible load the full response from cache.
|
|
|
|
if (Caching.HTTPCacheService.IsCachedEntityExpiresInTheFuture(request))
|
|
|
|
{
|
|
|
|
DateTime started = DateTime.Now;
|
|
|
|
PlatformSupport.Threading.ThreadedRunner.RunShortLiving<HTTPRequest>((req) =>
|
|
|
|
{
|
|
|
|
if (Connections.ConnectionHelper.TryLoadAllFromCache("HTTPManager", req, req.Context))
|
|
|
|
{
|
|
|
|
req.Timing.Add("Full Cache Load", DateTime.Now - started);
|
|
|
|
req.State = HTTPRequestStates.Finished;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// If for some reason it couldn't load we place back the request to the queue.
|
|
|
|
|
|
|
|
request.State = HTTPRequestStates.Queued;
|
|
|
|
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(request, RequestEvents.Resend));
|
|
|
|
}
|
|
|
|
}, request);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
request.State = HTTPRequestStates.Queued;
|
|
|
|
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(request, RequestEvents.Resend));
|
|
|
|
}
|
|
|
|
|
|
|
|
return request;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Internal Helper Functions
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Will return where the various caches should be saved.
|
|
|
|
/// </summary>
|
|
|
|
public 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 UnityEngine.Application.persistentDataPath;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
#if UNITY_2019_3_OR_NEWER
|
|
|
|
[UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)]
|
|
|
|
#endif
|
|
|
|
public static void ResetSetup()
|
|
|
|
{
|
|
|
|
IsSetupCalled = false;
|
|
|
|
BufferedReadNetworkStream.ResetNetworkStats();
|
|
|
|
HTTPManager.Logger.Information("HTTPManager", "Reset called!");
|
|
|
|
}
|
|
|
|
#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()
|
|
|
|
{
|
|
|
|
using (var _ = new ProfilerMarker(nameof(RequestEventHelper)).Auto())
|
|
|
|
RequestEventHelper.ProcessQueue();
|
|
|
|
|
|
|
|
using (var _ = new ProfilerMarker(nameof(ConnectionEventHelper)).Auto())
|
|
|
|
ConnectionEventHelper.ProcessQueue();
|
|
|
|
|
|
|
|
using (var _ = new ProfilerMarker(nameof(ProtocolEventHelper)).Auto())
|
|
|
|
ProtocolEventHelper.ProcessQueue();
|
|
|
|
|
|
|
|
using (var _ = new ProfilerMarker(nameof(PluginEventHelper)).Auto())
|
|
|
|
PluginEventHelper.ProcessQueue();
|
|
|
|
|
|
|
|
using (var _ = new ProfilerMarker(nameof(Timer)).Auto())
|
|
|
|
BestHTTP.Extensions.Timer.Process();
|
|
|
|
|
|
|
|
if (heartbeats != null)
|
|
|
|
{
|
|
|
|
using (var _ = new ProfilerMarker(nameof(HeartbeatManager)).Auto())
|
|
|
|
heartbeats.Update();
|
|
|
|
}
|
|
|
|
|
|
|
|
using (var _ = new ProfilerMarker(nameof(BufferPool)).Auto())
|
|
|
|
BufferPool.Maintain();
|
|
|
|
|
|
|
|
using (var _ = new ProfilerMarker(nameof(StringBuilderPool)).Auto())
|
|
|
|
StringBuilderPool.Maintain();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void OnQuit()
|
|
|
|
{
|
|
|
|
HTTPManager.Logger.Information("HTTPManager", "OnQuit called!");
|
|
|
|
|
|
|
|
IsQuitting = true;
|
|
|
|
|
|
|
|
AbortAll();
|
|
|
|
|
|
|
|
#if !BESTHTTP_DISABLE_CACHING
|
|
|
|
HTTPCacheService.SaveLibrary();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if !BESTHTTP_DISABLE_COOKIES
|
|
|
|
CookieJar.Persist();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
OnUpdate();
|
|
|
|
|
|
|
|
HostManager.Clear();
|
|
|
|
|
|
|
|
Heartbeats.Clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void AbortAll()
|
|
|
|
{
|
|
|
|
HTTPManager.Logger.Information("HTTPManager", "AbortAll called!");
|
|
|
|
|
|
|
|
// This is an immediate shutdown request!
|
|
|
|
|
|
|
|
RequestEventHelper.Clear();
|
|
|
|
ConnectionEventHelper.Clear();
|
|
|
|
PluginEventHelper.Clear();
|
|
|
|
ProtocolEventHelper.Clear();
|
|
|
|
|
|
|
|
HostManager.Shutdown();
|
|
|
|
|
|
|
|
ProtocolEventHelper.CancelActiveProtocols();
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
}
|
|
|