123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889 |
- 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
- {
- /// <summary>
- /// https://tools.ietf.org/html/draft-thomson-hybi-http-timeout-03
- /// Test servers: http://tools.ietf.org/ http://nginx.org/
- /// </summary>
- internal sealed class KeepAliveHeader
- {
- /// <summary>
- /// 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.
- /// </summary>
- public TimeSpan TimeOut { get; private set; }
- /// <summary>
- /// 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.
- /// </summary>
- public int MaxRequests { get; private set; }
- public void Parse(List<string> 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
- {
- /// <summary>
- /// The request processed without any special case.
- /// </summary>
- None,
- /// <summary>
- /// 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.
- /// </summary>
- Reconnect,
- /// <summary>
- /// We need an another try with Authorization header set.
- /// </summary>
- Authenticate,
- #if !BESTHTTP_DISABLE_PROXY
- /// <summary>
- /// The proxy needs authentication.
- /// </summary>
- ProxyAuthenticate,
- #endif
- }
- /// <summary>
- /// Represents and manages a connection to a server.
- /// </summary>
- 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");
- // Set the Referer header to the last Uri.
- CurrentRequest.SetHeader("Referer", CurrentRequest.CurrentUri.ToString());
- // Set 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<string> 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<string>(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<string>(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);
- }
- }
- }
|