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.
269 lines
11 KiB
269 lines
11 KiB
8 months ago
|
#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
|