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.
261 lines
9.4 KiB
261 lines
9.4 KiB
#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
|
|
|