@@ -174,7 +174,7 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
174
174
[ ConditionalTheory ]
175
175
[ MsQuicSupported ]
176
176
[ InlineData ( HttpProtocols . Http3 , 11 ) ]
177
- [ InlineData ( HttpProtocols . Http3 , 1024 , Skip = "HttpClient issue https://github.com/dotnet/runtime/issues/56115" ) ]
177
+ [ InlineData ( HttpProtocols . Http3 , 1024 ) ]
178
178
[ InlineData ( HttpProtocols . Http2 , 11 ) ]
179
179
[ InlineData ( HttpProtocols . Http2 , 1024 ) ]
180
180
public async Task GET_ServerStreaming_ClientReadsPartialResponse ( HttpProtocols protocol , int clientBufferSize )
@@ -219,7 +219,7 @@ public async Task GET_ServerStreaming_ClientReadsPartialResponse(HttpProtocols p
219
219
220
220
[ ConditionalTheory ]
221
221
[ MsQuicSupported ]
222
- [ InlineData ( HttpProtocols . Http3 , Skip = "https://github.com/dotnet/runtime/issues/56969" ) ]
222
+ [ InlineData ( HttpProtocols . Http3 ) ]
223
223
[ InlineData ( HttpProtocols . Http2 ) ]
224
224
public async Task POST_ClientSendsOnlyHeaders_RequestReceivedOnServer ( HttpProtocols protocol )
225
225
{
@@ -514,6 +514,164 @@ public async Task GET_MultipleRequestsInSequence_ReusedState()
514
514
}
515
515
}
516
516
517
+ // Verify HTTP/2 and HTTP/3 match behavior
518
+ [ ConditionalTheory ]
519
+ [ MsQuicSupported ]
520
+ [ InlineData ( HttpProtocols . Http3 , Skip = "https://github.com/dotnet/runtime/issues/56129" ) ]
521
+ [ InlineData ( HttpProtocols . Http2 ) ]
522
+ public async Task POST_ClientCancellationBidirectional_RequestAbortRaised ( HttpProtocols protocol )
523
+ {
524
+ // Arrange
525
+ var cancelledTcs = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
526
+ var readAsyncTask = new TaskCompletionSource < Task > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
527
+ var clientHasCancelledSyncPoint = new SyncPoint ( ) ;
528
+
529
+ var builder = CreateHostBuilder ( async context =>
530
+ {
531
+ context . RequestAborted . Register ( ( ) =>
532
+ {
533
+ Logger . LogInformation ( "Server received request aborted." ) ;
534
+ cancelledTcs . SetResult ( ) ;
535
+ } ) ;
536
+
537
+ var requestBody = context . Request . Body ;
538
+ var responseBody = context . Response . Body ;
539
+
540
+ // Read content
541
+ var data = await requestBody . ReadAtLeastLengthAsync ( TestData . Length ) ;
542
+
543
+ await responseBody . WriteAsync ( data ) ;
544
+ await responseBody . FlushAsync ( ) ;
545
+
546
+ await clientHasCancelledSyncPoint . WaitForSyncPoint ( ) . DefaultTimeout ( ) ;
547
+ clientHasCancelledSyncPoint . Continue ( ) ;
548
+
549
+ for ( var i = 0 ; i < 5 ; i ++ )
550
+ {
551
+ await Task . Delay ( 100 ) ;
552
+
553
+ Logger . LogInformation ( $ "Server writing to response after cancellation { i } .") ;
554
+ await responseBody . WriteAsync ( data ) ;
555
+ await responseBody . FlushAsync ( ) ;
556
+ }
557
+
558
+ // Wait for task cancellation
559
+ await cancelledTcs . Task ;
560
+
561
+ readAsyncTask . SetResult ( requestBody . ReadAsync ( data ) . AsTask ( ) ) ;
562
+ } , protocol : protocol ) ;
563
+
564
+ var httpClientHandler = new HttpClientHandler ( ) ;
565
+ httpClientHandler . ServerCertificateCustomValidationCallback = HttpClientHandler . DangerousAcceptAnyServerCertificateValidator ;
566
+
567
+ using ( var host = builder . Build ( ) )
568
+ using ( var client = new HttpClient ( httpClientHandler ) )
569
+ {
570
+ await host . StartAsync ( ) . DefaultTimeout ( ) ;
571
+
572
+ var cts = new CancellationTokenSource ( ) ;
573
+ var requestContent = new StreamingHttpContext ( ) ;
574
+
575
+ var request = new HttpRequestMessage ( HttpMethod . Post , $ "https://127.0.0.1:{ host . GetPort ( ) } /") ;
576
+ request . Content = requestContent ;
577
+ request . Version = GetProtocol ( protocol ) ;
578
+ request . VersionPolicy = HttpVersionPolicy . RequestVersionExact ;
579
+
580
+ // Act
581
+ var responseTask = client . SendAsync ( request , HttpCompletionOption . ResponseHeadersRead ) ;
582
+
583
+ var requestStream = await requestContent . GetStreamAsync ( ) . DefaultTimeout ( ) ;
584
+
585
+ // Send headers
586
+ await requestStream . FlushAsync ( ) . DefaultTimeout ( ) ;
587
+ // Write content
588
+ await requestStream . WriteAsync ( TestData ) . DefaultTimeout ( ) ;
589
+
590
+ var response = await responseTask . DefaultTimeout ( ) ;
591
+
592
+ var responseStream = await response . Content . ReadAsStreamAsync ( ) . DefaultTimeout ( ) ;
593
+
594
+ var data = await responseStream . ReadAtLeastLengthAsync ( TestData . Length ) . DefaultTimeout ( ) ;
595
+
596
+ Logger . LogInformation ( "Client canceled request." ) ;
597
+ response . Dispose ( ) ;
598
+
599
+ await clientHasCancelledSyncPoint . WaitToContinue ( ) . DefaultTimeout ( ) ;
600
+
601
+ // Assert
602
+ await cancelledTcs . Task . DefaultTimeout ( ) ;
603
+
604
+ var serverWriteTask = await readAsyncTask . Task . DefaultTimeout ( ) ;
605
+
606
+ await Assert . ThrowsAnyAsync < Exception > ( ( ) => serverWriteTask ) . DefaultTimeout ( ) ;
607
+
608
+ await host . StopAsync ( ) . DefaultTimeout ( ) ;
609
+ }
610
+
611
+ // Ensure this log wasn't written:
612
+ // Critical: Http3OutputProducer.ProcessDataWrites observed an unexpected exception.
613
+ var badLogWrite = TestSink . Writes . FirstOrDefault ( w => w . LogLevel == LogLevel . Critical ) ;
614
+ if ( badLogWrite != null )
615
+ {
616
+ Assert . True ( false , "Bad log write: " + badLogWrite + Environment . NewLine + badLogWrite . Exception ) ;
617
+ }
618
+ }
619
+
620
+ // Verify HTTP/2 and HTTP/3 match behavior
621
+ [ ConditionalTheory ]
622
+ [ MsQuicSupported ]
623
+ [ InlineData ( HttpProtocols . Http3 , Skip = "https://github.com/dotnet/runtime/issues/56129" ) ]
624
+ [ InlineData ( HttpProtocols . Http2 ) ]
625
+ public async Task GET_ClientCancellationAfterResponseHeaders_RequestAbortRaised ( HttpProtocols protocol )
626
+ {
627
+ // Arrange
628
+ var cancelledTcs = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
629
+
630
+ var builder = CreateHostBuilder ( async context =>
631
+ {
632
+ context . RequestAborted . Register ( ( ) =>
633
+ {
634
+ Logger . LogInformation ( "Server received request aborted." ) ;
635
+ cancelledTcs . SetResult ( ) ;
636
+ } ) ;
637
+
638
+ var responseBody = context . Response . Body ;
639
+ await responseBody . WriteAsync ( TestData ) ;
640
+ await responseBody . FlushAsync ( ) ;
641
+
642
+ // Wait for task cancellation
643
+ await cancelledTcs . Task ;
644
+ } , protocol : protocol ) ;
645
+
646
+ var httpClientHandler = new HttpClientHandler ( ) ;
647
+ httpClientHandler . ServerCertificateCustomValidationCallback = HttpClientHandler . DangerousAcceptAnyServerCertificateValidator ;
648
+
649
+ using ( var host = builder . Build ( ) )
650
+ using ( var client = new HttpClient ( httpClientHandler ) )
651
+ {
652
+ await host . StartAsync ( ) . DefaultTimeout ( ) ;
653
+
654
+ var request = new HttpRequestMessage ( HttpMethod . Get , $ "https://127.0.0.1:{ host . GetPort ( ) } /") ;
655
+ request . Version = GetProtocol ( protocol ) ;
656
+ request . VersionPolicy = HttpVersionPolicy . RequestVersionExact ;
657
+
658
+ // Act
659
+ var response = await client . SendAsync ( request , HttpCompletionOption . ResponseHeadersRead ) ;
660
+
661
+ var responseStream = await response . Content . ReadAsStreamAsync ( ) . DefaultTimeout ( ) ;
662
+
663
+ var data = await responseStream . ReadAtLeastLengthAsync ( TestData . Length ) . DefaultTimeout ( ) ;
664
+
665
+ Logger . LogInformation ( "Client canceled request." ) ;
666
+ response . Dispose ( ) ;
667
+
668
+ // Assert
669
+ await cancelledTcs . Task . DefaultTimeout ( ) ;
670
+
671
+ await host . StopAsync ( ) . DefaultTimeout ( ) ;
672
+ }
673
+ }
674
+
517
675
[ ConditionalFact ]
518
676
[ MsQuicSupported ]
519
677
public async Task StreamResponseContent_DelayAndTrailers_ClientSuccess ( )
0 commit comments