RequestEvents.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. using BestHTTP.Extensions;
  2. using BestHTTP.Logger;
  3. using BestHTTP.PlatformSupport.Memory;
  4. using BestHTTP.Timings;
  5. using System;
  6. using System.Collections.Concurrent;
  7. using System.Collections.Generic;
  8. namespace BestHTTP.Core
  9. {
  10. public enum RequestEvents
  11. {
  12. Upgraded,
  13. DownloadProgress,
  14. UploadProgress,
  15. StreamingData,
  16. StateChange,
  17. Resend,
  18. Headers,
  19. TimingData
  20. }
  21. public
  22. #if CSHARP_7_OR_LATER
  23. readonly
  24. #endif
  25. struct RequestEventInfo
  26. {
  27. public readonly HTTPRequest SourceRequest;
  28. public readonly RequestEvents Event;
  29. public readonly HTTPRequestStates State;
  30. public readonly long Progress;
  31. public readonly long ProgressLength;
  32. public readonly byte[] Data;
  33. public readonly int DataLength;
  34. // Timing Data
  35. public readonly string Name;
  36. public readonly DateTime Time;
  37. public readonly TimeSpan Duration;
  38. // Headers
  39. public readonly Dictionary<string, List<string>> Headers;
  40. public RequestEventInfo(HTTPRequest request, RequestEvents @event)
  41. {
  42. this.SourceRequest = request;
  43. this.Event = @event;
  44. this.State = HTTPRequestStates.Initial;
  45. this.Progress = this.ProgressLength = 0;
  46. this.Data = null;
  47. this.DataLength = 0;
  48. // TimingData
  49. this.Name = null;
  50. this.Time = DateTime.MinValue;
  51. this.Duration = TimeSpan.Zero;
  52. // Headers
  53. this.Headers = null;
  54. }
  55. public RequestEventInfo(HTTPRequest request, HTTPRequestStates newState)
  56. {
  57. this.SourceRequest = request;
  58. this.Event = RequestEvents.StateChange;
  59. this.State = newState;
  60. this.Progress = this.ProgressLength = 0;
  61. this.Data = null;
  62. this.DataLength = 0;
  63. // TimingData
  64. this.Name = null;
  65. this.Time = DateTime.MinValue;
  66. this.Duration = TimeSpan.Zero;
  67. // Headers
  68. this.Headers = null;
  69. }
  70. public RequestEventInfo(HTTPRequest request, RequestEvents @event, long progress, long progressLength)
  71. {
  72. this.SourceRequest = request;
  73. this.Event = @event;
  74. this.State = HTTPRequestStates.Initial;
  75. this.Progress = progress;
  76. this.ProgressLength = progressLength;
  77. this.Data = null;
  78. this.DataLength = 0;
  79. // TimingData
  80. this.Name = null;
  81. this.Time = DateTime.MinValue;
  82. this.Duration = TimeSpan.Zero;
  83. // Headers
  84. this.Headers = null;
  85. }
  86. public RequestEventInfo(HTTPRequest request, byte[] data, int dataLength)
  87. {
  88. this.SourceRequest = request;
  89. this.Event = RequestEvents.StreamingData;
  90. this.State = HTTPRequestStates.Initial;
  91. this.Progress = this.ProgressLength = 0;
  92. this.Data = data;
  93. this.DataLength = dataLength;
  94. // TimingData
  95. this.Name = null;
  96. this.Time = DateTime.MinValue;
  97. this.Duration = TimeSpan.Zero;
  98. // Headers
  99. this.Headers = null;
  100. }
  101. public RequestEventInfo(HTTPRequest request, string name, DateTime time)
  102. {
  103. this.SourceRequest = request;
  104. this.Event = RequestEvents.TimingData;
  105. this.State = HTTPRequestStates.Initial;
  106. this.Progress = this.ProgressLength = 0;
  107. this.Data = null;
  108. this.DataLength = 0;
  109. // TimingData
  110. this.Name = name;
  111. this.Time = time;
  112. this.Duration = TimeSpan.Zero;
  113. // Headers
  114. this.Headers = null;
  115. }
  116. public RequestEventInfo(HTTPRequest request, string name, TimeSpan duration)
  117. {
  118. this.SourceRequest = request;
  119. this.Event = RequestEvents.TimingData;
  120. this.State = HTTPRequestStates.Initial;
  121. this.Progress = this.ProgressLength = 0;
  122. this.Data = null;
  123. this.DataLength = 0;
  124. // TimingData
  125. this.Name = name;
  126. this.Time = DateTime.Now;
  127. this.Duration = duration;
  128. // Headers
  129. this.Headers = null;
  130. }
  131. public RequestEventInfo(HTTPRequest request, Dictionary<string, List<string>> headers)
  132. {
  133. this.SourceRequest = request;
  134. this.Event = RequestEvents.Headers;
  135. this.State = HTTPRequestStates.Initial;
  136. this.Progress = this.ProgressLength = 0;
  137. this.Data = null;
  138. this.DataLength = 0;
  139. // TimingData
  140. this.Name = null;
  141. this.Time = DateTime.MinValue;
  142. this.Duration = TimeSpan.Zero;
  143. // Headers
  144. this.Headers = headers;
  145. }
  146. public override string ToString()
  147. {
  148. switch (this.Event)
  149. {
  150. case RequestEvents.Upgraded:
  151. return string.Format("[RequestEventInfo SourceRequest: {0}, Event: Upgraded]", this.SourceRequest.CurrentUri);
  152. case RequestEvents.DownloadProgress:
  153. return string.Format("[RequestEventInfo SourceRequest: {0}, Event: DownloadProgress, Progress: {1}, ProgressLength: {2}]", this.SourceRequest.CurrentUri, this.Progress, this.ProgressLength);
  154. case RequestEvents.UploadProgress:
  155. return string.Format("[RequestEventInfo SourceRequest: {0}, Event: UploadProgress, Progress: {1}, ProgressLength: {2}]", this.SourceRequest.CurrentUri, this.Progress, this.ProgressLength);
  156. case RequestEvents.StreamingData:
  157. return string.Format("[RequestEventInfo SourceRequest: {0}, Event: StreamingData, DataLength: {1}]", this.SourceRequest.CurrentUri, this.DataLength);
  158. case RequestEvents.StateChange:
  159. return string.Format("[RequestEventInfo SourceRequest: {0}, Event: StateChange, State: {1}]", this.SourceRequest.CurrentUri, this.State);
  160. case RequestEvents.Resend:
  161. return string.Format("[RequestEventInfo SourceRequest: {0}, Event: Resend]", this.SourceRequest.CurrentUri);
  162. case RequestEvents.Headers:
  163. return string.Format("[RequestEventInfo SourceRequest: {0}, Event: Headers]", this.SourceRequest.CurrentUri);
  164. case RequestEvents.TimingData:
  165. if (this.Duration == TimeSpan.Zero)
  166. return string.Format("[RequestEventInfo SourceRequest: {0}, Event: TimingData, Name: {1}, Time: {2}]", this.SourceRequest.CurrentUri, this.Name, this.Time);
  167. else
  168. return string.Format("[RequestEventInfo SourceRequest: {0}, Event: TimingData, Name: {1}, Time: {2}, Duration: {3}]", this.SourceRequest.CurrentUri, this.Name, this.Time, this.Duration);
  169. default:
  170. throw new NotImplementedException(this.Event.ToString());
  171. }
  172. }
  173. }
  174. class ProgressFlattener
  175. {
  176. struct FlattenedProgress
  177. {
  178. public HTTPRequest request;
  179. public OnProgressDelegate onProgress;
  180. public long progress;
  181. public long length;
  182. }
  183. private FlattenedProgress[] progresses;
  184. private bool hasProgress;
  185. public void InsertOrUpdate(RequestEventInfo info, OnProgressDelegate onProgress)
  186. {
  187. if (progresses == null)
  188. progresses = new FlattenedProgress[1];
  189. hasProgress = true;
  190. var newProgresss = new FlattenedProgress { request = info.SourceRequest, progress = info.Progress, length = info.ProgressLength, onProgress = onProgress };
  191. int firstEmptyIdx = -1;
  192. for (int i = 0; i < progresses.Length; i++)
  193. {
  194. var progress = progresses[i];
  195. if (object.ReferenceEquals(progress.request, info.SourceRequest))
  196. {
  197. progresses[i] = newProgresss;
  198. return;
  199. }
  200. if (firstEmptyIdx == -1 && progress.request == null)
  201. firstEmptyIdx = i;
  202. }
  203. if (firstEmptyIdx == -1)
  204. {
  205. Array.Resize(ref progresses, progresses.Length + 1);
  206. progresses[progresses.Length - 1] = newProgresss;
  207. }
  208. else
  209. progresses[firstEmptyIdx] = newProgresss;
  210. }
  211. public void DispatchProgressCallbacks()
  212. {
  213. if (progresses == null || !hasProgress)
  214. return;
  215. for (int i = 0; i < progresses.Length; ++i)
  216. {
  217. var @event = progresses[i];
  218. var source = @event.request;
  219. if (source != null && @event.onProgress != null)
  220. {
  221. try
  222. {
  223. @event.onProgress(source, @event.progress, @event.length);
  224. }
  225. catch (Exception ex)
  226. {
  227. HTTPManager.Logger.Exception("ProgressFlattener", "DispatchProgressCallbacks", ex, source.Context);
  228. }
  229. }
  230. }
  231. Array.Clear(progresses, 0, progresses.Length);
  232. hasProgress = false;
  233. }
  234. }
  235. public static class RequestEventHelper
  236. {
  237. private static ConcurrentQueue<RequestEventInfo> requestEventQueue = new ConcurrentQueue<RequestEventInfo>();
  238. #pragma warning disable 0649
  239. public static Action<RequestEventInfo> OnEvent;
  240. #pragma warning restore
  241. // Low frame rate and hight download/upload speed can add more download/upload progress events to dispatch in one frame.
  242. // This can add higher CPU usage as it might cause updating the UI/do other things unnecessary in the same frame.
  243. // 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.
  244. private static ProgressFlattener downloadProgress;
  245. private static ProgressFlattener uploadProgress;
  246. public static void EnqueueRequestEvent(RequestEventInfo @event)
  247. {
  248. if (HTTPManager.Logger.Level == Loglevels.All)
  249. HTTPManager.Logger.Information("RequestEventHelper", "Enqueue request event: " + @event.ToString(), @event.SourceRequest.Context);
  250. requestEventQueue.Enqueue(@event);
  251. }
  252. internal static void Clear()
  253. {
  254. requestEventQueue.Clear();
  255. }
  256. internal static void ProcessQueue()
  257. {
  258. RequestEventInfo requestEvent;
  259. while (requestEventQueue.TryDequeue(out requestEvent))
  260. {
  261. HTTPRequest source = requestEvent.SourceRequest;
  262. if (HTTPManager.Logger.Level == Loglevels.All)
  263. HTTPManager.Logger.Information("RequestEventHelper", "Processing request event: " + requestEvent.ToString(), source.Context);
  264. if (OnEvent != null)
  265. {
  266. try
  267. {
  268. OnEvent(requestEvent);
  269. }
  270. catch (Exception ex)
  271. {
  272. HTTPManager.Logger.Exception("RequestEventHelper", "ProcessQueue", ex, source.Context);
  273. }
  274. }
  275. switch (requestEvent.Event)
  276. {
  277. case RequestEvents.StreamingData:
  278. {
  279. var response = source.Response;
  280. if (response != null)
  281. System.Threading.Interlocked.Decrement(ref response.UnprocessedFragments);
  282. bool reuseBuffer = true;
  283. try
  284. {
  285. if (source.UseStreaming)
  286. reuseBuffer = source.OnStreamingData(source, response, requestEvent.Data, requestEvent.DataLength);
  287. }
  288. catch (Exception ex)
  289. {
  290. HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.StreamingData", ex, source.Context);
  291. }
  292. if (reuseBuffer)
  293. BufferPool.Release(requestEvent.Data);
  294. break;
  295. }
  296. case RequestEvents.DownloadProgress:
  297. try
  298. {
  299. if (source.OnDownloadProgress != null)
  300. {
  301. if (downloadProgress == null)
  302. downloadProgress = new ProgressFlattener();
  303. downloadProgress.InsertOrUpdate(requestEvent, source.OnDownloadProgress);
  304. }
  305. }
  306. catch (Exception ex)
  307. {
  308. HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.DownloadProgress", ex, source.Context);
  309. }
  310. break;
  311. case RequestEvents.UploadProgress:
  312. try
  313. {
  314. if (source.OnUploadProgress != null)
  315. {
  316. if (uploadProgress == null)
  317. uploadProgress = new ProgressFlattener();
  318. uploadProgress.InsertOrUpdate(requestEvent, source.OnUploadProgress);
  319. }
  320. }
  321. catch (Exception ex)
  322. {
  323. HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.UploadProgress", ex, source.Context);
  324. }
  325. break;
  326. #if !UNITY_WEBGL || UNITY_EDITOR
  327. case RequestEvents.Upgraded:
  328. try
  329. {
  330. if (source.OnUpgraded != null)
  331. source.OnUpgraded(source, source.Response);
  332. }
  333. catch (Exception ex)
  334. {
  335. HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.Upgraded", ex, source.Context);
  336. }
  337. IProtocol protocol = source.Response as IProtocol;
  338. if (protocol != null)
  339. ProtocolEventHelper.AddProtocol(protocol);
  340. break;
  341. #endif
  342. case RequestEvents.Resend:
  343. source.State = HTTPRequestStates.Initial;
  344. var host = HostManager.GetHost(source.CurrentUri.Host);
  345. host.Send(source);
  346. break;
  347. case RequestEvents.Headers:
  348. {
  349. try
  350. {
  351. var response = source.Response;
  352. if (source.OnHeadersReceived != null && response != null)
  353. source.OnHeadersReceived(source, response, requestEvent.Headers);
  354. }
  355. catch (Exception ex)
  356. {
  357. HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.Headers", ex, source.Context);
  358. }
  359. break;
  360. }
  361. case RequestEvents.StateChange:
  362. try
  363. {
  364. RequestEventHelper.HandleRequestStateChange(requestEvent);
  365. }
  366. catch(Exception ex)
  367. {
  368. HTTPManager.Logger.Exception("RequestEventHelper", "HandleRequestStateChange", ex, source.Context);
  369. }
  370. break;
  371. case RequestEvents.TimingData:
  372. source.Timing.AddEvent(requestEvent.Name, requestEvent.Time, requestEvent.Duration);
  373. break;
  374. }
  375. }
  376. uploadProgress?.DispatchProgressCallbacks();
  377. downloadProgress?.DispatchProgressCallbacks();
  378. }
  379. private static bool AbortRequestWhenTimedOut(DateTime now, object context)
  380. {
  381. HTTPRequest request = context as HTTPRequest;
  382. if (request.State >= HTTPRequestStates.Finished)
  383. return false; // don't repeat
  384. // Protocols will shut down themselves
  385. if (request.Response is IProtocol)
  386. return false;
  387. if (request.IsTimedOut)
  388. {
  389. HTTPManager.Logger.Information("RequestEventHelper", "AbortRequestWhenTimedOut - Request timed out. CurrentUri: " + request.CurrentUri.ToString(), request.Context);
  390. request.Abort();
  391. return false; // don't repeat
  392. }
  393. return true; // repeat
  394. }
  395. internal static void HandleRequestStateChange(RequestEventInfo @event)
  396. {
  397. HTTPRequest source = @event.SourceRequest;
  398. // Because there's a race condition between setting the request's State in its Abort() function running on Unity's main thread
  399. // and the HTTP1/HTTP2 handlers running on an another one.
  400. // Because of these race conditions cases violating expectations can be:
  401. // 1.) State is finished but the response null
  402. // 2.) State is (Connection)TimedOut and the response non-null
  403. // We have to make sure that no callbacks are called twice and in the request must be in a consistent state!
  404. // State | Request
  405. // --------- +---------
  406. // 1 Null
  407. // Finished | Skip
  408. // Timeout/Abort | Deliver
  409. //
  410. // 2 Non-Null
  411. // Finished | Deliver
  412. // Timeout/Abort | Skip
  413. switch (@event.State)
  414. {
  415. case HTTPRequestStates.Queued:
  416. source.QueuedAt = DateTime.UtcNow;
  417. if ((!source.UseStreaming && source.UploadStream == null) || source.EnableTimoutForStreaming)
  418. BestHTTP.Extensions.Timer.Add(new TimerData(TimeSpan.FromSeconds(1), @event.SourceRequest, AbortRequestWhenTimedOut));
  419. break;
  420. case HTTPRequestStates.ConnectionTimedOut:
  421. case HTTPRequestStates.TimedOut:
  422. case HTTPRequestStates.Error:
  423. case HTTPRequestStates.Aborted:
  424. source.Response = null;
  425. goto case HTTPRequestStates.Finished;
  426. case HTTPRequestStates.Finished:
  427. #if !BESTHTTP_DISABLE_CACHING
  428. // Here we will try to load content for a failed load. Failed load is a request with ConnectionTimedOut, TimedOut or Error state.
  429. // A request with Finished state but response with status code >= 500 also something that we will try to load from the cache.
  430. // We have to set what we going to try to load here too (other place is inside IsCachedEntityExpiresInTheFuture) as we don't want to load a cached content for
  431. // a request that just finished without any problem!
  432. try
  433. {
  434. bool tryLoad = !source.DisableCache && source.State != HTTPRequestStates.Aborted && (source.State != HTTPRequestStates.Finished || source.Response == null || source.Response.StatusCode >= 500);
  435. if (tryLoad && Caching.HTTPCacheService.IsCachedEntityExpiresInTheFuture(source))
  436. {
  437. HTTPManager.Logger.Information("RequestEventHelper", "IsCachedEntityExpiresInTheFuture check returned true! CurrentUri: " + source.CurrentUri.ToString(), source.Context);
  438. PlatformSupport.Threading.ThreadedRunner.RunShortLiving<HTTPRequest>((req) =>
  439. {
  440. // Disable any other cache activity.
  441. req.DisableCache = true;
  442. var originalState = req.State;
  443. if (Connections.ConnectionHelper.TryLoadAllFromCache("RequestEventHelper", req, req.Context))
  444. {
  445. if (req.State != HTTPRequestStates.Finished)
  446. req.State = HTTPRequestStates.Finished;
  447. else
  448. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(req, HTTPRequestStates.Finished));
  449. }
  450. else
  451. {
  452. HTTPManager.Logger.Information("RequestEventHelper", "TryLoadAllFromCache failed to load! CurrentUri: " + req.CurrentUri.ToString(), source.Context);
  453. // If for some reason it couldn't load we place back the request to the queue.
  454. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(req, originalState));
  455. }
  456. }, source);
  457. break;
  458. }
  459. }
  460. catch (Exception ex)
  461. {
  462. HTTPManager.Logger.Exception("RequestEventHelper", string.Format("HandleRequestStateChange - Cache probe - CurrentUri: \"{0}\" State: {1} StatusCode: {2}", source.CurrentUri, source.State, source.Response != null ? source.Response.StatusCode : 0), ex, source.Context);
  463. }
  464. #endif
  465. // Dispatch any collected download/upload progress, otherwise they would _after_ the callback!
  466. uploadProgress?.DispatchProgressCallbacks();
  467. downloadProgress?.DispatchProgressCallbacks();
  468. source.Timing.AddEvent(TimingEventNames.Queued_For_Disptach, DateTime.Now, TimeSpan.Zero);
  469. source.Timing.AddEvent(TimingEventNames.Finished, DateTime.Now, DateTime.Now - source.Timing.Start);
  470. if (source.Callback != null)
  471. {
  472. try
  473. {
  474. source.Callback(source, source.Response);
  475. source.Timing.AddEvent(TimingEventNames.Callback, DateTime.Now, TimeSpan.Zero);
  476. if (HTTPManager.Logger.Level <= Loglevels.Information)
  477. HTTPManager.Logger.Information("RequestEventHelper", "Finishing request. Timings: " + source.Timing.ToString(), source.Context);
  478. }
  479. catch (Exception ex)
  480. {
  481. HTTPManager.Logger.Exception("RequestEventHelper", "HandleRequestStateChange " + @event.State, ex, source.Context);
  482. }
  483. }
  484. source.Dispose();
  485. HostManager.GetHost(source.CurrentUri.Host)
  486. .GetHostDefinition(HostDefinition.GetKeyForRequest(source))
  487. .TryToSendQueuedRequests();
  488. break;
  489. }
  490. }
  491. }
  492. }