Skip to content

Instantly share code, notes, and snippets.

@kendallmiller
Created December 29, 2015 23:22
Show Gist options
  • Select an option

  • Save kendallmiller/a1e780023c072d517722 to your computer and use it in GitHub Desktop.

Select an option

Save kendallmiller/a1e780023c072d517722 to your computer and use it in GitHub Desktop.
WebClient Execute Request Wrapper
/// <summary>
/// Synchronously execute the provided request.
/// </summary>
/// <param name="newRequest"></param>
/// <param name="maxRetries">The maximum number of times to retry the connection. Use -1 to retry indefinitely.</param>
public void ExecuteRequest(IWebRequest newRequest, int maxRetries)
{
if (newRequest == null)
throw new ArgumentNullException("newRequest");
if ((newRequest.RequiresAuthentication) && (m_AuthenticationProvider == null))
throw new ArgumentException("The request requires authentication and no authentication provider is available.", "newRequest");
EnsureConnectionInitialized();
m_Cancel = false;
bool requestComplete = false;
bool lastCallWasAuthentication = false; //indicates if we just tried an auth, so another 401 means no go.
int errorCount = 0;
try
{
while ((m_Cancel == false)
&& (requestComplete == false)
&& ((maxRetries < 0) || (errorCount <= maxRetries)))
{
try
{
if (m_ConnectionState == ChannelConnectionState.Disconnected)
SetConnectionState(ChannelConnectionState.Connecting);
else if (m_ConnectionState == ChannelConnectionState.Connected)
SetConnectionState(ChannelConnectionState.TransferingData);
if((newRequest.RequiresAuthentication) && (m_AuthenticationProvider.IsAuthenticated == false))
{
//no point in waiting for the failure, go ahead and authenticate now.
Authenticate();
}
//Now, because we know we're not MT-safe we can do this "pass around"
m_RequestSupportsAuthentication = newRequest.SupportsAuthentication;
newRequest.ProcessRequest(this);
SetConnectionState(ChannelConnectionState.Connected);
requestComplete = true;
}
catch (WebException ex)
{
//but WHY did we fail?
switch (ex.Status)
{
//find the terminal failures...
case WebExceptionStatus.MessageLengthLimitExceeded:
throw;
case WebExceptionStatus.ProtocolError:
//get the inner web response to figure out exactly what the deal is.
HttpWebResponse response = (HttpWebResponse)ex.Response;
if (response.StatusCode == HttpStatusCode.NotFound)
{
//throw our dedicated file not found exception.
throw new WebChannelFileNotFoundException("File not found", ex, response.ResponseUri);
}
if (response.StatusCode == HttpStatusCode.Unauthorized) //it's an auth error
{
if ((m_AuthenticationProvider != null) && newRequest.SupportsAuthentication //we can do an auth
&& (lastCallWasAuthentication == false)) //and we didn't just try to do an auth..
{
if (EnableLogging) m_Logger.Write(LogMessageSeverity.Information, ex, true, LogCategory, "Attempting to authenticate to server", "Because we got an HTTP 401 error from the server we're going to attempt to authenticate with our server credentials and try again. Status Description:\r\n{0}", response.StatusDescription);
lastCallWasAuthentication = true;
Authenticate();
}
else
{
//create a new exception that tells our caller it's an authorization problem.
throw new WebChannelAuthorizationException("Username or password not valid", ex, response.ResponseUri);
}
}
else if ((response.StatusCode == HttpStatusCode.MethodNotAllowed) && (m_UseCompatibilityMethods == false))
{
//most likely we did a delete or put and the caller doesn't support that, enable compatibility methods.
m_UseCompatibilityMethods = true;
SetUseCompatiblilityMethodsOverride(m_HostName, m_UseCompatibilityMethods); //so we don't have to repeatedly run into this for this server
if (EnableLogging) m_Logger.Write(LogMessageSeverity.Information, ex, true, LogCategory, "Switching to http method compatibility mode", "Because we got an HTTP 405 error from the server we're going to turn on Http method compatibility translation and try again. Status Description:\r\n{0}", response.StatusDescription);
}
else
{
//we don't have a specific error for this, just throw the underlying exception.
throw;
}
break;
default:
//assume a retryable connection error
if (EnableLogging) m_Logger.Write(LogMessageSeverity.Warning, ex, true, LogCategory, "Connection error while making web channel request", "We received a communication exception while executing the current request on our channel. Since it isn't an authentication exception we're going to retry the request.\r\nRequest:{0}\r\nError Count:{1}\r\nException:\r\n{0}", newRequest, errorCount, ex.Message);
SetConnectionState(ChannelConnectionState.Disconnected);
errorCount++;
lastCallWasAuthentication = false; //so if we get another request to authenticate we'll give it a shot.
if (CanRetry(maxRetries, errorCount))
{
if (errorCount > 1)
{
//back down our rate.
SleepForConnection();
}
}
else
{
//we can't retry any more - throw an exception
if ((ex.Status == WebExceptionStatus.ConnectFailure) || (ex.Status == WebExceptionStatus.NameResolutionFailure))
{
//throw a specific exception here.
throw new WebChannelConnectFailureException("Unable to connect to the destination server", ex, (ex.Response == null) ? null : ex.Response.ResponseUri);
}
else if (ex.Status == WebExceptionStatus.ConnectionClosed)
{
throw new WebChannelConnectFailureException("Server refused connection", ex, (ex.Response == null) ? null : ex.Response.ResponseUri);
}
else
{
throw new WebChannelException(ex.Message, ex, (ex.Response == null) ? null : ex.Response.ResponseUri);
}
}
break;
}
}
}
}
catch (Exception ex)
{
//this is just here so we can log exceptions that we're letting get thrown.
if (EnableLogging) m_Logger.Write(LogMessageSeverity.Verbose, ex, true, LogCategory, ex.Message, "While executing a web channel request an exception was thrown, which will be thrown to our caller.\r\nRequest: {0}\r\n", newRequest);
throw;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment