HTTPConnection.cs 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889
  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. #if !NETFX_CORE
  167. Client.NoDelay = CurrentRequest.TryToMinimizeTCPLatency;
  168. #endif
  169. CurrentRequest.SendOutTo(Stream);
  170. sentRequest = true;
  171. }
  172. catch (Exception ex)
  173. {
  174. Close();
  175. if (State == HTTPConnectionStates.TimedOut ||
  176. State == HTTPConnectionStates.AbortRequested)
  177. throw new Exception("AbortRequested");
  178. // We will try again only once
  179. if (!alreadyReconnected && !CurrentRequest.DisableRetry)
  180. {
  181. alreadyReconnected = true;
  182. cause = RetryCauses.Reconnect;
  183. }
  184. else // rethrow exception
  185. throw ex;
  186. }
  187. // If sending out the request succeeded, we will try read the response.
  188. if (sentRequest)
  189. {
  190. bool received = Receive();
  191. if (State == HTTPConnectionStates.TimedOut ||
  192. State == HTTPConnectionStates.AbortRequested)
  193. throw new Exception("AbortRequested");
  194. if (!received && !alreadyReconnected && !CurrentRequest.DisableRetry)
  195. {
  196. alreadyReconnected = true;
  197. cause = RetryCauses.Reconnect;
  198. }
  199. if (CurrentRequest.Response != null)
  200. {
  201. #if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR)
  202. // Try to store cookies before we do anything else, as we may remove the response deleting the cookies as well.
  203. if (CurrentRequest.IsCookiesEnabled)
  204. CookieJar.Set(CurrentRequest.Response);
  205. #endif
  206. switch (CurrentRequest.Response.StatusCode)
  207. {
  208. // Not authorized
  209. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2
  210. case 401:
  211. {
  212. string authHeader = DigestStore.FindBest(CurrentRequest.Response.GetHeaderValues("www-authenticate"));
  213. if (!string.IsNullOrEmpty(authHeader))
  214. {
  215. var digest = DigestStore.GetOrCreate(CurrentRequest.CurrentUri);
  216. digest.ParseChallange(authHeader);
  217. if (CurrentRequest.Credentials != null && digest.IsUriProtected(CurrentRequest.CurrentUri) && (!CurrentRequest.HasHeader("Authorization") || digest.Stale))
  218. cause = RetryCauses.Authenticate;
  219. }
  220. goto default;
  221. }
  222. #if !BESTHTTP_DISABLE_PROXY
  223. // Proxy authentication required
  224. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.8
  225. case 407:
  226. {
  227. if (CurrentRequest.HasProxy)
  228. {
  229. string authHeader = DigestStore.FindBest(CurrentRequest.Response.GetHeaderValues("proxy-authenticate"));
  230. if (!string.IsNullOrEmpty(authHeader))
  231. {
  232. var digest = DigestStore.GetOrCreate(CurrentRequest.Proxy.Address);
  233. digest.ParseChallange(authHeader);
  234. if (CurrentRequest.Proxy.Credentials != null && digest.IsUriProtected(CurrentRequest.Proxy.Address) && (!CurrentRequest.HasHeader("Proxy-Authorization") || digest.Stale))
  235. cause = RetryCauses.ProxyAuthenticate;
  236. }
  237. }
  238. goto default;
  239. }
  240. #endif
  241. // Redirected
  242. case 301: // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2
  243. case 302: // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.3
  244. case 307: // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.8
  245. case 308: // http://tools.ietf.org/html/rfc7238
  246. {
  247. if (CurrentRequest.RedirectCount >= CurrentRequest.MaxRedirects)
  248. goto default;
  249. CurrentRequest.RedirectCount++;
  250. string location = CurrentRequest.Response.GetFirstHeaderValue("location");
  251. if (!string.IsNullOrEmpty(location))
  252. {
  253. Uri redirectUri = GetRedirectUri(location);
  254. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  255. HTTPManager.Logger.Verbose("HTTPConnection", string.Format("{0} - Redirected to Location: '{1}' redirectUri: '{1}'", this.CurrentRequest.CurrentUri.ToString(), location, redirectUri));
  256. // Let the user to take some control over the redirection
  257. if (!CurrentRequest.CallOnBeforeRedirection(redirectUri))
  258. {
  259. HTTPManager.Logger.Information("HTTPConnection", "OnBeforeRedirection returned False");
  260. goto default;
  261. }
  262. // Remove the previously set Host header.
  263. CurrentRequest.RemoveHeader("Host");
  264. // SaveLocal the Referer header to the last Uri.
  265. CurrentRequest.SetHeader("Referer", CurrentRequest.CurrentUri.ToString());
  266. // SaveLocal the new Uri, the CurrentUri will return this while the IsRedirected property is true
  267. CurrentRequest.RedirectUri = redirectUri;
  268. // Discard the redirect response, we don't need it any more
  269. CurrentRequest.Response = null;
  270. redirected = CurrentRequest.IsRedirected = true;
  271. }
  272. else
  273. #if !NETFX_CORE
  274. throw new MissingFieldException(string.Format("Got redirect status({0}) without 'location' header!", CurrentRequest.Response.StatusCode.ToString()));
  275. #else
  276. throw new Exception(string.Format("Got redirect status({0}) without 'location' header!", CurrentRequest.Response.StatusCode.ToString()));
  277. #endif
  278. goto default;
  279. }
  280. default:
  281. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  282. TryStoreInCache();
  283. #endif
  284. break;
  285. }
  286. // Closing the stream is done manually
  287. if (CurrentRequest.Response == null || !CurrentRequest.Response.IsClosedManually) {
  288. // If we have a response and the server telling us that it closed the connection after the message sent to us, then
  289. // we will close the connection too.
  290. bool closeByServer = CurrentRequest.Response == null || CurrentRequest.Response.HasHeaderWithValue("connection", "close");
  291. bool closeByClient = !CurrentRequest.IsKeepAlive;
  292. if (closeByServer || closeByClient)
  293. Close();
  294. else if (CurrentRequest.Response != null)
  295. {
  296. var keepAliveheaderValues = CurrentRequest.Response.GetHeaderValues("keep-alive");
  297. if (keepAliveheaderValues != null && keepAliveheaderValues.Count > 0)
  298. {
  299. if (KeepAlive == null)
  300. KeepAlive = new KeepAliveHeader();
  301. KeepAlive.Parse(keepAliveheaderValues);
  302. }
  303. }
  304. }
  305. }
  306. }
  307. } while (cause != RetryCauses.None);
  308. }
  309. catch(TimeoutException e)
  310. {
  311. CurrentRequest.Response = null;
  312. CurrentRequest.Exception = e;
  313. CurrentRequest.State = HTTPRequestStates.ConnectionTimedOut;
  314. Close();
  315. }
  316. catch (Exception e)
  317. {
  318. if (CurrentRequest != null)
  319. {
  320. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  321. if (CurrentRequest.UseStreaming)
  322. HTTPCacheService.DeleteEntity(CurrentRequest.CurrentUri);
  323. #endif
  324. // Something gone bad, Response must be null!
  325. CurrentRequest.Response = null;
  326. switch (State)
  327. {
  328. case HTTPConnectionStates.Closed:
  329. case HTTPConnectionStates.AbortRequested:
  330. CurrentRequest.State = HTTPRequestStates.Aborted;
  331. break;
  332. case HTTPConnectionStates.TimedOut:
  333. CurrentRequest.State = HTTPRequestStates.TimedOut;
  334. break;
  335. default:
  336. CurrentRequest.Exception = e;
  337. CurrentRequest.State = HTTPRequestStates.Error;
  338. break;
  339. }
  340. }
  341. Close();
  342. }
  343. finally
  344. {
  345. if (CurrentRequest != null)
  346. {
  347. // Avoid state changes. While we are in this block changing the connection's State, on Unity's main thread
  348. // the HTTPManager's OnUpdate will check the connections's State and call functions that can change the inner state of
  349. // the object. (Like setting the CurrentRequest to null in function Recycle() causing a NullRef exception)
  350. lock (HTTPManager.Locker)
  351. {
  352. if (CurrentRequest != null && CurrentRequest.Response != null && CurrentRequest.Response.IsUpgraded)
  353. State = HTTPConnectionStates.Upgraded;
  354. else
  355. State = redirected ? HTTPConnectionStates.Redirected : (Client == null ? HTTPConnectionStates.Closed : HTTPConnectionStates.WaitForRecycle);
  356. // Change the request's state only when the whole processing finished
  357. if (CurrentRequest.State == HTTPRequestStates.Processing && (State == HTTPConnectionStates.Closed || State == HTTPConnectionStates.WaitForRecycle))
  358. {
  359. if (CurrentRequest.Response != null)
  360. CurrentRequest.State = HTTPRequestStates.Finished;
  361. else
  362. {
  363. CurrentRequest.Exception = new Exception(string.Format("Remote server closed the connection before sending response header! Previous request state: {0}. Connection state: {1}",
  364. CurrentRequest.State.ToString(),
  365. State.ToString()));
  366. CurrentRequest.State = HTTPRequestStates.Error;
  367. }
  368. }
  369. if (CurrentRequest.State == HTTPRequestStates.ConnectionTimedOut)
  370. State = HTTPConnectionStates.Closed;
  371. LastProcessTime = DateTime.UtcNow;
  372. if (OnConnectionRecycled != null)
  373. RecycleNow();
  374. }
  375. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  376. HTTPCacheService.SaveLibrary();
  377. #endif
  378. #if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR)
  379. CookieJar.Persist();
  380. #endif
  381. }
  382. }
  383. }
  384. private void Connect()
  385. {
  386. Uri uri =
  387. #if !BESTHTTP_DISABLE_PROXY
  388. CurrentRequest.HasProxy ? CurrentRequest.Proxy.Address :
  389. #endif
  390. CurrentRequest.CurrentUri;
  391. #region TCP Connection
  392. if (Client == null)
  393. Client = new TcpClient();
  394. if (!Client.Connected)
  395. {
  396. Client.ConnectTimeout = CurrentRequest.ConnectTimeout;
  397. #if NETFX_CORE || (UNITY_WP8 && !UNITY_EDITOR)
  398. Client.UseHTTPSProtocol =
  399. #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
  400. !CurrentRequest.UseAlternateSSL &&
  401. #endif
  402. HTTPProtocolFactory.IsSecureProtocol(uri);
  403. #endif
  404. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  405. HTTPManager.Logger.Verbose("HTTPConnection", string.Format("'{0}' - Connecting to {1}:{2}", this.CurrentRequest.CurrentUri.ToString(), uri.Host, uri.Port.ToString()));
  406. #if !NETFX_CORE && (!UNITY_WEBGL || UNITY_EDITOR)
  407. Client.SendBufferSize = HTTPManager.SendBufferSize;
  408. Client.ReceiveBufferSize = HTTPManager.ReceiveBufferSize;
  409. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  410. 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()));
  411. #endif
  412. Client.Connect(uri.Host, uri.Port);
  413. if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
  414. HTTPManager.Logger.Information("HTTPConnection", "Connected to " + uri.Host + ":" + uri.Port.ToString());
  415. }
  416. else if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
  417. HTTPManager.Logger.Information("HTTPConnection", "Already connected to " + uri.Host + ":" + uri.Port.ToString());
  418. #endregion
  419. StartTime = DateTime.UtcNow;
  420. if (Stream == null)
  421. {
  422. bool isSecure = HTTPProtocolFactory.IsSecureProtocol(CurrentRequest.CurrentUri);
  423. Stream = Client.GetStream();
  424. /*if (Stream.CanTimeout)
  425. Stream.ReadTimeout = Stream.WriteTimeout = (int)CurrentRequest.Timeout.TotalMilliseconds;*/
  426. #if !BESTHTTP_DISABLE_PROXY
  427. #region Proxy Handling
  428. if (HasProxy && (!Proxy.IsTransparent || (isSecure && Proxy.NonTransparentForHTTPS)))
  429. {
  430. var outStream = new BinaryWriter(new WriteOnlyBufferedStream(Stream, HTTPRequest.UploadChunkSize));
  431. bool retry;
  432. do
  433. {
  434. // If we have to because of a authentication request, we will switch it to true
  435. retry = false;
  436. string connectStr = string.Format("CONNECT {0}:{1} HTTP/1.1", CurrentRequest.CurrentUri.Host, CurrentRequest.CurrentUri.Port);
  437. HTTPManager.Logger.Information("HTTPConnection", "Sending " + connectStr);
  438. outStream.SendAsASCII(connectStr);
  439. outStream.Write(HTTPRequest.EOL);
  440. outStream.SendAsASCII("Proxy-Connection: Keep-Alive");
  441. outStream.Write(HTTPRequest.EOL);
  442. outStream.SendAsASCII("Connection: Keep-Alive");
  443. outStream.Write(HTTPRequest.EOL);
  444. outStream.SendAsASCII(string.Format("Host: {0}:{1}", CurrentRequest.CurrentUri.Host, CurrentRequest.CurrentUri.Port));
  445. outStream.Write(HTTPRequest.EOL);
  446. // Proxy Authentication
  447. if (HasProxy && Proxy.Credentials != null)
  448. {
  449. switch (Proxy.Credentials.Type)
  450. {
  451. case AuthenticationTypes.Basic:
  452. // With Basic authentication we don't want to wait for a challenge, we will send the hash with the first request
  453. outStream.Write(string.Format("Proxy-Authorization: {0}", string.Concat("Basic ", Convert.ToBase64String(Encoding.UTF8.GetBytes(Proxy.Credentials.UserName + ":" + Proxy.Credentials.Password)))).GetASCIIBytes());
  454. outStream.Write(HTTPRequest.EOL);
  455. break;
  456. case AuthenticationTypes.Unknown:
  457. case AuthenticationTypes.Digest:
  458. var digest = DigestStore.Get(Proxy.Address);
  459. if (digest != null)
  460. {
  461. string authentication = digest.GenerateResponseHeader(CurrentRequest, Proxy.Credentials, true);
  462. if (!string.IsNullOrEmpty(authentication))
  463. {
  464. string auth = string.Format("Proxy-Authorization: {0}", authentication);
  465. if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
  466. HTTPManager.Logger.Information("HTTPConnection", "Sending proxy authorization header: " + auth);
  467. outStream.Write(auth.GetASCIIBytes());
  468. outStream.Write(HTTPRequest.EOL);
  469. }
  470. }
  471. break;
  472. }
  473. }
  474. outStream.Write(HTTPRequest.EOL);
  475. // Make sure to send all the wrote data to the wire
  476. outStream.Flush();
  477. CurrentRequest.ProxyResponse = new HTTPResponse(CurrentRequest, Stream, false, false);
  478. // Read back the response of the proxy
  479. if (!CurrentRequest.ProxyResponse.Receive(-1, true))
  480. throw new Exception("Connection to the Proxy Server failed!");
  481. if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
  482. HTTPManager.Logger.Information("HTTPConnection", "Proxy returned - status code: " + CurrentRequest.ProxyResponse.StatusCode + " message: " + CurrentRequest.ProxyResponse.Message + " Body: " + CurrentRequest.ProxyResponse.DataAsText);
  483. switch(CurrentRequest.ProxyResponse.StatusCode)
  484. {
  485. // Proxy authentication required
  486. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.8
  487. case 407:
  488. {
  489. string authHeader = DigestStore.FindBest(CurrentRequest.ProxyResponse.GetHeaderValues("proxy-authenticate"));
  490. if (!string.IsNullOrEmpty(authHeader))
  491. {
  492. var digest = DigestStore.GetOrCreate(Proxy.Address);
  493. digest.ParseChallange(authHeader);
  494. if (Proxy.Credentials != null && digest.IsUriProtected(Proxy.Address) && (!CurrentRequest.HasHeader("Proxy-Authorization") || digest.Stale))
  495. retry = true;
  496. }
  497. break;
  498. }
  499. default:
  500. if (!CurrentRequest.ProxyResponse.IsSuccess)
  501. throw new Exception(string.Format("Proxy returned Status Code: \"{0}\", Message: \"{1}\" and Response: {2}", CurrentRequest.ProxyResponse.StatusCode, CurrentRequest.ProxyResponse.Message, CurrentRequest.ProxyResponse.DataAsText));
  502. break;
  503. }
  504. } while (retry);
  505. }
  506. #endregion
  507. #endif // #if !BESTHTTP_DISABLE_PROXY
  508. // We have to use CurrentRequest.CurrentUri here, because uri can be a proxy uri with a different protocol
  509. if (isSecure)
  510. {
  511. // Under the new experimental runtime there's a bug in the Socket.Send implementation that can cause a
  512. // connection when the TLS protocol is used.
  513. #if !NETFX_CORE && (!UNITY_WEBGL || UNITY_EDITOR) && NET_4_6
  514. //Client.SendBufferSize = 0;
  515. #endif
  516. #region SSL Upgrade
  517. #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
  518. if (CurrentRequest.UseAlternateSSL)
  519. {
  520. var handler = new TlsClientProtocol(Client.GetStream(), new Org.BouncyCastle.Security.SecureRandom());
  521. // http://tools.ietf.org/html/rfc3546#section-3.1
  522. // -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.
  523. // -Literal IPv4 and IPv6 addresses are not permitted in "HostName".
  524. // User-defined list has a higher priority
  525. List<string> hostNames = CurrentRequest.CustomTLSServerNameList;
  526. // If there's no user defined one and the host isn't an IP address, add the default one
  527. if ((hostNames == null || hostNames.Count == 0) && !CurrentRequest.CurrentUri.IsHostIsAnIPAddress())
  528. {
  529. hostNames = new List<string>(1);
  530. hostNames.Add(CurrentRequest.CurrentUri.Host);
  531. }
  532. handler.Connect(new LegacyTlsClient(CurrentRequest.CurrentUri,
  533. CurrentRequest.CustomCertificateVerifyer == null ? new AlwaysValidVerifyer() : CurrentRequest.CustomCertificateVerifyer,
  534. CurrentRequest.CustomClientCredentialsProvider,
  535. hostNames));
  536. Stream = handler.Stream;
  537. }
  538. else
  539. #endif
  540. {
  541. #if !NETFX_CORE && !UNITY_WP8
  542. SslStream sslStream = new SslStream(Client.GetStream(), false, (sender, cert, chain, errors) =>
  543. {
  544. return CurrentRequest.CallCustomCertificationValidator(cert, chain);
  545. });
  546. if (!sslStream.IsAuthenticated)
  547. sslStream.AuthenticateAsClient(CurrentRequest.CurrentUri.Host);
  548. Stream = sslStream;
  549. #else
  550. Stream = Client.GetStream();
  551. #endif
  552. }
  553. #endregion
  554. }
  555. }
  556. }
  557. private bool Receive()
  558. {
  559. SupportedProtocols protocol = CurrentRequest.ProtocolHandler == SupportedProtocols.Unknown ? HTTPProtocolFactory.GetProtocolFromUri(CurrentRequest.CurrentUri) : CurrentRequest.ProtocolHandler;
  560. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  561. HTTPManager.Logger.Verbose("HTTPConnection", string.Format("{0} - Receive - protocol: {1}", this.CurrentRequest.CurrentUri.ToString(), protocol.ToString()));
  562. CurrentRequest.Response = HTTPProtocolFactory.Get(protocol, CurrentRequest, Stream, CurrentRequest.UseStreaming, false);
  563. if (!CurrentRequest.Response.Receive())
  564. {
  565. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  566. HTTPManager.Logger.Verbose("HTTPConnection", string.Format("{0} - Receive - Failed! Response will be null, returning with false.", this.CurrentRequest.CurrentUri.ToString()));
  567. CurrentRequest.Response = null;
  568. return false;
  569. }
  570. if (CurrentRequest.Response.StatusCode == 304
  571. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  572. && !CurrentRequest.DisableCache
  573. #endif
  574. )
  575. {
  576. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  577. if (CurrentRequest.IsRedirected)
  578. {
  579. if (!LoadFromCache(CurrentRequest.RedirectUri))
  580. LoadFromCache(CurrentRequest.Uri);
  581. }
  582. else
  583. LoadFromCache(CurrentRequest.Uri);
  584. #else
  585. return false;
  586. #endif
  587. }
  588. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  589. HTTPManager.Logger.Verbose("HTTPConnection", string.Format("{0} - Receive - Finished Successfully!", this.CurrentRequest.CurrentUri.ToString()));
  590. return true;
  591. }
  592. #endregion
  593. #region Helper Functions
  594. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  595. private bool LoadFromCache(Uri uri)
  596. {
  597. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  598. HTTPManager.Logger.Verbose("HTTPConnection", string.Format("{0} - LoadFromCache for Uri: {1}", this.CurrentRequest.CurrentUri.ToString(), uri.ToString()));
  599. var cacheEntity = HTTPCacheService.GetEntity(uri);
  600. if (cacheEntity == null)
  601. {
  602. HTTPManager.Logger.Warning("HTTPConnection", string.Format("{0} - LoadFromCache for Uri: {1} - Cached entity not found!", this.CurrentRequest.CurrentUri.ToString(), uri.ToString()));
  603. return false;
  604. }
  605. CurrentRequest.Response.CacheFileInfo = cacheEntity;
  606. int bodyLength;
  607. using (var cacheStream = cacheEntity.GetBodyStream(out bodyLength))
  608. {
  609. if (cacheStream == null)
  610. return false;
  611. if (!CurrentRequest.Response.HasHeader("content-length"))
  612. CurrentRequest.Response.Headers.Add("content-length", new List<string>(1) { bodyLength.ToString() });
  613. CurrentRequest.Response.IsFromCache = true;
  614. if (!CurrentRequest.CacheOnly)
  615. CurrentRequest.Response.ReadRaw(cacheStream, bodyLength);
  616. }
  617. return true;
  618. }
  619. private bool TryLoadAllFromCache()
  620. {
  621. if (CurrentRequest.DisableCache || !HTTPCacheService.IsSupported)
  622. return false;
  623. // We will try read the response from the cache, but if something happens we will fallback to the normal way.
  624. try
  625. {
  626. //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,
  627. // MAY return it without validation if it is fresh, and MAY return it after successful validation.
  628. // MAY return it without validation if it is fresh!
  629. if (HTTPCacheService.IsCachedEntityExpiresInTheFuture(CurrentRequest))
  630. {
  631. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  632. HTTPManager.Logger.Verbose("HTTPConnection", string.Format("{0} - TryLoadAllFromCache - whole response loading from cache", this.CurrentRequest.CurrentUri.ToString()));
  633. CurrentRequest.Response = HTTPCacheService.GetFullResponse(CurrentRequest);
  634. if (CurrentRequest.Response != null)
  635. return true;
  636. }
  637. }
  638. catch
  639. {
  640. HTTPCacheService.DeleteEntity(CurrentRequest.CurrentUri);
  641. }
  642. return false;
  643. }
  644. #endif
  645. #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
  646. private void TryStoreInCache()
  647. {
  648. // if UseStreaming && !DisableCache then we already wrote the response to the cache
  649. if (!CurrentRequest.UseStreaming &&
  650. !CurrentRequest.DisableCache &&
  651. CurrentRequest.Response != null &&
  652. HTTPCacheService.IsSupported &&
  653. HTTPCacheService.IsCacheble(CurrentRequest.CurrentUri, CurrentRequest.MethodType, CurrentRequest.Response))
  654. {
  655. if(CurrentRequest.IsRedirected)
  656. HTTPCacheService.Store(CurrentRequest.Uri, CurrentRequest.MethodType, CurrentRequest.Response);
  657. else
  658. HTTPCacheService.Store(CurrentRequest.CurrentUri, CurrentRequest.MethodType, CurrentRequest.Response);
  659. }
  660. }
  661. #endif
  662. private Uri GetRedirectUri(string location)
  663. {
  664. Uri result = null;
  665. try
  666. {
  667. result = new Uri(location);
  668. if (result.IsFile || result.AbsolutePath == location)
  669. result = null;
  670. }
  671. #if !NETFX_CORE
  672. catch (UriFormatException)
  673. #else
  674. catch
  675. #endif
  676. {
  677. // Sometimes the server sends back only the path and query component of the new uri
  678. result = null;
  679. }
  680. if (result == null)
  681. {
  682. var uri = CurrentRequest.Uri;
  683. var builder = new UriBuilder(uri.Scheme, uri.Host, uri.Port, location);
  684. result = builder.Uri;
  685. }
  686. return result;
  687. }
  688. internal override void Abort(HTTPConnectionStates newState)
  689. {
  690. State = newState;
  691. switch(State)
  692. {
  693. case HTTPConnectionStates.TimedOut: TimedOutStart = DateTime.UtcNow; break;
  694. }
  695. if (Stream != null)
  696. {
  697. try
  698. {
  699. Stream.Dispose();
  700. }
  701. catch
  702. { }
  703. }
  704. }
  705. private void Close()
  706. {
  707. KeepAlive = null;
  708. LastProcessedUri = null;
  709. if (Client != null)
  710. {
  711. try
  712. {
  713. Client.Close();
  714. }
  715. catch
  716. {
  717. }
  718. finally
  719. {
  720. Stream = null;
  721. Client = null;
  722. }
  723. }
  724. }
  725. #endregion
  726. protected override void Dispose(bool disposing)
  727. {
  728. Close();
  729. base.Dispose(disposing);
  730. }
  731. }
  732. }