HTTPConnection.cs 32 KB

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