diff --git a/RestSharp.IntegrationTests/NonProtocolExceptionHandlingTests.cs b/RestSharp.IntegrationTests/NonProtocolExceptionHandlingTests.cs new file mode 100644 index 000000000..6a1410a8f --- /dev/null +++ b/RestSharp.IntegrationTests/NonProtocolExceptionHandlingTests.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Xunit; +using RestSharp.IntegrationTests.Helpers; +using System.Net; +using System.Threading; + +namespace RestSharp.IntegrationTests +{ + public class NonProtocolExceptionHandlingTests + { + + /// + /// Success of this test is based largely on the behavior of your current DNS. + /// For example, if you're using OpenDNS this will test will fail; ResponseStatus will be Completed. + /// + [Fact] + public void Handles_Non_Existent_Domain() + { + var client = new RestClient("http://nonexistantdomainimguessing.org"); + var request = new RestRequest("foo"); + var response = client.Execute(request); + + Assert.Equal(ResponseStatus.Error, response.ResponseStatus); + } + + /// + /// Tests that RestSharp properly handles a non-protocol error. + /// Simulates a server timeout, then verifies that the ErrorException + /// property is correctly populated. + /// + [Fact] + public void Handles_Server_Timeout_Error() + { + const string baseUrl = "http://localhost:8080/"; + using (SimpleServer.Create(baseUrl, TimeoutHandler)) + { + var client = new RestClient(baseUrl); + var request = new RestRequest("404"); + var response = client.Execute(request); + + Assert.NotNull(response.ErrorException); + Assert.IsAssignableFrom(typeof(WebException), response.ErrorException); + Assert.Equal(response.ErrorException.Message, "The operation has timed out"); + + } + } + + [Fact] + public void Handles_Server_Timeout_Error_Async() + { + const string baseUrl = "http://localhost:8080/"; + var resetEvent = new ManualResetEvent(false); + + using (SimpleServer.Create(baseUrl, TimeoutHandler)) + { + var client = new RestClient(baseUrl); + var request = new RestRequest("404"); + client.ExecuteAsync(request, response => { + + Assert.NotNull(response.ErrorException); + Assert.IsAssignableFrom(typeof(WebException), response.ErrorException); + Assert.Equal(response.ErrorException.Message, "The operation has timed out"); + resetEvent.Set(); + }); + resetEvent.WaitOne(); + } + } + + /// + /// Tests that RestSharp properly handles a non-protocol error. + /// Simulates a server timeout, then verifies that the ErrorException + /// property is correctly populated. + /// + [Fact] + public void Handles_Server_Timeout_Error_With_Deserializer() + { + const string baseUrl = "http://localhost:8080/"; + using (SimpleServer.Create(baseUrl, TimeoutHandler)) + { + var client = new RestClient(baseUrl); + var request = new RestRequest("404"); + var response = client.Execute(request); + + Assert.Null(response.Data); + Assert.NotNull(response.ErrorException); + Assert.IsAssignableFrom(typeof(WebException), response.ErrorException); + Assert.Equal(response.ErrorException.Message, "The operation has timed out"); + + } + } + + + /// + /// Simulates a long server process that should result in a client timeout + /// + /// + public static void TimeoutHandler(HttpListenerContext context) + { + System.Threading.Thread.Sleep(101000); + } + + + } +} diff --git a/RestSharp.IntegrationTests/RestSharp.IntegrationTests.csproj b/RestSharp.IntegrationTests/RestSharp.IntegrationTests.csproj index 62479f791..3872ba5bb 100644 --- a/RestSharp.IntegrationTests/RestSharp.IntegrationTests.csproj +++ b/RestSharp.IntegrationTests/RestSharp.IntegrationTests.csproj @@ -64,6 +64,7 @@ + diff --git a/RestSharp.IntegrationTests/StatusCodeTests.cs b/RestSharp.IntegrationTests/StatusCodeTests.cs index 33bfe529c..ad7e61762 100644 --- a/RestSharp.IntegrationTests/StatusCodeTests.cs +++ b/RestSharp.IntegrationTests/StatusCodeTests.cs @@ -21,47 +21,48 @@ public void Handles_GET_Request_404_Error() } } + [Fact] + public void Handles_GET_Request_404_Error_With_Body() + { + const string baseUrl = "http://localhost:8080/"; + using (SimpleServer.Create(baseUrl, Handlers.Generic())) + { + var client = new RestClient(baseUrl); + var request = new RestRequest("404WithBody"); + var response = client.Execute(request); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + } + void UrlToStatusCodeHandler(HttpListenerContext obj) { obj.Response.StatusCode = int.Parse(obj.Request.Url.Segments.Last()); } - /// - /// Success of this test is based largely on the behavior of your current DNS. - /// For example, if you're using OpenDNS this will test will fail; ResponseStatus will be Completed. - /// - [Fact] - public void Handles_Non_Existent_Domain() - { - var client = new RestClient("http://nonexistantdomainimguessing.org"); - var request = new RestRequest("foo"); - var response = client.Execute(request); - Assert.Equal(ResponseStatus.Error, response.ResponseStatus); - } + [Fact] + public void Handles_Different_Root_Element_On_Http_Error() + { + const string baseUrl = "http://localhost:8080/"; + using(SimpleServer.Create(baseUrl, Handlers.Generic())) + { + var client = new RestClient(baseUrl); + var request = new RestRequest("error"); + request.RootElement = "Success"; + request.OnBeforeDeserialization = resp => + { + if(resp.StatusCode == HttpStatusCode.BadRequest) + { + request.RootElement = "Error"; + } + }; - [Fact] - public void Handles_Different_Root_Element_On_Error() - { - const string baseUrl = "http://localhost:8080/"; - using(SimpleServer.Create(baseUrl, Handlers.Generic())) - { - var client = new RestClient(baseUrl); - var request = new RestRequest("error"); - request.RootElement = "Success"; - request.OnBeforeDeserialization = resp => - { - if(resp.StatusCode == HttpStatusCode.BadRequest) - { - request.RootElement = "Error"; - } - }; - - var response = client.Execute(request); + var response = client.Execute(request); - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - Assert.Null(response.Data); - } - } + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + Assert.Equal("Not found!", response.Data.Message); + } + } [Fact] public void Handles_Default_Root_Element_On_No_Error() @@ -102,6 +103,21 @@ void error(HttpListenerContext context) "); } + + void errorwithbody(HttpListenerContext context) + { + context.Response.StatusCode = 400; + context.Response.Headers.Add("Content-Type", "application/xml"); + context.Response.OutputStream.WriteStringUtf8( +@" + + + Not found! + +"); + } + + void success(HttpListenerContext context) { context.Response.OutputStream.WriteStringUtf8( diff --git a/RestSharp.Tests/NuSpecUpdateTask.cs b/RestSharp.Tests/NuSpecUpdateTask.cs index fa9db80e3..33b3ba1bd 100644 --- a/RestSharp.Tests/NuSpecUpdateTask.cs +++ b/RestSharp.Tests/NuSpecUpdateTask.cs @@ -67,7 +67,7 @@ public class WhenSpecFileIsValid : BaseNuSpecUpdateTest private string _expectedDescription = "Simple REST and HTTP API Client"; private string _expectedAuthors = "John Sheehan, RestSharp Community"; private string _expectedOwners = "John Sheehan, RestSharp Community"; - private Regex _expectedVersion = new Regex(@"^\d+\.\d+\.\d+(-\w+)$", RegexOptions.Compiled); + private Regex _expectedVersion = new Regex(@"^\d+\.\d+\.\d+(-\w+)?$", RegexOptions.Compiled); protected override void Setup() { diff --git a/RestSharp/Http.Async.cs b/RestSharp/Http.Async.cs index 62e90df58..69f4a6a33 100644 --- a/RestSharp/Http.Async.cs +++ b/RestSharp/Http.Async.cs @@ -276,26 +276,33 @@ private static void GetRawResponseAsync(IAsyncResult result, Action= 400 ) + // return the underlying HTTP response, otherwise assume a + // transport exception (ex: connection timeout) and + // rethrow the exception + + if (ex.Response is HttpWebResponse) + { + raw = ex.Response as HttpWebResponse; + } + else + { + throw ex; + } + } callback(raw); raw.Close(); diff --git a/RestSharp/Http.Sync.cs b/RestSharp/Http.Sync.cs index 6ed9c65bc..3adc1e317 100644 --- a/RestSharp/Http.Sync.cs +++ b/RestSharp/Http.Sync.cs @@ -163,18 +163,24 @@ private HttpResponse GetResponse(HttpWebRequest request) private static HttpWebResponse GetRawResponse(HttpWebRequest request) { - try - { - return (HttpWebResponse)request.GetResponse(); - } - catch (WebException ex) - { - if (ex.Response is HttpWebResponse) - { - return ex.Response as HttpWebResponse; - } - throw; - } + try + { + return (HttpWebResponse)request.GetResponse(); + } + catch (WebException ex) + { + // Check to see if this is an HTTP error or a transport error. + // In cases where an HTTP error occurs ( status code >= 400 ) + // return the underlying HTTP response, otherwise assume a + // transport exception (ex: connection timeout) and + // rethrow the exception + + if (ex.Response is HttpWebResponse) + { + return ex.Response as HttpWebResponse; + } + throw; + } } private void PreparePostData(HttpWebRequest webRequest) diff --git a/RestSharp/IRestResponse.cs b/RestSharp/IRestResponse.cs index 2f452d545..8573401c5 100644 --- a/RestSharp/IRestResponse.cs +++ b/RestSharp/IRestResponse.cs @@ -84,8 +84,9 @@ public interface IRestResponse string ErrorMessage { get; set; } /// - /// The exception thrown during the request, if any + /// Exceptions thrown during the request, if any. /// + /// Will contain only network transport or framework exceptions thrown during the request. HTTP protocol errors are handled by RestSharp and will not appear here. Exception ErrorException { get; set; } } diff --git a/RestSharp/RestClient.cs b/RestSharp/RestClient.cs index 6ba871526..5e00ebb10 100644 --- a/RestSharp/RestClient.cs +++ b/RestSharp/RestClient.cs @@ -469,31 +469,32 @@ private IRestResponse Deserialize(IRestRequest request, IRestResponse raw) { request.OnBeforeDeserialization(raw); - IRestResponse response = new RestResponse(); - try - { - response = raw.toAsyncResponse(); - response.Request = request; - - // Only attempt to deserialize if the request has a chance of containing a valid entry - if (response.StatusCode == HttpStatusCode.OK - || response.StatusCode == HttpStatusCode.Created - || response.StatusCode == HttpStatusCode.NonAuthoritativeInformation) - { - IDeserializer handler = GetHandler(raw.ContentType); - handler.RootElement = request.RootElement; - handler.DateFormat = request.DateFormat; - handler.Namespace = request.XmlNamespace; - - response.Data = handler.Deserialize(raw); - } - } - catch (Exception ex) - { - response.ResponseStatus = ResponseStatus.Error; - response.ErrorMessage = ex.Message; - response.ErrorException = ex; - } + IRestResponse response = new RestResponse(); + try + { + response = raw.toAsyncResponse(); + response.Request = request; + + // Only attempt to deserialize if the request has not errored due + // to a transport or framework exception. HTTP errors should attempt to + // be deserialized + + if (response.ErrorException==null) + { + IDeserializer handler = GetHandler(raw.ContentType); + handler.RootElement = request.RootElement; + handler.DateFormat = request.DateFormat; + handler.Namespace = request.XmlNamespace; + + response.Data = handler.Deserialize(raw); + } + } + catch (Exception ex) + { + response.ResponseStatus = ResponseStatus.Error; + response.ErrorMessage = ex.Message; + response.ErrorException = ex; + } return response; }