WebGLXHRConnection.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. #if UNITY_WEBGL && !UNITY_EDITOR
  2. using System;
  3. using System.IO;
  4. using System.Threading;
  5. using Best.HTTP.Caching;
  6. using Best.HTTP.Hosts.Connections.File;
  7. using Best.HTTP.Hosts.Connections.HTTP1;
  8. using Best.HTTP.HostSetting;
  9. using Best.HTTP.Request.Authentication;
  10. using Best.HTTP.Request.Timings;
  11. using Best.HTTP.Shared;
  12. using Best.HTTP.Shared.PlatformSupport.Memory;
  13. using Best.HTTP.Shared.Streams;
  14. namespace Best.HTTP.Hosts.Connections.WebGL
  15. {
  16. class PeekableIncomingSegmentContentProviderStream : PeekableContentProviderStream
  17. {
  18. private int peek_listIdx;
  19. private int peek_pos;
  20. public override void BeginPeek()
  21. {
  22. peek_listIdx = 0;
  23. peek_pos = base.bufferList.Count > 0 ? base.bufferList[0].Offset : 0;
  24. }
  25. public override int PeekByte()
  26. {
  27. if (base.bufferList.Count == 0)
  28. return -1;
  29. var segment = base.bufferList[this.peek_listIdx];
  30. if (peek_pos >= segment.Offset + segment.Count)
  31. {
  32. if (base.bufferList.Count <= this.peek_listIdx + 1)
  33. return -1;
  34. segment = base.bufferList[++this.peek_listIdx];
  35. this.peek_pos = segment.Offset;
  36. }
  37. return segment.Data[this.peek_pos++];
  38. }
  39. }
  40. internal sealed class WebGLXHRConnection : ConnectionBase
  41. {
  42. public PeekableContentProviderStream ContentProvider { get; private set; }
  43. int NativeId;
  44. PeekableHTTP1Response _response;
  45. public WebGLXHRConnection(HostKey hostKey)
  46. : base(hostKey, false)
  47. {
  48. WebGLXHRNativeInterface.XHR_SetLoglevel((byte)HTTPManager.Logger.Level);
  49. }
  50. public override void Shutdown(ShutdownTypes type)
  51. {
  52. base.Shutdown(type);
  53. WebGLXHRNativeInterface.XHR_Abort(this.NativeId);
  54. }
  55. protected override void ThreadFunc()
  56. {
  57. // XmlHttpRequest setup
  58. CurrentRequest.Prepare();
  59. Credentials credentials = null;// CurrentRequest.Authenticator?.Credentials;
  60. this.NativeId = WebGLXHRNativeInterface.XHR_Create(HTTPRequest.MethodNames[(byte)CurrentRequest.MethodType],
  61. CurrentRequest.CurrentUri.OriginalString,
  62. credentials?.UserName, credentials?.Password, CurrentRequest.WithCredentials ? 1 : 0);
  63. WebGLXHRNativeConnectionLayer.Add(NativeId, this);
  64. CurrentRequest.EnumerateHeaders((header, values) =>
  65. {
  66. if (!header.Equals("Content-Length"))
  67. for (int i = 0; i < values.Count; ++i)
  68. WebGLXHRNativeInterface.XHR_SetRequestHeader(NativeId, header, values[i]);
  69. }, /*callBeforeSendCallback:*/ true);
  70. WebGLXHRNativeConnectionLayer.SetupHandlers(NativeId, CurrentRequest);
  71. WebGLXHRNativeInterface.XHR_SetTimeout(NativeId, (uint)(CurrentRequest.TimeoutSettings.ConnectTimeout.TotalMilliseconds + CurrentRequest.TimeoutSettings.Timeout.TotalMilliseconds));
  72. Stream upStream = CurrentRequest.UploadSettings.UploadStream;
  73. byte[] body = null;
  74. int length = 0;
  75. bool releaseBodyBuffer = false;
  76. if (upStream != null)
  77. {
  78. var internalBuffer = BufferPool.Get(upStream.Length > 0 ? upStream.Length : CurrentRequest.UploadSettings.UploadChunkSize, true);
  79. using (BufferPoolMemoryStream ms = new BufferPoolMemoryStream(internalBuffer, 0, internalBuffer.Length, true, true, false, true))
  80. {
  81. var buffer = BufferPool.Get(CurrentRequest.UploadSettings.UploadChunkSize, true);
  82. int readCount = -1;
  83. while ((readCount = upStream.Read(buffer, 0, buffer.Length)) > 0)
  84. ms.Write(buffer, 0, readCount);
  85. BufferPool.Release(buffer);
  86. length = (int)ms.Position;
  87. body = ms.GetBuffer();
  88. releaseBodyBuffer = true;
  89. }
  90. }
  91. if (this._response == null)
  92. this.CurrentRequest.Response = this._response = new PeekableHTTP1Response(this.CurrentRequest, false, null);
  93. this.ContentProvider = new PeekableIncomingSegmentContentProviderStream();
  94. WebGLXHRNativeInterface.XHR_Send(NativeId, body, length);
  95. if (releaseBodyBuffer)
  96. BufferPool.Release(body);
  97. this.CurrentRequest.TimeoutSettings.QueuedAt = DateTime.MinValue;
  98. this.CurrentRequest.TimeoutSettings.ProcessingStarted = DateTime.Now;
  99. this.CurrentRequest.OnCancellationRequested += OnCancellationRequested;
  100. }
  101. #region Callback Implementations
  102. private void OnCancellationRequested(HTTPRequest req)
  103. {
  104. if (HTTPManager.Logger.IsDiagnostic)
  105. HTTPManager.Logger.Verbose(nameof(WebGLXHRConnection), $"{this.NativeId} - OnCancellationRequested()", this.Context);
  106. Interlocked.Exchange(ref this._response, null);
  107. req.OnCancellationRequested -= OnCancellationRequested;
  108. WebGLXHRNativeInterface.XHR_Abort(this.NativeId);
  109. }
  110. internal void OnBuffer(BufferSegment buffer)
  111. {
  112. if (HTTPManager.Logger.IsDiagnostic)
  113. HTTPManager.Logger.Verbose(nameof(WebGLXHRConnection), $"{this.NativeId} - OnBuffer({buffer})", this.Context);
  114. try
  115. {
  116. if (this.CurrentRequest.TimeoutSettings.IsTimedOut(DateTime.Now))
  117. throw new TimeoutException();
  118. if (this.CurrentRequest.IsCancellationRequested)
  119. throw new Exception("Cancellation requested!");
  120. this.ContentProvider?.Write(buffer);
  121. this._response.ProcessPeekable(this.ContentProvider);
  122. }
  123. catch (Exception e)
  124. {
  125. BufferPool.Release(buffer);
  126. if (this.ShutdownType == ShutdownTypes.Immediate)
  127. return;
  128. FinishedProcessing(e);
  129. }
  130. // After an exception, this._response will be null!
  131. if (this._response != null && this._response.ReadState == PeekableHTTP1Response.PeekableReadState.Finished)
  132. FinishedProcessing(null);
  133. }
  134. internal void OnError(string error)
  135. {
  136. if (HTTPManager.Logger.IsDiagnostic)
  137. HTTPManager.Logger.Verbose(nameof(WebGLXHRConnection), $"{this.NativeId} - OnError({error})", this.Context);
  138. FinishedProcessing(new Exception(error));
  139. }
  140. internal void OnResponse(BufferSegment payload)
  141. {
  142. if (HTTPManager.Logger.IsDiagnostic)
  143. HTTPManager.Logger.Verbose(nameof(WebGLXHRConnection), $"{this.NativeId} - OnResponse({payload})", this.Context);
  144. this._response.DownStream.EmergencyIncreaseMaxBuffered();
  145. OnBuffer(payload);
  146. }
  147. void FinishedProcessing(Exception ex)
  148. {
  149. // Warning: FinishedProcessing might be called from different threads in parallel:
  150. // - send thread triggered by a write failure
  151. // - read thread oncontent/OnError/OnConnectionClosed
  152. var resp = Interlocked.Exchange(ref this._response, null);
  153. if (resp == null)
  154. return;
  155. HTTPManager.Logger.Verbose(nameof(WebGLXHRConnection), $"{nameof(FinishedProcessing)}({resp}, {ex})", this.Context);
  156. // Unset the consumer, we no longer expect another OnContent call until further notice.
  157. this.ContentProvider?.Unbind();
  158. this.ContentProvider?.Dispose();
  159. this.ContentProvider = null;
  160. var req = this.CurrentRequest;
  161. req.OnCancellationRequested -= OnCancellationRequested;
  162. bool resendRequest = false;
  163. HTTPRequestStates requestState = HTTPRequestStates.Finished;
  164. HTTPConnectionStates connectionState = HTTPConnectionStates.Recycle;
  165. Exception error = ex;
  166. if (error != null)
  167. {
  168. // Timeout is a non-retryable error
  169. if (ex is TimeoutException)
  170. {
  171. error = null;
  172. requestState = HTTPRequestStates.TimedOut;
  173. }
  174. else if (ex is HTTPCacheAcquireLockException)
  175. {
  176. error = null;
  177. resendRequest = true;
  178. }
  179. else
  180. {
  181. if (req.RetrySettings.Retries < req.RetrySettings.MaxRetries)
  182. {
  183. req.RetrySettings.Retries++;
  184. error = null;
  185. resendRequest = true;
  186. }
  187. else
  188. {
  189. requestState = HTTPRequestStates.Error;
  190. }
  191. }
  192. // Any exception means that the connection is in an unknown state, we shouldn't try to reuse it.
  193. connectionState = HTTPConnectionStates.Closed;
  194. resp.Dispose();
  195. }
  196. else
  197. {
  198. // After HandleResponse connectionState can have the following values:
  199. // - Processing: nothing interesting, caller side can decide what happens with the connection (recycle connection).
  200. // - Closed: server sent an connection: close header.
  201. // - ClosedResendRequest: in this case resendRequest is true, and the connection must not be reused.
  202. // In this case we can send only one ConnectionEvent to handle both case and avoid concurrency issues.
  203. KeepAliveHeader keepAlive = null;
  204. error = ConnectionHelper.HandleResponse(req, out resendRequest, out connectionState, ref keepAlive, this.Context);
  205. connectionState = HTTPConnectionStates.Recycle;
  206. if (error != null)
  207. requestState = HTTPRequestStates.Error;
  208. else if (!resendRequest && resp.IsUpgraded)
  209. requestState = HTTPRequestStates.Processing;
  210. }
  211. req.Timing.StartNext(TimingEventNames.Queued);
  212. HTTPManager.Logger.Verbose(nameof(WebGLXHRConnection), $"{nameof(FinishedProcessing)} final decision. ResendRequest: {resendRequest}, RequestState: {requestState}, ConnectionState: {connectionState}", this.Context);
  213. // If HandleResponse returned with ClosedResendRequest or there were an error and we can retry the request
  214. if (connectionState == HTTPConnectionStates.ClosedResendRequest || (resendRequest && connectionState == HTTPConnectionStates.Closed))
  215. {
  216. ConnectionHelper.ResendRequestAndCloseConnection(this, req);
  217. }
  218. else if (resendRequest && requestState == HTTPRequestStates.Finished)
  219. {
  220. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(req, RequestEvents.Resend));
  221. ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, connectionState));
  222. }
  223. else
  224. {
  225. // Otherwise set the request's then the connection's state
  226. ConnectionHelper.EnqueueEvents(this, connectionState, req, requestState, error);
  227. }
  228. }
  229. internal void OnDownloadProgress(int down, int total) => RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.CurrentRequest, RequestEvents.DownloadProgress, down, total));
  230. internal void OnUploadProgress(int up, int total) => RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.CurrentRequest, RequestEvents.UploadProgress, up, total));
  231. internal void OnTimeout()
  232. {
  233. if (HTTPManager.Logger.IsDiagnostic)
  234. HTTPManager.Logger.Verbose(nameof(WebGLXHRConnection), $"{this.NativeId} - OnTimeout", this.Context);
  235. CurrentRequest.Response = null;
  236. CurrentRequest.State = HTTPRequestStates.TimedOut;
  237. ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
  238. }
  239. internal void OnAborted()
  240. {
  241. if (HTTPManager.Logger.IsDiagnostic)
  242. HTTPManager.Logger.Verbose(nameof(WebGLXHRConnection), $"{this.NativeId} - OnAborted", this.Context);
  243. CurrentRequest.Response = null;
  244. CurrentRequest.State = HTTPRequestStates.Aborted;
  245. ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
  246. }
  247. protected override void Dispose(bool disposing)
  248. {
  249. base.Dispose(disposing);
  250. if (disposing)
  251. {
  252. WebGLXHRNativeConnectionLayer.Remove(NativeId);
  253. WebGLXHRNativeInterface.XHR_Release(NativeId);
  254. this.ContentProvider?.Dispose();
  255. this.ContentProvider = null;
  256. }
  257. }
  258. #endregion
  259. }
  260. }
  261. #endif