Skip to content

Commit 13d9827

Browse files
authored
Fix ConnectCallback_UseNamedPipe_Success test (#89748)
* Fix ConnectCallback_UseNamedPipe_Success test * Extend the test to HTTP 1 * Workaround HTTP/1 over TLS over NamedPipe issue
1 parent e435da0 commit 13d9827

File tree

2 files changed

+93
-42
lines changed

2 files changed

+93
-42
lines changed

src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs

+90-42
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using System.Runtime.CompilerServices;
1616
using System.Security.Authentication;
1717
using System.Security.Cryptography.X509Certificates;
18+
using System.Security.Principal;
1819
using System.Text;
1920
using System.Threading;
2021
using System.Threading.Tasks;
@@ -3481,65 +3482,112 @@ public async Task ConnectCallback_MultipleRequests_EachRequestIsUsedOnce()
34813482
Assert.Equal(new[] { 1, 2, 3 }, requestsSeen);
34823483
}
34833484

3484-
private static bool PlatformSupportsUnixDomainSockets => Socket.OSSupportsUnixDomainSockets;
3485-
}
3486-
3487-
[SkipOnPlatform(TestPlatforms.Browser, "Socket is not supported on Browser")]
3488-
public sealed class SocketsHttpHandlerTest_ConnectCallback_Http11 : SocketsHttpHandlerTest_ConnectCallback
3489-
{
3490-
public SocketsHttpHandlerTest_ConnectCallback_Http11(ITestOutputHelper output) : base(output) { }
3491-
}
3492-
3493-
[ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.SupportsAlpn))]
3494-
public sealed class SocketsHttpHandlerTest_ConnectCallback_Http2 : SocketsHttpHandlerTest_ConnectCallback
3495-
{
3496-
public SocketsHttpHandlerTest_ConnectCallback_Http2(ITestOutputHelper output) : base(output) { }
3497-
protected override Version UseVersion => HttpVersion.Version20;
3498-
34993485
[Theory]
35003486
[InlineData(true)]
35013487
[InlineData(false)]
3502-
[ActiveIssue("https://github.com/dotnet/runtime/issues/73772", typeof(PlatformDetection), nameof(PlatformDetection.IsWindows), nameof(PlatformDetection.IsNativeAot))]
35033488
public async Task ConnectCallback_UseNamedPipe_Success(bool useSsl)
35043489
{
3505-
GenericLoopbackOptions options = new GenericLoopbackOptions() { UseSsl = useSsl };
3506-
35073490
string guid = $"{Guid.NewGuid():N}";
3508-
using (HttpClientHandler handler = CreateHttpClientHandler(allowAllCertificates: true))
3491+
3492+
Task clientTask = Task.Run(async () =>
35093493
{
3510-
var socketsHandler = (SocketsHttpHandler)GetUnderlyingSocketsHttpHandler(handler);
3511-
using (NamedPipeServerStream serverStream = new NamedPipeServerStream(pipeName: guid, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.WriteThrough | PipeOptions.Asynchronous))
3512-
using (NamedPipeClientStream clientStream = new NamedPipeClientStream(".", pipeName: guid, PipeDirection.InOut, PipeOptions.WriteThrough | PipeOptions.Asynchronous, System.Security.Principal.TokenImpersonationLevel.Anonymous))
3513-
{
3514-
await Task.WhenAll(serverStream.WaitForConnectionAsync(), clientStream.ConnectAsync());
3515-
socketsHandler.ConnectCallback = (context, token) =>
3516-
{
3517-
return new ValueTask<Stream>(clientStream);
3518-
};
3494+
await using NamedPipeClientStream clientStream = new(".", pipeName: guid, PipeDirection.InOut, PipeOptions.WriteThrough | PipeOptions.Asynchronous, TokenImpersonationLevel.Anonymous);
3495+
await clientStream.ConnectAsync(TestHelper.PassingTestTimeoutMilliseconds);
35193496

3520-
using (HttpClient client = CreateHttpClient(handler))
3521-
{
3522-
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact;
3497+
using HttpClientHandler handler = CreateHttpClientHandler(UseVersion);
3498+
using HttpClient client = CreateHttpClient(handler);
3499+
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact;
35233500

3524-
Task<string> clientTask = client.GetStringAsync($"{(options.UseSsl ? "https" : "http")}://{guid}/foo");
3525-
await using (GenericLoopbackConnection loopbackConnection = await LoopbackServerFactory.CreateConnectionAsync(socket: null, serverStream, options))
3526-
{
3527-
await loopbackConnection.InitializeConnectionAsync();
3501+
GetUnderlyingSocketsHttpHandler(handler).ConnectCallback = (_, _) => new ValueTask<Stream>(clientStream);
35283502

3529-
HttpRequestData requestData = await loopbackConnection.ReadRequestDataAsync();
3530-
Assert.Equal("/foo", requestData.Path);
3503+
string url = $"{(useSsl ? "https" : "http")}://{guid}/foo";
3504+
Assert.Equal("foo", await client.GetStringAsync(url).WaitAsync(TestHelper.PassingTestTimeoutMilliseconds));
3505+
});
35313506

3532-
await loopbackConnection.SendResponseAsync(content: "foo");
3507+
Task serverTask = Task.Run(async () =>
3508+
{
3509+
await using NamedPipeServerStream serverStream = new(pipeName: guid, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.WriteThrough | PipeOptions.Asynchronous);
3510+
await serverStream.WaitForConnectionAsync().WaitAsync(TestHelper.PassingTestTimeoutMilliseconds);
35333511

3534-
string response = await clientTask;
3535-
Assert.Equal("foo", response);
3536-
}
3512+
// HTTP/1.1 doesn't work over named pipes when going through SslStream as it runs into a deadlock while performing the handshake.
3513+
// The client is trying to write the request headers while the server is trying to write the end of its handshake.
3514+
// As neither side is reading, the writes never complete.
3515+
// We workaround that in tests by always having a pending read on the connection.
3516+
await using Stream serverStreamWrapper = useSsl && UseVersion.Major == 1
3517+
? new ReadAheadStream(serverStream, _output)
3518+
: serverStream;
3519+
3520+
var options = new GenericLoopbackOptions { UseSsl = useSsl };
3521+
await using GenericLoopbackConnection connection = await LoopbackServerFactory.CreateConnectionAsync(socket: null, serverStreamWrapper, options);
3522+
await connection.InitializeConnectionAsync();
3523+
3524+
HttpRequestData requestData = await connection.HandleRequestAsync(content: "foo").WaitAsync(TestHelper.PassingTestTimeoutMilliseconds);
3525+
Assert.Equal("/foo", requestData.Path);
3526+
});
3527+
3528+
await TestHelper.WhenAllCompletedOrAnyFailedWithTimeout(GenericLoopbackServer.LoopbackServerTimeoutMilliseconds, clientTask, serverTask);
3529+
}
3530+
3531+
private static bool PlatformSupportsUnixDomainSockets => Socket.OSSupportsUnixDomainSockets;
3532+
3533+
private sealed class ReadAheadStream : DelegatingStream
3534+
{
3535+
private readonly ITestOutputHelper _output;
3536+
private readonly IO.Pipelines.Pipe _pipe;
3537+
private readonly Stream _pipeReaderStream;
3538+
3539+
public ReadAheadStream(Stream innerStream, ITestOutputHelper output) : base(innerStream)
3540+
{
3541+
_output = output;
3542+
3543+
_pipe = new IO.Pipelines.Pipe();
3544+
_pipeReaderStream = _pipe.Reader.AsStream();
3545+
3546+
_ = Task.Run(async () =>
3547+
{
3548+
try
3549+
{
3550+
await IO.Pipelines.StreamPipeExtensions.CopyToAsync(innerStream, _pipe.Writer);
35373551
}
3538-
}
3552+
catch (Exception ex)
3553+
{
3554+
_output.WriteLine($"ReadAheadStream ignored exception: {ex}");
3555+
}
3556+
});
35393557
}
3558+
3559+
public override bool CanSeek => false;
3560+
3561+
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
3562+
_pipeReaderStream.ReadAsync(buffer, offset, count, cancellationToken);
3563+
3564+
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default) =>
3565+
_pipeReaderStream.ReadAsync(buffer, cancellationToken);
3566+
3567+
public override int Read(byte[] buffer, int offset, int count) =>
3568+
_pipeReaderStream.Read(buffer, offset, count);
3569+
3570+
public override int Read(Span<byte> buffer) =>
3571+
_pipeReaderStream.Read(buffer);
3572+
3573+
public override int ReadByte() =>
3574+
_pipeReaderStream.ReadByte();
35403575
}
35413576
}
35423577

3578+
[SkipOnPlatform(TestPlatforms.Browser, "Socket is not supported on Browser")]
3579+
public sealed class SocketsHttpHandlerTest_ConnectCallback_Http11 : SocketsHttpHandlerTest_ConnectCallback
3580+
{
3581+
public SocketsHttpHandlerTest_ConnectCallback_Http11(ITestOutputHelper output) : base(output) { }
3582+
}
3583+
3584+
[ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.SupportsAlpn))]
3585+
public sealed class SocketsHttpHandlerTest_ConnectCallback_Http2 : SocketsHttpHandlerTest_ConnectCallback
3586+
{
3587+
public SocketsHttpHandlerTest_ConnectCallback_Http2(ITestOutputHelper output) : base(output) { }
3588+
protected override Version UseVersion => HttpVersion.Version20;
3589+
}
3590+
35433591
public abstract class SocketsHttpHandlerTest_PlaintextStreamFilter : HttpClientHandlerTestBase
35443592
{
35453593
public SocketsHttpHandlerTest_PlaintextStreamFilter(ITestOutputHelper output) : base(output) { }

src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -305,4 +305,7 @@
305305
<ItemGroup>
306306
<ProjectReference Include="$(CommonTestPath)StreamConformanceTests\StreamConformanceTests.csproj" />
307307
</ItemGroup>
308+
<ItemGroup>
309+
<PackageReference Include="System.IO.Pipelines" Version="7.0.0" />
310+
</ItemGroup>
308311
</Project>

0 commit comments

Comments
 (0)