#if UNITY_WEBGL && !UNITY_EDITOR using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using BestHTTP.Authentication; namespace BestHTTP { delegate void OnWebGLRequestHandlerDelegate(int nativeId, int httpStatus, IntPtr pBuffer, int length, int zero); delegate void OnWebGLBufferDelegate(int nativeId, IntPtr pBuffer, int length); delegate void OnWebGLProgressDelegate(int nativeId, int downloaded, int total); delegate void OnWebGLErrorDelegate(int nativeId, string error); delegate void OnWebGLTimeoutDelegate(int nativeId); delegate void OnWebGLAbortedDelegate(int nativeId); internal sealed class WebGLConnection : ConnectionBase { static Dictionary Connections = new Dictionary(4); int NativeId; MemoryStream Stream; public WebGLConnection(string serverAddress) : base(serverAddress, false) { XHR_SetLoglevel((byte)HTTPManager.Logger.Level); } internal override void Abort(HTTPConnectionStates newState) { State = newState; switch (State) { case HTTPConnectionStates.TimedOut: TimedOutStart = DateTime.UtcNow; break; } XHR_Abort(this.NativeId); } protected override void ThreadFunc(object param /*null*/) { // XmlHttpRequest setup this.NativeId = XHR_Create(HTTPRequest.MethodNames[(byte)CurrentRequest.MethodType], CurrentRequest.CurrentUri.OriginalString, CurrentRequest.Credentials != null ? CurrentRequest.Credentials.UserName : null, CurrentRequest.Credentials != null ? CurrentRequest.Credentials.Password : null); Connections.Add(NativeId, this); CurrentRequest.EnumerateHeaders((header, values) => { if (header != "Content-Length") for (int i = 0; i < values.Count; ++i) XHR_SetRequestHeader(NativeId, header, values[i]); }, /*callBeforeSendCallback:*/ true); byte[] body = CurrentRequest.GetEntityBody(); XHR_SetResponseHandler(NativeId, WebGLConnection.OnResponse, WebGLConnection.OnError, WebGLConnection.OnTimeout, WebGLConnection.OnAborted); XHR_SetProgressHandler(NativeId, WebGLConnection.OnDownloadProgress, WebGLConnection.OnUploadProgress); XHR_SetTimeout(NativeId, (uint)(CurrentRequest.ConnectTimeout.TotalMilliseconds + CurrentRequest.Timeout.TotalMilliseconds)); XHR_Send(NativeId, body, body != null ? body.Length : 0); } #region Callback Implementations void OnResponse(int httpStatus, byte[] buffer) { try { using (MemoryStream ms = new MemoryStream()) { Stream = ms; XHR_GetStatusLine(NativeId, OnBufferCallback); XHR_GetResponseHeaders(NativeId, OnBufferCallback); if (buffer != null && buffer.Length > 0) ms.Write(buffer, 0, buffer.Length); ms.Seek(0L, SeekOrigin.Begin); SupportedProtocols protocol = CurrentRequest.ProtocolHandler == SupportedProtocols.Unknown ? HTTPProtocolFactory.GetProtocolFromUri(CurrentRequest.CurrentUri) : CurrentRequest.ProtocolHandler; CurrentRequest.Response = HTTPProtocolFactory.Get(protocol, CurrentRequest, ms, CurrentRequest.UseStreaming, false); CurrentRequest.Response.Receive(buffer != null && buffer.Length > 0 ? (int)buffer.Length : -1, true); } } catch (Exception e) { HTTPManager.Logger.Exception(this.NativeId + " WebGLConnection", "OnResponse", e); if (CurrentRequest != null) { // Something gone bad, Response must be null! CurrentRequest.Response = null; switch (State) { case HTTPConnectionStates.AbortRequested: CurrentRequest.State = HTTPRequestStates.Aborted; break; case HTTPConnectionStates.TimedOut: CurrentRequest.State = HTTPRequestStates.TimedOut; break; default: CurrentRequest.Exception = e; CurrentRequest.State = HTTPRequestStates.Error; break; } } } finally { Connections.Remove(NativeId); Stream = null; if (CurrentRequest != null) lock (HTTPManager.Locker) { State = HTTPConnectionStates.Closed; if (CurrentRequest.State == HTTPRequestStates.Processing) { if (CurrentRequest.Response != null) CurrentRequest.State = HTTPRequestStates.Finished; else CurrentRequest.State = HTTPRequestStates.Error; } } LastProcessTime = DateTime.UtcNow; if (OnConnectionRecycled != null) RecycleNow(); XHR_Release(NativeId); } } void OnBuffer(byte[] buffer) { if (Stream != null) { Stream.Write(buffer, 0, buffer.Length); Stream.Write(new byte[2] { HTTPResponse.CR, HTTPResponse.LF }, 0, 2); } } void OnDownloadProgress(int down, int total) { CurrentRequest.Downloaded = down; CurrentRequest.DownloadLength = total; CurrentRequest.DownloadProgressChanged = true; } void OnUploadProgress(int up, int total) { CurrentRequest.Uploaded = up; CurrentRequest.UploadLength = total; CurrentRequest.UploadProgressChanged = true; } void OnError(string error) { HTTPManager.Logger.Information(this.NativeId + " WebGLConnection - OnError", error); Connections.Remove(NativeId); Stream = null; if (CurrentRequest != null) lock (HTTPManager.Locker) { State = HTTPConnectionStates.Closed; CurrentRequest.State = HTTPRequestStates.Error; CurrentRequest.Exception = new Exception(error); } LastProcessTime = DateTime.UtcNow; if (OnConnectionRecycled != null) RecycleNow(); XHR_Release(NativeId); } void OnTimeout() { HTTPManager.Logger.Information(this.NativeId + " WebGLConnection - OnResponse", string.Empty); Connections.Remove(NativeId); Stream = null; if (CurrentRequest != null) lock (HTTPManager.Locker) { State = HTTPConnectionStates.Closed; CurrentRequest.State = HTTPRequestStates.TimedOut; } LastProcessTime = DateTime.UtcNow; if (OnConnectionRecycled != null) RecycleNow(); XHR_Release(NativeId); } void OnAborted() { HTTPManager.Logger.Information(this.NativeId + " WebGLConnection - OnAborted", string.Empty); Connections.Remove(NativeId); Stream = null; if (CurrentRequest != null) lock (HTTPManager.Locker) { State = HTTPConnectionStates.Closed; CurrentRequest.State = HTTPRequestStates.Aborted; } LastProcessTime = DateTime.UtcNow; if (OnConnectionRecycled != null) RecycleNow(); XHR_Release(NativeId); } #endregion #region WebGL Static Callbacks [AOT.MonoPInvokeCallback(typeof(OnWebGLRequestHandlerDelegate))] static void OnResponse(int nativeId, int httpStatus, IntPtr pBuffer, int length, int err) { HTTPManager.Logger.Information("WebGLConnection - OnResponse", string.Format("{0} {1} {2} {3}", nativeId, httpStatus, length, err)); WebGLConnection conn = null; if (!Connections.TryGetValue(nativeId, out conn)) { HTTPManager.Logger.Error("WebGLConnection - OnResponse", "No WebGL connection found for nativeId: " + nativeId.ToString()); return; } byte[] buffer = new byte[length]; // Copy data from the 'unmanaged' memory to managed memory. Buffer will be reclaimed by the GC. Marshal.Copy(pBuffer, buffer, 0, length); conn.OnResponse(httpStatus, buffer); } [AOT.MonoPInvokeCallback(typeof(OnWebGLBufferDelegate))] static void OnBufferCallback(int nativeId, IntPtr pBuffer, int length) { WebGLConnection conn = null; if (!Connections.TryGetValue(nativeId, out conn)) { HTTPManager.Logger.Error("WebGLConnection - OnBufferCallback", "No WebGL connection found for nativeId: " + nativeId.ToString()); return; } byte[] buffer = new byte[length]; // Copy data from the 'unmanaged' memory to managed memory. Buffer will be reclaimed by the GC. Marshal.Copy(pBuffer, buffer, 0, length); conn.OnBuffer(buffer); } [AOT.MonoPInvokeCallback(typeof(OnWebGLProgressDelegate))] static void OnDownloadProgress(int nativeId, int downloaded, int total) { HTTPManager.Logger.Information(nativeId + " OnDownloadProgress", downloaded.ToString() + " / " + total.ToString()); WebGLConnection conn = null; if (!Connections.TryGetValue(nativeId, out conn)) { HTTPManager.Logger.Error("WebGLConnection - OnDownloadProgress", "No WebGL connection found for nativeId: " + nativeId.ToString()); return; } conn.OnDownloadProgress(downloaded, total); } [AOT.MonoPInvokeCallback(typeof(OnWebGLProgressDelegate))] static void OnUploadProgress(int nativeId, int uploaded, int total) { HTTPManager.Logger.Information(nativeId + " OnUploadProgress", uploaded.ToString() + " / " + total.ToString()); WebGLConnection conn = null; if (!Connections.TryGetValue(nativeId, out conn)) { HTTPManager.Logger.Error("WebGLConnection - OnUploadProgress", "No WebGL connection found for nativeId: " + nativeId.ToString()); return; } conn.OnUploadProgress(uploaded, total); } [AOT.MonoPInvokeCallback(typeof(OnWebGLErrorDelegate))] static void OnError(int nativeId, string error) { WebGLConnection conn = null; if (!Connections.TryGetValue(nativeId, out conn)) { HTTPManager.Logger.Error("WebGLConnection - OnError", "No WebGL connection found for nativeId: " + nativeId.ToString() + " Error: " + error); return; } conn.OnError(error); } [AOT.MonoPInvokeCallback(typeof(OnWebGLTimeoutDelegate))] static void OnTimeout(int nativeId) { WebGLConnection conn = null; if (!Connections.TryGetValue(nativeId, out conn)) { HTTPManager.Logger.Error("WebGLConnection - OnTimeout", "No WebGL connection found for nativeId: " + nativeId.ToString()); return; } conn.OnTimeout(); } [AOT.MonoPInvokeCallback(typeof(OnWebGLAbortedDelegate))] static void OnAborted(int nativeId) { WebGLConnection conn = null; if (!Connections.TryGetValue(nativeId, out conn)) { HTTPManager.Logger.Error("WebGLConnection - OnAborted", "No WebGL connection found for nativeId: " + nativeId.ToString()); return; } conn.OnAborted(); } #endregion #region WebGL Interface [DllImport("__Internal")] private static extern int XHR_Create(string method, string url, string userName, string passwd); /// /// 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. /// [DllImport("__Internal")] private static extern void XHR_SetTimeout(int nativeId, uint timeout); [DllImport("__Internal")] private static extern void XHR_SetRequestHeader(int nativeId, string header, string value); [DllImport("__Internal")] private static extern void XHR_SetResponseHandler(int nativeId, OnWebGLRequestHandlerDelegate onresponse, OnWebGLErrorDelegate onerror, OnWebGLTimeoutDelegate ontimeout, OnWebGLAbortedDelegate onabort); [DllImport("__Internal")] private static extern void XHR_SetProgressHandler(int nativeId, OnWebGLProgressDelegate onDownloadProgress, OnWebGLProgressDelegate onUploadProgress); [DllImport("__Internal")] private static extern void XHR_Send(int nativeId, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 2)] byte[] body, int length); [DllImport("__Internal")] private static extern void XHR_GetResponseHeaders(int nativeId, OnWebGLBufferDelegate callback); [DllImport("__Internal")] private static extern void XHR_GetStatusLine(int nativeId, OnWebGLBufferDelegate callback); [DllImport("__Internal")] private static extern void XHR_Abort(int nativeId); [DllImport("__Internal")] private static extern void XHR_Release(int nativeId); [DllImport("__Internal")] private static extern void XHR_SetLoglevel(int logLevel); #endregion } } #endif