OverHTTP1.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. #if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_WEBSOCKET
  2. using System;
  3. using BestHTTP.Connections;
  4. using BestHTTP.Extensions;
  5. using BestHTTP.PlatformSupport.Memory;
  6. using BestHTTP.WebSocket.Frames;
  7. namespace BestHTTP.WebSocket
  8. {
  9. internal sealed class OverHTTP1 : WebSocketBaseImplementation
  10. {
  11. public override bool IsOpen => webSocket != null && !webSocket.IsClosed;
  12. public override int BufferedAmount => webSocket.BufferedAmount;
  13. public override int Latency => this.webSocket.Latency;
  14. public override DateTime LastMessageReceived => this.webSocket.lastMessage;
  15. /// <summary>
  16. /// Indicates whether we sent out the connection request to the server.
  17. /// </summary>
  18. private bool requestSent;
  19. /// <summary>
  20. /// The internal WebSocketResponse object
  21. /// </summary>
  22. private WebSocketResponse webSocket;
  23. public OverHTTP1(WebSocket parent, Uri uri, string origin, string protocol) : base(parent, uri, origin, protocol)
  24. {
  25. string scheme = HTTPProtocolFactory.IsSecureProtocol(uri) ? "wss" : "ws";
  26. int port = uri.Port != -1 ? uri.Port : (scheme.Equals("wss", StringComparison.OrdinalIgnoreCase) ? 443 : 80);
  27. // Somehow if i use the UriBuilder it's not the same as if the uri is constructed from a string...
  28. //uri = new UriBuilder(uri.Scheme, uri.Host, uri.Scheme.Equals("wss", StringComparison.OrdinalIgnoreCase) ? 443 : 80, uri.PathAndQuery).Uri;
  29. base.Uri = new Uri(scheme + "://" + uri.Host + ":" + port + uri.GetRequestPathAndQueryURL());
  30. }
  31. protected override void CreateInternalRequest()
  32. {
  33. if (this._internalRequest != null)
  34. return;
  35. this._internalRequest = new HTTPRequest(base.Uri, OnInternalRequestCallback);
  36. this._internalRequest.Context.Add("WebSocket", this.Parent.Context);
  37. // Called when the regular GET request is successfully upgraded to WebSocket
  38. this._internalRequest.OnUpgraded = OnInternalRequestUpgraded;
  39. //http://tools.ietf.org/html/rfc6455#section-4
  40. // The request MUST contain an |Upgrade| header field whose value MUST include the "websocket" keyword.
  41. this._internalRequest.SetHeader("Upgrade", "websocket");
  42. // The request MUST contain a |Connection| header field whose value MUST include the "Upgrade" token.
  43. this._internalRequest.SetHeader("Connection", "Upgrade");
  44. // 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
  45. // randomly selected 16-byte value that has been base64-encoded (see Section 4 of [RFC4648]). The nonce MUST be selected randomly for each connection.
  46. this._internalRequest.SetHeader("Sec-WebSocket-Key", WebSocket.GetSecKey(new object[] { this, InternalRequest, base.Uri, new object() }));
  47. // The request MUST include a header field with the name |Origin| [RFC6454] if the request is coming from a browser client.
  48. // 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.
  49. // More on Origin Considerations: http://tools.ietf.org/html/rfc6455#section-10.2
  50. if (!string.IsNullOrEmpty(Origin))
  51. this._internalRequest.SetHeader("Origin", Origin);
  52. // The request MUST include a header field with the name |Sec-WebSocket-Version|. The value of this header field MUST be 13.
  53. this._internalRequest.SetHeader("Sec-WebSocket-Version", "13");
  54. if (!string.IsNullOrEmpty(Protocol))
  55. this._internalRequest.SetHeader("Sec-WebSocket-Protocol", Protocol);
  56. // Disable caching
  57. this._internalRequest.SetHeader("Cache-Control", "no-cache");
  58. this._internalRequest.SetHeader("Pragma", "no-cache");
  59. #if !BESTHTTP_DISABLE_CACHING
  60. this._internalRequest.DisableCache = true;
  61. #endif
  62. #if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR)
  63. this._internalRequest.Proxy = this.Parent.GetProxy(this.Uri);
  64. #endif
  65. this._internalRequest.OnBeforeRedirection += InternalRequest_OnBeforeRedirection;
  66. if (this.Parent.OnInternalRequestCreated != null)
  67. {
  68. try
  69. {
  70. this.Parent.OnInternalRequestCreated(this.Parent, this._internalRequest);
  71. }
  72. catch (Exception ex)
  73. {
  74. HTTPManager.Logger.Exception("OverHTTP1", "CreateInternalRequest", ex, this.Parent.Context);
  75. }
  76. }
  77. }
  78. private bool InternalRequest_OnBeforeRedirection(HTTPRequest originalRequest, HTTPResponse response, Uri redirectUri)
  79. {
  80. // We have to re-select/reset the implementation in the parent Websocket, as the redirected request might gets served over a HTTP/2 connection!
  81. this.Parent.SelectImplementation(redirectUri, originalRequest.GetFirstHeaderValue("Origin"), originalRequest.GetFirstHeaderValue("Sec-WebSocket-Protocol"))
  82. .StartOpen();
  83. originalRequest.Callback = null;
  84. return false;
  85. }
  86. public override void StartClose(UInt16 code, string message)
  87. {
  88. if (this.State == WebSocketStates.Connecting)
  89. {
  90. if (this.InternalRequest != null)
  91. this.InternalRequest.Abort();
  92. this.State = WebSocketStates.Closed;
  93. if (this.Parent.OnClosed != null)
  94. this.Parent.OnClosed(this.Parent, (ushort)WebSocketStausCodes.NoStatusCode, string.Empty);
  95. }
  96. else
  97. {
  98. this.State = WebSocketStates.Closing;
  99. webSocket.Close(code, message);
  100. }
  101. }
  102. public override void StartOpen()
  103. {
  104. if (requestSent)
  105. throw new InvalidOperationException("Open already called! You can't reuse this WebSocket instance!");
  106. if (this.Parent.Extensions != null)
  107. {
  108. try
  109. {
  110. for (int i = 0; i < this.Parent.Extensions.Length; ++i)
  111. {
  112. var ext = this.Parent.Extensions[i];
  113. if (ext != null)
  114. ext.AddNegotiation(InternalRequest);
  115. }
  116. }
  117. catch (Exception ex)
  118. {
  119. HTTPManager.Logger.Exception("OverHTTP1", "Open", ex, this.Parent.Context);
  120. }
  121. }
  122. InternalRequest.Send();
  123. requestSent = true;
  124. this.State = WebSocketStates.Connecting;
  125. }
  126. private void OnInternalRequestCallback(HTTPRequest req, HTTPResponse resp)
  127. {
  128. string reason = string.Empty;
  129. switch (req.State)
  130. {
  131. case HTTPRequestStates.Finished:
  132. HTTPManager.Logger.Information("OverHTTP1", string.Format("Request finished. Status Code: {0} Message: {1}", resp.StatusCode.ToString(), resp.Message), this.Parent.Context);
  133. if (resp.StatusCode == 101)
  134. {
  135. // The request upgraded successfully.
  136. return;
  137. }
  138. else
  139. reason = string.Format("Request Finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}",
  140. resp.StatusCode,
  141. resp.Message,
  142. resp.DataAsText);
  143. break;
  144. // The request finished with an unexpected error. The request's Exception property may contain more info about the error.
  145. case HTTPRequestStates.Error:
  146. reason = "Request Finished with Error! " + (req.Exception != null ? ("Exception: " + req.Exception.Message + req.Exception.StackTrace) : string.Empty);
  147. break;
  148. // The request aborted, initiated by the user.
  149. case HTTPRequestStates.Aborted:
  150. reason = "Request Aborted!";
  151. break;
  152. // Connecting to the server is timed out.
  153. case HTTPRequestStates.ConnectionTimedOut:
  154. reason = "Connection Timed Out!";
  155. break;
  156. // The request didn't finished in the given time.
  157. case HTTPRequestStates.TimedOut:
  158. reason = "Processing the request Timed Out!";
  159. break;
  160. default:
  161. return;
  162. }
  163. if (this.State != WebSocketStates.Connecting || !string.IsNullOrEmpty(reason))
  164. {
  165. if (this.Parent.OnError != null)
  166. this.Parent.OnError(this.Parent, reason);
  167. else if (!HTTPManager.IsQuitting)
  168. HTTPManager.Logger.Error("OverHTTP1", reason, this.Parent.Context);
  169. }
  170. else if (this.Parent.OnClosed != null)
  171. this.Parent.OnClosed(this.Parent, (ushort)WebSocketStausCodes.NormalClosure, "Closed while opening");
  172. this.State = WebSocketStates.Closed;
  173. if (!req.IsKeepAlive && resp != null && resp is WebSocketResponse)
  174. (resp as WebSocketResponse).CloseStream();
  175. }
  176. private void OnInternalRequestUpgraded(HTTPRequest req, HTTPResponse resp)
  177. {
  178. HTTPManager.Logger.Information("OverHTTP1", "Internal request upgraded!", this.Parent.Context);
  179. webSocket = resp as WebSocketResponse;
  180. if (webSocket == null)
  181. {
  182. if (this.Parent.OnError != null)
  183. {
  184. string reason = string.Empty;
  185. if (req.Exception != null)
  186. reason = req.Exception.Message + " " + req.Exception.StackTrace;
  187. this.Parent.OnError(this.Parent, reason);
  188. }
  189. this.State = WebSocketStates.Closed;
  190. return;
  191. }
  192. // If Close called while we connected
  193. if (this.State == WebSocketStates.Closed)
  194. {
  195. webSocket.CloseStream();
  196. return;
  197. }
  198. if (!resp.HasHeader("sec-websocket-accept"))
  199. {
  200. this.State = WebSocketStates.Closed;
  201. webSocket.CloseStream();
  202. if (this.Parent.OnError != null)
  203. this.Parent.OnError(this.Parent, "No Sec-Websocket-Accept header is sent by the server!");
  204. return;
  205. }
  206. webSocket.WebSocket = this.Parent;
  207. if (this.Parent.Extensions != null)
  208. {
  209. for (int i = 0; i < this.Parent.Extensions.Length; ++i)
  210. {
  211. var ext = this.Parent.Extensions[i];
  212. try
  213. {
  214. if (ext != null && !ext.ParseNegotiation(webSocket))
  215. this.Parent.Extensions[i] = null; // Keep extensions only that successfully negotiated
  216. }
  217. catch (Exception ex)
  218. {
  219. HTTPManager.Logger.Exception("OverHTTP1", "ParseNegotiation", ex, this.Parent.Context);
  220. // Do not try to use a defective extension in the future
  221. this.Parent.Extensions[i] = null;
  222. }
  223. }
  224. }
  225. this.State = WebSocketStates.Open;
  226. if (this.Parent.OnOpen != null)
  227. {
  228. try
  229. {
  230. this.Parent.OnOpen(this.Parent);
  231. }
  232. catch (Exception ex)
  233. {
  234. HTTPManager.Logger.Exception("OverHTTP1", "OnOpen", ex, this.Parent.Context);
  235. }
  236. }
  237. webSocket.OnText = (ws, msg) =>
  238. {
  239. if (this.Parent.OnMessage != null)
  240. this.Parent.OnMessage(this.Parent, msg);
  241. };
  242. webSocket.OnBinaryNoAlloc = (ws, frame) =>
  243. {
  244. if (this.Parent.OnBinary != null)
  245. {
  246. var bin = new byte[frame.Count];
  247. Array.Copy(frame.Data, 0, bin, 0, frame.Count);
  248. this.Parent.OnBinary(this.Parent, bin);
  249. }
  250. if (this.Parent.OnBinaryNoAlloc != null)
  251. this.Parent.OnBinaryNoAlloc(this.Parent, frame);
  252. };
  253. webSocket.OnClosed = (ws, code, msg) =>
  254. {
  255. this.State = WebSocketStates.Closed;
  256. if (this.Parent.OnClosed != null)
  257. this.Parent.OnClosed(this.Parent, code, msg);
  258. };
  259. if (this.Parent.OnIncompleteFrame != null)
  260. webSocket.OnIncompleteFrame = (ws, frame) =>
  261. {
  262. if (this.Parent.OnIncompleteFrame != null)
  263. this.Parent.OnIncompleteFrame(this.Parent, frame);
  264. };
  265. if (this.Parent.StartPingThread)
  266. webSocket.StartPinging(Math.Max(this.Parent.PingFrequency, 100));
  267. webSocket.StartReceive();
  268. }
  269. public override void Send(string message)
  270. {
  271. webSocket.Send(message);
  272. }
  273. public override void Send(byte[] buffer)
  274. {
  275. webSocket.Send(buffer);
  276. }
  277. public override void Send(byte[] buffer, ulong offset, ulong count)
  278. {
  279. webSocket.Send(buffer, offset, count);
  280. }
  281. public override void SendAsBinary(BufferSegment data)
  282. {
  283. webSocket.Send(WebSocketFrameTypes.Binary, data);
  284. }
  285. public override void SendAsText(BufferSegment data)
  286. {
  287. webSocket.Send(WebSocketFrameTypes.Text, data);
  288. }
  289. public override void Send(WebSocketFrame frame)
  290. {
  291. webSocket.Send(frame);
  292. }
  293. }
  294. }
  295. #endif