WebGLConnection.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. #if UNITY_WEBGL && !UNITY_EDITOR
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Runtime.InteropServices;
  6. #if !BESTHTTP_DISABLE_CACHING
  7. using BestHTTP.Caching;
  8. #endif
  9. using BestHTTP.Core;
  10. using BestHTTP.Extensions;
  11. using BestHTTP.Connections;
  12. using BestHTTP.PlatformSupport.Memory;
  13. namespace BestHTTP.Connections
  14. {
  15. delegate void OnWebGLRequestHandlerDelegate(int nativeId, int httpStatus, IntPtr pBuffer, int length, int zero);
  16. delegate void OnWebGLBufferDelegate(int nativeId, IntPtr pBuffer, int length);
  17. delegate void OnWebGLProgressDelegate(int nativeId, int downloaded, int total);
  18. delegate void OnWebGLErrorDelegate(int nativeId, string error);
  19. delegate void OnWebGLTimeoutDelegate(int nativeId);
  20. delegate void OnWebGLAbortedDelegate(int nativeId);
  21. internal sealed class WebGLConnection : ConnectionBase
  22. {
  23. static Dictionary<int, WebGLConnection> Connections = new Dictionary<int, WebGLConnection>(4);
  24. int NativeId;
  25. BufferSegmentStream Stream;
  26. public WebGLConnection(string serverAddress)
  27. : base(serverAddress, false)
  28. {
  29. XHR_SetLoglevel((byte)HTTPManager.Logger.Level);
  30. }
  31. public override void Shutdown(ShutdownTypes type)
  32. {
  33. base.Shutdown(type);
  34. XHR_Abort(this.NativeId);
  35. }
  36. protected override void ThreadFunc()
  37. {
  38. // XmlHttpRequest setup
  39. this.NativeId = XHR_Create(HTTPRequest.MethodNames[(byte)CurrentRequest.MethodType],
  40. CurrentRequest.CurrentUri.OriginalString,
  41. CurrentRequest.Credentials != null ? CurrentRequest.Credentials.UserName : null,
  42. CurrentRequest.Credentials != null ? CurrentRequest.Credentials.Password : null,
  43. CurrentRequest.WithCredentials ? 1 : 0);
  44. Connections.Add(NativeId, this);
  45. CurrentRequest.EnumerateHeaders((header, values) =>
  46. {
  47. if (!header.Equals("Content-Length"))
  48. for (int i = 0; i < values.Count; ++i)
  49. XHR_SetRequestHeader(NativeId, header, values[i]);
  50. }, /*callBeforeSendCallback:*/ true);
  51. XHR_SetResponseHandler(NativeId, WebGLConnection.OnResponse, WebGLConnection.OnError, WebGLConnection.OnTimeout, WebGLConnection.OnAborted);
  52. // Setting OnUploadProgress result in an addEventListener("progress", ...) call making the request non-simple.
  53. // https://forum.unity.com/threads/best-http-released.200006/page-49#post-3696220
  54. XHR_SetProgressHandler(NativeId,
  55. CurrentRequest.OnDownloadProgress == null ? (OnWebGLProgressDelegate)null : WebGLConnection.OnDownloadProgress,
  56. CurrentRequest.OnUploadProgress == null ? (OnWebGLProgressDelegate)null : WebGLConnection.OnUploadProgress);
  57. XHR_SetTimeout(NativeId, (uint)(CurrentRequest.ConnectTimeout.TotalMilliseconds + CurrentRequest.Timeout.TotalMilliseconds));
  58. byte[] body = CurrentRequest.GetEntityBody();
  59. int length = 0;
  60. bool releaseBodyBuffer = false;
  61. if (body == null)
  62. {
  63. var upStreamInfo = CurrentRequest.GetUpStream();
  64. if (upStreamInfo.Stream != null)
  65. {
  66. var internalBuffer = BufferPool.Get(upStreamInfo.Length > 0 ? upStreamInfo.Length : HTTPRequest.UploadChunkSize, true);
  67. using (BufferPoolMemoryStream ms = new BufferPoolMemoryStream(internalBuffer, 0, internalBuffer.Length, true, true, false, true))
  68. {
  69. var buffer = BufferPool.Get(HTTPRequest.UploadChunkSize, true);
  70. int readCount = -1;
  71. while ((readCount = upStreamInfo.Stream.Read(buffer, 0, buffer.Length)) > 0)
  72. ms.Write(buffer, 0, readCount);
  73. BufferPool.Release(buffer);
  74. length = (int)ms.Position;
  75. body = ms.GetBuffer();
  76. releaseBodyBuffer = true;
  77. }
  78. }
  79. }
  80. else
  81. {
  82. length = body.Length;
  83. }
  84. XHR_Send(NativeId, body, length);
  85. if (releaseBodyBuffer)
  86. BufferPool.Release(body);
  87. }
  88. #region Callback Implementations
  89. void OnResponse(int httpStatus, BufferSegment payload)
  90. {
  91. HTTPConnectionStates proposedConnectionState = HTTPConnectionStates.Processing;
  92. bool resendRequest = false;
  93. try
  94. {
  95. if (this.CurrentRequest.IsCancellationRequested)
  96. return;
  97. using (var ms = new BufferSegmentStream())
  98. {
  99. Stream = ms;
  100. XHR_GetStatusLine(NativeId, OnBufferCallback);
  101. XHR_GetResponseHeaders(NativeId, OnBufferCallback);
  102. if (payload != BufferSegment.Empty)
  103. ms.Write(payload);
  104. SupportedProtocols protocol = HTTPProtocolFactory.GetProtocolFromUri(CurrentRequest.CurrentUri);
  105. CurrentRequest.Response = HTTPProtocolFactory.Get(protocol, CurrentRequest, ms, CurrentRequest.UseStreaming, false);
  106. CurrentRequest.Response.Receive(payload != BufferSegment.Empty && payload.Count > 0 ? (int)payload.Count : -1, true);
  107. KeepAliveHeader keepAlive = null;
  108. ConnectionHelper.HandleResponse(this.ToString(), this.CurrentRequest, out resendRequest, out proposedConnectionState, ref keepAlive);
  109. }
  110. }
  111. catch (Exception e)
  112. {
  113. HTTPManager.Logger.Exception(this.NativeId + " WebGLConnection", "OnResponse", e, this.Context);
  114. if (this.ShutdownType == ShutdownTypes.Immediate)
  115. return;
  116. #if !BESTHTTP_DISABLE_CACHING
  117. if (this.CurrentRequest.UseStreaming)
  118. HTTPCacheService.DeleteEntity(this.CurrentRequest.CurrentUri);
  119. #endif
  120. // Something gone bad, Response must be null!
  121. this.CurrentRequest.Response = null;
  122. if (!this.CurrentRequest.IsCancellationRequested)
  123. {
  124. this.CurrentRequest.Exception = e;
  125. this.CurrentRequest.State = HTTPRequestStates.Error;
  126. }
  127. proposedConnectionState = HTTPConnectionStates.Closed;
  128. }
  129. finally
  130. {
  131. // Exit ASAP
  132. if (this.ShutdownType != ShutdownTypes.Immediate)
  133. {
  134. if (this.CurrentRequest.IsCancellationRequested)
  135. {
  136. // we don't know what stage the request is cancelled, we can't safely reuse the tcp channel.
  137. proposedConnectionState = HTTPConnectionStates.Closed;
  138. this.CurrentRequest.Response = null;
  139. this.CurrentRequest.State = this.CurrentRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
  140. }
  141. else if (resendRequest)
  142. {
  143. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.CurrentRequest, RequestEvents.Resend));
  144. }
  145. else if (this.CurrentRequest.Response != null && this.CurrentRequest.Response.IsUpgraded)
  146. {
  147. proposedConnectionState = HTTPConnectionStates.WaitForProtocolShutdown;
  148. }
  149. else if (this.CurrentRequest.State == HTTPRequestStates.Processing)
  150. {
  151. if (this.CurrentRequest.Response != null)
  152. this.CurrentRequest.State = HTTPRequestStates.Finished;
  153. else
  154. {
  155. this.CurrentRequest.Exception = new Exception(string.Format("[{0}] Remote server closed the connection before sending response header! Previous request state: {1}. Connection state: {2}",
  156. this.ToString(),
  157. this.CurrentRequest.State.ToString(),
  158. this.State.ToString()));
  159. this.CurrentRequest.State = HTTPRequestStates.Error;
  160. proposedConnectionState = HTTPConnectionStates.Closed;
  161. }
  162. }
  163. this.CurrentRequest = null;
  164. if (proposedConnectionState == HTTPConnectionStates.Processing)
  165. proposedConnectionState = HTTPConnectionStates.Closed;
  166. ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, proposedConnectionState));
  167. }
  168. }
  169. }
  170. void OnBuffer(BufferSegment buffer)
  171. {
  172. if (Stream != null)
  173. {
  174. Stream.Write(buffer);
  175. //Stream.Write(HTTPRequest.EOL, 0, HTTPRequest.EOL.Length);
  176. }
  177. }
  178. void OnDownloadProgress(int down, int total)
  179. {
  180. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.CurrentRequest, RequestEvents.DownloadProgress, down, total));
  181. }
  182. void OnUploadProgress(int up, int total)
  183. {
  184. RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.CurrentRequest, RequestEvents.UploadProgress, up, total));
  185. }
  186. void OnError(string error)
  187. {
  188. HTTPManager.Logger.Information(this.NativeId + " WebGLConnection - OnError", error, this.Context);
  189. LastProcessTime = DateTime.UtcNow;
  190. CurrentRequest.Response = null;
  191. CurrentRequest.Exception = new Exception(error);
  192. CurrentRequest.State = HTTPRequestStates.Error;
  193. ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
  194. }
  195. void OnTimeout()
  196. {
  197. HTTPManager.Logger.Information(this.NativeId + " WebGLConnection - OnResponse", string.Empty, this.Context);
  198. CurrentRequest.Response = null;
  199. CurrentRequest.State = HTTPRequestStates.TimedOut;
  200. ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
  201. }
  202. void OnAborted()
  203. {
  204. HTTPManager.Logger.Information(this.NativeId + " WebGLConnection - OnAborted", string.Empty, this.Context);
  205. CurrentRequest.Response = null;
  206. CurrentRequest.State = HTTPRequestStates.Aborted;
  207. ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
  208. }
  209. protected override void Dispose(bool disposing)
  210. {
  211. base.Dispose(disposing);
  212. if (disposing)
  213. {
  214. Connections.Remove(NativeId);
  215. XHR_Release(NativeId);
  216. Stream = null;
  217. }
  218. }
  219. #endregion
  220. #region WebGL Static Callbacks
  221. [AOT.MonoPInvokeCallback(typeof(OnWebGLRequestHandlerDelegate))]
  222. static void OnResponse(int nativeId, int httpStatus, IntPtr pBuffer, int length, int err)
  223. {
  224. WebGLConnection conn = null;
  225. if (!Connections.TryGetValue(nativeId, out conn))
  226. {
  227. HTTPManager.Logger.Error("WebGLConnection - OnResponse", "No WebGL connection found for nativeId: " + nativeId.ToString());
  228. return;
  229. }
  230. HTTPManager.Logger.Information("WebGLConnection - OnResponse", string.Format("{0} {1} {2} {3}", nativeId, httpStatus, length, err), conn.Context);
  231. BufferSegment payload = BufferSegment.Empty;
  232. if (length > 0)
  233. {
  234. var buffer = BufferPool.Get(length, true);
  235. XHR_CopyResponseTo(nativeId, buffer, length);
  236. payload = new BufferSegment(buffer, 0, length);
  237. }
  238. conn.OnResponse(httpStatus, payload);
  239. }
  240. [AOT.MonoPInvokeCallback(typeof(OnWebGLBufferDelegate))]
  241. static void OnBufferCallback(int nativeId, IntPtr pBuffer, int length)
  242. {
  243. WebGLConnection conn = null;
  244. if (!Connections.TryGetValue(nativeId, out conn))
  245. {
  246. HTTPManager.Logger.Error("WebGLConnection - OnBufferCallback", "No WebGL connection found for nativeId: " + nativeId.ToString());
  247. return;
  248. }
  249. byte[] buffer = BufferPool.Get(length, true);
  250. // Copy data from the 'unmanaged' memory to managed memory. Buffer will be reclaimed by the GC.
  251. Marshal.Copy(pBuffer, buffer, 0, length);
  252. conn.OnBuffer(new BufferSegment(buffer, 0, length));
  253. }
  254. [AOT.MonoPInvokeCallback(typeof(OnWebGLProgressDelegate))]
  255. static void OnDownloadProgress(int nativeId, int downloaded, int total)
  256. {
  257. WebGLConnection conn = null;
  258. if (!Connections.TryGetValue(nativeId, out conn))
  259. {
  260. HTTPManager.Logger.Error("WebGLConnection - OnDownloadProgress", "No WebGL connection found for nativeId: " + nativeId.ToString());
  261. return;
  262. }
  263. HTTPManager.Logger.Information(nativeId + " OnDownloadProgress", downloaded.ToString() + " / " + total.ToString(), conn.Context);
  264. conn.OnDownloadProgress(downloaded, total);
  265. }
  266. [AOT.MonoPInvokeCallback(typeof(OnWebGLProgressDelegate))]
  267. static void OnUploadProgress(int nativeId, int uploaded, int total)
  268. {
  269. WebGLConnection conn = null;
  270. if (!Connections.TryGetValue(nativeId, out conn))
  271. {
  272. HTTPManager.Logger.Error("WebGLConnection - OnUploadProgress", "No WebGL connection found for nativeId: " + nativeId.ToString());
  273. return;
  274. }
  275. HTTPManager.Logger.Information(nativeId + " OnUploadProgress", uploaded.ToString() + " / " + total.ToString(), conn.Context);
  276. conn.OnUploadProgress(uploaded, total);
  277. }
  278. [AOT.MonoPInvokeCallback(typeof(OnWebGLErrorDelegate))]
  279. static void OnError(int nativeId, string error)
  280. {
  281. WebGLConnection conn = null;
  282. if (!Connections.TryGetValue(nativeId, out conn))
  283. {
  284. HTTPManager.Logger.Error("WebGLConnection - OnError", "No WebGL connection found for nativeId: " + nativeId.ToString() + " Error: " + error);
  285. return;
  286. }
  287. conn.OnError(error);
  288. }
  289. [AOT.MonoPInvokeCallback(typeof(OnWebGLTimeoutDelegate))]
  290. static void OnTimeout(int nativeId)
  291. {
  292. WebGLConnection conn = null;
  293. if (!Connections.TryGetValue(nativeId, out conn))
  294. {
  295. HTTPManager.Logger.Error("WebGLConnection - OnTimeout", "No WebGL connection found for nativeId: " + nativeId.ToString());
  296. return;
  297. }
  298. conn.OnTimeout();
  299. }
  300. [AOT.MonoPInvokeCallback(typeof(OnWebGLAbortedDelegate))]
  301. static void OnAborted(int nativeId)
  302. {
  303. WebGLConnection conn = null;
  304. if (!Connections.TryGetValue(nativeId, out conn))
  305. {
  306. HTTPManager.Logger.Error("WebGLConnection - OnAborted", "No WebGL connection found for nativeId: " + nativeId.ToString());
  307. return;
  308. }
  309. conn.OnAborted();
  310. }
  311. #endregion
  312. #region WebGL Interface
  313. [DllImport("__Internal")]
  314. private static extern int XHR_Create(string method, string url, string userName, string passwd, int withCredentials);
  315. /// <summary>
  316. /// Is an unsigned long representing the number of milliseconds a request can take before automatically being terminated. A value of 0 (which is the default) means there is no timeout.
  317. /// </summary>
  318. [DllImport("__Internal")]
  319. private static extern void XHR_SetTimeout(int nativeId, uint timeout);
  320. [DllImport("__Internal")]
  321. private static extern void XHR_SetRequestHeader(int nativeId, string header, string value);
  322. [DllImport("__Internal")]
  323. private static extern void XHR_SetResponseHandler(int nativeId, OnWebGLRequestHandlerDelegate onresponse, OnWebGLErrorDelegate onerror, OnWebGLTimeoutDelegate ontimeout, OnWebGLAbortedDelegate onabort);
  324. [DllImport("__Internal")]
  325. private static extern void XHR_SetProgressHandler(int nativeId, OnWebGLProgressDelegate onDownloadProgress, OnWebGLProgressDelegate onUploadProgress);
  326. [DllImport("__Internal")]
  327. private static extern void XHR_CopyResponseTo(int nativeId, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 2)] byte[] response, int length);
  328. [DllImport("__Internal")]
  329. private static extern void XHR_Send(int nativeId, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 2)] byte[] body, int length);
  330. [DllImport("__Internal")]
  331. private static extern void XHR_GetResponseHeaders(int nativeId, OnWebGLBufferDelegate callback);
  332. [DllImport("__Internal")]
  333. private static extern void XHR_GetStatusLine(int nativeId, OnWebGLBufferDelegate callback);
  334. [DllImport("__Internal")]
  335. private static extern void XHR_Abort(int nativeId);
  336. [DllImport("__Internal")]
  337. private static extern void XHR_Release(int nativeId);
  338. [DllImport("__Internal")]
  339. private static extern void XHR_SetLoglevel(int logLevel);
  340. #endregion
  341. }
  342. }
  343. #endif