655 lines
26 KiB
C#
655 lines
26 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
|
|
using Best.HTTP.Caching;
|
|
using Best.HTTP.HostSetting;
|
|
using Best.HTTP.Request.Settings;
|
|
using Best.HTTP.Request.Timings;
|
|
using Best.HTTP.Shared;
|
|
using Best.HTTP.Shared.Extensions;
|
|
|
|
namespace Best.HTTP.Hosts.Connections
|
|
{
|
|
public enum RequestEvents
|
|
{
|
|
Upgraded,
|
|
DownloadProgress,
|
|
UploadProgress,
|
|
StreamingData,
|
|
DownloadStarted,
|
|
StateChange,
|
|
SetState,
|
|
QueuedResend,
|
|
Resend,
|
|
Headers,
|
|
Timing,
|
|
}
|
|
|
|
public readonly struct RequestEventInfo
|
|
{
|
|
public readonly HTTPRequest SourceRequest;
|
|
public readonly RequestEvents Event;
|
|
|
|
public readonly HTTPRequestStates State;
|
|
|
|
public readonly Exception Error;
|
|
|
|
public readonly long Progress;
|
|
public readonly long ProgressLength;
|
|
|
|
public readonly byte[] Data;
|
|
public readonly int DataLength;
|
|
|
|
public readonly TimingEventInfo timingEvent;
|
|
|
|
// Headers
|
|
public readonly Dictionary<string, List<string>> Headers;
|
|
|
|
public RequestEventInfo(HTTPRequest request, RequestEvents @event)
|
|
{
|
|
this.SourceRequest = request;
|
|
this.Event = @event;
|
|
|
|
this.State = HTTPRequestStates.Initial;
|
|
|
|
this.Error = null;
|
|
|
|
this.Progress = this.ProgressLength = 0;
|
|
|
|
this.Data = null;
|
|
this.DataLength = 0;
|
|
|
|
// Headers
|
|
this.Headers = null;
|
|
|
|
this.timingEvent = default;
|
|
}
|
|
|
|
public RequestEventInfo(HTTPRequest request, RequestEvents @event, HTTPRequestStates newState)
|
|
{
|
|
this.SourceRequest = request;
|
|
this.Event = @event;
|
|
|
|
this.State = newState;
|
|
|
|
this.Error = null;
|
|
|
|
this.Progress = this.ProgressLength = 0;
|
|
|
|
this.Data = null;
|
|
this.DataLength = 0;
|
|
|
|
// Headers
|
|
this.Headers = null;
|
|
|
|
this.timingEvent = default;
|
|
}
|
|
|
|
public RequestEventInfo(HTTPRequest request, HTTPRequestStates newState)
|
|
{
|
|
this.SourceRequest = request;
|
|
this.Event = RequestEvents.StateChange;
|
|
this.State = newState;
|
|
|
|
this.Error = null;
|
|
|
|
this.Progress = this.ProgressLength = 0;
|
|
this.Data = null;
|
|
this.DataLength = 0;
|
|
|
|
// Headers
|
|
this.Headers = null;
|
|
|
|
this.timingEvent = default;
|
|
}
|
|
|
|
public RequestEventInfo(HTTPRequest request, HTTPRequestStates newState, Exception error)
|
|
{
|
|
this.SourceRequest = request;
|
|
this.Event = RequestEvents.SetState;
|
|
this.State = newState;
|
|
|
|
this.Error = error;
|
|
|
|
this.Progress = this.ProgressLength = 0;
|
|
this.Data = null;
|
|
this.DataLength = 0;
|
|
|
|
// Headers
|
|
this.Headers = null;
|
|
|
|
this.timingEvent = default;
|
|
}
|
|
|
|
public RequestEventInfo(HTTPRequest request, RequestEvents @event, long progress, long progressLength)
|
|
{
|
|
this.SourceRequest = request;
|
|
this.Event = @event;
|
|
this.State = HTTPRequestStates.Initial;
|
|
|
|
this.Error = null;
|
|
|
|
this.Progress = progress;
|
|
this.ProgressLength = progressLength;
|
|
this.Data = null;
|
|
this.DataLength = 0;
|
|
|
|
// Headers
|
|
this.Headers = null;
|
|
|
|
this.timingEvent = default;
|
|
}
|
|
|
|
public RequestEventInfo(HTTPRequest request, byte[] data, int dataLength)
|
|
{
|
|
this.SourceRequest = request;
|
|
this.Event = RequestEvents.StreamingData;
|
|
this.State = HTTPRequestStates.Initial;
|
|
|
|
this.Error = null;
|
|
|
|
this.Progress = this.ProgressLength = 0;
|
|
this.Data = data;
|
|
this.DataLength = dataLength;
|
|
|
|
// Headers
|
|
this.Headers = null;
|
|
|
|
this.timingEvent = default;
|
|
}
|
|
|
|
public RequestEventInfo(HTTPRequest request, Dictionary<string, List<string>> headers)
|
|
{
|
|
this.SourceRequest = request;
|
|
this.Event = RequestEvents.Headers;
|
|
this.State = HTTPRequestStates.Initial;
|
|
|
|
this.Error = null;
|
|
|
|
this.Progress = this.ProgressLength = 0;
|
|
this.Data = null;
|
|
this.DataLength = 0;
|
|
|
|
// Headers
|
|
this.Headers = headers;
|
|
|
|
this.timingEvent = default;
|
|
}
|
|
|
|
public RequestEventInfo(HTTPRequest request, TimingEventInfo timingEvent)
|
|
{
|
|
this.SourceRequest = request;
|
|
this.Event = RequestEvents.Timing;
|
|
this.State = HTTPRequestStates.Initial;
|
|
|
|
this.Error = null;
|
|
|
|
this.Progress = this.ProgressLength = 0;
|
|
this.Data = null;
|
|
this.DataLength = 0;
|
|
|
|
// Headers
|
|
this.Headers = null;
|
|
|
|
this.timingEvent = timingEvent;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
switch (this.Event)
|
|
{
|
|
case RequestEvents.Upgraded:
|
|
return string.Format("[RequestEventInfo Event: Upgraded, Source: {0}]", this.SourceRequest.Context.Hash);
|
|
case RequestEvents.DownloadProgress:
|
|
return string.Format("[RequestEventInfo Event: DownloadProgress, Progress: {1}, ProgressLength: {2}, Source: {0}]", this.SourceRequest.Context.Hash, this.Progress, this.ProgressLength);
|
|
case RequestEvents.UploadProgress:
|
|
return string.Format("[RequestEventInfo Event: UploadProgress, Progress: {1}, ProgressLength: {2}, Source: {0}]", this.SourceRequest.Context.Hash, this.Progress, this.ProgressLength);
|
|
case RequestEvents.StreamingData:
|
|
return string.Format("[RequestEventInfo Event: StreamingData, DataLength: {1}, Source: {0}]", this.SourceRequest.Context.Hash, this.DataLength);
|
|
case RequestEvents.DownloadStarted:
|
|
return $"[RequestEventInfo Event: DownloadStarted, Source: {this.SourceRequest.Context.Hash}]";
|
|
case RequestEvents.StateChange:
|
|
return string.Format("[RequestEventInfo Event: StateChange, State: {1}, Source: {0}]", this.SourceRequest.Context.Hash, this.State);
|
|
case RequestEvents.SetState:
|
|
return string.Format("[RequestEventInfo Event: SetState, State: {1}, Source: {0}]", this.SourceRequest.Context.Hash, this.State);
|
|
case RequestEvents.Resend:
|
|
return string.Format("[RequestEventInfo Event: Resend, Source: {0}]", this.SourceRequest.Context.Hash);
|
|
case RequestEvents.Headers:
|
|
return string.Format("[RequestEventInfo Event: Headers, Source: {0}]", this.SourceRequest.Context.Hash);
|
|
case RequestEvents.QueuedResend:
|
|
return $"[RequestEventInfo Event: QueuedResend, Source: {this.SourceRequest.Context.Hash}]";
|
|
case RequestEvents.Timing:
|
|
return $"[RequestEventInfo Event: Timing, TimingEvent: {this.timingEvent}, Source:{this.SourceRequest.Context.Hash}]";
|
|
default:
|
|
throw new NotImplementedException(this.Event.ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
class ProgressFlattener
|
|
{
|
|
struct FlattenedProgress
|
|
{
|
|
public HTTPRequest request;
|
|
public OnProgressDelegate onProgress;
|
|
public long progress;
|
|
public long length;
|
|
}
|
|
|
|
private FlattenedProgress[] progresses;
|
|
private bool hasProgress;
|
|
|
|
public void InsertOrUpdate(RequestEventInfo info, OnProgressDelegate onProgress)
|
|
{
|
|
if (progresses == null)
|
|
progresses = new FlattenedProgress[1];
|
|
|
|
hasProgress = true;
|
|
|
|
var newProgress = new FlattenedProgress { request = info.SourceRequest, progress = info.Progress, length = info.ProgressLength, onProgress = onProgress };
|
|
|
|
int firstEmptyIdx = -1;
|
|
for (int i = 0; i < progresses.Length; i++)
|
|
{
|
|
var progress = progresses[i];
|
|
if (object.ReferenceEquals(progress.request, info.SourceRequest))
|
|
{
|
|
progresses[i] = newProgress;
|
|
return;
|
|
}
|
|
|
|
if (firstEmptyIdx == -1 && progress.request == null)
|
|
firstEmptyIdx = i;
|
|
}
|
|
|
|
if (firstEmptyIdx == -1)
|
|
{
|
|
Array.Resize(ref progresses, progresses.Length + 1);
|
|
progresses[progresses.Length - 1] = newProgress;
|
|
}
|
|
else
|
|
progresses[firstEmptyIdx] = newProgress;
|
|
}
|
|
|
|
public void DispatchProgressCallbacks()
|
|
{
|
|
if (progresses == null || !hasProgress)
|
|
return;
|
|
|
|
for (int i = 0; i < progresses.Length; ++i)
|
|
{
|
|
var @event = progresses[i];
|
|
var source = @event.request;
|
|
if (source != null && @event.onProgress != null)
|
|
{
|
|
try
|
|
{
|
|
@event.onProgress(source, @event.progress, @event.length);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HTTPManager.Logger.Exception("ProgressFlattener", "DispatchProgressCallbacks", ex, source.Context);
|
|
}
|
|
}
|
|
}
|
|
|
|
Array.Clear(progresses, 0, progresses.Length);
|
|
hasProgress = false;
|
|
}
|
|
}
|
|
|
|
public static class RequestEventHelper
|
|
{
|
|
private static ConcurrentQueue<RequestEventInfo> requestEventQueue = new ConcurrentQueue<RequestEventInfo>();
|
|
|
|
#pragma warning disable 0649
|
|
public static Action<RequestEventInfo> OnEvent;
|
|
#pragma warning restore
|
|
|
|
// Low frame rate and high download/upload speed can add more download/upload progress events to dispatch in one frame.
|
|
// This can add higher CPU usage as it might cause updating the UI/do other things unnecessary in the same frame.
|
|
// To avoid this, instead of calling the events directly, we store the last event's data and call download/upload callbacks only once per frame.
|
|
|
|
private static ProgressFlattener downloadProgress;
|
|
private static ProgressFlattener uploadProgress;
|
|
|
|
private static List<HTTPRequest> registeredRequests = new List<HTTPRequest>();
|
|
|
|
public static void EnqueueRequestEvent(RequestEventInfo ev)
|
|
{
|
|
if (HTTPManager.Logger.IsDiagnostic)
|
|
HTTPManager.Logger.Information("RequestEventHelper", "Enqueue " + ev.ToString(), ev.SourceRequest.Context);
|
|
|
|
requestEventQueue.Enqueue(ev);
|
|
}
|
|
|
|
internal static void Clear()
|
|
{
|
|
requestEventQueue.Clear();
|
|
}
|
|
|
|
internal static void RegisterRequest(HTTPRequest request) => registeredRequests.Add(request);
|
|
|
|
internal static void AbortAllRequests()
|
|
{
|
|
foreach (var request in registeredRequests)
|
|
request.Abort();
|
|
}
|
|
|
|
internal static bool UnregisterRequest(HTTPRequest request) => registeredRequests.Remove(request);
|
|
internal static void RemoveAllRegisteredRequests() => registeredRequests.Clear();
|
|
|
|
internal static void ProcessQueue()
|
|
{
|
|
RequestEventInfo requestEvent;
|
|
while (requestEventQueue.TryDequeue(out requestEvent))
|
|
{
|
|
HTTPRequest source = requestEvent.SourceRequest;
|
|
|
|
if (HTTPManager.Logger.IsDiagnostic)
|
|
HTTPManager.Logger.Information("RequestEventHelper", "Processing request event: " + requestEvent.ToString(), source.Context);
|
|
|
|
if (OnEvent != null)
|
|
{
|
|
try
|
|
{
|
|
using (var _ = new Unity.Profiling.ProfilerMarker(nameof(OnEvent)).Auto())
|
|
OnEvent(requestEvent);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HTTPManager.Logger.Exception("RequestEventHelper", "ProcessQueue", ex, source.Context);
|
|
}
|
|
}
|
|
|
|
switch (requestEvent.Event)
|
|
{
|
|
case RequestEvents.DownloadProgress:
|
|
try
|
|
{
|
|
if (source.DownloadSettings.OnDownloadProgress != null)
|
|
{
|
|
if (downloadProgress == null)
|
|
downloadProgress = new ProgressFlattener();
|
|
downloadProgress.InsertOrUpdate(requestEvent, source.DownloadSettings.OnDownloadProgress);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.DownloadProgress", ex, source.Context);
|
|
}
|
|
break;
|
|
|
|
case RequestEvents.UploadProgress:
|
|
try
|
|
{
|
|
if (source.UploadSettings.OnUploadProgress != null)
|
|
{
|
|
if (uploadProgress == null)
|
|
uploadProgress = new ProgressFlattener();
|
|
uploadProgress.InsertOrUpdate(requestEvent, source.UploadSettings.OnUploadProgress);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.UploadProgress", ex, source.Context);
|
|
}
|
|
break;
|
|
|
|
case RequestEvents.QueuedResend:
|
|
if (source.IsCancellationRequested || HTTPManager.IsQuitting)
|
|
break;
|
|
|
|
HandleQueued(source);
|
|
goto case RequestEvents.Resend;
|
|
|
|
case RequestEvents.Resend:
|
|
source.State = HTTPRequestStates.Initial;
|
|
|
|
var host = HostManager.GetHostVariant(source);
|
|
|
|
host.Send(source);
|
|
break;
|
|
|
|
case RequestEvents.Headers:
|
|
{
|
|
try
|
|
{
|
|
var response = source.Response;
|
|
if (source.DownloadSettings.OnHeadersReceived != null && response != null)
|
|
source.DownloadSettings.OnHeadersReceived(source, response, requestEvent.Headers);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.Headers", ex, source.Context);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case RequestEvents.DownloadStarted:
|
|
try
|
|
{
|
|
// It's possible that response.DownStream is already null when this event is handled!
|
|
|
|
var response = source.Response;
|
|
var downStream = response?.DownStream;
|
|
|
|
if (response != null && downStream != null)
|
|
source.DownloadSettings.OnDownloadStarted?.Invoke(source, response, response.DownStream);
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
HTTPManager.Logger.Exception("RequestEventHelper", "DownloadStarted", ex, source.Context);
|
|
}
|
|
break;
|
|
|
|
case RequestEvents.Timing:
|
|
requestEvent.timingEvent.SourceRequest.Timing.AddEvent(requestEvent.timingEvent);
|
|
break;
|
|
|
|
case RequestEvents.SetState:
|
|
// In a case where the request is aborted its state is set to a >= Finished state then,
|
|
// on another thread the request processing will fail too queuing up a >= Finished state again.
|
|
if (source.State >= HTTPRequestStates.Finished && requestEvent.State >= HTTPRequestStates.Finished)
|
|
continue;
|
|
|
|
// It's different from the next condition! (this is >= and the next is only >)
|
|
if (requestEvent.State >= HTTPRequestStates.Finished)
|
|
source?.Response?.DownStream?.CompleteAdding(requestEvent.Error);
|
|
|
|
if (requestEvent.State > HTTPRequestStates.Finished)
|
|
{
|
|
HTTPManager.Logger.Information("RequestEventHelper", $"{requestEvent.State}: discarding response!", source.Response?.Context ?? source.Context);
|
|
|
|
source.Response?.Dispose();
|
|
source.Response = null;
|
|
}
|
|
|
|
source.Exception = requestEvent.Error;
|
|
source.State = requestEvent.State;
|
|
|
|
// https://www.rfc-editor.org/rfc/rfc5861.html#section-1
|
|
// The stale-if-error HTTP Cache-Control extension allows a cache to
|
|
// return a stale response when an error -- e.g., a 500 Internal Server
|
|
// Error, a network segment, or DNS failure -- is encountered, rather
|
|
// than returning a "hard" error.
|
|
if (requestEvent.State > HTTPRequestStates.Finished && requestEvent.State != HTTPRequestStates.Aborted)
|
|
{
|
|
if (HTTPManager.LocalCache != null && !source.DownloadSettings.DisableCache)
|
|
{
|
|
var hash = Caching.HTTPCache.CalculateHash(source.MethodType, source.CurrentUri);
|
|
if (HTTPManager.LocalCache.CanServeWithoutValidation(hash, ErrorTypeForValidation.ConnectionError, source.Context))
|
|
{
|
|
HTTPManager.LocalCache.Redirect(source, hash);
|
|
goto case RequestEvents.Resend;
|
|
}
|
|
}
|
|
}
|
|
|
|
goto case RequestEvents.StateChange;
|
|
|
|
case RequestEvents.StateChange:
|
|
try
|
|
{
|
|
using (var _ = new Unity.Profiling.ProfilerMarker(nameof(RequestEventHelper.HandleRequestStateChange)).Auto())
|
|
RequestEventHelper.HandleRequestStateChange(ref requestEvent);
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
HTTPManager.Logger.Exception("RequestEventHelper", "HandleRequestStateChange", ex, source.Context);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
uploadProgress?.DispatchProgressCallbacks();
|
|
downloadProgress?.DispatchProgressCallbacks();
|
|
}
|
|
|
|
// TODO: don't start/repeat if can't time out?
|
|
private static bool AbortRequestWhenTimedOut(DateTime now, object context)
|
|
{
|
|
HTTPRequest request = context as HTTPRequest;
|
|
|
|
if (request.State >= HTTPRequestStates.Finished || request.IsCancellationRequested)
|
|
return false; // don't repeat
|
|
|
|
var downStream = request.Response?.DownStream;
|
|
|
|
if (downStream != null && downStream.DoFullCheck(limit: 2))
|
|
{
|
|
var warning = $"Request's download stream is full({downStream.Length:N0}/{downStream.MaxBuffered:N0}) without any Read attempt! You can either increase HTTPRequest's DownloadSettings.ContentStreamMaxBuffered or use streaming. Request's uri: {request.Uri}. See https://bestdocshub.pages.dev/HTTP/getting-started/downloads/ for more details!";
|
|
if (HTTPManager.Logger.IsDiagnostic)
|
|
HTTPManager.Logger.Warning(nameof(RequestEventHelper), warning, request.Context);
|
|
else
|
|
UnityEngine.Debug.Log(warning);
|
|
|
|
// increase buffer limit
|
|
downStream.EmergencyIncreaseMaxBuffered();
|
|
|
|
return false;
|
|
}
|
|
|
|
// Upgradable protocols will shut down themselves
|
|
if (request?.Response?.IsUpgraded is bool upgraded && upgraded)
|
|
return false;
|
|
|
|
if (request.TimeoutSettings.IsTimedOut(HTTPManager.CurrentFrameDateTime))
|
|
{
|
|
HTTPManager.Logger.Information("RequestEventHelper", "AbortRequestWhenTimedOut - Request timed out. CurrentUri: " + request.CurrentUri.ToString(), request.Context);
|
|
request.Abort();
|
|
|
|
return false; // don't repeat
|
|
}
|
|
|
|
return true; // repeat
|
|
}
|
|
|
|
private static void HandleQueued(HTTPRequest source)
|
|
{
|
|
source.Timing.StartNext(TimingEventNames.Queued);
|
|
|
|
source.TimeoutSettings.QueuedAt = HTTPManager.CurrentFrameDateTime;
|
|
Timer.Add(new TimerData(TimeSpan.FromSeconds(1), source, AbortRequestWhenTimedOut));
|
|
}
|
|
|
|
static readonly string[] RequestStateNames = new string[] { "Initial", "Queued", "Processing", "Finished", "Error", "Aborted", "ConnectionTimedOut", "TimedOut" };
|
|
|
|
private static void HandleRequestStateChange(ref RequestEventInfo @event)
|
|
{
|
|
HTTPRequest source = @event.SourceRequest;
|
|
|
|
// Because there's a race condition between setting the request's State in its Abort() function running on Unity's main thread
|
|
// and the HTTP1/HTTP2 handlers running on an another one.
|
|
// Because of these race conditions cases violating expectations can be:
|
|
// 1.) State is finished but the response null
|
|
// 2.) State is (Connection)TimedOut and the response non-null
|
|
// We have to make sure that no callbacks are called twice and in the request must be in a consistent state!
|
|
|
|
// State | Request
|
|
// --------- +---------
|
|
// 1 Null
|
|
// Finished | Skip
|
|
// Timeout/Abort | Deliver
|
|
//
|
|
// 2 Non-Null
|
|
// Finished | Deliver
|
|
// Timeout/Abort | Skip
|
|
|
|
using var _ = new Unity.Profiling.ProfilerMarker(RequestStateNames[(int)@event.State]).Auto();
|
|
|
|
switch (@event.State)
|
|
{
|
|
case HTTPRequestStates.Queued:
|
|
HandleQueued(source);
|
|
break;
|
|
|
|
case HTTPRequestStates.ConnectionTimedOut:
|
|
case HTTPRequestStates.TimedOut:
|
|
case HTTPRequestStates.Error:
|
|
case HTTPRequestStates.Aborted:
|
|
if (HTTPManager.Logger.IsDiagnostic)
|
|
HTTPManager.Logger.Information("RequestEventHelper", $"{@event.State}: discarding response!", source.Response?.Context ?? source.Context);
|
|
|
|
source.Response?.Dispose();
|
|
source.Response = null;
|
|
goto case HTTPRequestStates.Finished;
|
|
|
|
case HTTPRequestStates.Finished:
|
|
// Dispatch any collected download/upload progress, otherwise they would _after_ the callback!
|
|
uploadProgress?.DispatchProgressCallbacks();
|
|
downloadProgress?.DispatchProgressCallbacks();
|
|
|
|
if (source.Callback != null)
|
|
{
|
|
source.Timing.AddEvent(new TimingEventInfo(source, TimingEvents.StartNext, TimingEventNames.Callback));
|
|
try
|
|
{
|
|
if (registeredRequests.Contains(source))
|
|
{
|
|
using (var __ = new Unity.Profiling.ProfilerMarker(nameof(source.Callback)).Auto())
|
|
source.Callback(source, source.Response);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HTTPManager.Logger.Exception("RequestEventHelper", "HandleRequestStateChange " + @event.State, ex, source.Context);
|
|
}
|
|
}
|
|
|
|
source.Timing.AddEvent(new TimingEventInfo(source, TimingEvents.Finish, null));
|
|
|
|
if (source.Callback == null)
|
|
{
|
|
// This delay required because with coroutines these lines are executed first
|
|
// before the coroutine has a chance to do something with a finished request.
|
|
// By adding a delay there's a time window that the coroutine can run its logic too inbetween.
|
|
Timer.Add(new TimerData(TimeSpan.FromSeconds(1), source, OnDelayedDisposeTimer));
|
|
}
|
|
else
|
|
{
|
|
if (HTTPManager.Logger.IsDiagnostic)
|
|
HTTPManager.Logger.Information("RequestEventHelper", "disposing response!", source.Context);
|
|
source.Dispose();
|
|
}
|
|
|
|
HostManager.GetHostVariant(source)
|
|
.TryToSendQueuedRequests();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private static bool OnDelayedDisposeTimer(DateTime time, object request)
|
|
{
|
|
var source = request as HTTPRequest;
|
|
|
|
if (HTTPManager.Logger.IsDiagnostic)
|
|
HTTPManager.Logger.Information("RequestEventHelper", $"{nameof(OnDelayedDisposeTimer)} - disposing response!", source.Context);
|
|
source.Dispose();
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|