|
15 | 15 | using System.Runtime.CompilerServices;
|
16 | 16 | using System.Security.Authentication;
|
17 | 17 | using System.Security.Cryptography.X509Certificates;
|
| 18 | +using System.Security.Principal; |
18 | 19 | using System.Text;
|
19 | 20 | using System.Threading;
|
20 | 21 | using System.Threading.Tasks;
|
@@ -3481,65 +3482,112 @@ public async Task ConnectCallback_MultipleRequests_EachRequestIsUsedOnce()
|
3481 | 3482 | Assert.Equal(new[] { 1, 2, 3 }, requestsSeen);
|
3482 | 3483 | }
|
3483 | 3484 |
|
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 |
| - |
3499 | 3485 | [Theory]
|
3500 | 3486 | [InlineData(true)]
|
3501 | 3487 | [InlineData(false)]
|
3502 |
| - [ActiveIssue("https://github.com/dotnet/runtime/issues/73772", typeof(PlatformDetection), nameof(PlatformDetection.IsWindows), nameof(PlatformDetection.IsNativeAot))] |
3503 | 3488 | public async Task ConnectCallback_UseNamedPipe_Success(bool useSsl)
|
3504 | 3489 | {
|
3505 |
| - GenericLoopbackOptions options = new GenericLoopbackOptions() { UseSsl = useSsl }; |
3506 |
| - |
3507 | 3490 | string guid = $"{Guid.NewGuid():N}";
|
3508 |
| - using (HttpClientHandler handler = CreateHttpClientHandler(allowAllCertificates: true)) |
| 3491 | + |
| 3492 | + Task clientTask = Task.Run(async () => |
3509 | 3493 | {
|
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); |
3519 | 3496 |
|
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; |
3523 | 3500 |
|
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); |
3528 | 3502 |
|
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 | + }); |
3531 | 3506 |
|
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); |
3533 | 3511 |
|
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); |
3537 | 3551 | }
|
3538 |
| - } |
| 3552 | + catch (Exception ex) |
| 3553 | + { |
| 3554 | + _output.WriteLine($"ReadAheadStream ignored exception: {ex}"); |
| 3555 | + } |
| 3556 | + }); |
3539 | 3557 | }
|
| 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(); |
3540 | 3575 | }
|
3541 | 3576 | }
|
3542 | 3577 |
|
| 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 | + |
3543 | 3591 | public abstract class SocketsHttpHandlerTest_PlaintextStreamFilter : HttpClientHandlerTestBase
|
3544 | 3592 | {
|
3545 | 3593 | public SocketsHttpHandlerTest_PlaintextStreamFilter(ITestOutputHelper output) : base(output) { }
|
|
0 commit comments