diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/CoreStrings.resx b/src/Microsoft.AspNetCore.Server.Kestrel.Core/CoreStrings.resx index e85918600f..c024ae82a9 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/CoreStrings.resx +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/CoreStrings.resx @@ -306,4 +306,7 @@ Connection processing ended abnormally. + + Cannot upgrade a non-upgradable request. Check IHttpUpgradeFeature.IsUpgradableRequest to determine if a request can be upgraded. + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.FeatureCollection.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.FeatureCollection.cs index bd1d8e2a56..429c517899 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.FeatureCollection.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.FeatureCollection.cs @@ -230,6 +230,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http async Task IHttpUpgradeFeature.UpgradeAsync() { + if (!((IHttpUpgradeFeature)this).IsUpgradableRequest) + { + throw new InvalidOperationException(CoreStrings.CannotUpgradeNonUpgradableRequest); + } + StatusCode = StatusCodes.Status101SwitchingProtocols; ReasonPhrase = "Switching Protocols"; ResponseHeaders["Connection"] = "Upgrade"; diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Properties/CoreStrings.Designer.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Properties/CoreStrings.Designer.cs index 1a86007def..2971494a5b 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Properties/CoreStrings.Designer.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Properties/CoreStrings.Designer.cs @@ -892,6 +892,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core internal static string FormatRequestProcessingEndError() => GetString("RequestProcessingEndError"); + /// + /// Cannot upgrade a non-upgradable request. Check IHttpUpgradeFeature.IsUpgradableRequest to determine if a request can be upgraded. + /// + internal static string CannotUpgradeNonUpgradableRequest + { + get => GetString("CannotUpgradeNonUpgradableRequest"); + } + + /// + /// Cannot upgrade a non-upgradable request. Check IHttpUpgradeFeature.IsUpgradableRequest to determine if a request can be upgraded. + /// + internal static string FormatCannotUpgradeNonUpgradableRequest() + => GetString("CannotUpgradeNonUpgradableRequest"); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/KeepAliveTimeoutTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/KeepAliveTimeoutTests.cs index 707dd4f511..4e6d0f1304 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/KeepAliveTimeoutTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/KeepAliveTimeoutTests.cs @@ -162,6 +162,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests await connection.Send( "GET /upgrade HTTP/1.1", "Host:", + "Connection: Upgrade", "", ""); await connection.Receive( diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ResponseTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ResponseTests.cs index 6b1d658aa8..39513f5002 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ResponseTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ResponseTests.cs @@ -1775,6 +1775,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests await connection.Send( "GET / HTTP/1.1", "Host:", + "Connection: Upgrade", "", ""); await connection.ReceiveForcedEnd( @@ -1789,7 +1790,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.Send( "GET / HTTP/1.0", - "Connection: keep-alive", + "Connection: keep-alive, Upgrade", "", ""); await connection.ReceiveForcedEnd( diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/UpgradeTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/UpgradeTests.cs index 1249524bc5..3696219f1c 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/UpgradeTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/UpgradeTests.cs @@ -182,5 +182,41 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests ""); } } + + [Fact] + public async Task ThrowsWhenUpgradingNonUpgradableRequest() + { + var upgradeTcs = new TaskCompletionSource(); + using (var server = new TestServer(async context => + { + var feature = context.Features.Get(); + Assert.False(feature.IsUpgradableRequest); + try + { + var stream = await feature.UpgradeAsync(); + } + catch (Exception e) + { + upgradeTcs.TrySetException(e); + } + finally + { + upgradeTcs.TrySetResult(false); + } + })) + { + using (var connection = server.CreateConnection()) + { + await connection.Send("GET / HTTP/1.1", + "Host:", + "", + ""); + await connection.Receive("HTTP/1.1 200 OK"); + } + } + + var ex = await Assert.ThrowsAsync(async () => await upgradeTcs.Task).TimeoutAfter(TimeSpan.FromSeconds(15)); + Assert.Equal(CoreStrings.CannotUpgradeNonUpgradableRequest, ex.Message); + } } }