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);
}
}
}