WebSocket.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  1. #if !BESTHTTP_DISABLE_WEBSOCKET
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Text;
  5. using System.IO;
  6. using BestHTTP.Extensions;
  7. #if UNITY_WEBGL && !UNITY_EDITOR
  8. using System.Runtime.InteropServices;
  9. #else
  10. using BestHTTP.WebSocket.Frames;
  11. using BestHTTP.WebSocket.Extensions;
  12. #endif
  13. namespace BestHTTP.WebSocket
  14. {
  15. /// <summary>
  16. /// States of the underlying browser's WebSocket implementation's state.
  17. /// </summary>
  18. public enum WebSocketStates : byte
  19. {
  20. Connecting = 0,
  21. Open = 1,
  22. Closing = 2,
  23. Closed = 3,
  24. Unknown
  25. };
  26. public delegate void OnWebSocketOpenDelegate(WebSocket webSocket);
  27. public delegate void OnWebSocketMessageDelegate(WebSocket webSocket, string message);
  28. public delegate void OnWebSocketBinaryDelegate(WebSocket webSocket, byte[] data);
  29. public delegate void OnWebSocketClosedDelegate(WebSocket webSocket, UInt16 code, string message);
  30. public delegate void OnWebSocketErrorDelegate(WebSocket webSocket, Exception ex);
  31. public delegate void OnWebSocketErrorDescriptionDelegate(WebSocket webSocket, string reason);
  32. #if (!UNITY_WEBGL || UNITY_EDITOR)
  33. public delegate void OnWebSocketIncompleteFrameDelegate(WebSocket webSocket, WebSocketFrameReader frame);
  34. #else
  35. delegate void OnWebGLWebSocketOpenDelegate(uint id);
  36. delegate void OnWebGLWebSocketTextDelegate(uint id, string text);
  37. delegate void OnWebGLWebSocketBinaryDelegate(uint id, IntPtr pBuffer, int length);
  38. delegate void OnWebGLWebSocketErrorDelegate(uint id, string error);
  39. delegate void OnWebGLWebSocketCloseDelegate(uint id, int code, string reason);
  40. #endif
  41. public sealed class WebSocket
  42. {
  43. #region Properties
  44. #if !UNITY_WEBGL || UNITY_EDITOR
  45. public WebSocketStates State { get; private set; }
  46. #else
  47. public WebSocketStates State { get { return ImplementationId != 0 ? WS_GetState(ImplementationId) : WebSocketStates.Unknown; } }
  48. #endif
  49. /// <summary>
  50. /// The connection to the WebSocket server is open.
  51. /// </summary>
  52. public bool IsOpen
  53. {
  54. get
  55. {
  56. #if (!UNITY_WEBGL || UNITY_EDITOR)
  57. return webSocket != null && !webSocket.IsClosed;
  58. #else
  59. return ImplementationId != 0 && WS_GetState(ImplementationId) == WebSocketStates.Open;
  60. #endif
  61. }
  62. }
  63. public int BufferedAmount
  64. {
  65. get
  66. {
  67. #if (!UNITY_WEBGL || UNITY_EDITOR)
  68. return webSocket.BufferedAmount;
  69. #else
  70. return WS_GetBufferedAmount(ImplementationId);
  71. #endif
  72. }
  73. }
  74. #if (!UNITY_WEBGL || UNITY_EDITOR)
  75. /// <summary>
  76. /// Set to true to start a new thread to send Pings to the WebSocket server
  77. /// </summary>
  78. public bool StartPingThread { get; set; }
  79. /// <summary>
  80. /// The delay between two Pings in millisecs. Minimum value is 100, default is 1000.
  81. /// </summary>
  82. public int PingFrequency { get; set; }
  83. /// <summary>
  84. /// If StartPingThread set to true, the plugin will close the connection and emit an OnError/OnErrorDesc event if no
  85. /// message is received from the server in the given time. Its default value is 10 sec.
  86. /// </summary>
  87. public TimeSpan CloseAfterNoMesssage { get; set; }
  88. /// <summary>
  89. /// The internal HTTPRequest object.
  90. /// </summary>
  91. public HTTPRequest InternalRequest { get; private set; }
  92. /// <summary>
  93. /// IExtension implementations the plugin will negotiate with the server to use.
  94. /// </summary>
  95. public IExtension[] Extensions { get; private set; }
  96. /// <summary>
  97. /// Latency calculated from the ping-pong message round-trip times.
  98. /// </summary>
  99. public int Latency { get { return webSocket.Latency; } }
  100. #endif
  101. /// <summary>
  102. /// Called when the connection to the WebSocket server is established.
  103. /// </summary>
  104. public OnWebSocketOpenDelegate OnOpen;
  105. /// <summary>
  106. /// Called when a new textual message is received from the server.
  107. /// </summary>
  108. public OnWebSocketMessageDelegate OnMessage;
  109. /// <summary>
  110. /// Called when a new binary message is received from the server.
  111. /// </summary>
  112. public OnWebSocketBinaryDelegate OnBinary;
  113. /// <summary>
  114. /// Called when the WebSocket connection is closed.
  115. /// </summary>
  116. public OnWebSocketClosedDelegate OnClosed;
  117. /// <summary>
  118. /// Called when an error is encountered. The Exception parameter may be null.
  119. /// </summary>
  120. public OnWebSocketErrorDelegate OnError;
  121. /// <summary>
  122. /// Called when an error is encountered. The parameter will be the description of the error.
  123. /// </summary>
  124. public OnWebSocketErrorDescriptionDelegate OnErrorDesc;
  125. #if (!UNITY_WEBGL || UNITY_EDITOR)
  126. /// <summary>
  127. /// Called when an incomplete frame received. No attempt will be made to reassemble these fragments internally, and no reference are stored after this event to this frame.
  128. /// </summary>
  129. public OnWebSocketIncompleteFrameDelegate OnIncompleteFrame;
  130. #endif
  131. #endregion
  132. #region Private Fields
  133. #if (!UNITY_WEBGL || UNITY_EDITOR)
  134. /// <summary>
  135. /// Indicates wheter we sent out the connection request to the server.
  136. /// </summary>
  137. private bool requestSent;
  138. /// <summary>
  139. /// The internal WebSocketResponse object
  140. /// </summary>
  141. private WebSocketResponse webSocket;
  142. #else
  143. internal static Dictionary<uint, WebSocket> WebSockets = new Dictionary<uint, WebSocket>();
  144. private uint ImplementationId;
  145. private Uri Uri;
  146. private string Protocol;
  147. #endif
  148. #endregion
  149. #region Constructors
  150. /// <summary>
  151. /// Creates a WebSocket instance from the given uri.
  152. /// </summary>
  153. /// <param name="uri">The uri of the WebSocket server</param>
  154. public WebSocket(Uri uri)
  155. :this(uri, string.Empty, string.Empty)
  156. {
  157. #if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_GZIP
  158. this.Extensions = new IExtension[] { new PerMessageCompression(/*compression level: */ Decompression.Zlib.CompressionLevel.Default,
  159. /*clientNoContextTakeover: */ false,
  160. /*serverNoContextTakeover: */ false,
  161. /*clientMaxWindowBits: */ Decompression.Zlib.ZlibConstants.WindowBitsMax,
  162. /*desiredServerMaxWindowBits: */ Decompression.Zlib.ZlibConstants.WindowBitsMax,
  163. /*minDatalengthToCompress: */ PerMessageCompression.MinDataLengthToCompressDefault) };
  164. #endif
  165. }
  166. /// <summary>
  167. /// Creates a WebSocket instance from the given uri, protocol and origin.
  168. /// </summary>
  169. /// <param name="uri">The uri of the WebSocket server</param>
  170. /// <param name="origin">Servers that are not intended to process input from any web page but only for certain sites SHOULD verify the |Origin| field is an origin they expect.
  171. /// If the origin indicated is unacceptable to the server, then it SHOULD respond to the WebSocket handshake with a reply containing HTTP 403 Forbidden status code.</param>
  172. /// <param name="protocol">The application-level protocol that the client want to use(eg. "chat", "leaderboard", etc.). Can be null or empty string if not used.</param>
  173. /// <param name="extensions">Optional IExtensions implementations</param>
  174. public WebSocket(Uri uri, string origin, string protocol
  175. #if !UNITY_WEBGL || UNITY_EDITOR
  176. , params IExtension[] extensions
  177. #endif
  178. )
  179. {
  180. string scheme = HTTPProtocolFactory.IsSecureProtocol(uri) ? "wss" : "ws";
  181. int port = uri.Port != -1 ? uri.Port : (scheme.Equals("wss", StringComparison.OrdinalIgnoreCase) ? 443 : 80);
  182. // Somehow if i use the UriBuilder it's not the same as if the uri is constructed from a string...
  183. //uri = new UriBuilder(uri.Scheme, uri.Host, uri.Scheme.Equals("wss", StringComparison.OrdinalIgnoreCase) ? 443 : 80, uri.PathAndQuery).Uri;
  184. uri = new Uri(scheme + "://" + uri.Host + ":" + port + uri.GetRequestPathAndQueryURL());
  185. #if !UNITY_WEBGL || UNITY_EDITOR
  186. // Set up some default values.
  187. this.PingFrequency = 1000;
  188. this.CloseAfterNoMesssage = TimeSpan.FromSeconds(10);
  189. InternalRequest = new HTTPRequest(uri, OnInternalRequestCallback);
  190. // Called when the regular GET request is successfully upgraded to WebSocket
  191. InternalRequest.OnUpgraded = OnInternalRequestUpgraded;
  192. //http://tools.ietf.org/html/rfc6455#section-4
  193. //The request MUST contain a |Host| header field whose value contains /host/ plus optionally ":" followed by /port/ (when not using the default port).
  194. if ((!HTTPProtocolFactory.IsSecureProtocol(uri) && uri.Port != 80) && (HTTPProtocolFactory.IsSecureProtocol(uri) && uri.Port != 443))
  195. InternalRequest.SetHeader("Host", uri.Host + ":" + uri.Port);
  196. else
  197. InternalRequest.SetHeader("Host", uri.Host);
  198. // The request MUST contain an |Upgrade| header field whose value MUST include the "websocket" keyword.
  199. InternalRequest.SetHeader("Upgrade", "websocket");
  200. // The request MUST contain a |Connection| header field whose value MUST include the "Upgrade" token.
  201. InternalRequest.SetHeader("Connection", "Upgrade");
  202. // The request MUST include a header field with the name |Sec-WebSocket-Key|. The value of this header field MUST be a nonce consisting of a
  203. // randomly selected 16-byte value that has been base64-encoded (see Section 4 of [RFC4648]). The nonce MUST be selected randomly for each connection.
  204. InternalRequest.SetHeader("Sec-WebSocket-Key", GetSecKey(new object[] { this, InternalRequest, uri, new object() }));
  205. // The request MUST include a header field with the name |Origin| [RFC6454] if the request is coming from a browser client.
  206. // If the connection is from a non-browser client, the request MAY include this header field if the semantics of that client match the use-case described here for browser clients.
  207. // More on Origin Considerations: http://tools.ietf.org/html/rfc6455#section-10.2
  208. if (!string.IsNullOrEmpty(origin))
  209. InternalRequest.SetHeader("Origin", origin);
  210. // The request MUST include a header field with the name |Sec-WebSocket-Version|. The value of this header field MUST be 13.
  211. InternalRequest.SetHeader("Sec-WebSocket-Version", "13");
  212. if (!string.IsNullOrEmpty(protocol))
  213. InternalRequest.SetHeader("Sec-WebSocket-Protocol", protocol);
  214. // Disable caching
  215. InternalRequest.SetHeader("Cache-Control", "no-cache");
  216. InternalRequest.SetHeader("Pragma", "no-cache");
  217. this.Extensions = extensions;
  218. #if !BESTHTTP_DISABLE_CACHING
  219. InternalRequest.DisableCache = true;
  220. InternalRequest.DisableRetry = true;
  221. #endif
  222. InternalRequest.TryToMinimizeTCPLatency = true;
  223. #if !BESTHTTP_DISABLE_PROXY
  224. // WebSocket is not a request-response based protocol, so we need a 'tunnel' through the proxy
  225. HTTPProxy httpProxy = HTTPManager.Proxy as HTTPProxy;
  226. if (httpProxy != null)
  227. InternalRequest.Proxy = new HTTPProxy(httpProxy.Address,
  228. httpProxy.Credentials,
  229. false, /*turn on 'tunneling'*/
  230. false, /*sendWholeUri*/
  231. httpProxy.NonTransparentForHTTPS);
  232. #endif
  233. #else
  234. this.Uri = uri;
  235. this.Protocol = protocol;
  236. #endif
  237. // Under WebGL when only the WebSocket protocol is used Setup() isn't called, so we have to call it here.
  238. HTTPManager.Setup();
  239. }
  240. #endregion
  241. #region Request Callbacks
  242. #if (!UNITY_WEBGL || UNITY_EDITOR)
  243. private void OnInternalRequestCallback(HTTPRequest req, HTTPResponse resp)
  244. {
  245. string reason = string.Empty;
  246. switch (req.State)
  247. {
  248. case HTTPRequestStates.Finished:
  249. if (resp.IsSuccess || resp.StatusCode == 101)
  250. {
  251. // The request finished without any problem.
  252. HTTPManager.Logger.Information("WebSocket", string.Format("Request finished. Status Code: {0} Message: {1}", resp.StatusCode.ToString(), resp.Message));
  253. return;
  254. }
  255. else
  256. reason = string.Format("Request Finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}",
  257. resp.StatusCode,
  258. resp.Message,
  259. resp.DataAsText);
  260. break;
  261. // The request finished with an unexpected error. The request's Exception property may contain more info about the error.
  262. case HTTPRequestStates.Error:
  263. reason = "Request Finished with Error! " + (req.Exception != null ? ("Exception: " + req.Exception.Message + req.Exception.StackTrace) : string.Empty);
  264. break;
  265. // The request aborted, initiated by the user.
  266. case HTTPRequestStates.Aborted:
  267. reason = "Request Aborted!";
  268. break;
  269. // Connecting to the server is timed out.
  270. case HTTPRequestStates.ConnectionTimedOut:
  271. reason = "Connection Timed Out!";
  272. break;
  273. // The request didn't finished in the given time.
  274. case HTTPRequestStates.TimedOut:
  275. reason = "Processing the request Timed Out!";
  276. break;
  277. default:
  278. return;
  279. }
  280. if (this.State != WebSocketStates.Connecting || !string.IsNullOrEmpty(reason))
  281. {
  282. if (OnError != null)
  283. OnError(this, req.Exception);
  284. if (OnErrorDesc != null)
  285. OnErrorDesc(this, reason);
  286. if (OnError == null && OnErrorDesc == null)
  287. HTTPManager.Logger.Error("WebSocket", reason);
  288. }
  289. else if (OnClosed != null)
  290. OnClosed(this, (ushort)WebSocketStausCodes.NormalClosure, "Closed while opening");
  291. if (!req.IsKeepAlive && resp != null && resp is WebSocketResponse)
  292. (resp as WebSocketResponse).CloseStream();
  293. }
  294. private void OnInternalRequestUpgraded(HTTPRequest req, HTTPResponse resp)
  295. {
  296. webSocket = resp as WebSocketResponse;
  297. if (webSocket == null)
  298. {
  299. if (OnError != null)
  300. OnError(this, req.Exception);
  301. if (OnErrorDesc != null)
  302. {
  303. string reason = string.Empty;
  304. if (req.Exception != null)
  305. reason = req.Exception.Message + " " + req.Exception.StackTrace;
  306. OnErrorDesc(this, reason);
  307. }
  308. this.State = WebSocketStates.Closed;
  309. return;
  310. }
  311. // If Close called while we connected
  312. if (this.State == WebSocketStates.Closed)
  313. {
  314. webSocket.CloseStream();
  315. return;
  316. }
  317. webSocket.WebSocket = this;
  318. if (this.Extensions != null)
  319. {
  320. for (int i = 0; i < this.Extensions.Length; ++i)
  321. {
  322. var ext = this.Extensions[i];
  323. try
  324. {
  325. if (ext != null && !ext.ParseNegotiation(webSocket))
  326. this.Extensions[i] = null; // Keep extensions only that successfully negotiated
  327. }
  328. catch (Exception ex)
  329. {
  330. HTTPManager.Logger.Exception("WebSocket", "ParseNegotiation", ex);
  331. // Do not try to use a defective extension in the future
  332. this.Extensions[i] = null;
  333. }
  334. }
  335. }
  336. this.State = WebSocketStates.Open;
  337. if (OnOpen != null)
  338. {
  339. try
  340. {
  341. OnOpen(this);
  342. }
  343. catch(Exception ex)
  344. {
  345. HTTPManager.Logger.Exception("WebSocket", "OnOpen", ex);
  346. }
  347. }
  348. webSocket.OnText = (ws, msg) =>
  349. {
  350. if (OnMessage != null)
  351. OnMessage(this, msg);
  352. };
  353. webSocket.OnBinary = (ws, bin) =>
  354. {
  355. if (OnBinary != null)
  356. OnBinary(this, bin);
  357. };
  358. webSocket.OnClosed = (ws, code, msg) =>
  359. {
  360. this.State = WebSocketStates.Closed;
  361. if (OnClosed != null)
  362. OnClosed(this, code, msg);
  363. };
  364. if (OnIncompleteFrame != null)
  365. webSocket.OnIncompleteFrame = (ws, frame) =>
  366. {
  367. if (OnIncompleteFrame != null)
  368. OnIncompleteFrame(this, frame);
  369. };
  370. if (StartPingThread)
  371. webSocket.StartPinging(Math.Max(PingFrequency, 100));
  372. webSocket.StartReceive();
  373. }
  374. #endif
  375. #endregion
  376. #region Public Interface
  377. /// <summary>
  378. /// Start the opening process.
  379. /// </summary>
  380. public void Open()
  381. {
  382. #if (!UNITY_WEBGL || UNITY_EDITOR)
  383. if (requestSent)
  384. throw new InvalidOperationException("Open already called! You can't reuse this WebSocket instance!");
  385. if (this.Extensions != null)
  386. {
  387. try
  388. {
  389. for (int i = 0; i < this.Extensions.Length; ++i)
  390. {
  391. var ext = this.Extensions[i];
  392. if (ext != null)
  393. ext.AddNegotiation(InternalRequest);
  394. }
  395. }
  396. catch(Exception ex)
  397. {
  398. HTTPManager.Logger.Exception("WebSocket", "Open", ex);
  399. }
  400. }
  401. InternalRequest.Send();
  402. requestSent = true;
  403. this.State = WebSocketStates.Connecting;
  404. #else
  405. try
  406. {
  407. ImplementationId = WS_Create(this.Uri.OriginalString, this.Protocol, OnOpenCallback, OnTextCallback, OnBinaryCallback, OnErrorCallback, OnCloseCallback);
  408. WebSockets.Add(ImplementationId, this);
  409. }
  410. catch(Exception ex)
  411. {
  412. HTTPManager.Logger.Exception("WebSocket", "Open", ex);
  413. }
  414. #endif
  415. }
  416. /// <summary>
  417. /// It will send the given message to the server in one frame.
  418. /// </summary>
  419. public void Send(string message)
  420. {
  421. if (!IsOpen)
  422. return;
  423. #if (!UNITY_WEBGL || UNITY_EDITOR)
  424. webSocket.Send(message);
  425. #else
  426. WS_Send_String(this.ImplementationId, message);
  427. #endif
  428. }
  429. /// <summary>
  430. /// It will send the given data to the server in one frame.
  431. /// </summary>
  432. public void Send(byte[] buffer)
  433. {
  434. if (!IsOpen)
  435. return;
  436. #if (!UNITY_WEBGL || UNITY_EDITOR)
  437. webSocket.Send(buffer);
  438. #else
  439. WS_Send_Binary(this.ImplementationId, buffer, 0, buffer.Length);
  440. #endif
  441. }
  442. /// <summary>
  443. /// Will send count bytes from a byte array, starting from offset.
  444. /// </summary>
  445. public void Send(byte[] buffer, ulong offset, ulong count)
  446. {
  447. if (!IsOpen)
  448. return;
  449. #if (!UNITY_WEBGL || UNITY_EDITOR)
  450. webSocket.Send(buffer, offset, count);
  451. #else
  452. WS_Send_Binary(this.ImplementationId, buffer, (int)offset, (int)count);
  453. #endif
  454. }
  455. #if (!UNITY_WEBGL || UNITY_EDITOR)
  456. /// <summary>
  457. /// It will send the given frame to the server.
  458. /// </summary>
  459. public void Send(WebSocketFrame frame)
  460. {
  461. if (IsOpen)
  462. webSocket.Send(frame);
  463. }
  464. #endif
  465. /// <summary>
  466. /// It will initiate the closing of the connection to the server.
  467. /// </summary>
  468. public void Close()
  469. {
  470. if (State >= WebSocketStates.Closing)
  471. return;
  472. #if !UNITY_WEBGL || UNITY_EDITOR
  473. if (this.State == WebSocketStates.Connecting)
  474. {
  475. this.State = WebSocketStates.Closed;
  476. if (OnClosed != null)
  477. OnClosed(this, (ushort)WebSocketStausCodes.NoStatusCode, string.Empty);
  478. }
  479. else
  480. {
  481. this.State = WebSocketStates.Closing;
  482. webSocket.Close();
  483. }
  484. #else
  485. WS_Close(this.ImplementationId, 1000, "Bye!");
  486. #endif
  487. }
  488. /// <summary>
  489. /// It will initiate the closing of the connection to the server sending the given code and message.
  490. /// </summary>
  491. public void Close(UInt16 code, string message)
  492. {
  493. if (!IsOpen)
  494. return;
  495. #if (!UNITY_WEBGL || UNITY_EDITOR)
  496. webSocket.Close(code, message);
  497. #else
  498. WS_Close(this.ImplementationId, code, message);
  499. #endif
  500. }
  501. public static byte[] EncodeCloseData(UInt16 code, string message)
  502. {
  503. //If there is a body, the first two bytes of the body MUST be a 2-byte unsigned integer
  504. // (in network byte order) representing a status code with value /code/ defined in Section 7.4 (http://tools.ietf.org/html/rfc6455#section-7.4). Following the 2-byte integer,
  505. // the body MAY contain UTF-8-encoded data with value /reason/, the interpretation of which is not defined by this specification.
  506. // This data is not necessarily human readable but may be useful for debugging or passing information relevant to the script that opened the connection.
  507. int msgLen = Encoding.UTF8.GetByteCount(message);
  508. using (BufferPoolMemoryStream ms = new BufferPoolMemoryStream(2 + msgLen))
  509. {
  510. byte[] buff = BitConverter.GetBytes(code);
  511. if (BitConverter.IsLittleEndian)
  512. Array.Reverse(buff, 0, buff.Length);
  513. ms.Write(buff, 0, buff.Length);
  514. buff = Encoding.UTF8.GetBytes(message);
  515. ms.Write(buff, 0, buff.Length);
  516. return ms.ToArray();
  517. }
  518. }
  519. #endregion
  520. #region Private Helpers
  521. #if !UNITY_WEBGL || UNITY_EDITOR
  522. private string GetSecKey(object[] from)
  523. {
  524. byte[] keys = new byte[16];
  525. int pos = 0;
  526. for (int i = 0; i < from.Length; ++i)
  527. {
  528. byte[] hash = BitConverter.GetBytes((Int32)from[i].GetHashCode());
  529. for (int cv = 0; cv < hash.Length && pos < keys.Length; ++cv)
  530. keys[pos++] = hash[cv];
  531. }
  532. return Convert.ToBase64String(keys);
  533. }
  534. #endif
  535. #endregion
  536. #region WebGL Static Callbacks
  537. #if UNITY_WEBGL && !UNITY_EDITOR
  538. [AOT.MonoPInvokeCallback(typeof(OnWebGLWebSocketOpenDelegate))]
  539. static void OnOpenCallback(uint id)
  540. {
  541. WebSocket ws;
  542. if (WebSockets.TryGetValue(id, out ws))
  543. {
  544. if (ws.OnOpen != null)
  545. {
  546. try
  547. {
  548. ws.OnOpen(ws);
  549. }
  550. catch(Exception ex)
  551. {
  552. HTTPManager.Logger.Exception("WebSocket", "OnOpen", ex);
  553. }
  554. }
  555. }
  556. else
  557. HTTPManager.Logger.Warning("WebSocket", "OnOpenCallback - No WebSocket found for id: " + id.ToString());
  558. }
  559. [AOT.MonoPInvokeCallback(typeof(OnWebGLWebSocketTextDelegate))]
  560. static void OnTextCallback(uint id, string text)
  561. {
  562. WebSocket ws;
  563. if (WebSockets.TryGetValue(id, out ws))
  564. {
  565. if (ws.OnMessage != null)
  566. {
  567. try
  568. {
  569. ws.OnMessage(ws, text);
  570. }
  571. catch (Exception ex)
  572. {
  573. HTTPManager.Logger.Exception("WebSocket", "OnMessage", ex);
  574. }
  575. }
  576. }
  577. else
  578. HTTPManager.Logger.Warning("WebSocket", "OnTextCallback - No WebSocket found for id: " + id.ToString());
  579. }
  580. [AOT.MonoPInvokeCallback(typeof(OnWebGLWebSocketBinaryDelegate))]
  581. static void OnBinaryCallback(uint id, IntPtr pBuffer, int length)
  582. {
  583. WebSocket ws;
  584. if (WebSockets.TryGetValue(id, out ws))
  585. {
  586. if (ws.OnBinary != null)
  587. {
  588. try
  589. {
  590. byte[] buffer = new byte[length];
  591. // Copy data from the 'unmanaged' memory to managed memory. Buffer will be reclaimed by the GC.
  592. Marshal.Copy(pBuffer, buffer, 0, length);
  593. ws.OnBinary(ws, buffer);
  594. }
  595. catch (Exception ex)
  596. {
  597. HTTPManager.Logger.Exception("WebSocket", "OnBinary", ex);
  598. }
  599. }
  600. }
  601. else
  602. HTTPManager.Logger.Warning("WebSocket", "OnBinaryCallback - No WebSocket found for id: " + id.ToString());
  603. }
  604. [AOT.MonoPInvokeCallback(typeof(OnWebGLWebSocketErrorDelegate))]
  605. static void OnErrorCallback(uint id, string error)
  606. {
  607. WebSocket ws;
  608. if (WebSockets.TryGetValue(id, out ws))
  609. {
  610. WebSockets.Remove(id);
  611. if (ws.OnError != null)
  612. {
  613. try
  614. {
  615. ws.OnError(ws, new Exception(error));
  616. }
  617. catch (Exception ex)
  618. {
  619. HTTPManager.Logger.Exception("WebSocket", "OnError", ex);
  620. }
  621. }
  622. if (ws.OnErrorDesc != null)
  623. {
  624. try
  625. {
  626. ws.OnErrorDesc(ws, error);
  627. }
  628. catch (Exception ex)
  629. {
  630. HTTPManager.Logger.Exception("WebSocket", "OnErrorDesc", ex);
  631. }
  632. }
  633. }
  634. else
  635. HTTPManager.Logger.Warning("WebSocket", "OnErrorCallback - No WebSocket found for id: " + id.ToString());
  636. try
  637. {
  638. WS_Release(id);
  639. }
  640. catch(Exception ex)
  641. {
  642. HTTPManager.Logger.Exception("WebSocket", "WS_Release", ex);
  643. }
  644. }
  645. [AOT.MonoPInvokeCallback(typeof(OnWebGLWebSocketCloseDelegate))]
  646. static void OnCloseCallback(uint id, int code, string reason)
  647. {
  648. // To match non-webgl behavior, we have to treat this client-side generated message as an error
  649. if (code == (int)WebSocketStausCodes.ClosedAbnormally)
  650. {
  651. OnErrorCallback(id, "Abnormal disconnection.");
  652. return;
  653. }
  654. WebSocket ws;
  655. if (WebSockets.TryGetValue(id, out ws))
  656. {
  657. WebSockets.Remove(id);
  658. if (ws.OnClosed != null)
  659. {
  660. try
  661. {
  662. ws.OnClosed(ws, (ushort)code, reason);
  663. }
  664. catch (Exception ex)
  665. {
  666. HTTPManager.Logger.Exception("WebSocket", "OnClosed", ex);
  667. }
  668. }
  669. }
  670. else
  671. HTTPManager.Logger.Warning("WebSocket", "OnCloseCallback - No WebSocket found for id: " + id.ToString());
  672. try
  673. {
  674. WS_Release(id);
  675. }
  676. catch(Exception ex)
  677. {
  678. HTTPManager.Logger.Exception("WebSocket", "WS_Release", ex);
  679. }
  680. }
  681. #endif
  682. #endregion
  683. #region WebGL Interface
  684. #if UNITY_WEBGL && !UNITY_EDITOR
  685. [DllImport("__Internal")]
  686. static extern uint WS_Create(string url, string protocol, OnWebGLWebSocketOpenDelegate onOpen, OnWebGLWebSocketTextDelegate onText, OnWebGLWebSocketBinaryDelegate onBinary, OnWebGLWebSocketErrorDelegate onError, OnWebGLWebSocketCloseDelegate onClose);
  687. [DllImport("__Internal")]
  688. static extern WebSocketStates WS_GetState(uint id);
  689. [DllImport("__Internal")]
  690. static extern int WS_GetBufferedAmount(uint id);
  691. [DllImport("__Internal")]
  692. static extern int WS_Send_String(uint id, string strData);
  693. [DllImport("__Internal")]
  694. static extern int WS_Send_Binary(uint id, byte[] buffer, int pos, int length);
  695. [DllImport("__Internal")]
  696. static extern void WS_Close(uint id, ushort code, string reason);
  697. [DllImport("__Internal")]
  698. static extern void WS_Release(uint id);
  699. #endif
  700. #endregion
  701. }
  702. }
  703. #endif