@@ -288,6 +288,42 @@ void testGracefulCloseEndsStreamAfterDrain() throws Exception {
288288 Assertions .assertTrue (session .isOpen (), "Ending tunnel stream must not close physical HTTP/2 connection" );
289289 }
290290
291+ @ Test
292+ void testStreamEndAfterEstablishedTunnelDoesNotFireFailure () {
293+ final ScriptedProxySession session = new ScriptedProxySession (HttpStatus .SC_OK , true , false , false , false , true );
294+ final RecordingCallback <ProtocolIOSession > callback = new RecordingCallback <>();
295+
296+ H2OverH2TunnelSupport .establish (
297+ session ,
298+ new HttpHost ("http" , "example.org" , 80 ),
299+ Timeout .ofSeconds (1 ),
300+ false ,
301+ null ,
302+ callback );
303+
304+ Assertions .assertTrue (callback .completed , "Tunnel should complete successfully" );
305+ Assertions .assertNull (callback .failed , "streamEnd after established tunnel must not overwrite with failure" );
306+ }
307+
308+ @ Test
309+ void testStreamEndBeforeTunnelEstablishedFiresFailure () {
310+ // 200 but no entity details -> consumeResponse throws -> fail() fires
311+ // then streamEnd arrives on the already-failed handler
312+ final ScriptedProxySession session = new ScriptedProxySession (HttpStatus .SC_OK , false , false , false , false , true );
313+ final RecordingCallback <ProtocolIOSession > callback = new RecordingCallback <>();
314+
315+ H2OverH2TunnelSupport .establish (
316+ session ,
317+ new HttpHost ("http" , "example.org" , 80 ),
318+ Timeout .ofSeconds (1 ),
319+ false ,
320+ null ,
321+ callback );
322+
323+ Assertions .assertFalse (callback .completed );
324+ Assertions .assertNotNull (callback .failed , "streamEnd with no tunnel must report failure" );
325+ }
326+
291327 static class RecordingCallback <T > implements FutureCallback <T > {
292328
293329 volatile boolean completed ;
@@ -389,6 +425,7 @@ static class ScriptedProxySession implements IOSession {
389425 private final boolean signalCapacity ;
390426 private final boolean emitInputData ;
391427 private final boolean provideStreamControl ;
428+ private final boolean sendStreamEnd ;
392429
393430 private final Lock lock ;
394431 private Timeout socketTimeout ;
@@ -401,11 +438,11 @@ static class ScriptedProxySession implements IOSession {
401438 final AtomicBoolean endStreamCalled ;
402439
403440 ScriptedProxySession (final int responseCode , final boolean withTunnelStream ) {
404- this (responseCode , withTunnelStream , false , false , false );
441+ this (responseCode , withTunnelStream , false , false , false , false );
405442 }
406443
407444 ScriptedProxySession (final int responseCode , final boolean withTunnelStream , final boolean signalCapacity ) {
408- this (responseCode , withTunnelStream , signalCapacity , false , false );
445+ this (responseCode , withTunnelStream , signalCapacity , false , false , false );
409446 }
410447
411448 ScriptedProxySession (
@@ -414,11 +451,22 @@ static class ScriptedProxySession implements IOSession {
414451 final boolean signalCapacity ,
415452 final boolean emitInputData ,
416453 final boolean provideStreamControl ) {
454+ this (responseCode , withTunnelStream , signalCapacity , emitInputData , provideStreamControl , false );
455+ }
456+
457+ ScriptedProxySession (
458+ final int responseCode ,
459+ final boolean withTunnelStream ,
460+ final boolean signalCapacity ,
461+ final boolean emitInputData ,
462+ final boolean provideStreamControl ,
463+ final boolean sendStreamEnd ) {
417464 this .responseCode = responseCode ;
418465 this .withTunnelStream = withTunnelStream ;
419466 this .signalCapacity = signalCapacity ;
420467 this .emitInputData = emitInputData ;
421468 this .provideStreamControl = provideStreamControl ;
469+ this .sendStreamEnd = sendStreamEnd ;
422470
423471 this .lock = new ReentrantLock ();
424472 this .socketTimeout = Timeout .ofSeconds (30 );
@@ -511,6 +559,10 @@ public void endStream(final java.util.List<? extends org.apache.hc.core5.http.He
511559 exchangeHandler .consume (ByteBuffer .wrap (new byte []{1 , 2 , 3 }));
512560 }
513561 }
562+
563+ if (sendStreamEnd ) {
564+ exchangeHandler .streamEnd (null );
565+ }
514566 } catch (final Exception ex ) {
515567 exchangeHandler .failed (ex );
516568 }
0 commit comments