From 631345b79a9618c3826f598d7661ea1eac9d99d6 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 4 Aug 2023 12:16:44 +0800 Subject: [PATCH] Validate Windows version when using WinHttpHandler --- src/Grpc.Net.Client/GrpcChannel.cs | 12 ++++ .../Internal/OperatingSystem.cs | 8 ++- test/Grpc.Net.Client.Tests/GetStatusTests.cs | 4 +- .../Grpc.Net.Client.Tests/GrpcChannelTests.cs | 60 +++++++++++++++++++ 4 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/Grpc.Net.Client/GrpcChannel.cs b/src/Grpc.Net.Client/GrpcChannel.cs index 84fa0ea69..84ed7d457 100644 --- a/src/Grpc.Net.Client/GrpcChannel.cs +++ b/src/Grpc.Net.Client/GrpcChannel.cs @@ -184,6 +184,18 @@ internal GrpcChannel(Uri address, GrpcChannelOptions channelOptions) : base(addr { Log.AddressPathUnused(Logger, Address.OriginalString); } + + // Validate the Windows version can support WinHttpHandler. + const int WinServer2022BuildVersion = 20348; + if (HttpHandlerType == HttpHandlerType.WinHttpHandler && + OperatingSystem.IsWindows && + OperatingSystem.OSVersion.Build < WinServer2022BuildVersion) + { + throw new InvalidOperationException("The channel configuration isn't valid on this operating system. " + + "The channel is configured to use WinHttpHandler and the current version of Windows " + + "doesn't support HTTP/2 features required by gRPC. Windows Server 2022 or Windows 11 or later is required. " + + "For more information, see https://aka.ms/aspnet/grpc/netframework."); + } } private void ResolveCredentials(GrpcChannelOptions channelOptions, out bool isSecure, out List? callCredentials) diff --git a/src/Grpc.Net.Client/Internal/OperatingSystem.cs b/src/Grpc.Net.Client/Internal/OperatingSystem.cs index 836771090..fcc7a0753 100644 --- a/src/Grpc.Net.Client/Internal/OperatingSystem.cs +++ b/src/Grpc.Net.Client/Internal/OperatingSystem.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -24,6 +24,8 @@ internal interface IOperatingSystem { bool IsBrowser { get; } bool IsAndroid { get; } + bool IsWindows { get; } + Version OSVersion { get; } } internal sealed class OperatingSystem : IOperatingSystem @@ -32,6 +34,8 @@ internal sealed class OperatingSystem : IOperatingSystem public bool IsBrowser { get; } public bool IsAndroid { get; } + public bool IsWindows { get; } + public Version OSVersion { get; } private OperatingSystem() { @@ -41,5 +45,7 @@ private OperatingSystem() #else IsAndroid = false; #endif + IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + OSVersion = Environment.OSVersion.Version; } } diff --git a/test/Grpc.Net.Client.Tests/GetStatusTests.cs b/test/Grpc.Net.Client.Tests/GetStatusTests.cs index 87e0bcb3f..e55a8d0c0 100644 --- a/test/Grpc.Net.Client.Tests/GetStatusTests.cs +++ b/test/Grpc.Net.Client.Tests/GetStatusTests.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -214,6 +214,8 @@ private class TestOperatingSystem : IOperatingSystem { public bool IsBrowser { get; set; } public bool IsAndroid { get; set; } + public bool IsWindows { get; set; } + public Version OSVersion { get; set; } = new Version(1, 2, 3, 4); } [Test] diff --git a/test/Grpc.Net.Client.Tests/GrpcChannelTests.cs b/test/Grpc.Net.Client.Tests/GrpcChannelTests.cs index 2e2e376ef..ff20ab60d 100644 --- a/test/Grpc.Net.Client.Tests/GrpcChannelTests.cs +++ b/test/Grpc.Net.Client.Tests/GrpcChannelTests.cs @@ -595,6 +595,66 @@ private class TestOperatingSystem : IOperatingSystem { public bool IsBrowser { get; set; } public bool IsAndroid { get; set; } + public bool IsWindows { get; set; } + public Version OSVersion { get; set; } = new Version(1, 2, 3, 4); + } + + [Test] + public void WinHttpHandler_UnsupportedWindows_Throw() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(new TestOperatingSystem + { + IsWindows = true, + OSVersion = new Version(1, 2, 3, 4) + }); + +#pragma warning disable CS0436 // Just need to have a type called WinHttpHandler to activate new behavior. + var winHttpHandler = new WinHttpHandler(new TestHttpMessageHandler()); +#pragma warning restore CS0436 + + // Act + var ex = Assert.Throws(() => + { + GrpcChannel.ForAddress("https://localhost", new GrpcChannelOptions + { + HttpHandler = winHttpHandler, + ServiceProvider = services.BuildServiceProvider() + }); + }); + + // Assert + Assert.AreEqual(ex!.Message, "The channel configuration isn't valid on this operating system. " + + "The channel is configured to use WinHttpHandler and the current version of Windows " + + "doesn't support HTTP/2 features required by gRPC. Windows Server 2022 or Windows 11 or later is required. " + + "For more information, see https://aka.ms/aspnet/grpc/netframework."); + } + + [Test] + public void WinHttpHandler_SupportedWindows_Success() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(new TestOperatingSystem + { + IsWindows = true, + OSVersion = Version.Parse("10.0.20348.169") + }); + +#pragma warning disable CS0436 // Just need to have a type called WinHttpHandler to activate new behavior. + var winHttpHandler = new WinHttpHandler(new TestHttpMessageHandler()); +#pragma warning restore CS0436 + + // Act + var channel = GrpcChannel.ForAddress("https://localhost", new GrpcChannelOptions + { + HttpHandler = winHttpHandler, + ServiceProvider = services.BuildServiceProvider() + }); + + // Assert + Assert.AreEqual(HttpHandlerType.WinHttpHandler, channel.HttpHandlerType); } #if SUPPORT_LOAD_BALANCING