Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Added custom SignalR error handling capability - Fixed #515 #585

Merged
merged 7 commits into from

2 participants

@NTaylorMullen
Collaborator

Fixed #515

Created an ErrorExtensions class to add a "GetError" capability to
Exceptions. GetError returns an error of type SignalRError which
unwraps and pulls unique attributes out of a thrown exception to expose
essential pieces to errors.

NTaylorMullen added some commits
@NTaylorMullen NTaylorMullen Unwrapped thrown exceptions
Developers were getting wrapped exceptions which were not very
distinguishable and required a lot of digging to pull out the base
exception which is the most relevant piece.
cfdd04c
@NTaylorMullen NTaylorMullen Added custom SignalR error handling capability
Fixed #515

Created an ErrorExtensions class to add a "GetError" capability to
Exceptions.  GetError returns an error of type SignalRError which
unwraps and pulls unique attributes out of a thrown exception to expose
essential pieces to errors.
b590bb0
@NTaylorMullen NTaylorMullen Disposed of cloned stream after usage ad6a1f8
SignalR.Client/Infrastructure/ErrorExtensions.cs
((47 lines not shown))
+ return error;
+ }
+
+ private static Stream Clone(Stream source)
+ {
+ Stream cloned = new MemoryStream();
+ byte[] buffer = new byte[2048];// Copy up to 2048 bytes at a time
+ int copiedBytes;// Maintains how many bytes were read
+
+ while ((copiedBytes = source.Read(buffer,0,buffer.Length)) > 0)// Read bytes and copy them into a buffer making sure not to trigger the dispose
+ {
+ cloned.Write(buffer, 0, copiedBytes);// Write the copied bytes from the buffer into the cloned stream
+ }
+
+ // Move the stream pointers back to the original start locations
+ source.Seek(0, 0);
@davidfowl Owner

Check if CanSeek is true on the source.

@NTaylorMullen Collaborator

CanSeek is set to true. If we use ReadToEnd the CanSeek turns to false, however if we read the stream piece by piece with Read it retains the CanSeek value.

@davidfowl Owner

This still bugs me but maybe it works out just fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
SignalR.Client/Infrastructure/ErrorExtensions.cs
((27 lines not shown))
+ Stream originStream = response.GetResponseStream();
+
+ if (originStream.CanRead)
+ {
+ // We need to copy the stream over and not consume it all on "ReadToEnd". If we consumed the entire stream GetError
+ // would only be able to be called once per Exception, otherwise you get inconsistent ResponseBody results.
+ Stream stream = Clone(originStream);
+
+ // Consume our copied stream
+ using (var sr = new StreamReader(stream))
+ {
+ error.ResponseBody = sr.ReadToEnd();
+ }
+
+ //Free up the resources for the cloned stream
+ stream.Dispose();
@davidfowl Owner

remove this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
SignalR.Client/Infrastructure/ErrorExtensions.cs
((37 lines not shown))
+ {
+ error.ResponseBody = sr.ReadToEnd();
+ }
+
+ //Free up the resources for the cloned stream
+ stream.Dispose();
+ }
+ }
+ }
+
+ return error;
+ }
+
+ private static Stream Clone(Stream source)
+ {
+ Stream cloned = new MemoryStream();
@davidfowl Owner

var

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
SignalR.Client/Infrastructure/ErrorExtensions.cs
((41 lines not shown))
+ //Free up the resources for the cloned stream
+ stream.Dispose();
+ }
+ }
+ }
+
+ return error;
+ }
+
+ private static Stream Clone(Stream source)
+ {
+ Stream cloned = new MemoryStream();
+ byte[] buffer = new byte[2048];// Copy up to 2048 bytes at a time
+ int copiedBytes;// Maintains how many bytes were read
+
+ while ((copiedBytes = source.Read(buffer,0,buffer.Length)) > 0)// Read bytes and copy them into a buffer making sure not to trigger the dispose
@davidfowl Owner

formatting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
NTaylorMullen added some commits
@NTaylorMullen NTaylorMullen Addressed PR comments
Made the SignalRError object disposable to assure no memory is leaked
via the HttpWebResponse associated with WebExceptions
7a83108
@NTaylorMullen NTaylorMullen Updated unit test to use new error handling 35f252d
@NTaylorMullen NTaylorMullen Pointed unit test url to new URL
The prior URL was pointing to localhost and if an IIS server was not
running the machine would actively refuse the connection, resulting in a
failure of the test (not a 404).
a2bdf66
@NTaylorMullen NTaylorMullen Modified inline comments c414a16
@davidfowl davidfowl commented on the diff
SignalR.Client/Infrastructure/SignalRError.cs
((11 lines not shown))
+ private HttpWebResponse _response;
+
+ /// <summary>
+ /// Create custom SignalR based error.
+ /// </summary>
+ /// <param name="exception">The exception to unwrap</param>
+ public SignalRError(Exception exception)
+ {
+ Exception = exception;
+ }
+
+ /// <summary>
+ /// Capture the response so we can dispose of it later
+ /// </summary>
+ /// <param name="response">The HttpWebResponse associated with the Exception, assuming its a WebException</param>
+ public void SetResponse(HttpWebResponse response)
@davidfowl Owner

Make this internal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@davidfowl davidfowl merged commit aea3e61 into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 7, 2012
  1. @NTaylorMullen

    Unwrapped thrown exceptions

    NTaylorMullen authored
    Developers were getting wrapped exceptions which were not very
    distinguishable and required a lot of digging to pull out the base
    exception which is the most relevant piece.
Commits on Aug 8, 2012
  1. @NTaylorMullen

    Added custom SignalR error handling capability

    NTaylorMullen authored
    Fixed #515
    
    Created an ErrorExtensions class to add a "GetError" capability to
    Exceptions.  GetError returns an error of type SignalRError which
    unwraps and pulls unique attributes out of a thrown exception to expose
    essential pieces to errors.
Commits on Aug 9, 2012
  1. @NTaylorMullen
Commits on Aug 13, 2012
  1. @NTaylorMullen

    Addressed PR comments

    NTaylorMullen authored
    Made the SignalRError object disposable to assure no memory is leaked
    via the HttpWebResponse associated with WebExceptions
  2. @NTaylorMullen
  3. @NTaylorMullen

    Pointed unit test url to new URL

    NTaylorMullen authored
    The prior URL was pointing to localhost and if an IIS server was not
    running the machine would actively refuse the connection, resulting in a
    failure of the test (not a 404).
  4. @NTaylorMullen

    Modified inline comments

    NTaylorMullen authored
This page is out of date. Refresh to see the latest.
View
6 SignalR.Client.Net35/SignalR.Client.Net35.csproj
@@ -109,6 +109,12 @@
<Compile Include="..\SignalR.Client\Infrastructure\DisposableAction.cs">
<Link>Infrastructure\DisposableAction.cs</Link>
</Compile>
+ <Compile Include="..\SignalR.Client\Infrastructure\ErrorExtensions.cs">
+ <Link>Infrastructure\ErrorExtensions.cs</Link>
+ </Compile>
+ <Compile Include="..\SignalR.Client\Infrastructure\SignalRError.cs">
+ <Link>Infrastructure\SignalRError.cs</Link>
+ </Compile>
<Compile Include="..\SignalR.Client\Infrastructure\ThreadSafeInvoker.cs">
<Link>Infrastructure\ThreadSafeInvoker.cs</Link>
</Compile>
View
2  SignalR.Client/Connection.cs
@@ -229,7 +229,7 @@ private Task Negotiate(IClientTransport transport)
if (task.IsFaulted)
{
Stop();
- tcs.SetException(task.Exception);
+ tcs.SetException(task.Exception.Unwrap());
}
else if (task.IsCanceled)
{
View
72 SignalR.Client/Infrastructure/ErrorExtensions.cs
@@ -0,0 +1,72 @@
+using System;
+using System.IO;
+using System.Net;
+
+namespace SignalR.Client
+{
+ public static class ErrorExtensions
+ {
+ /// <summary>
+ /// Simplifies error recognition by unwrapping complex exceptions.
+ /// </summary>
+ /// <param name="ex">The thrown exception.</param>
+ /// <returns>An unwrapped exception in the form of a SignalRError.</returns>
+ public static SignalRError GetError(this Exception ex)
+ {
+ ex = ex.Unwrap();
+ var wex = ex as WebException;
+
+ var error = new SignalRError(ex);
+
+ if (wex != null && wex.Response != null)
+ {
+ var response = wex.Response as HttpWebResponse;
+ if (response != null)
+ {
+ error.SetResponse(response);
+ error.StatusCode = response.StatusCode;
+ Stream originStream = response.GetResponseStream();
+
+ if (originStream.CanRead)
+ {
+ // We need to copy the stream over and not consume it all on "ReadToEnd". If we consumed the entire stream GetError
+ // would only be able to be called once per Exception, otherwise you get inconsistent ResponseBody results.
+ Stream stream = Clone(originStream);
+
+ // Consume our copied stream
+ using (var sr = new StreamReader(stream))
+ {
+ error.ResponseBody = sr.ReadToEnd();
+ }
+ }
+ }
+ }
+
+ return error;
+ }
+
+ private static Stream Clone(Stream source)
+ {
+ var cloned = new MemoryStream();
+ byte[] buffer = new byte[2048];// Copy up to 2048 bytes at a time
+ int copiedBytes;// Maintains how many bytes were read
+
+ // Read bytes and copy them into a buffer making sure not to trigger the dispose
+ while ((copiedBytes = source.Read(buffer, 0, buffer.Length)) > 0)
+ {
+ // Write the copied bytes from the buffer into the cloned stream
+ cloned.Write(buffer, 0, copiedBytes);
+ }
+
+ // Move the stream pointers back to the original start locations
+ if (source.CanSeek)
+ {
+ source.Seek(0, 0);
+ }
+
+ cloned.Seek(0, 0);
+
+ return cloned;
+ }
+ }
+}
View
66 SignalR.Client/Infrastructure/SignalRError.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Net;
+
+namespace SignalR.Client
+{
+ /// <summary>
+ /// Represents errors that are thrown by the SignalR client
+ /// </summary>
+ public class SignalRError : IDisposable
+ {
+ private HttpWebResponse _response;
+
+ /// <summary>
+ /// Create custom SignalR based error.
+ /// </summary>
+ /// <param name="exception">The exception to unwrap</param>
+ public SignalRError(Exception exception)
+ {
+ Exception = exception;
+ }
+
+ /// <summary>
+ /// Capture the response so we can dispose of it later
+ /// </summary>
+ /// <param name="response">The HttpWebResponse associated with the Exception, assuming its a WebException</param>
+ public void SetResponse(HttpWebResponse response)
@davidfowl Owner

Make this internal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ {
+ _response = response;
+ }
+
+ /// <summary>
+ /// The status code of the error (if it was a WebException)
+ /// </summary>
+ public HttpStatusCode StatusCode { get; set; }
+
+ /// <summary>
+ /// The response body of the error, if it was a WebException and the response is readable
+ /// </summary>
+ public string ResponseBody { get; set; }
+
+ /// <summary>
+ /// The unwrapped underlying exception
+ /// </summary>
+ public Exception Exception { get; private set; }
+
+ /// <summary>
+ /// Allow a SignalRError to be directly written to an output stream
+ /// </summary>
+ /// <returns>Exception error</returns>
+ public override string ToString()
+ {
+ return Exception.ToString();
+ }
+
+ /// <summary>
+ /// Dispose of the response
+ /// </summary>
+ public void Dispose()
+ {
+ if (_response != null)
+ {
+ _response.Close();
+ }
+ }
+ }
+}
View
2  SignalR.Client/SignalR.Client.csproj
@@ -66,6 +66,8 @@
<Compile Include="Hubs\IHubProxy.cs" />
<Compile Include="Hubs\Subscription.cs" />
<Compile Include="IConnection.cs" />
+ <Compile Include="Infrastructure\ErrorExtensions.cs" />
+ <Compile Include="Infrastructure\SignalRError.cs" />
<Compile Include="Infrastructure\ExceptionHelper.cs" />
<Compile Include="Infrastructure\ThreadSafeInvoker.cs" />
<Compile Include="Transports\ServerSentEvents\ChunkBuffer.cs" />
View
40 SignalR.Tests/ConnectionFacts.cs
@@ -1,7 +1,9 @@
-using System;
-using System.Threading;
-using Moq;
+using Moq;
using SignalR.Client.Transports;
+using SignalR.Hosting.Memory;
+using System;
+using System.Net;
+using System.Threading.Tasks;
using Xunit;
namespace SignalR.Client.Tests
@@ -62,6 +64,38 @@ public void FailedStartShouldNotBeActive()
Assert.Equal("Something failed.", ex.Message);
Assert.Equal(ConnectionState.Disconnected, connection.State);
}
+
+ [Fact]
+ public void ThrownWebExceptionShouldBeUnwrapped()
+ {
+ var host = new MemoryHost();
+ host.MapConnection<MyBadConnection>("/ErrorsAreFun");
+
+ var connection = new Client.Connection("http://test/ErrorsAreFun");
+
+ // Expecting 404
+ var aggEx = Assert.Throws<AggregateException>(() => connection.Start(host).Wait());
+
+ connection.Stop();
+
+ using (var ser = aggEx.GetError())
+ {
+ Assert.Equal(ser.StatusCode, HttpStatusCode.NotFound);
+ Assert.NotNull(ser.ResponseBody);
+ Assert.NotNull(ser.Exception);
+ }
+ }
+
+ public class MyBadConnection : PersistentConnection
+ {
+ protected override Task OnConnectedAsync(IRequest request, string connectionId)
+ {
+ // Should throw 404
+ using (HttpWebRequest.Create("http://www.microsoft.com/mairyhadalittlelambbut_shelikedhertwinkling_littlestar_better").GetResponse()) { }
+
+ return base.OnConnectedAsync(request, connectionId);
+ }
+ }
}
}
}
View
22 samples/SignalR.Client.Samples/Program.cs
@@ -1,11 +1,10 @@
using System;
-using System.Threading;
-using System.Threading.Tasks;
using SignalR.Client.Hubs;
#if !NET35
using SignalR.Hosting.Memory;
#endif
-using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
namespace SignalR.Client.Samples
{
@@ -85,7 +84,10 @@ private static void RunDemoHub(HubConnection hubConnection)
demo.Invoke("multipleCalls").ContinueWith(task =>
{
- Console.WriteLine(task.Exception);
+ using (var error = task.Exception.GetError())
+ {
+ Console.WriteLine(error);
+ }
}, TaskContinuationOptions.OnlyOnFaulted);
@@ -118,7 +120,10 @@ private static void RunStreamingSample()
connection.Error += e =>
{
Console.Error.WriteLine("========ERROR==========");
- Console.Error.WriteLine(e.GetBaseException());
+ using (var error = e.GetError())
+ {
+ Console.Error.WriteLine(error);
+ }
Console.Error.WriteLine("=======================");
};
@@ -154,10 +159,13 @@ private static void RunStreamingSample()
{
task.Wait();
}
- catch(Exception ex)
+ catch (Exception ex)
{
Console.Error.WriteLine("========ERROR==========");
- Console.Error.WriteLine(ex.GetBaseException());
+ using (var error = ex.GetError())
+ {
+ Console.Error.WriteLine(error);
+ }
Console.Error.WriteLine("=======================");
return;
}
Something went wrong with that request. Please try again.