Permalink
Browse files

Merge pull request #585 from SignalR/Issue#515

Added custom SignalR error handling capability - Fixed #515
  • Loading branch information...
2 parents 59de15e + c414a16 commit aea3e61b9d628a03fc1032d8012b4a761447307c @davidfowl davidfowl committed Aug 14, 2012
@@ -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>
@@ -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)
{
@@ -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;
+ }
+ }
+}
@@ -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)
+ {
+ _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();
+ }
+ }
+ }
+}
@@ -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" />
@@ -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);
+ }
+ }
}
}
}
@@ -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;
}

0 comments on commit aea3e61

Please sign in to comment.