You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
262 lines
9.4 KiB
262 lines
9.4 KiB
8 months ago
|
#if !UNITY_WEBGL || UNITY_EDITOR
|
||
|
|
||
|
using System;
|
||
|
|
||
|
#if !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls;
|
||
|
#endif
|
||
|
|
||
|
using BestHTTP.Core;
|
||
|
using BestHTTP.Timings;
|
||
|
|
||
|
namespace BestHTTP.Connections
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Represents and manages a connection to a server.
|
||
|
/// </summary>
|
||
|
public sealed class HTTPConnection : ConnectionBase
|
||
|
{
|
||
|
public TCPConnector connector;
|
||
|
public IHTTPRequestHandler requestHandler;
|
||
|
|
||
|
public override TimeSpan KeepAliveTime {
|
||
|
get {
|
||
|
if (this.requestHandler != null && this.requestHandler.KeepAlive != null)
|
||
|
{
|
||
|
if (this.requestHandler.KeepAlive.MaxRequests > 0)
|
||
|
{
|
||
|
if (base.KeepAliveTime < this.requestHandler.KeepAlive.TimeOut)
|
||
|
return base.KeepAliveTime;
|
||
|
else
|
||
|
return this.requestHandler.KeepAlive.TimeOut;
|
||
|
}
|
||
|
else
|
||
|
return TimeSpan.Zero;
|
||
|
}
|
||
|
|
||
|
return base.KeepAliveTime;
|
||
|
}
|
||
|
|
||
|
protected set
|
||
|
{
|
||
|
base.KeepAliveTime = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override bool CanProcessMultiple
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (this.requestHandler != null)
|
||
|
return this.requestHandler.CanProcessMultiple;
|
||
|
return base.CanProcessMultiple;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal HTTPConnection(string serverAddress)
|
||
|
:base(serverAddress)
|
||
|
{}
|
||
|
|
||
|
public override bool TestConnection()
|
||
|
{
|
||
|
#if !NETFX_CORE
|
||
|
try
|
||
|
{
|
||
|
#if !BESTHTTP_DISABLE_ALTERNATE_SSL
|
||
|
TlsStream stream = (this.connector?.Stream as TlsStream);
|
||
|
if (stream != null && stream.Protocol != null)
|
||
|
{
|
||
|
bool locked = stream.Protocol.TryEnterApplicationDataLock(0);
|
||
|
try
|
||
|
{
|
||
|
if (locked && this.connector.Client.Available > 0)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
var available = stream.Protocol.TestApplicationData();
|
||
|
return !stream.Protocol.IsClosed;
|
||
|
}
|
||
|
catch
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
if (locked)
|
||
|
stream.Protocol.ExitApplicationDataLock();
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
bool connected = this.connector.Client.Connected;
|
||
|
|
||
|
return connected;
|
||
|
}
|
||
|
catch
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
#else
|
||
|
return base.TestConnection();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
internal override void Process(HTTPRequest request)
|
||
|
{
|
||
|
this.LastProcessedUri = request.CurrentUri;
|
||
|
|
||
|
if (this.requestHandler == null || !this.requestHandler.HasCustomRequestProcessor)
|
||
|
base.Process(request);
|
||
|
else
|
||
|
{
|
||
|
this.requestHandler.Process(request);
|
||
|
LastProcessTime = DateTime.Now;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected override void ThreadFunc()
|
||
|
{
|
||
|
if (this.CurrentRequest.IsRedirected)
|
||
|
this.CurrentRequest.Timing.Add(TimingEventNames.Queued_For_Redirection);
|
||
|
else
|
||
|
this.CurrentRequest.Timing.Add(TimingEventNames.Queued);
|
||
|
|
||
|
if (this.connector != null && !this.connector.IsConnected)
|
||
|
{
|
||
|
// this will send the request back to the queue
|
||
|
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(CurrentRequest, RequestEvents.Resend));
|
||
|
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (this.connector == null)
|
||
|
{
|
||
|
this.connector = new Connections.TCPConnector();
|
||
|
|
||
|
try
|
||
|
{
|
||
|
this.connector.Connect(this.CurrentRequest);
|
||
|
}
|
||
|
catch(Exception ex)
|
||
|
{
|
||
|
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
|
||
|
HTTPManager.Logger.Exception("HTTPConnection", "Connector.Connect", ex, this.Context, this.CurrentRequest.Context);
|
||
|
|
||
|
|
||
|
if (ex is TimeoutException)
|
||
|
this.CurrentRequest.State = HTTPRequestStates.ConnectionTimedOut;
|
||
|
else if (!this.CurrentRequest.IsTimedOut) // Do nothing here if Abort() got called on the request, its State is already set.
|
||
|
{
|
||
|
this.CurrentRequest.Exception = ex;
|
||
|
this.CurrentRequest.State = HTTPRequestStates.Error;
|
||
|
}
|
||
|
|
||
|
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
#if !NETFX_CORE
|
||
|
// data sending is buffered for all protocols, so when we put data into the socket we want to send them asap
|
||
|
this.connector.Client.NoDelay = true;
|
||
|
#endif
|
||
|
StartTime = DateTime.UtcNow;
|
||
|
|
||
|
HTTPManager.Logger.Information("HTTPConnection", "Negotiated protocol through ALPN: '" + this.connector.NegotiatedProtocol + "'", this.Context, this.CurrentRequest.Context);
|
||
|
|
||
|
switch (this.connector.NegotiatedProtocol)
|
||
|
{
|
||
|
case HTTPProtocolFactory.W3C_HTTP1:
|
||
|
this.requestHandler = new Connections.HTTP1Handler(this);
|
||
|
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HostProtocolSupport.HTTP1));
|
||
|
break;
|
||
|
|
||
|
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
|
||
|
case HTTPProtocolFactory.W3C_HTTP2:
|
||
|
this.requestHandler = new Connections.HTTP2.HTTP2Handler(this);
|
||
|
this.CurrentRequest = null;
|
||
|
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HostProtocolSupport.HTTP2));
|
||
|
break;
|
||
|
#endif
|
||
|
|
||
|
default:
|
||
|
HTTPManager.Logger.Error("HTTPConnection", "Unknown negotiated protocol: " + this.connector.NegotiatedProtocol, this.Context, this.CurrentRequest.Context);
|
||
|
|
||
|
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(CurrentRequest, RequestEvents.Resend));
|
||
|
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.requestHandler.Context.Add("Connection", this.GetHashCode());
|
||
|
this.Context.Add("RequestHandler", this.requestHandler.GetHashCode());
|
||
|
|
||
|
this.requestHandler.RunHandler();
|
||
|
LastProcessTime = DateTime.Now;
|
||
|
}
|
||
|
|
||
|
public override void Shutdown(ShutdownTypes type)
|
||
|
{
|
||
|
base.Shutdown(type);
|
||
|
|
||
|
if (this.requestHandler != null)
|
||
|
this.requestHandler.Shutdown(type);
|
||
|
|
||
|
switch(this.ShutdownType)
|
||
|
{
|
||
|
case ShutdownTypes.Immediate:
|
||
|
this.connector.Dispose();
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected override void Dispose(bool disposing)
|
||
|
{
|
||
|
if (disposing)
|
||
|
{
|
||
|
LastProcessedUri = null;
|
||
|
if (this.State != HTTPConnectionStates.WaitForProtocolShutdown)
|
||
|
{
|
||
|
if (this.connector != null)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
this.connector.Close();
|
||
|
}
|
||
|
catch
|
||
|
{ }
|
||
|
this.connector = null;
|
||
|
}
|
||
|
|
||
|
if (this.requestHandler != null)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
this.requestHandler.Dispose();
|
||
|
}
|
||
|
catch
|
||
|
{ }
|
||
|
this.requestHandler = null;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// We have to connector to do not close its stream at any cost while disposing.
|
||
|
// All references to this connection will be removed, so this and the connector may be finalized after some time.
|
||
|
// But, finalizing (and disposing) the connector while the protocol is still active would be fatal,
|
||
|
// so we have to make sure that it will not happen. This also means that the protocol has the responsibility (as always had)
|
||
|
// to close the stream and TCP connection properly.
|
||
|
if (this.connector != null)
|
||
|
this.connector.LeaveOpen = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
base.Dispose(disposing);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|