RequestEvents.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  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. public static class RequestEventHelper
  175. {
  176. private static ConcurrentQueue<RequestEventInfo> requestEventQueue = new ConcurrentQueue<RequestEventInfo>();
  177. #pragma warning disable 0649
  178. public static Action<RequestEventInfo> OnEvent;
  179. #pragma warning restore
  180. public static void EnqueueRequestEvent(RequestEventInfo @event)
  181. {
  182. if (HTTPManager.Logger.Level == Loglevels.All)
  183. HTTPManager.Logger.Information("RequestEventHelper", "Enqueue request event: " + @event.ToString(), @event.SourceRequest.Context);
  184. requestEventQueue.Enqueue(@event);
  185. }
  186. internal static void Clear()
  187. {
  188. requestEventQueue.Clear();
  189. }
  190. internal static void ProcessQueue()
  191. {
  192. RequestEventInfo requestEvent;
  193. while (requestEventQueue.TryDequeue(out requestEvent))
  194. {
  195. HTTPRequest source = requestEvent.SourceRequest;
  196. if (HTTPManager.Logger.Level == Loglevels.All)
  197. HTTPManager.Logger.Information("RequestEventHelper", "Processing request event: " + requestEvent.ToString(), source.Context);
  198. if (OnEvent != null)
  199. {
  200. try
  201. {
  202. OnEvent(requestEvent);
  203. }
  204. catch (Exception ex)
  205. {
  206. HTTPManager.Logger.Exception("RequestEventHelper", "ProcessQueue", ex, source.Context);
  207. }
  208. }
  209. switch (requestEvent.Event)
  210. {
  211. case RequestEvents.StreamingData:
  212. {
  213. var response = source.Response;
  214. if (response != null)
  215. System.Threading.Interlocked.Decrement(ref response.UnprocessedFragments);
  216. bool reuseBuffer = true;
  217. try
  218. {
  219. if (source.UseStreaming)
  220. reuseBuffer = source.OnStreamingData(source, response, requestEvent.Data, requestEvent.DataLength);
  221. }
  222. catch (Exception ex)
  223. {
  224. HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.StreamingData", ex, source.Context);
  225. }
  226. if (reuseBuffer)
  227. BufferPool.Release(requestEvent.Data);
  228. break;
  229. }
  230. case RequestEvents.DownloadProgress:
  231. try
  232. {
  233. if (source.OnDownloadProgress != null)
  234. source.OnDownloadProgress(source, requestEvent.Progress, requestEvent.ProgressLength);
  235. }
  236. catch (Exception ex)
  237. {
  238. HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.DownloadProgress", ex, source.Context);
  239. }
  240. break;
  241. case RequestEvents.UploadProgress:
  242. try
  243. {
  244. if (source.OnUploadProgress != null)
  245. source.OnUploadProgress(source, requestEvent.Progress, requestEvent.ProgressLength);
  246. }
  247. catch (Exception ex)
  248. {
  249. HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.UploadProgress", ex, source.Context);
  250. }
  251. break;
  252. #if !UNITY_WEBGL || UNITY_EDITOR
  253. case RequestEvents.Upgraded:
  254. try
  255. {
  256. if (source.OnUpgraded != null)
  257. source.OnUpgraded(source, source.Response);
  258. }
  259. catch (Exception ex)
  260. {
  261. HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.Upgraded", ex, source.Context);
  262. }
  263. IProtocol protocol = source.Response as IProtocol;
  264. if (protocol != null)
  265. ProtocolEventHelper.AddProtocol(protocol);
  266. break;
  267. #endif
  268. case RequestEvents.Resend:
  269. source.State = HTTPRequestStates.Initial;
  270. var host = HostManager.GetHost(source.CurrentUri.Host);
  271. host.Send(source);
  272. break;
  273. case RequestEvents.Headers:
  274. {
  275. try
  276. {
  277. var response = source.Response;
  278. if (source.OnHeadersReceived != null && response != null)
  279. source.OnHeadersReceived(source, response, requestEvent.Headers);
  280. }
  281. catch (Exception ex)
  282. {
  283. HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.Headers", ex, source.Context);
  284. }
  285. break;
  286. }
  287. case RequestEvents.StateChange:
  288. try
  289. {
  290. RequestEventHelper.HandleRequestStateChange(requestEvent);
  291. }
  292. catch(Exception ex)
  293. {
  294. HTTPManager.Logger.Exception("RequestEventHelper", "HandleRequestStateChange", ex, source.Context);
  295. }
  296. break;
  297. case RequestEvents.TimingData:
  298. source.Timing.AddEvent(requestEvent.Name, requestEvent.Time, requestEvent.Duration);
  299. break;
  300. }
  301. }
  302. }
  303. private static bool AbortRequestWhenTimedOut(DateTime now, object context)
  304. {
  305. HTTPRequest request = context as HTTPRequest;
  306. if (request.State >= HTTPRequestStates.Finished)
  307. return false; // don't repeat
  308. // Protocols will shut down themselves
  309. if (request.Response is IProtocol)
  310. return false;
  311. if (request.IsTimedOut)
  312. {
  313. HTTPManager.Logger.Information("RequestEventHelper", "AbortRequestWhenTimedOut - Request timed out. CurrentUri: " + request.CurrentUri.ToString(), request.Context);
  314. request.Abort();
  315. return false; // don't repeat
  316. }
  317. return true; // repeat
  318. }
  319. internal static void HandleRequestStateChange(RequestEventInfo @event)
  320. {
  321. HTTPRequest source = @event.SourceRequest;
  322. // Because there's a race condition between setting the request's State in its Abort() function running on Unity's main thread
  323. // and the HTTP1/HTTP2 handlers running on an another one.
  324. // Because of these race conditions cases violating expectations can be:
  325. // 1.) State is finished but the response null
  326. // 2.) State is (Connection)TimedOut and the response non-null
  327. // We have to make sure that no callbacks are called twice and in the request must be in a consistent state!
  328. // State | Request
  329. // --------- +---------
  330. // 1 Null
  331. // Finished | Skip
  332. // Timeout/Abort | Deliver
  333. //
  334. // 2 Non-Null
  335. // Finished | Deliver
  336. // Timeout/Abort | Skip
  337. switch (@event.State)
  338. {
  339. case HTTPRequestStates.Queued:
  340. source.QueuedAt = DateTime.UtcNow;
  341. if ((!source.UseStreaming && source.UploadStream == null) || source.EnableTimoutForStreaming)
  342. BestHTTP.Extensions.Timer.Add(new TimerData(TimeSpan.FromSeconds(1), @event.SourceRequest, AbortRequestWhenTimedOut));
  343. break;
  344. case HTTPRequestStates.ConnectionTimedOut:
  345. case HTTPRequestStates.TimedOut:
  346. case HTTPRequestStates.Error:
  347. case HTTPRequestStates.Aborted:
  348. source.Response = null;
  349. goto case HTTPRequestStates.Finished;
  350. case HTTPRequestStates.Finished:
  351. #if !BESTHTTP_DISABLE_CACHING
  352. // Here we will try to load content for a failed load. Failed load is a request with ConnectionTimedOut, TimedOut or Error state.
  353. // A request with Finished state but response with status code >= 500 also something that we will try to load from the cache.
  354. // 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
  355. // a request that just finished without any problem!
  356. try
  357. {
  358. bool tryLoad = !source.DisableCache && source.State != HTTPRequestStates.Aborted && (source.State != HTTPRequestStates.Finished || source.Response == null || source.Response.StatusCode >= 500);
  359. if (tryLoad && Caching.HTTPCacheService.IsCachedEntityExpiresInTheFuture(source))
  360. {
  361. HTTPManager.Logger.Information("RequestEventHelper", "IsCachedEntityExpiresInTheFuture check returned true! CurrentUri: " + source.CurrentUri.ToString(), source.Context);
  362. PlatformSupport.Threading.ThreadedRunner.RunShortLiving<HTTPRequest>((req) =>
  363. {
  364. // Disable any other cache activity.
  365. req.DisableCache = true;
  366. var originalState = req.State;
  367. if (Connections.ConnectionHelper.TryLoadAllFromCache("RequestEventHelper", req, req.Context))
  368. {
  369. if (req.State != HTTPRequestStates.Finished)
  370. req.State = HTTPRequestStates.Finished;
  371. else
  372. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(req, HTTPRequestStates.Finished));
  373. }
  374. else
  375. {
  376. HTTPManager.Logger.Information("RequestEventHelper", "TryLoadAllFromCache failed to load! CurrentUri: " + req.CurrentUri.ToString(), source.Context);
  377. // If for some reason it couldn't load we place back the request to the queue.
  378. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(req, originalState));
  379. }
  380. }, source);
  381. break;
  382. }
  383. }
  384. catch (Exception ex)
  385. {
  386. 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);
  387. }
  388. #endif
  389. source.Timing.AddEvent(TimingEventNames.Queued_For_Disptach, DateTime.Now, TimeSpan.Zero);
  390. source.Timing.AddEvent(TimingEventNames.Finished, DateTime.Now, DateTime.Now - source.Timing.Start);
  391. if (source.Callback != null)
  392. {
  393. try
  394. {
  395. source.Callback(source, source.Response);
  396. source.Timing.AddEvent(TimingEventNames.Callback, DateTime.Now, TimeSpan.Zero);
  397. if (HTTPManager.Logger.Level <= Loglevels.Information)
  398. HTTPManager.Logger.Information("RequestEventHelper", "Finishing request. Timings: " + source.Timing.ToString(), source.Context);
  399. }
  400. catch (Exception ex)
  401. {
  402. HTTPManager.Logger.Exception("RequestEventHelper", "HandleRequestStateChange " + @event.State, ex, source.Context);
  403. }
  404. }
  405. source.Dispose();
  406. HostManager.GetHost(source.CurrentUri.Host)
  407. .GetHostDefinition(HostDefinition.GetKeyForRequest(source))
  408. .TryToSendQueuedRequests();
  409. break;
  410. }
  411. }
  412. }
  413. }