Collect transport fallback errors to provide better errors (#4293)
This commit is contained in:
parent
a51ff6e6ad
commit
c487f019f5
|
|
@ -2622,14 +2622,12 @@
|
|||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
|
@ -2644,20 +2642,17 @@
|
|||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
|
|
@ -2774,8 +2769,7 @@
|
|||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
|
|
@ -2787,7 +2781,6 @@
|
|||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
|
|
@ -2802,7 +2795,6 @@
|
|||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
|
|
@ -2810,14 +2802,12 @@
|
|||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.2.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.1",
|
||||
"yallist": "^3.0.0"
|
||||
|
|
@ -2836,7 +2826,6 @@
|
|||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
|
|
@ -2917,8 +2906,7 @@
|
|||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
|
|
@ -2930,7 +2918,6 @@
|
|||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
|
|
@ -3052,7 +3039,6 @@
|
|||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
|
|
|
|||
|
|
@ -269,29 +269,34 @@ export class HttpConnection implements IConnection {
|
|||
return;
|
||||
}
|
||||
|
||||
const transportExceptions: any[] = [];
|
||||
const transports = negotiateResponse.availableTransports || [];
|
||||
for (const endpoint of transports) {
|
||||
this.connectionState = ConnectionState.Connecting;
|
||||
const transport = this.resolveTransport(endpoint, requestedTransport, requestedTransferFormat);
|
||||
if (typeof transport === "number") {
|
||||
this.transport = this.constructTransport(transport);
|
||||
if (!negotiateResponse.connectionId) {
|
||||
negotiateResponse = await this.getNegotiationResponse(url);
|
||||
connectUrl = this.createConnectUrl(url, negotiateResponse.connectionId);
|
||||
}
|
||||
try {
|
||||
try {
|
||||
this.connectionState = ConnectionState.Connecting;
|
||||
const transport = this.resolveTransport(endpoint, requestedTransport, requestedTransferFormat);
|
||||
if (typeof transport === "number") {
|
||||
this.transport = this.constructTransport(transport);
|
||||
if (!negotiateResponse.connectionId) {
|
||||
negotiateResponse = await this.getNegotiationResponse(url);
|
||||
connectUrl = this.createConnectUrl(url, negotiateResponse.connectionId);
|
||||
}
|
||||
await this.transport!.connect(connectUrl, requestedTransferFormat);
|
||||
this.changeState(ConnectionState.Connecting, ConnectionState.Connected);
|
||||
return;
|
||||
} catch (ex) {
|
||||
this.logger.log(LogLevel.Error, `Failed to start the transport '${HttpTransportType[transport]}': ${ex}`);
|
||||
this.connectionState = ConnectionState.Disconnected;
|
||||
negotiateResponse.connectionId = undefined;
|
||||
}
|
||||
} catch (ex) {
|
||||
this.logger.log(LogLevel.Error, `Failed to start the transport '${endpoint.transport}': ${ex}`);
|
||||
this.connectionState = ConnectionState.Disconnected;
|
||||
negotiateResponse.connectionId = undefined;
|
||||
transportExceptions.push(`${endpoint.transport} failed: ${ex}`);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Unable to initialize any of the available transports.");
|
||||
if (transportExceptions.length > 0) {
|
||||
throw new Error(`Unable to connect to the server with any of the available transports. ${transportExceptions.join(" ")}`);
|
||||
}
|
||||
throw new Error("None of the transports supported by the client are supported by the server.");
|
||||
}
|
||||
|
||||
private constructTransport(transport: HttpTransportType) {
|
||||
|
|
@ -324,15 +329,18 @@ export class HttpConnection implements IConnection {
|
|||
if ((transport === HttpTransportType.WebSockets && !this.options.WebSocket) ||
|
||||
(transport === HttpTransportType.ServerSentEvents && !this.options.EventSource)) {
|
||||
this.logger.log(LogLevel.Debug, `Skipping transport '${HttpTransportType[transport]}' because it is not supported in your environment.'`);
|
||||
throw new Error(`'${HttpTransportType[transport]}' is not supported in your environment.`);
|
||||
} else {
|
||||
this.logger.log(LogLevel.Debug, `Selecting transport '${HttpTransportType[transport]}'.`);
|
||||
return transport;
|
||||
}
|
||||
} else {
|
||||
this.logger.log(LogLevel.Debug, `Skipping transport '${HttpTransportType[transport]}' because it does not support the requested transfer format '${TransferFormat[requestedTransferFormat]}'.`);
|
||||
throw new Error(`'${HttpTransportType[transport]}' does not support ${TransferFormat[requestedTransferFormat]}.`);
|
||||
}
|
||||
} else {
|
||||
this.logger.log(LogLevel.Debug, `Skipping transport '${HttpTransportType[transport]}' because it was disabled by the client.`);
|
||||
throw new Error(`'${HttpTransportType[transport]}' is disabled by the client.`);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -171,9 +171,9 @@ describe("HttpConnection", () => {
|
|||
|
||||
await expect(connection.start(TransferFormat.Text))
|
||||
.rejects
|
||||
.toThrow("Unable to initialize any of the available transports.");
|
||||
.toThrow("None of the transports supported by the client are supported by the server.");
|
||||
},
|
||||
"Failed to start the connection: Error: Unable to initialize any of the available transports.");
|
||||
"Failed to start the connection: Error: None of the transports supported by the client are supported by the server.");
|
||||
});
|
||||
|
||||
it("preserves user's query string", async () => {
|
||||
|
|
@ -273,9 +273,11 @@ describe("HttpConnection", () => {
|
|||
|
||||
await expect(connection.start(TransferFormat.Text))
|
||||
.rejects
|
||||
.toThrow("Unable to initialize any of the available transports.");
|
||||
.toThrow(`Unable to connect to the server with any of the available transports. ${negotiateResponse.availableTransports[0].transport} failed: Error: '${negotiateResponse.availableTransports[0].transport}' is disabled by the client.` +
|
||||
` ${negotiateResponse.availableTransports[1].transport} failed: Error: '${negotiateResponse.availableTransports[1].transport}' is disabled by the client.`);
|
||||
},
|
||||
"Failed to start the connection: Error: Unable to initialize any of the available transports.");
|
||||
/Failed to start the connection: Error: Unable to connect to the server with any of the available transports. [a-zA-Z]+\b failed: Error: '[a-zA-Z]+\b' is disabled by the client. [a-zA-Z]+\b failed: Error: '[a-zA-Z]+\b' is disabled by the client./,
|
||||
/Failed to start the transport '[a-zA-Z]+': Error: '[a-zA-Z]+' is disabled by the client./);
|
||||
});
|
||||
|
||||
it(`cannot be started if server's only transport (${HttpTransportType[requestedTransport]}) is masked out by the transport option`, async () => {
|
||||
|
|
@ -311,10 +313,11 @@ describe("HttpConnection", () => {
|
|||
await connection.start(TransferFormat.Text);
|
||||
fail("Expected connection.start to throw!");
|
||||
} catch (e) {
|
||||
expect(e.message).toBe("Unable to initialize any of the available transports.");
|
||||
expect(e.message).toBe(`Unable to connect to the server with any of the available transports. ${HttpTransportType[requestedTransport]} failed: Error: '${HttpTransportType[requestedTransport]}' is disabled by the client.`);
|
||||
}
|
||||
},
|
||||
"Failed to start the connection: Error: Unable to initialize any of the available transports.");
|
||||
`Failed to start the connection: Error: Unable to connect to the server with any of the available transports. ${HttpTransportType[requestedTransport]} failed: Error: '${HttpTransportType[requestedTransport]}' is disabled by the client.`,
|
||||
`Failed to start the transport '${HttpTransportType[requestedTransport]}': Error: '${HttpTransportType[requestedTransport]}' is disabled by the client.`);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -378,9 +381,9 @@ describe("HttpConnection", () => {
|
|||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
await expect(connection.start(TransferFormat.Text))
|
||||
.rejects
|
||||
.toThrow("Unable to initialize any of the available transports.");
|
||||
.toThrow("None of the transports supported by the client are supported by the server.");
|
||||
},
|
||||
"Failed to start the connection: Error: Unable to initialize any of the available transports.");
|
||||
"Failed to start the connection: Error: None of the transports supported by the client are supported by the server.");
|
||||
});
|
||||
|
||||
it("does not send negotiate request if WebSockets transport requested explicitly and skipNegotiation is true", async () => {
|
||||
|
|
@ -694,12 +697,12 @@ describe("HttpConnection", () => {
|
|||
|
||||
await expect(connection.start(TransferFormat.Text))
|
||||
.rejects
|
||||
.toEqual(new Error("Unable to initialize any of the available transports."));
|
||||
.toEqual(new Error("Unable to connect to the server with any of the available transports. ServerSentEvents failed: Error: EventSource constructor called."));
|
||||
|
||||
expect(eventSourceConstructorCalled).toEqual(true);
|
||||
},
|
||||
"Failed to start the transport 'ServerSentEvents': Error: EventSource constructor called.",
|
||||
"Failed to start the connection: Error: Unable to initialize any of the available transports.");
|
||||
"Failed to start the connection: Error: Unable to connect to the server with any of the available transports. ServerSentEvents failed: Error: EventSource constructor called.");
|
||||
});
|
||||
|
||||
it("uses WebSocket constructor from options if provided", async () => {
|
||||
|
|
|
|||
|
|
@ -294,6 +294,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
|
|||
// Set the initial access token provider back to the original one from options
|
||||
_accessTokenProvider = _httpConnectionOptions.AccessTokenProvider;
|
||||
|
||||
var transportExceptions = new List<Exception>();
|
||||
|
||||
if (_httpConnectionOptions.SkipNegotiation)
|
||||
{
|
||||
if (_httpConnectionOptions.Transports == HttpTransportType.WebSockets)
|
||||
|
|
@ -349,12 +351,14 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
|
|||
if (!Enum.TryParse<HttpTransportType>(transport.Transport, out var transportType))
|
||||
{
|
||||
Log.TransportNotSupported(_logger, transport.Transport);
|
||||
transportExceptions.Add(new TransportFailedException(transport.Transport, "The transport is not supported by the client."));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (transportType == HttpTransportType.WebSockets && !IsWebSocketsSupported())
|
||||
{
|
||||
Log.WebSocketsNotSupportedByOperatingSystem(_logger);
|
||||
transportExceptions.Add(new TransportFailedException("WebSockets", "The transport is not supported on this operating system."));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -363,10 +367,12 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
|
|||
if ((transportType & _httpConnectionOptions.Transports) == 0)
|
||||
{
|
||||
Log.TransportDisabledByClient(_logger, transportType);
|
||||
transportExceptions.Add(new TransportFailedException(transportType.ToString(), "The transport is disabled by the client."));
|
||||
}
|
||||
else if (!transport.TransferFormats.Contains(transferFormatString, StringComparer.Ordinal))
|
||||
{
|
||||
Log.TransportDoesNotSupportTransferFormat(_logger, transportType, transferFormat);
|
||||
transportExceptions.Add(new TransportFailedException(transportType.ToString(), $"The transport does not support the '{transferFormat}' transfer format."));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -385,6 +391,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
|
|||
catch (Exception ex)
|
||||
{
|
||||
Log.TransportFailed(_logger, transportType, ex);
|
||||
|
||||
transportExceptions.Add(new TransportFailedException(transportType.ToString(), ex.Message, ex));
|
||||
|
||||
// Try the next transport
|
||||
// Clear the negotiation response so we know to re-negotiate.
|
||||
negotiationResponse = null;
|
||||
|
|
@ -394,7 +403,14 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
|
|||
|
||||
if (_transport == null)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to connect to the server with any of the available transports.");
|
||||
if (transportExceptions.Count > 0)
|
||||
{
|
||||
throw new AggregateException("Unable to connect to the server with any of the available transports.", transportExceptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NoTransportSupportedException("None of the transports supported by the client are supported by the server.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http.Connections.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown during negotiate when there are no supported transports between the client and server.
|
||||
/// </summary>
|
||||
public class NoTransportSupportedException : Exception
|
||||
{
|
||||
public NoTransportSupportedException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http.Connections.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown during negotiate when a transport fails to connect.
|
||||
/// </summary>
|
||||
public class TransportFailedException : Exception
|
||||
{
|
||||
public string TransportType { get; }
|
||||
|
||||
public TransportFailedException(string transportType, string message, Exception innerException = null)
|
||||
: base($"{transportType} failed: {message}", innerException)
|
||||
{
|
||||
TransportType = transportType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -154,8 +154,10 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
|||
transport: new TestTransport(onTransportStart: OnTransportStart)),
|
||||
async (connection) =>
|
||||
{
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => connection.StartAsync(TransferFormat.Text));
|
||||
Assert.Equal("Unable to connect to the server with any of the available transports.", ex.Message);
|
||||
var ex = await Assert.ThrowsAsync<AggregateException>(() => connection.StartAsync(TransferFormat.Text));
|
||||
Assert.Equal("Unable to connect to the server with any of the available transports. " +
|
||||
"(WebSockets failed: Transport failed to start) (ServerSentEvents failed: Transport failed to start) (LongPolling failed: Transport failed to start)",
|
||||
ex.Message);
|
||||
|
||||
// If websockets aren't supported then we expect one less attmept to start.
|
||||
if (!TestHelpers.IsWebSocketsSupported())
|
||||
|
|
@ -343,7 +345,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
|||
CreateConnection(httpHandler, loggerFactory: LoggerFactory, transport: sse),
|
||||
async (connection) =>
|
||||
{
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
await Assert.ThrowsAsync<AggregateException>(
|
||||
() => connection.StartAsync(TransferFormat.Text).OrTimeout());
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,17 +37,16 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public Task StartThrowsFormatExceptionIfNegotiationResponseHasNoTransports()
|
||||
public Task ConnectionCannotBeStartedIfNoCommonTransportsBetweenClientAndServer()
|
||||
{
|
||||
return RunInvalidNegotiateResponseTest<InvalidOperationException>(ResponseUtils.CreateNegotiationContent(transportTypes: 0), "Unable to connect to the server with any of the available transports.");
|
||||
return RunInvalidNegotiateResponseTest<AggregateException>(ResponseUtils.CreateNegotiationContent(transportTypes: HttpTransportType.ServerSentEvents),
|
||||
"Unable to connect to the server with any of the available transports. (ServerSentEvents failed: The transport is disabled by the client.)");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(HttpTransportType.None)]
|
||||
[InlineData(HttpTransportType.ServerSentEvents)]
|
||||
public Task ConnectionCannotBeStartedIfNoCommonTransportsBetweenClientAndServer(HttpTransportType serverTransports)
|
||||
[Fact]
|
||||
public Task ConnectionCannotBeStartedIfNoTransportProvidedByServer()
|
||||
{
|
||||
return RunInvalidNegotiateResponseTest<InvalidOperationException>(ResponseUtils.CreateNegotiationContent(transportTypes: serverTransports), "Unable to connect to the server with any of the available transports.");
|
||||
return RunInvalidNegotiateResponseTest<NoTransportSupportedException>(ResponseUtils.CreateNegotiationContent(transportTypes: HttpTransportType.None), "None of the transports supported by the client are supported by the server.");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
|
|||
Loading…
Reference in New Issue