Skip to content

Commit

Permalink
fix #4160 by detecting ASP.NET Core Servers (#4243)
Browse files Browse the repository at this point in the history
  • Loading branch information
analogrelay committed Oct 12, 2018
1 parent 556cacc commit 92f8d53
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 19 deletions.
11 changes: 11 additions & 0 deletions src/Microsoft.AspNet.SignalR.Client/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Microsoft.AspNet.SignalR.Client/Resources.resx
Expand Up @@ -192,4 +192,7 @@
<data name="Error_ErrorFromServer" xml:space="preserve">
<value>Error message received from the server: '{0}'.</value>
</data>
<data name="Error_AspNetCoreServerDetected" xml:space="preserve">
<value>Detected a connection attempt to an ASP.NET Core SignalR Server. This client only supports connecting to an ASP.NET SignalR Server. See https://aka.ms/signalr-core-differences for details.</value>
</data>
</root>
13 changes: 11 additions & 2 deletions src/Microsoft.AspNet.SignalR.Client/Transports/TransportHelper.cs
Expand Up @@ -5,8 +5,9 @@
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Client.Http;
using Newtonsoft.Json;
using Microsoft.AspNet.SignalR.Client.Infrastructure;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Microsoft.AspNet.SignalR.Client.Transports
{
Expand Down Expand Up @@ -38,7 +39,15 @@ public virtual Task<NegotiationResponse> GetNegotiationResponse(IHttpClient http
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_ServerNegotiationFailed));
}
return JsonConvert.DeserializeObject<NegotiationResponse>(raw);
// We need to parse it into a JObject first so that we can check if this is an ASP.NET Core SignalR server
var jobj = JObject.Parse(raw);
if(jobj.Property("availableTransports") != null)
{
// This is ASP.NET Core!
throw new InvalidOperationException(Resources.Error_AspNetCoreServerDetected);
}
return jobj.ToObject<NegotiationResponse>();
});
}

Expand Down
9 changes: 9 additions & 0 deletions src/Microsoft.AspNet.SignalR.JS/jquery.signalR.core.js
Expand Up @@ -26,6 +26,7 @@
errorParsingStartResponse: "Error parsing start response: '{0}'. Stopping the connection.",
invalidStartResponse: "Invalid start response: '{0}'. Stopping the connection.",
protocolIncompatible: "You are using a version of the client that isn't compatible with the server. Client version {0}, server version {1}.",
aspnetCoreSignalrServer: "Detected a connection attempt to an ASP.NET Core SignalR Server. This client only supports connecting to an ASP.NET SignalR Server. See https://aka.ms/signalr-core-differences for details.",
sendFailed: "Send failed.",
parseFailed: "Failed at parsing response: {0}",
longPollFailed: "Long polling request failed.",
Expand Down Expand Up @@ -720,6 +721,14 @@
return;
}

// Check if the server is an ASP.NET Core app
if (res.availableTransports) {
protocolError = signalR._.error(resources.aspnetCoreSignalrServer);
$(connection).triggerHandler(events.onError, [protocolError]);
deferred.reject(protocolError);
return;
}

if (!res.ProtocolVersion || (connection.supportedProtocols.indexOf(res.ProtocolVersion) === -1)) {
protocolError = signalR._.error(signalR._.format(resources.protocolIncompatible, connection.clientProtocol, res.ProtocolVersion));
$(connection).triggerHandler(events.onError, [protocolError]);
Expand Down
Expand Up @@ -133,5 +133,22 @@ testUtilities.module("Core - Negotiate Functional Tests");
};
});

QUnit.asyncTimeoutTest(transport = ": connection fails to start with useful error when connecting to ASP.NET Core", testUtilities.defaultTestTimeout, function (end, assert, testName) {
var connection = testUtilities.createTestConnection(testName, end, assert, { wrapStart: false, url: "/aspnetcore-signalr", ignoreErrors: true });

connection.start()
.done(function () {
assert.fail("should have failed to connect");
end();
})
.catch(function (e) {
assert.equal("Detected a connection attempt to an ASP.NET Core SignalR Server. This client only supports connecting to an ASP.NET SignalR Server. See https://aka.ms/signalr-core-differences for details.", e.message);
end();
});

return function () {
connection.stop();
};
});
});
})($, window);
Expand Up @@ -469,5 +469,27 @@ public async Task ConnectionFunctionsCorrectlyAfterCallingStartMutlipleTimes(Hos
}
}
}

// Negotiation is handled per-transport (even though it uses common code) so we do this for each transport to make sure we didn't miss something
[Theory]
[InlineData(HostType.Memory, TransportType.ServerSentEvents)]
[InlineData(HostType.Memory, TransportType.LongPolling)]
[InlineData(HostType.HttpListener, TransportType.Websockets)]
[InlineData(HostType.HttpListener, TransportType.Auto)]
public async Task ConnectionFailsWithHelpfulErrorWhenAttemptingToConnectToAspNetCoreApp(HostType hostType, TransportType transportType)
{
using (var host = CreateHost(hostType, transportType))
{
host.Initialize();

using (var connection = CreateConnection(host, "/aspnetcore-signalr"))
{
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => connection.Start(host.TransportFactory())).OrTimeout();
Assert.Equal(
"Detected a connection attempt to an ASP.NET Core SignalR Server. This client only supports connecting to an ASP.NET SignalR Server. See https://aka.ms/signalr-core-differences for details.",
ex.Message);
}
}
}
}
}
41 changes: 24 additions & 17 deletions test/Microsoft.AspNet.SignalR.Tests.Common/App_Start/Initializer.cs
Expand Up @@ -132,6 +132,30 @@ public static void ConfigureRoutes(IAppBuilder app, IDependencyResolver resolver
RegisterSignalREndpoints(app, resolver, hubConfig);
}

// Simulated ASP.NET Core SignalR negotiate endpoint
app.Use((context, next) =>
{
if(context.Request.Path.StartsWithSegments(new PathString("/aspnetcore-signalr")))
{
// Send an ASP.NET Core SignalR negotiate response
context.Response.StatusCode = 200;
context.Response.ContentType = "application/json";
using (var writer = new JsonTextWriter(new StreamWriter(context.Response.Body)))
{
writer.WriteStartObject();
writer.WritePropertyName("connectionId");
writer.WriteValue("fakeConnectionId");
writer.WritePropertyName("availableTransports");
writer.WriteStartArray();
writer.WriteEndArray();
writer.WriteEndObject();
}
return Task.CompletedTask;
}
return next();
});

// Redirectors:

// Valid redirect chain
Expand Down Expand Up @@ -403,23 +427,6 @@ private static void RegisterSignalREndpoints(IAppBuilder app, IDependencyResolve
{
map.MapSignalR();
});

// Redirectors:

// Valid redirect chain
// Overload detection doesn't like it when we use this as an extension method
// We *intentionally* use paths that do NOT end in a trailing '/' in some places as the client needs to support both
AppBuilderUseExtensions.Use(app, CreateRedirector("/redirect", "/redirect2"));
AppBuilderUseExtensions.Use(app, CreateRedirector("/redirect2", "/redirect3/"));
AppBuilderUseExtensions.Use(app, CreateRedirector("/redirect3", "/redirect4"));
AppBuilderUseExtensions.Use(app, CreateRedirector("/redirect4", "/signalr/"));

// Looping redirect chain
AppBuilderUseExtensions.Use(app, CreateRedirector("/redirect-loop", "/redirect-loop2"));
AppBuilderUseExtensions.Use(app, CreateRedirector("/redirect-loop2", "/redirect-loop"));

// Wrong protocol version
AppBuilderUseExtensions.Use(app, CreateRedirector("/redirect-old-proto", "/signalr", protocolVersion: "1.5"));
}

private static Func<IOwinContext, Func<Task>, Task> CreateRedirector(string sourcePath, string targetPath, string protocolVersion = null)
Expand Down

0 comments on commit 92f8d53

Please sign in to comment.