Skip to content

Commit 1feeb30

Browse files
authored
Add Http/2 WebSocket HttpClient interop tests #41895 (#43851)
1 parent 8975c57 commit 1feeb30

File tree

3 files changed

+121
-0
lines changed

3 files changed

+121
-0
lines changed

Diff for: src/Servers/Kestrel/Kestrel.slnf

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"src\\Middleware\\HostFiltering\\src\\Microsoft.AspNetCore.HostFiltering.csproj",
2828
"src\\Middleware\\HttpOverrides\\src\\Microsoft.AspNetCore.HttpOverrides.csproj",
2929
"src\\Middleware\\StaticFiles\\src\\Microsoft.AspNetCore.StaticFiles.csproj",
30+
"src\\Middleware\\WebSockets\\src\\Microsoft.AspNetCore.WebSockets.csproj",
3031
"src\\ObjectPool\\src\\Microsoft.Extensions.ObjectPool.csproj",
3132
"src\\Security\\Authentication\\Core\\src\\Microsoft.AspNetCore.Authentication.csproj",
3233
"src\\Security\\Authorization\\Core\\src\\Microsoft.AspNetCore.Authorization.csproj",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Net;
5+
using System.Net.Http;
6+
using System.Net.WebSockets;
7+
using System.Text;
8+
using Microsoft.AspNetCore.Builder;
9+
using Microsoft.AspNetCore.Hosting;
10+
using Microsoft.AspNetCore.Server.Kestrel.Core;
11+
using Microsoft.AspNetCore.Testing;
12+
using Microsoft.Extensions.Hosting;
13+
14+
namespace Interop.FunctionalTests;
15+
16+
/// <summary>
17+
/// This tests interop with System.Net.Http.HttpClient (SocketHttpHandler) using HTTP/2 (H2 and H2C) WebSockets
18+
/// https://www.rfc-editor.org/rfc/rfc8441.html
19+
/// </summary>
20+
public class Http2WebSocketInteropTests : LoggedTest
21+
{
22+
public static IEnumerable<object[]> NegotiationScenarios
23+
{
24+
get
25+
{
26+
var list = new List<object[]>()
27+
{
28+
new object[] { "http", "1.1", HttpVersionPolicy.RequestVersionExact, HttpProtocols.Http1, "HTTP/1.1" },
29+
new object[] { "http", "2.0", HttpVersionPolicy.RequestVersionExact, HttpProtocols.Http2, "HTTP/2" },
30+
new object[] { "http", "1.1", HttpVersionPolicy.RequestVersionOrHigher, HttpProtocols.Http1AndHttp2, "HTTP/1.1" }, // No TLS/APLN, Can't upgrade
31+
new object[] { "http", "2.0", HttpVersionPolicy.RequestVersionOrLower, HttpProtocols.Http1AndHttp2, "HTTP/1.1" }, // No TLS/APLN, Downgrade
32+
};
33+
34+
if (Utilities.CurrentPlatformSupportsHTTP2OverTls())
35+
{
36+
list.Add(new object[] { "https", "1.1", HttpVersionPolicy.RequestVersionExact, HttpProtocols.Http1, "HTTP/1.1" });
37+
list.Add(new object[] { "https", "2.0", HttpVersionPolicy.RequestVersionExact, HttpProtocols.Http2, "HTTP/2" });
38+
list.Add(new object[] { "https", "1.1", HttpVersionPolicy.RequestVersionOrHigher, HttpProtocols.Http1AndHttp2, "HTTP/2" }); // Upgrade
39+
list.Add(new object[] { "https", "2.0", HttpVersionPolicy.RequestVersionOrLower, HttpProtocols.Http1AndHttp2, "HTTP/2" });
40+
list.Add(new object[] { "https", "2.0", HttpVersionPolicy.RequestVersionOrLower, HttpProtocols.Http1, "HTTP/1.1" }); // Downgrade
41+
}
42+
43+
return list;
44+
}
45+
}
46+
47+
[Theory]
48+
[MemberData(nameof(NegotiationScenarios))]
49+
public async Task HttpVersionNegotationWorks(string scheme, string clientVersion, HttpVersionPolicy clientPolicy, HttpProtocols serverProtocols, string expectedVersion)
50+
{
51+
var hostBuilder = new HostBuilder()
52+
.ConfigureWebHost(webHostBuilder =>
53+
{
54+
ConfigureKestrel(webHostBuilder, scheme, serverProtocols);
55+
webHostBuilder.ConfigureServices(AddTestLogging)
56+
.Configure(app =>
57+
{
58+
app.UseWebSockets();
59+
app.Run(async context =>
60+
{
61+
Assert.Equal(expectedVersion, context.Request.Protocol);
62+
Assert.True(context.WebSockets.IsWebSocketRequest);
63+
var ws = await context.WebSockets.AcceptWebSocketAsync();
64+
var bytes = new byte[1024];
65+
var result = await ws.ReceiveAsync(bytes, default);
66+
Assert.True(result.EndOfMessage);
67+
Assert.Equal(WebSocketMessageType.Text, result.MessageType);
68+
Assert.Equal("Hello", Encoding.UTF8.GetString(bytes, 0, result.Count));
69+
70+
await ws.SendAsync(Encoding.UTF8.GetBytes("Hi there"), WebSocketMessageType.Text, endOfMessage: true, default);
71+
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Server closed", default);
72+
});
73+
});
74+
});
75+
using var host = await hostBuilder.StartAsync().DefaultTimeout();
76+
77+
var url = host.MakeUrl(scheme == "http" ? "ws" : "wss");
78+
using var client = CreateClient();
79+
var wsClient = new ClientWebSocket();
80+
wsClient.Options.HttpVersion = Version.Parse(clientVersion);
81+
wsClient.Options.HttpVersionPolicy = clientPolicy;
82+
wsClient.Options.CollectHttpResponseDetails = true;
83+
await wsClient.ConnectAsync(new Uri(url), client, default);
84+
Assert.Equal(expectedVersion == "HTTP/2" ? HttpStatusCode.OK : HttpStatusCode.SwitchingProtocols, wsClient.HttpStatusCode);
85+
86+
await wsClient.SendAsync(Encoding.UTF8.GetBytes("Hello"), WebSocketMessageType.Text, endOfMessage: true, default);
87+
88+
var bytes = new byte[1024];
89+
var result = await wsClient.ReceiveAsync(bytes, default);
90+
Assert.True(result.EndOfMessage);
91+
Assert.Equal(WebSocketMessageType.Text, result.MessageType);
92+
Assert.Equal("Hi there", Encoding.UTF8.GetString(bytes, 0, result.Count));
93+
94+
await wsClient.CloseAsync(WebSocketCloseStatus.NormalClosure, "Client closed", default);
95+
}
96+
97+
private static HttpClient CreateClient()
98+
{
99+
var handler = new HttpClientHandler();
100+
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
101+
var client = new HttpClient(handler);
102+
return client;
103+
}
104+
105+
private static void ConfigureKestrel(IWebHostBuilder webHostBuilder, string scheme, HttpProtocols protocols)
106+
{
107+
webHostBuilder.UseKestrel(options =>
108+
{
109+
options.Listen(IPAddress.Loopback, 0, listenOptions =>
110+
{
111+
listenOptions.Protocols = protocols;
112+
if (scheme == "https")
113+
{
114+
listenOptions.UseHttps(TestResources.GetTestCertificate());
115+
}
116+
});
117+
});
118+
}
119+
}

Diff for: src/Servers/Kestrel/test/Interop.FunctionalTests/Interop.FunctionalTests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
<ItemGroup>
3636
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
3737
<Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Quic" />
38+
<Reference Include="Microsoft.AspNetCore.WebSockets" />
3839
<Reference Include="Microsoft.Extensions.Hosting" />
3940
<Reference Include="Microsoft.Internal.AspNetCore.H2Spec.All" />
4041
<Reference Include="Selenium.WebDriver" />

0 commit comments

Comments
 (0)