Handling exceptions thrown on the server side

Addresses: #62
This commit is contained in:
moozzyk 2016-12-20 14:20:00 -08:00 committed by moozzyk
parent 3a01d6cff1
commit 8022afd3a2
10 changed files with 85 additions and 11 deletions

View File

@ -24,7 +24,7 @@ namespace ChatSample.Hubs
return Task.CompletedTask; return Task.CompletedTask;
} }
public override Task OnDisconnectedAsync() public override Task OnDisconnectedAsync(Exception ex)
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@ -14,7 +14,7 @@ namespace SocketsSample.Hubs
await Clients.All.InvokeAsync("Send", $"{Context.Connection.ConnectionId} joined"); await Clients.All.InvokeAsync("Send", $"{Context.Connection.ConnectionId} joined");
} }
public override async Task OnDisconnectedAsync() public override async Task OnDisconnectedAsync(Exception ex)
{ {
await Clients.All.InvokeAsync("Send", $"{Context.Connection.ConnectionId} left"); await Clients.All.InvokeAsync("Send", $"{Context.Connection.ConnectionId} left");
} }

View File

@ -38,7 +38,7 @@ export class WebSocketTransport implements ITransport {
webSocket.onclose = (event: CloseEvent) => { webSocket.onclose = (event: CloseEvent) => {
// webSocket will be null if the transport did not start successfully // webSocket will be null if the transport did not start successfully
if (thisWebSocketTransport.webSocket && event.wasClean === false) { if (thisWebSocketTransport.webSocket && (event.wasClean === false || event.code !== 1000)) {
if (thisWebSocketTransport.onError) { if (thisWebSocketTransport.onError) {
thisWebSocketTransport.onError(event); thisWebSocketTransport.onError(event);
} }

View File

@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.SignalR.Client
_reader = ReceiveMessages(_readerCts.Token); _reader = ReceiveMessages(_readerCts.Token);
_connection.Output.Writing.ContinueWith( _connection.Output.Writing.ContinueWith(
t => CompletePendingCalls(t.IsFaulted ? t.Exception : null)); t => CompletePendingCalls(t.IsFaulted ? t.Exception.InnerException : null));
} }
// TODO: Client return values/tasks? // TODO: Client return values/tasks?

View File

@ -66,7 +66,7 @@ namespace Microsoft.AspNetCore.SignalR
return TaskCache.CompletedTask; return TaskCache.CompletedTask;
} }
public virtual Task OnDisconnectedAsync() public virtual Task OnDisconnectedAsync(Exception exception)
{ {
return TaskCache.CompletedTask; return TaskCache.CompletedTask;
} }

View File

@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.SignalR
{ {
// TODO: Dispatch from the caller // TODO: Dispatch from the caller
await Task.Yield(); await Task.Yield();
Exception exception = null;
try try
{ {
await _lifetimeManager.OnConnectedAsync(connection); await _lifetimeManager.OnConnectedAsync(connection);
@ -76,6 +76,13 @@ namespace Microsoft.AspNetCore.SignalR
await DispatchMessagesAsync(connection); await DispatchMessagesAsync(connection);
} }
catch (Exception ex)
{
_logger.LogError(0, ex, "Error when processing requests.");
exception = ex;
connection.Channel.Input.Complete(exception);
connection.Channel.Output.Complete(exception);
}
finally finally
{ {
using (var scope = _serviceScopeFactory.CreateScope()) using (var scope = _serviceScopeFactory.CreateScope())
@ -83,7 +90,7 @@ namespace Microsoft.AspNetCore.SignalR
bool created; bool created;
var hub = CreateHub(scope.ServiceProvider, connection, out created); var hub = CreateHub(scope.ServiceProvider, connection, out created);
await hub.OnDisconnectedAsync(); await hub.OnDisconnectedAsync(exception);
if (created) if (created)
{ {
@ -232,7 +239,7 @@ namespace Microsoft.AspNetCore.SignalR
private static bool IsHubMethod(MethodInfo m) private static bool IsHubMethod(MethodInfo m)
{ {
// TODO: Add more checks // TODO: Add more checks
return m.IsPublic; return m.IsPublic && !m.IsSpecialName;
} }
Type IInvocationBinder.GetReturnType(string invocationId) Type IInvocationBinder.GetReturnType(string invocationId)
@ -243,8 +250,11 @@ namespace Microsoft.AspNetCore.SignalR
Type[] IInvocationBinder.GetParameterTypes(string methodName) Type[] IInvocationBinder.GetParameterTypes(string methodName)
{ {
Type[] types; Type[] types;
// TODO: null or throw? if (!_paramTypes.TryGetValue(methodName, out types))
return _paramTypes.TryGetValue(methodName, out types) ? types : null; {
throw new InvalidOperationException($"The hub method '{methodName}' could not be resolved.");
}
return types;
} }
} }
} }

View File

@ -96,6 +96,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
// Shut down the output pipeline and log // Shut down the output pipeline and log
_logger.LogError("Error while polling '{0}': {1}", pollUrl, ex); _logger.LogError("Error while polling '{0}': {1}", pollUrl, ex);
_pipeline.Output.Complete(ex); _pipeline.Output.Complete(ex);
_pipeline.Input.Complete(ex);
} }
} }

View File

@ -104,6 +104,28 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
} }
} }
[Fact]
public async Task ServerClosesConnectionIfHubMethodCannotBeResolved()
{
var loggerFactory = new LoggerFactory();
using (var httpClient = _testServer.CreateClient())
using (var pipelineFactory = new PipelineFactory())
{
var transport = new LongPollingTransport(httpClient, loggerFactory);
using (var connection = await HubConnection.ConnectAsync(new Uri("http://test/hubs"), new JsonNetInvocationAdapter(), transport, httpClient, pipelineFactory, loggerFactory))
{
//TODO: Get rid of this. This is to prevent "No channel" failures due to sends occuring before the first poll.
await Task.Delay(500);
var ex = await Assert.ThrowsAnyAsync<InvalidOperationException>(
async () => await connection.Invoke<Task>("!@#$%"));
Assert.Equal(ex.Message, "The hub method '!@#$%' could not be resolved.");
}
}
}
public void Dispose() public void Dispose()
{ {
_testServer.Dispose(); _testServer.Dispose();

View File

@ -4,9 +4,12 @@
using System; using System;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Security.Claims; using System.Security.Claims;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Sockets; using Microsoft.AspNetCore.Sockets;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Moq;
using Moq.Protected;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.SignalR.Tests namespace Microsoft.AspNetCore.SignalR.Tests
@ -18,7 +21,6 @@ namespace Microsoft.AspNetCore.SignalR.Tests
{ {
var trackDispose = new TrackDispose(); var trackDispose = new TrackDispose();
var serviceProvider = CreateServiceProvider(s => s.AddSingleton(trackDispose)); var serviceProvider = CreateServiceProvider(s => s.AddSingleton(trackDispose));
var endPoint = serviceProvider.GetService<HubEndPoint<TestHub>>(); var endPoint = serviceProvider.GetService<HubEndPoint<TestHub>>();
using (var connectionWrapper = new ConnectionWrapper()) using (var connectionWrapper = new ConnectionWrapper())
@ -36,6 +38,44 @@ namespace Microsoft.AspNetCore.SignalR.Tests
} }
} }
[Fact]
public async Task OnDisconnectedCalledWithExceptionIfHubMethodNotFound()
{
var hub = Mock.Of<Hub>();
var endPointType = GetEndPointType(hub.GetType());
var serviceProvider = CreateServiceProvider(s =>
{
s.AddSingleton(endPointType);
s.AddTransient(hub.GetType(), sp => hub);
});
dynamic endPoint = serviceProvider.GetService(endPointType);
using (var connectionWrapper = new ConnectionWrapper())
{
var endPointTask = endPoint.OnConnectedAsync(connectionWrapper.Connection);
await connectionWrapper.HttpConnection.Input.ReadingStarted;
var buffer = connectionWrapper.HttpConnection.Input.Alloc();
buffer.Write(Encoding.UTF8.GetBytes("0xdeadbeef"));
await buffer.FlushAsync();
connectionWrapper.Connection.Channel.Dispose();
await endPointTask;
Mock.Get(hub).Verify(h => h.OnDisconnectedAsync(It.IsNotNull<Exception>()), Times.Once());
}
}
private static Type GetEndPointType(Type hubType)
{
var endPointType = typeof(HubEndPoint<>);
return endPointType.MakeGenericType(hubType);
}
private class TestHub : Hub private class TestHub : Hub
{ {
private TrackDispose _trackDispose; private TrackDispose _trackDispose;

View File

@ -10,6 +10,7 @@
"Microsoft.AspNetCore.SignalR": "1.0.0-*", "Microsoft.AspNetCore.SignalR": "1.0.0-*",
"Microsoft.Extensions.DependencyInjection": "1.2.0-*", "Microsoft.Extensions.DependencyInjection": "1.2.0-*",
"Microsoft.Extensions.Logging": "1.2.0-*", "Microsoft.Extensions.Logging": "1.2.0-*",
"Moq": "4.6.36-*",
"xunit": "2.2.0-*" "xunit": "2.2.0-*"
}, },
"frameworks": { "frameworks": {