Skip to content

Commit f526f0c

Browse files
Fix IIS outofprocess to remove WebSocket compression handshake (#58846)
1 parent 3277b64 commit f526f0c

File tree

13 files changed

+226
-47
lines changed

13 files changed

+226
-47
lines changed

src/Hosting/Server.IntegrationTesting/src/Common/DeploymentParameters.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,12 @@ public override string ToString()
183183
{
184184
return string.Format(
185185
CultureInfo.InvariantCulture,
186-
"[Variation] :: ServerType={0}, Runtime={1}, Arch={2}, BaseUrlHint={3}, Publish={4}",
186+
"[Variation] :: ServerType={0}, Runtime={1}, Arch={2}, BaseUrlHint={3}, Publish={4}, HostingModel={5}",
187187
ServerType,
188188
RuntimeFlavor,
189189
RuntimeArchitecture,
190190
ApplicationBaseUriHint,
191-
PublishApplicationBeforeDeployment);
191+
PublishApplicationBeforeDeployment,
192+
HostingModel);
192193
}
193194
}

src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ FORWARDING_HANDLER::ExecuteRequestHandler()
180180
if (cchHeader == 9 && _stricmp(pszWebSocketHeader, "websocket") == 0)
181181
{
182182
m_fWebSocketEnabled = TRUE;
183+
184+
// WinHttp does not support any extensions being returned by the server, so we remove the request header to avoid the server
185+
// responding with any accepted extensions.
186+
pRequest->DeleteHeader("Sec-WebSocket-Extensions");
183187
}
184188
}
185189

src/Servers/IIS/IIS/test/Common.FunctionalTests/Infrastructure/IISTestSiteCollection.cs

+8-2
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,15 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests;
99
/// This type just maps collection names to available fixtures
1010
/// </summary>
1111
[CollectionDefinition(Name)]
12-
public class IISTestSiteCollection : ICollectionFixture<IISTestSiteFixture>
12+
public class IISTestSiteCollectionInProc : ICollectionFixture<IISTestSiteFixture>
1313
{
14-
public const string Name = nameof(IISTestSiteCollection);
14+
public const string Name = nameof(IISTestSiteCollectionInProc);
15+
}
16+
17+
[CollectionDefinition(Name)]
18+
public class IISTestSiteCollectionOutOfProc : ICollectionFixture<IISTestSiteFixture>
19+
{
20+
public const string Name = nameof(IISTestSiteCollectionOutOfProc);
1521
}
1622

1723
[CollectionDefinition(Name)]

src/Servers/IIS/IIS/test/Common.FunctionalTests/Infrastructure/IISTestSiteFixture.cs

+5
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ internal IISTestSiteFixture(Action<IISDeploymentParameters> configure)
4646
ApplicationPublisher = new PublishedApplicationPublisher(Helpers.GetInProcessTestSitesName()),
4747
ServerType = DeployerSelector.ServerType
4848
};
49+
50+
// Uncomment to add IIS debug logs to test output.
51+
//DeploymentParameters.EnvironmentVariables.Add("ASPNETCORE_MODULE_DEBUG", "console");
52+
53+
DeploymentParameters.EnableModule("WebSocketModule", "%IIS_BIN%/iiswsock.dll");
4954
}
5055

5156
public HttpClient Client => DeploymentResult.HttpClient;

src/Servers/IIS/IIS/test/Common.FunctionalTests/RequestResponseTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Server.IIS.NewShim.FunctionalTests;
2929
namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests;
3030
#endif
3131

32-
[Collection(IISTestSiteCollection.Name)]
32+
[Collection(IISTestSiteCollectionInProc.Name)]
3333
[SkipOnHelix("Unsupported queue", Queues = "Windows.Amd64.VS2022.Pre.Open;")]
3434
public class RequestResponseTests
3535
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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 Microsoft.AspNetCore.Server.IIS.FunctionalTests;
5+
using Microsoft.AspNetCore.InternalTesting;
6+
using Microsoft.AspNetCore.Server.IntegrationTesting;
7+
using Xunit.Abstractions;
8+
9+
#if !IIS_FUNCTIONALS
10+
using Microsoft.AspNetCore.Server.IIS.FunctionalTests;
11+
12+
#if IISEXPRESS_FUNCTIONALS
13+
namespace Microsoft.AspNetCore.Server.IIS.IISExpress.FunctionalTests;
14+
#elif NEWHANDLER_FUNCTIONALS
15+
namespace Microsoft.AspNetCore.Server.IIS.NewHandler.FunctionalTests;
16+
#elif NEWSHIM_FUNCTIONALS
17+
namespace Microsoft.AspNetCore.Server.IIS.NewShim.FunctionalTests;
18+
#endif
19+
#else
20+
21+
namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests;
22+
#endif
23+
24+
[Collection(IISTestSiteCollectionInProc.Name)]
25+
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8, SkipReason = "No WebSocket supported on Win7")]
26+
[SkipOnHelix("Unsupported queue", Queues = "Windows.Amd64.VS2022.Pre.Open;")]
27+
public class WebSocketsInProcessTests : WebSocketsTests
28+
{
29+
public WebSocketsInProcessTests(IISTestSiteFixture fixture, ITestOutputHelper testOutput) : base(fixture, testOutput)
30+
{
31+
Fixture.DeploymentParameters.HostingModel = HostingModel.InProcess;
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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 Microsoft.AspNetCore.Server.IIS.FunctionalTests;
5+
using Microsoft.AspNetCore.InternalTesting;
6+
using Microsoft.AspNetCore.Server.IntegrationTesting;
7+
using Xunit.Abstractions;
8+
9+
#if !IIS_FUNCTIONALS
10+
using Microsoft.AspNetCore.Server.IIS.FunctionalTests;
11+
12+
#if IISEXPRESS_FUNCTIONALS
13+
namespace Microsoft.AspNetCore.Server.IIS.IISExpress.FunctionalTests;
14+
#elif NEWHANDLER_FUNCTIONALS
15+
namespace Microsoft.AspNetCore.Server.IIS.NewHandler.FunctionalTests;
16+
#elif NEWSHIM_FUNCTIONALS
17+
namespace Microsoft.AspNetCore.Server.IIS.NewShim.FunctionalTests;
18+
#endif
19+
#else
20+
21+
namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests;
22+
#endif
23+
24+
[Collection(IISTestSiteCollectionOutOfProc.Name)]
25+
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8, SkipReason = "No WebSocket supported on Win7")]
26+
[SkipOnHelix("Unsupported queue", Queues = "Windows.Amd64.VS2022.Pre.Open;")]
27+
public class WebSocketsOutOfProcessTests : WebSocketsTests
28+
{
29+
public WebSocketsOutOfProcessTests(IISTestSiteFixture fixture, ITestOutputHelper testOutput) : base(fixture, testOutput)
30+
{
31+
Fixture.DeploymentParameters.HostingModel = HostingModel.OutOfProcess;
32+
}
33+
}

src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/InProcess/WebSocketTests.cs renamed to src/Servers/IIS/IIS/test/Common.FunctionalTests/WebSocketTests.cs

+73-24
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,76 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
4+
using System.Diagnostics;
55
using System.Globalization;
6-
using System.Linq;
76
using System.Net.Http;
87
using System.Net.Sockets;
98
using System.Net.WebSockets;
109
using System.Text;
11-
using System.Threading;
12-
using System.Threading.Tasks;
13-
using Microsoft.AspNetCore.Server.IIS.FunctionalTests;
1410
using Microsoft.AspNetCore.InternalTesting;
15-
using Xunit;
11+
using Microsoft.AspNetCore.Server.IIS.FunctionalTests;
12+
using Microsoft.AspNetCore.Server.IntegrationTesting;
13+
using Microsoft.AspNetCore.Server.IntegrationTesting.IIS;
14+
using Xunit.Abstractions;
15+
16+
#if !IIS_FUNCTIONALS
17+
using Microsoft.AspNetCore.Server.IIS.FunctionalTests;
1618

19+
#if IISEXPRESS_FUNCTIONALS
1720
namespace Microsoft.AspNetCore.Server.IIS.IISExpress.FunctionalTests;
21+
#elif NEWHANDLER_FUNCTIONALS
22+
namespace Microsoft.AspNetCore.Server.IIS.NewHandler.FunctionalTests;
23+
#elif NEWSHIM_FUNCTIONALS
24+
namespace Microsoft.AspNetCore.Server.IIS.NewShim.FunctionalTests;
25+
#endif
26+
#else
27+
28+
namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests;
29+
#endif
1830

19-
[Collection(IISTestSiteCollection.Name)]
2031
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8, SkipReason = "No WebSocket supported on Win7")]
2132
[SkipOnHelix("Unsupported queue", Queues = "Windows.Amd64.VS2022.Pre.Open;")]
22-
public class WebSocketsTests
33+
public abstract class WebSocketsTests : FunctionalTestsBase
2334
{
24-
private readonly string _requestUri;
25-
private readonly string _webSocketUri;
35+
public IISTestSiteFixture Fixture { get; }
2636

27-
public WebSocketsTests(IISTestSiteFixture fixture)
37+
public WebSocketsTests(IISTestSiteFixture fixture, ITestOutputHelper testOutput) : base(testOutput)
2838
{
29-
_requestUri = fixture.DeploymentResult.ApplicationBaseUri;
30-
_webSocketUri = _requestUri.Replace("http:", "ws:");
39+
Fixture = fixture;
40+
Fixture.DeploymentParameters.EnableLogging("C:/github/aspnetcore/artifacts/log");
3141
}
3242

3343
[ConditionalFact]
3444
public async Task RequestWithBody_NotUpgradable()
3545
{
3646
using var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(200) };
37-
using var response = await client.PostAsync(_requestUri + "WebSocketNotUpgradable", new StringContent("Hello World"));
47+
using var response = await client.PostAsync(Fixture.DeploymentResult.ApplicationBaseUri + "WebSocketNotUpgradable", new StringContent("Hello World"));
3848
response.EnsureSuccessStatusCode();
3949
}
4050

4151
[ConditionalFact]
4252
public async Task RequestWithoutBody_Upgradable()
4353
{
54+
if (Fixture.DeploymentParameters.HostingModel == HostingModel.OutOfProcess)
55+
{
56+
// OutOfProcess doesn't support upgrade requests without the "Upgrade": "websocket" header.
57+
return;
58+
}
59+
4460
using var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(200) };
4561
// POST with Content-Length: 0 counts as not having a body.
46-
using var response = await client.PostAsync(_requestUri + "WebSocketUpgradable", new StringContent(""));
62+
using var response = await client.PostAsync(Fixture.DeploymentResult.ApplicationBaseUri + "WebSocketUpgradable", new StringContent(""));
4763
response.EnsureSuccessStatusCode();
4864
}
4965

5066
[ConditionalFact]
5167
public async Task OnStartedCalledForWebSocket()
5268
{
53-
var cws = new ClientWebSocket();
54-
await cws.ConnectAsync(new Uri(_webSocketUri + "WebSocketLifetimeEvents"), default);
69+
var webSocketUri = Fixture.DeploymentResult.ApplicationBaseUri;
70+
webSocketUri = webSocketUri.Replace("http:", "ws:");
71+
72+
using var cws = new ClientWebSocket();
73+
await cws.ConnectAsync(new Uri(webSocketUri + "WebSocketLifetimeEvents"), default);
5574

5675
await ReceiveMessage(cws, "OnStarting");
5776
await ReceiveMessage(cws, "Upgraded");
@@ -60,17 +79,23 @@ public async Task OnStartedCalledForWebSocket()
6079
[ConditionalFact]
6180
public async Task WebReadBeforeUpgrade()
6281
{
63-
var cws = new ClientWebSocket();
64-
await cws.ConnectAsync(new Uri(_webSocketUri + "WebSocketReadBeforeUpgrade"), default);
82+
var webSocketUri = Fixture.DeploymentResult.ApplicationBaseUri;
83+
webSocketUri = webSocketUri.Replace("http:", "ws:");
84+
85+
using var cws = new ClientWebSocket();
86+
await cws.ConnectAsync(new Uri(webSocketUri + "WebSocketReadBeforeUpgrade"), default);
6587

6688
await ReceiveMessage(cws, "Yay");
6789
}
6890

6991
[ConditionalFact]
7092
public async Task CanSendAndReceieveData()
7193
{
72-
var cws = new ClientWebSocket();
73-
await cws.ConnectAsync(new Uri(_webSocketUri + "WebSocketEcho"), default);
94+
var webSocketUri = Fixture.DeploymentResult.ApplicationBaseUri;
95+
webSocketUri = webSocketUri.Replace("http:", "ws:");
96+
97+
using var cws = new ClientWebSocket();
98+
await cws.ConnectAsync(new Uri(webSocketUri + "WebSocketEcho"), default);
7499

75100
for (int i = 0; i < 1000; i++)
76101
{
@@ -80,10 +105,33 @@ public async Task CanSendAndReceieveData()
80105
}
81106
}
82107

108+
[ConditionalFact]
109+
public async Task AttemptCompressionWorks()
110+
{
111+
var webSocketUri = Fixture.DeploymentResult.ApplicationBaseUri;
112+
webSocketUri = webSocketUri.Replace("http:", "ws:");
113+
114+
using var cws = new ClientWebSocket();
115+
cws.Options.DangerousDeflateOptions = new WebSocketDeflateOptions();
116+
await cws.ConnectAsync(new Uri(webSocketUri + "WebSocketAllowCompression"), default);
117+
118+
// Compression doesn't work with OutOfProcess, let's make sure the websocket extensions aren't forwarded and the connection still works
119+
var expected = Fixture.DeploymentParameters.HostingModel == HostingModel.InProcess
120+
? "permessage-deflate; client_max_window_bits=15" : "None";
121+
await ReceiveMessage(cws, expected);
122+
123+
for (int i = 0; i < 1000; i++)
124+
{
125+
var message = i.ToString(CultureInfo.InvariantCulture);
126+
await SendMessage(cws, message);
127+
await ReceiveMessage(cws, message);
128+
}
129+
}
130+
83131
[ConditionalFact]
84132
public async Task Http1_0_Request_NotUpgradable()
85133
{
86-
Uri uri = new Uri(_requestUri + "WebSocketNotUpgradable");
134+
Uri uri = new Uri(Fixture.DeploymentResult.ApplicationBaseUri + "WebSocketNotUpgradable");
87135
using TcpClient client = new TcpClient();
88136

89137
await client.ConnectAsync(uri.Host, uri.Port);
@@ -103,7 +151,7 @@ public async Task Http1_0_Request_NotUpgradable()
103151
[ConditionalFact]
104152
public async Task Http1_0_Request_UpgradeErrors()
105153
{
106-
Uri uri = new Uri(_requestUri + "WebSocketUpgradeFails");
154+
Uri uri = new Uri(Fixture.DeploymentResult.ApplicationBaseUri + "WebSocketUpgradeFails");
107155
using TcpClient client = new TcpClient();
108156

109157
await client.ConnectAsync(uri.Host, uri.Port);
@@ -148,6 +196,7 @@ private async Task SendMessage(ClientWebSocket webSocket, string message)
148196

149197
private async Task ReceiveMessage(ClientWebSocket webSocket, string expectedMessage)
150198
{
199+
Debug.Assert(expectedMessage.Length > 0);
151200
var received = new byte[expectedMessage.Length];
152201

153202
var offset = 0;
@@ -156,7 +205,7 @@ private async Task ReceiveMessage(ClientWebSocket webSocket, string expectedMess
156205
{
157206
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(received, offset, received.Length - offset), default);
158207
offset += result.Count;
159-
} while (!result.EndOfMessage);
208+
} while (!result.EndOfMessage && result.CloseStatus is null && received.Length - offset > 0);
160209

161210
Assert.Equal(expectedMessage, Encoding.ASCII.GetString(received));
162211
}

src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj

-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
1212
</PropertyGroup>
1313

14-
1514
<ItemGroup>
1615
<Reference Include="Microsoft.AspNetCore.Server.IIS" />
1716
<ProjectReference Include="..\testassets\IIS.Common.TestLib\IIS.Common.TestLib.csproj" />

src/Servers/IIS/IIS/test/testassets/InProcessWebSite/InProcessWebSite.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<Reference Include="Microsoft.AspNetCore.Server.IISIntegration" />
2929
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
3030
<Reference Include="Microsoft.AspNetCore.HttpsPolicy" />
31+
<Reference Include="Microsoft.AspNetCore.WebSockets" />
3132
<Reference Include="Microsoft.AspNetCore.WebUtilities" />
3233
<Reference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
3334
<Reference Include="Microsoft.Extensions.Configuration.Json" />

0 commit comments

Comments
 (0)