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.
268 lines
11 KiB
268 lines
11 KiB
#if !UNITY_WEBGL || UNITY_EDITOR |
|
using System; |
|
using BestHTTP.Core; |
|
using BestHTTP.Logger; |
|
using BestHTTP.PlatformSupport.Threading; |
|
|
|
#if !BESTHTTP_DISABLE_CACHING |
|
using BestHTTP.Caching; |
|
#endif |
|
|
|
using BestHTTP.Timings; |
|
|
|
namespace BestHTTP.Connections |
|
{ |
|
public sealed class HTTP1Handler : IHTTPRequestHandler |
|
{ |
|
public bool HasCustomRequestProcessor { get { return false; } } |
|
public KeepAliveHeader KeepAlive { get { return this._keepAlive; } } |
|
private KeepAliveHeader _keepAlive; |
|
|
|
public bool CanProcessMultiple { get { return false; } } |
|
|
|
private readonly HTTPConnection conn; |
|
|
|
public LoggingContext Context { get; private set; } |
|
|
|
public HTTP1Handler(HTTPConnection conn) |
|
{ |
|
this.Context = new LoggingContext(this); |
|
this.conn = conn; |
|
} |
|
|
|
public void Process(HTTPRequest request) |
|
{ |
|
} |
|
|
|
public void RunHandler() |
|
{ |
|
HTTPManager.Logger.Information("HTTP1Handler", string.Format("[{0}] started processing request '{1}'", this, this.conn.CurrentRequest.CurrentUri.ToString()), this.Context, this.conn.CurrentRequest.Context); |
|
|
|
ThreadedRunner.SetThreadName("BestHTTP.HTTP1 R&W"); |
|
|
|
HTTPConnectionStates proposedConnectionState = HTTPConnectionStates.Processing; |
|
|
|
bool resendRequest = false; |
|
|
|
try |
|
{ |
|
if (this.conn.CurrentRequest.IsCancellationRequested) |
|
return; |
|
|
|
#if !BESTHTTP_DISABLE_CACHING |
|
// Setup cache control headers before we send out the request |
|
if (!this.conn.CurrentRequest.DisableCache) |
|
HTTPCacheService.SetHeaders(this.conn.CurrentRequest); |
|
#endif |
|
|
|
// Write the request to the stream |
|
this.conn.CurrentRequest.QueuedAt = DateTime.MinValue; |
|
this.conn.CurrentRequest.ProcessingStarted = DateTime.UtcNow; |
|
this.conn.CurrentRequest.SendOutTo(this.conn.connector.Stream); |
|
this.conn.CurrentRequest.Timing.Add(TimingEventNames.Request_Sent); |
|
|
|
if (this.conn.CurrentRequest.IsCancellationRequested) |
|
return; |
|
|
|
this.conn.CurrentRequest.OnCancellationRequested += OnCancellationRequested; |
|
|
|
// Receive response from the server |
|
bool received = Receive(this.conn.CurrentRequest); |
|
|
|
this.conn.CurrentRequest.Timing.Add(TimingEventNames.Response_Received); |
|
|
|
if (this.conn.CurrentRequest.IsCancellationRequested) |
|
return; |
|
|
|
if (!received && this.conn.CurrentRequest.Retries < this.conn.CurrentRequest.MaxRetries) |
|
{ |
|
proposedConnectionState = HTTPConnectionStates.Closed; |
|
this.conn.CurrentRequest.Retries++; |
|
resendRequest = true; |
|
return; |
|
} |
|
|
|
ConnectionHelper.HandleResponse(this.conn.ToString(), this.conn.CurrentRequest, out resendRequest, out proposedConnectionState, ref this._keepAlive, this.conn.Context, this.conn.CurrentRequest.Context); |
|
} |
|
catch (TimeoutException e) |
|
{ |
|
this.conn.CurrentRequest.Response = null; |
|
|
|
// Do nothing here if Abort() got called on the request, its State is already set. |
|
if (!this.conn.CurrentRequest.IsTimedOut) |
|
{ |
|
// We will try again only once |
|
if (this.conn.CurrentRequest.Retries < this.conn.CurrentRequest.MaxRetries) |
|
{ |
|
this.conn.CurrentRequest.Retries++; |
|
resendRequest = true; |
|
} |
|
else |
|
{ |
|
this.conn.CurrentRequest.Exception = e; |
|
this.conn.CurrentRequest.State = HTTPRequestStates.ConnectionTimedOut; |
|
} |
|
} |
|
|
|
proposedConnectionState = HTTPConnectionStates.Closed; |
|
} |
|
catch (Exception e) |
|
{ |
|
if (this.ShutdownType == ShutdownTypes.Immediate) |
|
return; |
|
|
|
string exceptionMessage = string.Empty; |
|
if (e == null) |
|
exceptionMessage = "null"; |
|
else |
|
{ |
|
System.Text.StringBuilder sb = new System.Text.StringBuilder(); |
|
|
|
Exception exception = e; |
|
int counter = 1; |
|
while (exception != null) |
|
{ |
|
sb.AppendFormat("{0}: {1} {2}", counter++.ToString(), exception.Message, exception.StackTrace); |
|
|
|
exception = exception.InnerException; |
|
|
|
if (exception != null) |
|
sb.AppendLine(); |
|
} |
|
|
|
exceptionMessage = sb.ToString(); |
|
} |
|
HTTPManager.Logger.Verbose("HTTP1Handler", exceptionMessage, this.Context, this.conn.CurrentRequest.Context); |
|
|
|
#if !BESTHTTP_DISABLE_CACHING |
|
if (this.conn.CurrentRequest.UseStreaming) |
|
HTTPCacheService.DeleteEntity(this.conn.CurrentRequest.CurrentUri); |
|
#endif |
|
|
|
// Something gone bad, Response must be null! |
|
this.conn.CurrentRequest.Response = null; |
|
|
|
// Do nothing here if Abort() got called on the request, its State is already set. |
|
if (!this.conn.CurrentRequest.IsCancellationRequested) |
|
{ |
|
this.conn.CurrentRequest.Exception = e; |
|
this.conn.CurrentRequest.State = HTTPRequestStates.Error; |
|
} |
|
|
|
proposedConnectionState = HTTPConnectionStates.Closed; |
|
} |
|
finally |
|
{ |
|
this.conn.CurrentRequest.OnCancellationRequested -= OnCancellationRequested; |
|
|
|
// Exit ASAP |
|
if (this.ShutdownType != ShutdownTypes.Immediate) |
|
{ |
|
if (this.conn.CurrentRequest.IsCancellationRequested) |
|
{ |
|
// we don't know what stage the request is canceled, we can't safely reuse the tcp channel. |
|
proposedConnectionState = HTTPConnectionStates.Closed; |
|
|
|
this.conn.CurrentRequest.Response = null; |
|
|
|
// The request's State already set, or going to be set soon in RequestEvents.cs. |
|
//this.conn.CurrentRequest.State = this.conn.CurrentRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted; |
|
} |
|
else if (resendRequest) |
|
{ |
|
// Here introducing a ClosedResendRequest connection state, where we have to process the connection's state change to Closed |
|
// than we have to resend the request. |
|
// If we would send the Resend request here, than a few lines below the Closed connection state change, |
|
// request events are processed before connection events (just switching the EnqueueRequestEvent and EnqueueConnectionEvent wouldn't work |
|
// see order of ProcessQueues in HTTPManager.OnUpdate!) and it would pick this very same closing/closed connection! |
|
|
|
if (proposedConnectionState == HTTPConnectionStates.Closed || proposedConnectionState == HTTPConnectionStates.ClosedResendRequest) |
|
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, this.conn.CurrentRequest)); |
|
else |
|
RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.conn.CurrentRequest, RequestEvents.Resend)); |
|
} |
|
else if (this.conn.CurrentRequest.Response != null && this.conn.CurrentRequest.Response.IsUpgraded) |
|
{ |
|
proposedConnectionState = HTTPConnectionStates.WaitForProtocolShutdown; |
|
} |
|
else if (this.conn.CurrentRequest.State == HTTPRequestStates.Processing) |
|
{ |
|
if (this.conn.CurrentRequest.Response != null) |
|
this.conn.CurrentRequest.State = HTTPRequestStates.Finished; |
|
else |
|
{ |
|
this.conn.CurrentRequest.Exception = new Exception(string.Format("[{0}] Remote server closed the connection before sending response header! Previous request state: {1}. Connection state: {2}", |
|
this.ToString(), |
|
this.conn.CurrentRequest.State.ToString(), |
|
this.conn.State.ToString())); |
|
this.conn.CurrentRequest.State = HTTPRequestStates.Error; |
|
|
|
proposedConnectionState = HTTPConnectionStates.Closed; |
|
} |
|
} |
|
|
|
this.conn.CurrentRequest = null; |
|
|
|
if (proposedConnectionState == HTTPConnectionStates.Processing) |
|
proposedConnectionState = HTTPConnectionStates.Recycle; |
|
|
|
if (proposedConnectionState != HTTPConnectionStates.ClosedResendRequest) |
|
ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, proposedConnectionState)); |
|
} |
|
} |
|
} |
|
|
|
private void OnCancellationRequested(HTTPRequest obj) |
|
{ |
|
if (this.conn != null && this.conn.connector != null) |
|
this.conn.connector.Dispose(); |
|
} |
|
|
|
private bool Receive(HTTPRequest request) |
|
{ |
|
SupportedProtocols protocol = HTTPProtocolFactory.GetProtocolFromUri(request.CurrentUri); |
|
|
|
if (HTTPManager.Logger.Level == Logger.Loglevels.All) |
|
HTTPManager.Logger.Verbose("HTTPConnection", string.Format("[{0}] - Receive - protocol: {1}", this.ToString(), protocol.ToString()), this.Context, request.Context); |
|
|
|
request.Response = HTTPProtocolFactory.Get(protocol, request, this.conn.connector.Stream, request.UseStreaming, false); |
|
|
|
if (!request.Response.Receive()) |
|
{ |
|
if (HTTPManager.Logger.Level == Logger.Loglevels.All) |
|
HTTPManager.Logger.Verbose("HTTP1Handler", string.Format("[{0}] - Receive - Failed! Response will be null, returning with false.", this.ToString()), this.Context, request.Context); |
|
request.Response = null; |
|
return false; |
|
} |
|
|
|
if (HTTPManager.Logger.Level == Logger.Loglevels.All) |
|
HTTPManager.Logger.Verbose("HTTP1Handler", string.Format("[{0}] - Receive - Finished Successfully!", this.ToString()), this.Context, request.Context); |
|
|
|
return true; |
|
} |
|
|
|
public ShutdownTypes ShutdownType { get; private set; } |
|
|
|
public void Shutdown(ShutdownTypes type) |
|
{ |
|
this.ShutdownType = type; |
|
} |
|
|
|
public void Dispose() |
|
{ |
|
Dispose(true); |
|
GC.SuppressFinalize(this); |
|
} |
|
|
|
private void Dispose(bool disposing) |
|
{ |
|
} |
|
|
|
~HTTP1Handler() |
|
{ |
|
Dispose(false); |
|
} |
|
} |
|
} |
|
|
|
#endif
|
|
|