using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; using BestHTTP.Extensions; using BestHTTP.Authentication; #if (!NETFX_CORE && !UNITY_WP8) || UNITY_EDITOR using System.Net.Security; #endif #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR) using BestHTTP.Caching; #endif #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR) using Org.BouncyCastle.Crypto.Tls; #endif #if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR) using BestHTTP.Cookies; #endif #if NETFX_CORE || BUILD_FOR_WP8 using System.Threading.Tasks; using Windows.Networking.Sockets; using TcpClient = BestHTTP.PlatformSupport.TcpClient.WinRT.TcpClient; //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. #pragma warning disable 4014 #elif UNITY_WP8 && !UNITY_EDITOR using TcpClient = BestHTTP.PlatformSupport.TcpClient.WP8.TcpClient; #else using TcpClient = BestHTTP.PlatformSupport.TcpClient.General.TcpClient; #endif namespace BestHTTP { /// /// https://tools.ietf.org/html/draft-thomson-hybi-http-timeout-03 /// Test servers: http://tools.ietf.org/ http://nginx.org/ /// internal sealed class KeepAliveHeader { /// /// 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. /// public TimeSpan TimeOut { get; private set; } /// /// 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. /// public int MaxRequests { get; private set; } public void Parse(List headerValues) { HeaderParser parser = new HeaderParser(headerValues[0]); HeaderValue value; if (parser.TryGet("timeout", out value) && value.HasValue) { int intValue = 0; if (int.TryParse(value.Value, out intValue)) this.TimeOut = TimeSpan.FromSeconds(intValue); else this.TimeOut = TimeSpan.MaxValue; } if (parser.TryGet("max", out value) && value.HasValue) { int intValue = 0; if (int.TryParse("max", out intValue)) this.MaxRequests = intValue; else this.MaxRequests = int.MaxValue; } } } internal enum RetryCauses { /// /// The request processed without any special case. /// None, /// /// 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. /// Reconnect, /// /// We need an another try with Authorization header set. /// Authenticate, #if !BESTHTTP_DISABLE_PROXY /// /// The proxy needs authentication. /// ProxyAuthenticate, #endif } /// /// Represents and manages a connection to a server. /// internal sealed class HTTPConnection : ConnectionBase { public override bool IsRemovable { get { // Plugin's own connection pooling if (base.IsRemovable) return true; // Overridden keep-alive timeout by a Keep-Alive header if (IsFree && KeepAlive != null && (DateTime.UtcNow - base.LastProcessTime) >= KeepAlive.TimeOut) return true; return false; } } #region Private Properties private TcpClient Client; private Stream Stream; private KeepAliveHeader KeepAlive; #endregion internal HTTPConnection(string serverAddress) :base(serverAddress) {} #region Request Processing Implementation protected override #if NETFX_CORE async #endif void ThreadFunc(object param) { bool alreadyReconnected = false; bool redirected = false; RetryCauses cause = RetryCauses.None; try { #if !BESTHTTP_DISABLE_PROXY if (!HasProxy && CurrentRequest.HasProxy) Proxy = CurrentRequest.Proxy; #endif #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR) // Try load the full response from an already saved cache entity. If the response if (TryLoadAllFromCache()) return; #endif if (Client != null && !Client.IsConnected()) Close(); do // of while (reconnect) { if (cause == RetryCauses.Reconnect) { Close(); #if NETFX_CORE await Task.Delay(100); #else Thread.Sleep(100); #endif } LastProcessedUri = CurrentRequest.CurrentUri; cause = RetryCauses.None; // Connect to the server Connect(); if (State == HTTPConnectionStates.AbortRequested) throw new Exception("AbortRequested"); #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR) // Setup cache control headers before we send out the request if (!CurrentRequest.DisableCache) HTTPCacheService.SetHeaders(CurrentRequest); #endif // Write the request to the stream // sentRequest will be true if the request sent out successfully(no SocketException), so we can try read the response bool sentRequest = false; try { #if !NETFX_CORE Client.NoDelay = CurrentRequest.TryToMinimizeTCPLatency; #endif CurrentRequest.SendOutTo(Stream); sentRequest = true; } catch (Exception ex) { Close(); if (State == HTTPConnectionStates.TimedOut || State == HTTPConnectionStates.AbortRequested) throw new Exception("AbortRequested"); // We will try again only once if (!alreadyReconnected && !CurrentRequest.DisableRetry) { alreadyReconnected = true; cause = RetryCauses.Reconnect; } else // rethrow exception throw ex; } // If sending out the request succeeded, we will try read the response. if (sentRequest) { bool received = Receive(); if (State == HTTPConnectionStates.TimedOut || State == HTTPConnectionStates.AbortRequested) throw new Exception("AbortRequested"); if (!received && !alreadyReconnected && !CurrentRequest.DisableRetry) { alreadyReconnected = true; cause = RetryCauses.Reconnect; } if (CurrentRequest.Response != null) { #if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR) // Try to store cookies before we do anything else, as we may remove the response deleting the cookies as well. if (CurrentRequest.IsCookiesEnabled) CookieJar.Set(CurrentRequest.Response); #endif switch (CurrentRequest.Response.StatusCode) { // Not authorized // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2 case 401: { string authHeader = DigestStore.FindBest(CurrentRequest.Response.GetHeaderValues("www-authenticate")); if (!string.IsNullOrEmpty(authHeader)) { var digest = DigestStore.GetOrCreate(CurrentRequest.CurrentUri); digest.ParseChallange(authHeader); if (CurrentRequest.Credentials != null && digest.IsUriProtected(CurrentRequest.CurrentUri) && (!CurrentRequest.HasHeader("Authorization") || digest.Stale)) cause = RetryCauses.Authenticate; } goto default; } #if !BESTHTTP_DISABLE_PROXY // Proxy authentication required // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.8 case 407: { if (CurrentRequest.HasProxy) { string authHeader = DigestStore.FindBest(CurrentRequest.Response.GetHeaderValues("proxy-authenticate")); if (!string.IsNullOrEmpty(authHeader)) { var digest = DigestStore.GetOrCreate(CurrentRequest.Proxy.Address); digest.ParseChallange(authHeader); if (CurrentRequest.Proxy.Credentials != null && digest.IsUriProtected(CurrentRequest.Proxy.Address) && (!CurrentRequest.HasHeader("Proxy-Authorization") || digest.Stale)) cause = RetryCauses.ProxyAuthenticate; } } goto default; } #endif // Redirected case 301: // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2 case 302: // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.3 case 307: // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.8 case 308: // http://tools.ietf.org/html/rfc7238 { if (CurrentRequest.RedirectCount >= CurrentRequest.MaxRedirects) goto default; CurrentRequest.RedirectCount++; string location = CurrentRequest.Response.GetFirstHeaderValue("location"); if (!string.IsNullOrEmpty(location)) { Uri redirectUri = GetRedirectUri(location); if (HTTPManager.Logger.Level == Logger.Loglevels.All) HTTPManager.Logger.Verbose("HTTPConnection", string.Format("{0} - Redirected to Location: '{1}' redirectUri: '{1}'", this.CurrentRequest.CurrentUri.ToString(), location, redirectUri)); // Let the user to take some control over the redirection if (!CurrentRequest.CallOnBeforeRedirection(redirectUri)) { HTTPManager.Logger.Information("HTTPConnection", "OnBeforeRedirection returned False"); goto default; } // Remove the previously set Host header. CurrentRequest.RemoveHeader("Host"); // SaveLocal the Referer header to the last Uri. CurrentRequest.SetHeader("Referer", CurrentRequest.CurrentUri.ToString()); // SaveLocal the new Uri, the CurrentUri will return this while the IsRedirected property is true CurrentRequest.RedirectUri = redirectUri; // Discard the redirect response, we don't need it any more CurrentRequest.Response = null; redirected = CurrentRequest.IsRedirected = true; } else #if !NETFX_CORE throw new MissingFieldException(string.Format("Got redirect status({0}) without 'location' header!", CurrentRequest.Response.StatusCode.ToString())); #else throw new Exception(string.Format("Got redirect status({0}) without 'location' header!", CurrentRequest.Response.StatusCode.ToString())); #endif goto default; } default: #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR) TryStoreInCache(); #endif break; } // Closing the stream is done manually if (CurrentRequest.Response == null || !CurrentRequest.Response.IsClosedManually) { // If we have a response and the server telling us that it closed the connection after the message sent to us, then // we will close the connection too. bool closeByServer = CurrentRequest.Response == null || CurrentRequest.Response.HasHeaderWithValue("connection", "close"); bool closeByClient = !CurrentRequest.IsKeepAlive; if (closeByServer || closeByClient) Close(); else if (CurrentRequest.Response != null) { var keepAliveheaderValues = CurrentRequest.Response.GetHeaderValues("keep-alive"); if (keepAliveheaderValues != null && keepAliveheaderValues.Count > 0) { if (KeepAlive == null) KeepAlive = new KeepAliveHeader(); KeepAlive.Parse(keepAliveheaderValues); } } } } } } while (cause != RetryCauses.None); } catch(TimeoutException e) { CurrentRequest.Response = null; CurrentRequest.Exception = e; CurrentRequest.State = HTTPRequestStates.ConnectionTimedOut; Close(); } catch (Exception e) { if (CurrentRequest != null) { #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR) if (CurrentRequest.UseStreaming) HTTPCacheService.DeleteEntity(CurrentRequest.CurrentUri); #endif // Something gone bad, Response must be null! CurrentRequest.Response = null; switch (State) { case HTTPConnectionStates.Closed: case HTTPConnectionStates.AbortRequested: CurrentRequest.State = HTTPRequestStates.Aborted; break; case HTTPConnectionStates.TimedOut: CurrentRequest.State = HTTPRequestStates.TimedOut; break; default: CurrentRequest.Exception = e; CurrentRequest.State = HTTPRequestStates.Error; break; } } Close(); } finally { if (CurrentRequest != null) { // Avoid state changes. While we are in this block changing the connection's State, on Unity's main thread // the HTTPManager's OnUpdate will check the connections's State and call functions that can change the inner state of // the object. (Like setting the CurrentRequest to null in function Recycle() causing a NullRef exception) lock (HTTPManager.Locker) { if (CurrentRequest != null && CurrentRequest.Response != null && CurrentRequest.Response.IsUpgraded) State = HTTPConnectionStates.Upgraded; else State = redirected ? HTTPConnectionStates.Redirected : (Client == null ? HTTPConnectionStates.Closed : HTTPConnectionStates.WaitForRecycle); // Change the request's state only when the whole processing finished if (CurrentRequest.State == HTTPRequestStates.Processing && (State == HTTPConnectionStates.Closed || State == HTTPConnectionStates.WaitForRecycle)) { if (CurrentRequest.Response != null) CurrentRequest.State = HTTPRequestStates.Finished; else { CurrentRequest.Exception = new Exception(string.Format("Remote server closed the connection before sending response header! Previous request state: {0}. Connection state: {1}", CurrentRequest.State.ToString(), State.ToString())); CurrentRequest.State = HTTPRequestStates.Error; } } if (CurrentRequest.State == HTTPRequestStates.ConnectionTimedOut) State = HTTPConnectionStates.Closed; LastProcessTime = DateTime.UtcNow; if (OnConnectionRecycled != null) RecycleNow(); } #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR) HTTPCacheService.SaveLibrary(); #endif #if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR) CookieJar.Persist(); #endif } } } private void Connect() { Uri uri = #if !BESTHTTP_DISABLE_PROXY CurrentRequest.HasProxy ? CurrentRequest.Proxy.Address : #endif CurrentRequest.CurrentUri; #region TCP Connection if (Client == null) Client = new TcpClient(); if (!Client.Connected) { Client.ConnectTimeout = CurrentRequest.ConnectTimeout; #if NETFX_CORE || (UNITY_WP8 && !UNITY_EDITOR) Client.UseHTTPSProtocol = #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR) !CurrentRequest.UseAlternateSSL && #endif HTTPProtocolFactory.IsSecureProtocol(uri); #endif if (HTTPManager.Logger.Level == Logger.Loglevels.All) HTTPManager.Logger.Verbose("HTTPConnection", string.Format("'{0}' - Connecting to {1}:{2}", this.CurrentRequest.CurrentUri.ToString(), uri.Host, uri.Port.ToString())); #if !NETFX_CORE && (!UNITY_WEBGL || UNITY_EDITOR) Client.SendBufferSize = HTTPManager.SendBufferSize; Client.ReceiveBufferSize = HTTPManager.ReceiveBufferSize; if (HTTPManager.Logger.Level == Logger.Loglevels.All) 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())); #endif Client.Connect(uri.Host, uri.Port); if (HTTPManager.Logger.Level <= Logger.Loglevels.Information) HTTPManager.Logger.Information("HTTPConnection", "Connected to " + uri.Host + ":" + uri.Port.ToString()); } else if (HTTPManager.Logger.Level <= Logger.Loglevels.Information) HTTPManager.Logger.Information("HTTPConnection", "Already connected to " + uri.Host + ":" + uri.Port.ToString()); #endregion StartTime = DateTime.UtcNow; if (Stream == null) { bool isSecure = HTTPProtocolFactory.IsSecureProtocol(CurrentRequest.CurrentUri); Stream = Client.GetStream(); /*if (Stream.CanTimeout) Stream.ReadTimeout = Stream.WriteTimeout = (int)CurrentRequest.Timeout.TotalMilliseconds;*/ #if !BESTHTTP_DISABLE_PROXY #region Proxy Handling if (HasProxy && (!Proxy.IsTransparent || (isSecure && Proxy.NonTransparentForHTTPS))) { var outStream = new BinaryWriter(new WriteOnlyBufferedStream(Stream, HTTPRequest.UploadChunkSize)); bool retry; do { // If we have to because of a authentication request, we will switch it to true retry = false; string connectStr = string.Format("CONNECT {0}:{1} HTTP/1.1", CurrentRequest.CurrentUri.Host, CurrentRequest.CurrentUri.Port); HTTPManager.Logger.Information("HTTPConnection", "Sending " + connectStr); outStream.SendAsASCII(connectStr); outStream.Write(HTTPRequest.EOL); outStream.SendAsASCII("Proxy-Connection: Keep-Alive"); outStream.Write(HTTPRequest.EOL); outStream.SendAsASCII("Connection: Keep-Alive"); outStream.Write(HTTPRequest.EOL); outStream.SendAsASCII(string.Format("Host: {0}:{1}", CurrentRequest.CurrentUri.Host, CurrentRequest.CurrentUri.Port)); outStream.Write(HTTPRequest.EOL); // Proxy Authentication if (HasProxy && Proxy.Credentials != null) { switch (Proxy.Credentials.Type) { case AuthenticationTypes.Basic: // With Basic authentication we don't want to wait for a challenge, we will send the hash with the first request outStream.Write(string.Format("Proxy-Authorization: {0}", string.Concat("Basic ", Convert.ToBase64String(Encoding.UTF8.GetBytes(Proxy.Credentials.UserName + ":" + Proxy.Credentials.Password)))).GetASCIIBytes()); outStream.Write(HTTPRequest.EOL); break; case AuthenticationTypes.Unknown: case AuthenticationTypes.Digest: var digest = DigestStore.Get(Proxy.Address); if (digest != null) { string authentication = digest.GenerateResponseHeader(CurrentRequest, Proxy.Credentials, true); if (!string.IsNullOrEmpty(authentication)) { string auth = string.Format("Proxy-Authorization: {0}", authentication); if (HTTPManager.Logger.Level <= Logger.Loglevels.Information) HTTPManager.Logger.Information("HTTPConnection", "Sending proxy authorization header: " + auth); outStream.Write(auth.GetASCIIBytes()); outStream.Write(HTTPRequest.EOL); } } break; } } outStream.Write(HTTPRequest.EOL); // Make sure to send all the wrote data to the wire outStream.Flush(); CurrentRequest.ProxyResponse = new HTTPResponse(CurrentRequest, Stream, false, false); // Read back the response of the proxy if (!CurrentRequest.ProxyResponse.Receive(-1, true)) throw new Exception("Connection to the Proxy Server failed!"); if (HTTPManager.Logger.Level <= Logger.Loglevels.Information) HTTPManager.Logger.Information("HTTPConnection", "Proxy returned - status code: " + CurrentRequest.ProxyResponse.StatusCode + " message: " + CurrentRequest.ProxyResponse.Message + " Body: " + CurrentRequest.ProxyResponse.DataAsText); switch(CurrentRequest.ProxyResponse.StatusCode) { // Proxy authentication required // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.8 case 407: { string authHeader = DigestStore.FindBest(CurrentRequest.ProxyResponse.GetHeaderValues("proxy-authenticate")); if (!string.IsNullOrEmpty(authHeader)) { var digest = DigestStore.GetOrCreate(Proxy.Address); digest.ParseChallange(authHeader); if (Proxy.Credentials != null && digest.IsUriProtected(Proxy.Address) && (!CurrentRequest.HasHeader("Proxy-Authorization") || digest.Stale)) retry = true; } break; } default: if (!CurrentRequest.ProxyResponse.IsSuccess) throw new Exception(string.Format("Proxy returned Status Code: \"{0}\", Message: \"{1}\" and Response: {2}", CurrentRequest.ProxyResponse.StatusCode, CurrentRequest.ProxyResponse.Message, CurrentRequest.ProxyResponse.DataAsText)); break; } } while (retry); } #endregion #endif // #if !BESTHTTP_DISABLE_PROXY // We have to use CurrentRequest.CurrentUri here, because uri can be a proxy uri with a different protocol if (isSecure) { // Under the new experimental runtime there's a bug in the Socket.Send implementation that can cause a // connection when the TLS protocol is used. #if !NETFX_CORE && (!UNITY_WEBGL || UNITY_EDITOR) && NET_4_6 //Client.SendBufferSize = 0; #endif #region SSL Upgrade #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR) if (CurrentRequest.UseAlternateSSL) { var handler = new TlsClientProtocol(Client.GetStream(), new Org.BouncyCastle.Security.SecureRandom()); // http://tools.ietf.org/html/rfc3546#section-3.1 // -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. // -Literal IPv4 and IPv6 addresses are not permitted in "HostName". // User-defined list has a higher priority List hostNames = CurrentRequest.CustomTLSServerNameList; // If there's no user defined one and the host isn't an IP address, add the default one if ((hostNames == null || hostNames.Count == 0) && !CurrentRequest.CurrentUri.IsHostIsAnIPAddress()) { hostNames = new List(1); hostNames.Add(CurrentRequest.CurrentUri.Host); } handler.Connect(new LegacyTlsClient(CurrentRequest.CurrentUri, CurrentRequest.CustomCertificateVerifyer == null ? new AlwaysValidVerifyer() : CurrentRequest.CustomCertificateVerifyer, CurrentRequest.CustomClientCredentialsProvider, hostNames)); Stream = handler.Stream; } else #endif { #if !NETFX_CORE && !UNITY_WP8 SslStream sslStream = new SslStream(Client.GetStream(), false, (sender, cert, chain, errors) => { return CurrentRequest.CallCustomCertificationValidator(cert, chain); }); if (!sslStream.IsAuthenticated) sslStream.AuthenticateAsClient(CurrentRequest.CurrentUri.Host); Stream = sslStream; #else Stream = Client.GetStream(); #endif } #endregion } } } private bool Receive() { SupportedProtocols protocol = CurrentRequest.ProtocolHandler == SupportedProtocols.Unknown ? HTTPProtocolFactory.GetProtocolFromUri(CurrentRequest.CurrentUri) : CurrentRequest.ProtocolHandler; if (HTTPManager.Logger.Level == Logger.Loglevels.All) HTTPManager.Logger.Verbose("HTTPConnection", string.Format("{0} - Receive - protocol: {1}", this.CurrentRequest.CurrentUri.ToString(), protocol.ToString())); CurrentRequest.Response = HTTPProtocolFactory.Get(protocol, CurrentRequest, Stream, CurrentRequest.UseStreaming, false); if (!CurrentRequest.Response.Receive()) { if (HTTPManager.Logger.Level == Logger.Loglevels.All) HTTPManager.Logger.Verbose("HTTPConnection", string.Format("{0} - Receive - Failed! Response will be null, returning with false.", this.CurrentRequest.CurrentUri.ToString())); CurrentRequest.Response = null; return false; } if (CurrentRequest.Response.StatusCode == 304 #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR) && !CurrentRequest.DisableCache #endif ) { #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR) if (CurrentRequest.IsRedirected) { if (!LoadFromCache(CurrentRequest.RedirectUri)) LoadFromCache(CurrentRequest.Uri); } else LoadFromCache(CurrentRequest.Uri); #else return false; #endif } if (HTTPManager.Logger.Level == Logger.Loglevels.All) HTTPManager.Logger.Verbose("HTTPConnection", string.Format("{0} - Receive - Finished Successfully!", this.CurrentRequest.CurrentUri.ToString())); return true; } #endregion #region Helper Functions #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR) private bool LoadFromCache(Uri uri) { if (HTTPManager.Logger.Level == Logger.Loglevels.All) HTTPManager.Logger.Verbose("HTTPConnection", string.Format("{0} - LoadFromCache for Uri: {1}", this.CurrentRequest.CurrentUri.ToString(), uri.ToString())); var cacheEntity = HTTPCacheService.GetEntity(uri); if (cacheEntity == null) { HTTPManager.Logger.Warning("HTTPConnection", string.Format("{0} - LoadFromCache for Uri: {1} - Cached entity not found!", this.CurrentRequest.CurrentUri.ToString(), uri.ToString())); return false; } CurrentRequest.Response.CacheFileInfo = cacheEntity; int bodyLength; using (var cacheStream = cacheEntity.GetBodyStream(out bodyLength)) { if (cacheStream == null) return false; if (!CurrentRequest.Response.HasHeader("content-length")) CurrentRequest.Response.Headers.Add("content-length", new List(1) { bodyLength.ToString() }); CurrentRequest.Response.IsFromCache = true; if (!CurrentRequest.CacheOnly) CurrentRequest.Response.ReadRaw(cacheStream, bodyLength); } return true; } private bool TryLoadAllFromCache() { if (CurrentRequest.DisableCache || !HTTPCacheService.IsSupported) return false; // We will try read the response from the cache, but if something happens we will fallback to the normal way. try { //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, // MAY return it without validation if it is fresh, and MAY return it after successful validation. // MAY return it without validation if it is fresh! if (HTTPCacheService.IsCachedEntityExpiresInTheFuture(CurrentRequest)) { if (HTTPManager.Logger.Level == Logger.Loglevels.All) HTTPManager.Logger.Verbose("HTTPConnection", string.Format("{0} - TryLoadAllFromCache - whole response loading from cache", this.CurrentRequest.CurrentUri.ToString())); CurrentRequest.Response = HTTPCacheService.GetFullResponse(CurrentRequest); if (CurrentRequest.Response != null) return true; } } catch { HTTPCacheService.DeleteEntity(CurrentRequest.CurrentUri); } return false; } #endif #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR) private void TryStoreInCache() { // if UseStreaming && !DisableCache then we already wrote the response to the cache if (!CurrentRequest.UseStreaming && !CurrentRequest.DisableCache && CurrentRequest.Response != null && HTTPCacheService.IsSupported && HTTPCacheService.IsCacheble(CurrentRequest.CurrentUri, CurrentRequest.MethodType, CurrentRequest.Response)) { if(CurrentRequest.IsRedirected) HTTPCacheService.Store(CurrentRequest.Uri, CurrentRequest.MethodType, CurrentRequest.Response); else HTTPCacheService.Store(CurrentRequest.CurrentUri, CurrentRequest.MethodType, CurrentRequest.Response); } } #endif private Uri GetRedirectUri(string location) { Uri result = null; try { result = new Uri(location); if (result.IsFile || result.AbsolutePath == location) result = null; } #if !NETFX_CORE catch (UriFormatException) #else catch #endif { // Sometimes the server sends back only the path and query component of the new uri result = null; } if (result == null) { var uri = CurrentRequest.Uri; var builder = new UriBuilder(uri.Scheme, uri.Host, uri.Port, location); result = builder.Uri; } return result; } internal override void Abort(HTTPConnectionStates newState) { State = newState; switch(State) { case HTTPConnectionStates.TimedOut: TimedOutStart = DateTime.UtcNow; break; } if (Stream != null) { try { Stream.Dispose(); } catch { } } } private void Close() { KeepAlive = null; LastProcessedUri = null; if (Client != null) { try { Client.Close(); } catch { } finally { Stream = null; Client = null; } } } #endregion protected override void Dispose(bool disposing) { Close(); base.Dispose(disposing); } } }