From d2776a5dc780e5b43986d30701f035d81f397335 Mon Sep 17 00:00:00 2001 From: Devin Rader Date: Wed, 9 Oct 2013 15:56:00 -0400 Subject: [PATCH 1/4] Fixing regex to allow non-prerelease version numbers --- RestSharp.Tests/NuSpecUpdateTask.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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() { From e85c1a4e58d82a572b341658a1e43510db7b59d7 Mon Sep 17 00:00:00 2001 From: Devin Rader Date: Wed, 9 Oct 2013 15:56:56 -0400 Subject: [PATCH 2/4] Updated RestClient to deserialize objects except in cases of non-protocol errors --- .../NonProtocolExceptionHandlingTests.cs | 107 ++++++++++++++++++ .../RestSharp.IntegrationTests.csproj | 1 + RestSharp.IntegrationTests/StatusCodeTests.cs | 46 +++++--- RestSharp/Http.Async.cs | 23 ++-- RestSharp/Http.Sync.cs | 6 + RestSharp/IRestResponse.cs | 3 +- RestSharp/RestClient.cs | 10 +- 7 files changed, 167 insertions(+), 29 deletions(-) create mode 100644 RestSharp.IntegrationTests/NonProtocolExceptionHandlingTests.cs 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..54e261df4 100644 --- a/RestSharp.IntegrationTests/StatusCodeTests.cs +++ b/RestSharp.IntegrationTests/StatusCodeTests.cs @@ -21,26 +21,27 @@ 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_Error() + public void Handles_Different_Root_Element_On_Http_Error() { const string baseUrl = "http://localhost:8080/"; using(SimpleServer.Create(baseUrl, Handlers.Generic())) @@ -59,7 +60,7 @@ public void Handles_Different_Root_Element_On_Error() var response = client.Execute(request); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - Assert.Null(response.Data); + Assert.Equal("Not found!", response.Data.Message); } } @@ -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/Http.Async.cs b/RestSharp/Http.Async.cs index 62e90df58..03fdca5b9 100644 --- a/RestSharp/Http.Async.cs +++ b/RestSharp/Http.Async.cs @@ -287,14 +287,21 @@ 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); diff --git a/RestSharp/Http.Sync.cs b/RestSharp/Http.Sync.cs index 6ed9c65bc..7b4b247e1 100644 --- a/RestSharp/Http.Sync.cs +++ b/RestSharp/Http.Sync.cs @@ -169,6 +169,12 @@ private static HttpWebResponse GetRawResponse(HttpWebRequest request) } 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; 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..484d70294 100644 --- a/RestSharp/RestClient.cs +++ b/RestSharp/RestClient.cs @@ -475,11 +475,11 @@ private IRestResponse Deserialize(IRestRequest request, IRestResponse raw) 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) - { + // 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; From 6d882f0896e8e2b3d2f0dec3fedf4962101e9f21 Mon Sep 17 00:00:00 2001 From: Devin Rader Date: Wed, 9 Oct 2013 16:00:50 -0400 Subject: [PATCH 3/4] Untabbify the things --- RestSharp/Http.Async.cs | 24 ++++++++++++------------ RestSharp/Http.Sync.cs | 24 ++++++++++++------------ RestSharp/RestClient.cs | 40 ++++++++++++++++++++-------------------- 3 files changed, 44 insertions(+), 44 deletions(-) diff --git a/RestSharp/Http.Async.cs b/RestSharp/Http.Async.cs index 03fdca5b9..69f4a6a33 100644 --- a/RestSharp/Http.Async.cs +++ b/RestSharp/Http.Async.cs @@ -276,17 +276,17 @@ private static void GetRawResponseAsync(IAsyncResult result, Action= 400 ) @@ -302,7 +302,7 @@ 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) - { - return ex.Response as HttpWebResponse; - } - throw; - } + if (ex.Response is HttpWebResponse) + { + return ex.Response as HttpWebResponse; + } + throw; + } } private void PreparePostData(HttpWebRequest webRequest) diff --git a/RestSharp/RestClient.cs b/RestSharp/RestClient.cs index 484d70294..05ad95f59 100644 --- a/RestSharp/RestClient.cs +++ b/RestSharp/RestClient.cs @@ -469,31 +469,31 @@ private IRestResponse Deserialize(IRestRequest request, IRestResponse raw) { request.OnBeforeDeserialization(raw); - IRestResponse response = new RestResponse(); - try - { - response = raw.toAsyncResponse(); - response.Request = request; + IRestResponse response = new RestResponse(); + try + { + response = raw.toAsyncResponse(); + response.Request = request; - // Only attempt to deserialize if the request has not errored due + // 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; - } + 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; } From 45b9605d5f4fbc451be1cb4e53f3e1a040f1ba55 Mon Sep 17 00:00:00 2001 From: Devin Rader Date: Fri, 11 Oct 2013 10:30:34 -0400 Subject: [PATCH 4/4] Code formatting fixes --- RestSharp.IntegrationTests/StatusCodeTests.cs | 44 +++++++++---------- RestSharp/RestClient.cs | 3 +- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/RestSharp.IntegrationTests/StatusCodeTests.cs b/RestSharp.IntegrationTests/StatusCodeTests.cs index 54e261df4..ad7e61762 100644 --- a/RestSharp.IntegrationTests/StatusCodeTests.cs +++ b/RestSharp.IntegrationTests/StatusCodeTests.cs @@ -40,29 +40,29 @@ void UrlToStatusCodeHandler(HttpListenerContext obj) obj.Response.StatusCode = int.Parse(obj.Request.Url.Segments.Last()); } - [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"; - } - }; - - var response = client.Execute(request); - - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + [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"; + } + }; + + var response = client.Execute(request); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); Assert.Equal("Not found!", response.Data.Message); - } - } + } + } [Fact] public void Handles_Default_Root_Element_On_No_Error() diff --git a/RestSharp/RestClient.cs b/RestSharp/RestClient.cs index 05ad95f59..5e00ebb10 100644 --- a/RestSharp/RestClient.cs +++ b/RestSharp/RestClient.cs @@ -479,7 +479,8 @@ private IRestResponse Deserialize(IRestRequest request, IRestResponse raw) // to a transport or framework exception. HTTP errors should attempt to // be deserialized - if (response.ErrorException==null) { + if (response.ErrorException==null) + { IDeserializer handler = GetHandler(raw.ContentType); handler.RootElement = request.RootElement; handler.DateFormat = request.DateFormat;