void ThreadFunc(object param) { bool alreadyReconnected = false; bool redirected = false; RetryCauses cause = RetryCauses.None; #if LOCK_ON_FILE object uriLock = null; #endif #if UNITY_WEBPLAYER // Right now, no caching supported in the webplayer if (!CurrentRequest.DisableCache) CurrentRequest.DisableCache = true; #endif try { if (!HasProxy && CurrentRequest.HasProxy) Proxy = CurrentRequest.Proxy; // Lock only if we will use the cached entity. #if LOCK_ON_FILE if (!CurrentRequest.DisableCache) Monitor.Enter(uriLock = HTTPCacheFileLock.Acquire(CurrentRequest.CurrentUri)); #endif // Try load the full response from an already saved cache entity. If the response if (TryLoadAllFromCache()) return; 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 } cause = RetryCauses.None; // Connect to the server #if NETFX_CORE await #endif Connect(); if (State == HTTPConnectionStates.AbortRequested) throw new Exception("AbortRequested"); lock(HTTPManager.Locker) StartTime = DateTime.UtcNow; // Setup cache control headers before we send out the request if (!CurrentRequest.DisableCache) HTTPCacheService.SetHeaders(CurrentRequest); // 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 = CurrentRequest.SendOutTo(Stream); // sentRequest only true if there are no exceptions during CurrentRequest.SendOutTo. if (!sentRequest) { Close(); // We will try again only once if (!alreadyReconnected) { alreadyReconnected = true; cause = RetryCauses.Reconnect; } } // If sending out the request succeded, we will try read the response. if (sentRequest) { bool received = Receive(); if (!received && !alreadyReconnected) { alreadyReconnected = true; cause = RetryCauses.Reconnect; } if (CurrentRequest.Response != null) { switch (CurrentRequest.Response.StatusCode) { // Not authorized // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2 case 401: { string authHeader = CurrentRequest.Response.GetFirstHeaderValue("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; } // Proxy authentication required // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.8 case 407: { if (CurrentRequest.HasProxy) { string authHeader = CurrentRequest.Response.GetFirstHeaderValue("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; } // 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)) { // 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 = GetRedirectUri(location); // 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 (CurrentRequest.IsCookiesEnabled) CookieJar.Set(CurrentRequest.Response); TryStoreInCache(); break; } // If we have a response and the server telling us that it closed the connection after the message sent to us, then // we will colse the connection too. if (CurrentRequest.Response == null || CurrentRequest.Response.HasHeaderWithValue("connection", "close") || CurrentRequest.UseAlternateSSL) Close(); } } } while (cause != RetryCauses.None); } catch(TimeoutException e) { CurrentRequest.Response = null; CurrentRequest.Exception = e; CurrentRequest.State = HTTPRequestStates.ConnectionTimedOut; } catch (Exception e) { if (CurrentRequest.UseStreaming) HTTPCacheService.DeleteEntity(CurrentRequest.CurrentUri); // Something gone bad, Response must be null! CurrentRequest.Response = null; switch(State) { case HTTPConnectionStates.AbortRequested: CurrentRequest.Exception = e; CurrentRequest.State = HTTPRequestStates.Error; break; case HTTPConnectionStates.TimedOut: CurrentRequest.State = HTTPRequestStates.TimedOut; break; default: CurrentRequest.State = HTTPRequestStates.Aborted; break; } Close(); } finally { #if LOCK_ON_FILE if (!CurrentRequest.DisableCache && uriLock != null) Monitor.Exit(uriLock); #endif // 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)) CurrentRequest.State = HTTPRequestStates.Finished; if (CurrentRequest.State == HTTPRequestStates.ConnectionTimedOut) State = HTTPConnectionStates.Closed; LastProcessTime = DateTime.UtcNow; } HTTPCacheService.SaveLibrary(); CookieJar.Persist(); } }
void ThreadFunc(object param) { bool alreadyReconnected = false; bool redirected = false; RetryCauses cause = RetryCauses.None; #if LOCK_ON_FILE object uriLock = null; #endif #if UNITY_WEBPLAYER // Right now, no caching supported in the webplayer if (!CurrentRequest.DisableCache) { CurrentRequest.DisableCache = true; } #endif try { if (!HasProxy && CurrentRequest.HasProxy) { Proxy = CurrentRequest.Proxy; } // Lock only if we will use the cached entity. #if LOCK_ON_FILE if (!CurrentRequest.DisableCache) { Monitor.Enter(uriLock = HTTPCacheFileLock.Acquire(CurrentRequest.CurrentUri)); } #endif // Try load the full response from an already saved cache entity. If the response if (TryLoadAllFromCache()) { return; } 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 } cause = RetryCauses.None; // Connect to the server #if NETFX_CORE await #endif Connect(); if (State == HTTPConnectionStates.AbortRequested) { throw new Exception("AbortRequested"); } lock (HTTPManager.Locker) StartTime = DateTime.UtcNow; // Setup cache control headers before we send out the request if (!CurrentRequest.DisableCache) { HTTPCacheService.SetHeaders(CurrentRequest); } // 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 = CurrentRequest.SendOutTo(Stream); // sentRequest only true if there are no exceptions during CurrentRequest.SendOutTo. if (!sentRequest) { Close(); // We will try again only once if (!alreadyReconnected) { alreadyReconnected = true; cause = RetryCauses.Reconnect; } } // If sending out the request succeded, we will try read the response. if (sentRequest) { bool received = Receive(); if (!received && !alreadyReconnected) { alreadyReconnected = true; cause = RetryCauses.Reconnect; } if (CurrentRequest.Response != null) { switch (CurrentRequest.Response.StatusCode) { // Not authorized // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2 case 401: { string authHeader = CurrentRequest.Response.GetFirstHeaderValue("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; } // Proxy authentication required // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.8 case 407: { if (CurrentRequest.HasProxy) { string authHeader = CurrentRequest.Response.GetFirstHeaderValue("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; } // 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)) { // 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 = GetRedirectUri(location); // 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 (CurrentRequest.IsCookiesEnabled) { CookieJar.Set(CurrentRequest.Response); } TryStoreInCache(); break; } // If we have a response and the server telling us that it closed the connection after the message sent to us, then // we will colse the connection too. if (CurrentRequest.Response == null || CurrentRequest.Response.HasHeaderWithValue("connection", "close") || CurrentRequest.UseAlternateSSL) { Close(); } } } } while (cause != RetryCauses.None); } catch (TimeoutException e) { CurrentRequest.Response = null; CurrentRequest.Exception = e; CurrentRequest.State = HTTPRequestStates.ConnectionTimedOut; } catch (Exception e) { if (CurrentRequest.UseStreaming) { HTTPCacheService.DeleteEntity(CurrentRequest.CurrentUri); } // Something gone bad, Response must be null! CurrentRequest.Response = null; switch (State) { case HTTPConnectionStates.AbortRequested: CurrentRequest.Exception = e; CurrentRequest.State = HTTPRequestStates.Error; break; case HTTPConnectionStates.TimedOut: CurrentRequest.State = HTTPRequestStates.TimedOut; break; default: CurrentRequest.State = HTTPRequestStates.Aborted; break; } Close(); } finally { #if LOCK_ON_FILE if (!CurrentRequest.DisableCache && uriLock != null) { Monitor.Exit(uriLock); } #endif // 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)) { CurrentRequest.State = HTTPRequestStates.Finished; } if (CurrentRequest.State == HTTPRequestStates.ConnectionTimedOut) { State = HTTPConnectionStates.Closed; } LastProcessTime = DateTime.UtcNow; } HTTPCacheService.SaveLibrary(); CookieJar.Persist(); } }