You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

HTTPConnection.cs 36KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Text;
  5. using System.Threading;
  6. using BestHTTP.Extensions;
  7. using BestHTTP.Authentication;
  8. #if (!NETFX_CORE && !UNITY_WP8) || UNITY_EDITOR
  9. using System.Net.Security;
  10. #endif
  11. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  12. using BestHTTP.Caching;
  13. #endif
  14. #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
  15. using Org.BouncyCastle.Crypto.Tls;
  16. #endif
  17. #if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR)
  18. using BestHTTP.Cookies;
  19. #endif
  20. #if NETFX_CORE || BUILD_FOR_WP8
  21. using System.Threading.Tasks;
  22. using Windows.Networking.Sockets;
  23. using TcpClient = BestHTTP.PlatformSupport.TcpClient.WinRT.TcpClient;
  24. //Disable CD4014: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
  25. #pragma warning disable 4014
  26. #elif UNITY_WP8 && !UNITY_EDITOR
  27. using TcpClient = BestHTTP.PlatformSupport.TcpClient.WP8.TcpClient;
  28. #else
  29. using TcpClient = BestHTTP.PlatformSupport.TcpClient.General.TcpClient;
  30. #endif
  31. namespace BestHTTP
  32. {
  33. /// <summary>
  34. /// https://tools.ietf.org/html/draft-thomson-hybi-http-timeout-03
  35. /// Test servers: http://tools.ietf.org/ http://nginx.org/
  36. /// </summary>
  37. internal sealed class KeepAliveHeader
  38. {
  39. /// <summary>
  40. /// A host sets the value of the "timeout" parameter to the time that the host will allow an idle connection to remain open before it is closed. A connection is idle if no data is sent or received by a host.
  41. /// </summary>
  42. public TimeSpan TimeOut { get; private set; }
  43. /// <summary>
  44. /// The "max" parameter has been used to indicate the maximum number of requests that would be made on the connection.This parameter is deprecated.Any limit on requests can be enforced by sending "Connection: close" and closing the connection.
  45. /// </summary>
  46. public int MaxRequests { get; private set; }
  47. public void Parse(List<string> headerValues)
  48. {
  49. HeaderParser parser = new HeaderParser(headerValues[0]);
  50. HeaderValue value;
  51. if (parser.TryGet("timeout", out value) && value.HasValue)
  52. {
  53. int intValue = 0;
  54. if (int.TryParse(value.Value, out intValue))
  55. this.TimeOut = TimeSpan.FromSeconds(intValue);
  56. else
  57. this.TimeOut = TimeSpan.MaxValue;
  58. }
  59. if (parser.TryGet("max", out value) && value.HasValue)
  60. {
  61. int intValue = 0;
  62. if (int.TryParse("max", out intValue))
  63. this.MaxRequests = intValue;
  64. else
  65. this.MaxRequests = int.MaxValue;
  66. }
  67. }
  68. }
  69. internal enum RetryCauses
  70. {
  71. /// <summary>
  72. /// The request processed without any special case.
  73. /// </summary>
  74. None,
  75. /// <summary>
  76. /// If the server closed the connection while we sending a request we should reconnect and send the request again. But we will try it once.
  77. /// </summary>
  78. Reconnect,
  79. /// <summary>
  80. /// We need an another try with Authorization header set.
  81. /// </summary>
  82. Authenticate,
  83. #if !BESTHTTP_DISABLE_PROXY
  84. /// <summary>
  85. /// The proxy needs authentication.
  86. /// </summary>
  87. ProxyAuthenticate,
  88. #endif
  89. }
  90. /// <summary>
  91. /// Represents and manages a connection to a server.
  92. /// </summary>
  93. internal sealed class HTTPConnection : ConnectionBase
  94. {
  95. public override bool IsRemovable
  96. {
  97. get
  98. {
  99. // Plugin's own connection pooling
  100. if (base.IsRemovable)
  101. return true;
  102. // Overridden keep-alive timeout by a Keep-Alive header
  103. if (IsFree && KeepAlive != null && (DateTime.UtcNow - base.LastProcessTime) >= KeepAlive.TimeOut)
  104. return true;
  105. return false;
  106. }
  107. }
  108. #region Private Properties
  109. private TcpClient Client;
  110. private Stream Stream;
  111. private KeepAliveHeader KeepAlive;
  112. #endregion
  113. internal HTTPConnection(string serverAddress)
  114. :base(serverAddress)
  115. {}
  116. #region Request Processing Implementation
  117. protected override
  118. #if NETFX_CORE
  119. async
  120. #endif
  121. void ThreadFunc(object param)
  122. {
  123. bool alreadyReconnected = false;
  124. bool redirected = false;
  125. RetryCauses cause = RetryCauses.None;
  126. try
  127. {
  128. #if !BESTHTTP_DISABLE_PROXY
  129. if (!HasProxy && CurrentRequest.HasProxy)
  130. Proxy = CurrentRequest.Proxy;
  131. #endif
  132. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  133. // Try load the full response from an already saved cache entity. If the response
  134. if (TryLoadAllFromCache())
  135. return;
  136. #endif
  137. if (Client != null && !Client.IsConnected())
  138. Close();
  139. do // of while (reconnect)
  140. {
  141. if (cause == RetryCauses.Reconnect)
  142. {
  143. Close();
  144. #if NETFX_CORE
  145. await Task.Delay(100);
  146. #else
  147. Thread.Sleep(100);
  148. #endif
  149. }
  150. LastProcessedUri = CurrentRequest.CurrentUri;
  151. cause = RetryCauses.None;
  152. // Connect to the server
  153. Connect();
  154. if (State == HTTPConnectionStates.AbortRequested)
  155. throw new Exception("AbortRequested");
  156. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  157. // Setup cache control headers before we send out the request
  158. if (!CurrentRequest.DisableCache)
  159. HTTPCacheService.SetHeaders(CurrentRequest);
  160. #endif
  161. // Write the request to the stream
  162. // sentRequest will be true if the request sent out successfully(no SocketException), so we can try read the response
  163. bool sentRequest = false;
  164. try
  165. {
  166. CurrentRequest.SendOutTo(Stream);
  167. sentRequest = true;
  168. }
  169. catch (Exception ex)
  170. {
  171. Close();
  172. if (State == HTTPConnectionStates.TimedOut ||
  173. State == HTTPConnectionStates.AbortRequested)
  174. throw new Exception("AbortRequested");
  175. // We will try again only once
  176. if (!alreadyReconnected && !CurrentRequest.DisableRetry)
  177. {
  178. alreadyReconnected = true;
  179. cause = RetryCauses.Reconnect;
  180. }
  181. else // rethrow exception
  182. throw ex;
  183. }
  184. // If sending out the request succeeded, we will try read the response.
  185. if (sentRequest)
  186. {
  187. bool received = Receive();
  188. if (State == HTTPConnectionStates.TimedOut ||
  189. State == HTTPConnectionStates.AbortRequested)
  190. throw new Exception("AbortRequested");
  191. if (!received && !alreadyReconnected && !CurrentRequest.DisableRetry)
  192. {
  193. alreadyReconnected = true;
  194. cause = RetryCauses.Reconnect;
  195. }
  196. if (CurrentRequest.Response != null)
  197. {
  198. #if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR)
  199. // Try to store cookies before we do anything else, as we may remove the response deleting the cookies as well.
  200. if (CurrentRequest.IsCookiesEnabled)
  201. CookieJar.Set(CurrentRequest.Response);
  202. #endif
  203. switch (CurrentRequest.Response.StatusCode)
  204. {
  205. // Not authorized
  206. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2
  207. case 401:
  208. {
  209. string authHeader = DigestStore.FindBest(CurrentRequest.Response.GetHeaderValues("www-authenticate"));
  210. if (!string.IsNullOrEmpty(authHeader))
  211. {
  212. var digest = DigestStore.GetOrCreate(CurrentRequest.CurrentUri);
  213. digest.ParseChallange(authHeader);
  214. if (CurrentRequest.Credentials != null && digest.IsUriProtected(CurrentRequest.CurrentUri) && (!CurrentRequest.HasHeader("Authorization") || digest.Stale))
  215. cause = RetryCauses.Authenticate;
  216. }
  217. goto default;
  218. }
  219. #if !BESTHTTP_DISABLE_PROXY
  220. // Proxy authentication required
  221. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.8
  222. case 407:
  223. {
  224. if (CurrentRequest.HasProxy)
  225. {
  226. string authHeader = DigestStore.FindBest(CurrentRequest.Response.GetHeaderValues("proxy-authenticate"));
  227. if (!string.IsNullOrEmpty(authHeader))
  228. {
  229. var digest = DigestStore.GetOrCreate(CurrentRequest.Proxy.Address);
  230. digest.ParseChallange(authHeader);
  231. if (CurrentRequest.Proxy.Credentials != null && digest.IsUriProtected(CurrentRequest.Proxy.Address) && (!CurrentRequest.HasHeader("Proxy-Authorization") || digest.Stale))
  232. cause = RetryCauses.ProxyAuthenticate;
  233. }
  234. }
  235. goto default;
  236. }
  237. #endif
  238. // Redirected
  239. case 301: // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2
  240. case 302: // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.3
  241. case 307: // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.8
  242. case 308: // http://tools.ietf.org/html/rfc7238
  243. {
  244. if (CurrentRequest.RedirectCount >= CurrentRequest.MaxRedirects)
  245. goto default;
  246. CurrentRequest.RedirectCount++;
  247. string location = CurrentRequest.Response.GetFirstHeaderValue("location");
  248. if (!string.IsNullOrEmpty(location))
  249. {
  250. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  251. HTTPManager.Logger.Verbose("HTTPConnection", string.Format("{0} - Redirected to {1}", this.CurrentRequest.CurrentUri.ToString(), location));
  252. Uri redirectUri = GetRedirectUri(location);
  253. // Let the user to take some control over the redirection
  254. if (!CurrentRequest.CallOnBeforeRedirection(redirectUri))
  255. {
  256. HTTPManager.Logger.Information("HTTPConnection", "OnBeforeRedirection returned False");
  257. goto default;
  258. }
  259. // Remove the previously set Host header.
  260. CurrentRequest.RemoveHeader("Host");
  261. // Set the Referer header to the last Uri.
  262. CurrentRequest.SetHeader("Referer", CurrentRequest.CurrentUri.ToString());
  263. // Set the new Uri, the CurrentUri will return this while the IsRedirected property is true
  264. CurrentRequest.RedirectUri = redirectUri;
  265. // Discard the redirect response, we don't need it any more
  266. CurrentRequest.Response = null;
  267. redirected = CurrentRequest.IsRedirected = true;
  268. }
  269. else
  270. #if !NETFX_CORE
  271. throw new MissingFieldException(string.Format("Got redirect status({0}) without 'location' header!", CurrentRequest.Response.StatusCode.ToString()));
  272. #else
  273. throw new Exception(string.Format("Got redirect status({0}) without 'location' header!", CurrentRequest.Response.StatusCode.ToString()));
  274. #endif
  275. goto default;
  276. }
  277. default:
  278. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  279. TryStoreInCache();
  280. #endif
  281. break;
  282. }
  283. // If we have a response and the server telling us that it closed the connection after the message sent to us, then
  284. // we will close the connection too.
  285. if (!CurrentRequest.IsKeepAlive ||
  286. CurrentRequest.Response == null ||
  287. (!CurrentRequest.Response.IsClosedManually && CurrentRequest.Response.HasHeaderWithValue("connection", "close")))
  288. Close();
  289. else if (CurrentRequest.Response != null)
  290. {
  291. var keepAliveheaderValues = CurrentRequest.Response.GetHeaderValues("keep-alive");
  292. if (keepAliveheaderValues != null && keepAliveheaderValues.Count > 0)
  293. {
  294. if (KeepAlive == null)
  295. KeepAlive = new KeepAliveHeader();
  296. KeepAlive.Parse(keepAliveheaderValues);
  297. }
  298. }
  299. }
  300. }
  301. } while (cause != RetryCauses.None);
  302. }
  303. catch(TimeoutException e)
  304. {
  305. CurrentRequest.Response = null;
  306. CurrentRequest.Exception = e;
  307. CurrentRequest.State = HTTPRequestStates.ConnectionTimedOut;
  308. Close();
  309. }
  310. catch (Exception e)
  311. {
  312. if (CurrentRequest != null)
  313. {
  314. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  315. if (CurrentRequest.UseStreaming)
  316. HTTPCacheService.DeleteEntity(CurrentRequest.CurrentUri);
  317. #endif
  318. // Something gone bad, Response must be null!
  319. CurrentRequest.Response = null;
  320. switch (State)
  321. {
  322. case HTTPConnectionStates.Closed:
  323. case HTTPConnectionStates.AbortRequested:
  324. CurrentRequest.State = HTTPRequestStates.Aborted;
  325. break;
  326. case HTTPConnectionStates.TimedOut:
  327. CurrentRequest.State = HTTPRequestStates.TimedOut;
  328. break;
  329. default:
  330. CurrentRequest.Exception = e;
  331. CurrentRequest.State = HTTPRequestStates.Error;
  332. break;
  333. }
  334. }
  335. Close();
  336. }
  337. finally
  338. {
  339. if (CurrentRequest != null)
  340. {
  341. // Avoid state changes. While we are in this block changing the connection's State, on Unity's main thread
  342. // the HTTPManager's OnUpdate will check the connections's State and call functions that can change the inner state of
  343. // the object. (Like setting the CurrentRequest to null in function Recycle() causing a NullRef exception)
  344. lock (HTTPManager.Locker)
  345. {
  346. if (CurrentRequest != null && CurrentRequest.Response != null && CurrentRequest.Response.IsUpgraded)
  347. State = HTTPConnectionStates.Upgraded;
  348. else
  349. State = redirected ? HTTPConnectionStates.Redirected : (Client == null ? HTTPConnectionStates.Closed : HTTPConnectionStates.WaitForRecycle);
  350. // Change the request's state only when the whole processing finished
  351. if (CurrentRequest.State == HTTPRequestStates.Processing && (State == HTTPConnectionStates.Closed || State == HTTPConnectionStates.WaitForRecycle))
  352. {
  353. if (CurrentRequest.Response != null)
  354. CurrentRequest.State = HTTPRequestStates.Finished;
  355. else
  356. {
  357. CurrentRequest.Exception = new Exception(string.Format("Remote server closed the connection before sending response header! Previous request state: {0}. Connection state: {1}",
  358. CurrentRequest.State.ToString(),
  359. State.ToString()));
  360. CurrentRequest.State = HTTPRequestStates.Error;
  361. }
  362. }
  363. if (CurrentRequest.State == HTTPRequestStates.ConnectionTimedOut)
  364. State = HTTPConnectionStates.Closed;
  365. LastProcessTime = DateTime.UtcNow;
  366. if (OnConnectionRecycled != null)
  367. RecycleNow();
  368. }
  369. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  370. HTTPCacheService.SaveLibrary();
  371. #endif
  372. #if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR)
  373. CookieJar.Persist();
  374. #endif
  375. }
  376. }
  377. }
  378. private void Connect()
  379. {
  380. Uri uri =
  381. #if !BESTHTTP_DISABLE_PROXY
  382. CurrentRequest.HasProxy ? CurrentRequest.Proxy.Address :
  383. #endif
  384. CurrentRequest.CurrentUri;
  385. #region TCP Connection
  386. if (Client == null)
  387. Client = new TcpClient();
  388. if (!Client.Connected)
  389. {
  390. Client.ConnectTimeout = CurrentRequest.ConnectTimeout;
  391. #if NETFX_CORE || (UNITY_WP8 && !UNITY_EDITOR)
  392. Client.UseHTTPSProtocol =
  393. #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
  394. !CurrentRequest.UseAlternateSSL &&
  395. #endif
  396. HTTPProtocolFactory.IsSecureProtocol(uri);
  397. #endif
  398. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  399. HTTPManager.Logger.Verbose("HTTPConnection", string.Format("'{0}' - Connecting to {1}:{2}", this.CurrentRequest.CurrentUri.ToString(), uri.Host, uri.Port.ToString()));
  400. Client.Connect(uri.Host, uri.Port);
  401. if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
  402. HTTPManager.Logger.Information("HTTPConnection", "Connected to " + uri.Host + ":" + uri.Port.ToString());
  403. }
  404. else if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
  405. HTTPManager.Logger.Information("HTTPConnection", "Already connected to " + uri.Host + ":" + uri.Port.ToString());
  406. #endregion
  407. StartTime = DateTime.UtcNow;
  408. if (Stream == null)
  409. {
  410. bool isSecure = HTTPProtocolFactory.IsSecureProtocol(CurrentRequest.CurrentUri);
  411. Stream = Client.GetStream();
  412. /*if (Stream.CanTimeout)
  413. Stream.ReadTimeout = Stream.WriteTimeout = (int)CurrentRequest.Timeout.TotalMilliseconds;*/
  414. #if !BESTHTTP_DISABLE_PROXY
  415. #region Proxy Handling
  416. if (HasProxy && (!Proxy.IsTransparent || (isSecure && Proxy.NonTransparentForHTTPS)))
  417. {
  418. var outStream = new BinaryWriter(Stream);
  419. bool retry;
  420. do
  421. {
  422. // If we have to because of a authentication request, we will switch it to true
  423. retry = false;
  424. string connectStr = string.Format("CONNECT {0}:{1} HTTP/1.1", CurrentRequest.CurrentUri.Host, CurrentRequest.CurrentUri.Port);
  425. HTTPManager.Logger.Information("HTTPConnection", "Sending " + connectStr);
  426. outStream.SendAsASCII(connectStr);
  427. outStream.Write(HTTPRequest.EOL);
  428. outStream.SendAsASCII("Proxy-Connection: Keep-Alive");
  429. outStream.Write(HTTPRequest.EOL);
  430. outStream.SendAsASCII("Connection: Keep-Alive");
  431. outStream.Write(HTTPRequest.EOL);
  432. outStream.SendAsASCII(string.Format("Host: {0}:{1}", CurrentRequest.CurrentUri.Host, CurrentRequest.CurrentUri.Port));
  433. outStream.Write(HTTPRequest.EOL);
  434. // Proxy Authentication
  435. if (HasProxy && Proxy.Credentials != null)
  436. {
  437. switch (Proxy.Credentials.Type)
  438. {
  439. case AuthenticationTypes.Basic:
  440. // With Basic authentication we don't want to wait for a challenge, we will send the hash with the first request
  441. outStream.Write(string.Format("Proxy-Authorization: {0}", string.Concat("Basic ", Convert.ToBase64String(Encoding.UTF8.GetBytes(Proxy.Credentials.UserName + ":" + Proxy.Credentials.Password)))).GetASCIIBytes());
  442. outStream.Write(HTTPRequest.EOL);
  443. break;
  444. case AuthenticationTypes.Unknown:
  445. case AuthenticationTypes.Digest:
  446. var digest = DigestStore.Get(Proxy.Address);
  447. if (digest != null)
  448. {
  449. string authentication = digest.GenerateResponseHeader(CurrentRequest, Proxy.Credentials);
  450. if (!string.IsNullOrEmpty(authentication))
  451. {
  452. outStream.Write(string.Format("Proxy-Authorization: {0}", authentication).GetASCIIBytes());
  453. outStream.Write(HTTPRequest.EOL);
  454. }
  455. }
  456. break;
  457. }
  458. }
  459. outStream.Write(HTTPRequest.EOL);
  460. // Make sure to send all the wrote data to the wire
  461. outStream.Flush();
  462. CurrentRequest.ProxyResponse = new HTTPResponse(CurrentRequest, Stream, false, false);
  463. // Read back the response of the proxy
  464. if (!CurrentRequest.ProxyResponse.Receive(-1, true))
  465. throw new Exception("Connection to the Proxy Server failed!");
  466. if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
  467. HTTPManager.Logger.Information("HTTPConnection", "Proxy returned - status code: " + CurrentRequest.ProxyResponse.StatusCode + " message: " + CurrentRequest.ProxyResponse.Message);
  468. switch(CurrentRequest.ProxyResponse.StatusCode)
  469. {
  470. // Proxy authentication required
  471. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.8
  472. case 407:
  473. {
  474. string authHeader = DigestStore.FindBest(CurrentRequest.ProxyResponse.GetHeaderValues("proxy-authenticate"));
  475. if (!string.IsNullOrEmpty(authHeader))
  476. {
  477. var digest = DigestStore.GetOrCreate(Proxy.Address);
  478. digest.ParseChallange(authHeader);
  479. if (Proxy.Credentials != null && digest.IsUriProtected(Proxy.Address) && (!CurrentRequest.HasHeader("Proxy-Authorization") || digest.Stale))
  480. retry = true;
  481. }
  482. break;
  483. }
  484. default:
  485. if (!CurrentRequest.ProxyResponse.IsSuccess)
  486. throw new Exception(string.Format("Proxy returned Status Code: \"{0}\", Message: \"{1}\" and Response: {2}", CurrentRequest.ProxyResponse.StatusCode, CurrentRequest.ProxyResponse.Message, CurrentRequest.ProxyResponse.DataAsText));
  487. break;
  488. }
  489. } while (retry);
  490. }
  491. #endregion
  492. #endif // #if !BESTHTTP_DISABLE_PROXY
  493. // We have to use CurrentRequest.CurrentUri here, because uri can be a proxy uri with a different protocol
  494. if (isSecure)
  495. {
  496. #region SSL Upgrade
  497. #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
  498. if (CurrentRequest.UseAlternateSSL)
  499. {
  500. var handler = new TlsClientProtocol(Client.GetStream(), new Org.BouncyCastle.Security.SecureRandom());
  501. // http://tools.ietf.org/html/rfc3546#section-3.1
  502. // It is RECOMMENDED that clients include an extension of type "server_name" in the client hello whenever they locate a server by a supported name type.
  503. List<string> hostNames = new List<string>(1);
  504. hostNames.Add(CurrentRequest.CurrentUri.Host);
  505. handler.Connect(new LegacyTlsClient(CurrentRequest.CurrentUri,
  506. CurrentRequest.CustomCertificateVerifyer == null ? new AlwaysValidVerifyer() : CurrentRequest.CustomCertificateVerifyer,
  507. CurrentRequest.CustomClientCredentialsProvider,
  508. hostNames));
  509. Stream = handler.Stream;
  510. }
  511. else
  512. #endif
  513. {
  514. #if !NETFX_CORE && !UNITY_WP8
  515. SslStream sslStream = new SslStream(Client.GetStream(), false, (sender, cert, chain, errors) =>
  516. {
  517. return CurrentRequest.CallCustomCertificationValidator(cert, chain);
  518. });
  519. if (!sslStream.IsAuthenticated)
  520. sslStream.AuthenticateAsClient(CurrentRequest.CurrentUri.Host);
  521. Stream = sslStream;
  522. #else
  523. Stream = Client.GetStream();
  524. #endif
  525. }
  526. #endregion
  527. }
  528. }
  529. }
  530. private bool Receive()
  531. {
  532. SupportedProtocols protocol = CurrentRequest.ProtocolHandler == SupportedProtocols.Unknown ? HTTPProtocolFactory.GetProtocolFromUri(CurrentRequest.CurrentUri) : CurrentRequest.ProtocolHandler;
  533. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  534. HTTPManager.Logger.Verbose("HTTPConnection", string.Format("{0} - Receive - protocol: {1}", this.CurrentRequest.CurrentUri.ToString(), protocol.ToString()));
  535. CurrentRequest.Response = HTTPProtocolFactory.Get(protocol, CurrentRequest, Stream, CurrentRequest.UseStreaming, false);
  536. if (!CurrentRequest.Response.Receive())
  537. {
  538. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  539. HTTPManager.Logger.Verbose("HTTPConnection", string.Format("{0} - Receive - Failed! Response will be null, returning with false.", this.CurrentRequest.CurrentUri.ToString()));
  540. CurrentRequest.Response = null;
  541. return false;
  542. }
  543. // We didn't check HTTPManager.IsCachingDisabled's value on purpose. (sending out a request with conditional get then change IsCachingDisabled to true may produce undefined behavior)
  544. if (CurrentRequest.Response.StatusCode == 304)
  545. {
  546. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  547. if (CurrentRequest.IsRedirected)
  548. {
  549. if (!LoadFromCache(CurrentRequest.RedirectUri))
  550. LoadFromCache(CurrentRequest.Uri);
  551. }
  552. else
  553. LoadFromCache(CurrentRequest.Uri);
  554. #else
  555. return false;
  556. #endif
  557. }
  558. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  559. HTTPManager.Logger.Verbose("HTTPConnection", string.Format("{0} - Receive - Finished Successfully!", this.CurrentRequest.CurrentUri.ToString()));
  560. return true;
  561. }
  562. #endregion
  563. #region Helper Functions
  564. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  565. private bool LoadFromCache(Uri uri)
  566. {
  567. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  568. HTTPManager.Logger.Verbose("HTTPConnection", string.Format("{0} - LoadFromCache for Uri: {1}", this.CurrentRequest.CurrentUri.ToString(), uri.ToString()));
  569. int bodyLength;
  570. using (var cacheStream = HTTPCacheService.GetBody(uri, out bodyLength))
  571. {
  572. if (cacheStream == null)
  573. return false;
  574. if (!CurrentRequest.Response.HasHeader("content-length"))
  575. CurrentRequest.Response.Headers.Add("content-length", new List<string>(1) { bodyLength.ToString() });
  576. CurrentRequest.Response.IsFromCache = true;
  577. CurrentRequest.Response.ReadRaw(cacheStream, bodyLength);
  578. }
  579. return true;
  580. }
  581. private bool TryLoadAllFromCache()
  582. {
  583. if (CurrentRequest.DisableCache || !HTTPCacheService.IsSupported)
  584. return false;
  585. // We will try read the response from the cache, but if something happens we will fallback to the normal way.
  586. try
  587. {
  588. //Unless specifically constrained by a cache-control (section 14.9) directive, a caching system MAY always store a successful response (see section 13.8) as a cache entity,
  589. // MAY return it without validation if it is fresh, and MAY return it after successful validation.
  590. // MAY return it without validation if it is fresh!
  591. if (HTTPCacheService.IsCachedEntityExpiresInTheFuture(CurrentRequest))
  592. {
  593. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  594. HTTPManager.Logger.Verbose("HTTPConnection", string.Format("{0} - TryLoadAllFromCache - whole response loading from cache", this.CurrentRequest.CurrentUri.ToString()));
  595. CurrentRequest.Response = HTTPCacheService.GetFullResponse(CurrentRequest);
  596. if (CurrentRequest.Response != null)
  597. return true;
  598. }
  599. }
  600. catch
  601. {
  602. HTTPCacheService.DeleteEntity(CurrentRequest.CurrentUri);
  603. }
  604. return false;
  605. }
  606. #endif
  607. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  608. private void TryStoreInCache()
  609. {
  610. // if UseStreaming && !DisableCache then we already wrote the response to the cache
  611. if (!CurrentRequest.UseStreaming &&
  612. !CurrentRequest.DisableCache &&
  613. CurrentRequest.Response != null &&
  614. HTTPCacheService.IsSupported &&
  615. HTTPCacheService.IsCacheble(CurrentRequest.CurrentUri, CurrentRequest.MethodType, CurrentRequest.Response))
  616. {
  617. if(CurrentRequest.IsRedirected)
  618. HTTPCacheService.Store(CurrentRequest.Uri, CurrentRequest.MethodType, CurrentRequest.Response);
  619. else
  620. HTTPCacheService.Store(CurrentRequest.CurrentUri, CurrentRequest.MethodType, CurrentRequest.Response);
  621. }
  622. }
  623. #endif
  624. private Uri GetRedirectUri(string location)
  625. {
  626. Uri result = null;
  627. try
  628. {
  629. result = new Uri(location);
  630. }
  631. #if !NETFX_CORE
  632. catch (UriFormatException)
  633. #else
  634. catch
  635. #endif
  636. {
  637. // Sometimes the server sends back only the path and query component of the new uri
  638. var uri = CurrentRequest.Uri;
  639. var builder = new UriBuilder(uri.Scheme, uri.Host, uri.Port, location);
  640. result = builder.Uri;
  641. }
  642. return result;
  643. }
  644. internal override void Abort(HTTPConnectionStates newState)
  645. {
  646. State = newState;
  647. switch(State)
  648. {
  649. case HTTPConnectionStates.TimedOut: TimedOutStart = DateTime.UtcNow; break;
  650. }
  651. if (Stream != null)
  652. Stream.Dispose();
  653. }
  654. private void Close()
  655. {
  656. KeepAlive = null;
  657. LastProcessedUri = null;
  658. if (Client != null)
  659. {
  660. try
  661. {
  662. Client.Close();
  663. }
  664. catch
  665. {
  666. }
  667. finally
  668. {
  669. Stream = null;
  670. Client = null;
  671. }
  672. }
  673. }
  674. #endregion
  675. protected override void Dispose(bool disposing)
  676. {
  677. Close();
  678. base.Dispose(disposing);
  679. }
  680. }
  681. }