2828
2929import org .apache .hc .client5 .http .async .methods .SimpleHttpRequest ;
3030import org .apache .hc .client5 .http .config .ConnectionConfig ;
31+ import org .apache .hc .client5 .http .config .ConnectionConfig .Builder ;
3132import org .apache .hc .client5 .http .config .TlsConfig ;
3233import org .apache .hc .client5 .http .impl .async .CloseableHttpAsyncClient ;
3334import org .apache .hc .client5 .http .impl .async .HttpAsyncClientBuilder ;
3637import org .apache .hc .client5 .http .ssl .DefaultClientTlsStrategy ;
3738import org .apache .hc .client5 .testing .SSLTestContexts ;
3839import org .apache .hc .client5 .testing .tls .TlsHandshakeTimeoutServer ;
40+ import org .apache .hc .core5 .http2 .HttpVersionPolicy ;
3941import org .apache .hc .core5 .reactor .IOReactorConfig ;
4042import org .apache .hc .core5 .util .TimeValue ;
41- import org .junit .jupiter .api .Tag ;
4243import org .junit .jupiter .api .Timeout ;
4344import org .junit .jupiter .params .ParameterizedTest ;
44- import org .junit .jupiter .params .provider .ValueSource ;
45+ import org .junit .jupiter .params .provider .CsvSource ;
4546
46- import java .time .Duration ;
47- import java .time .temporal .ChronoUnit ;
4847import java .util .concurrent .ExecutionException ;
48+ import java .util .concurrent .atomic .AtomicReference ;
4949
50- import static org .apache .hc .core5 .util .ReflectionUtils .determineJRELevel ;
51- import static java .lang .String .format ;
5250import static java .util .concurrent .TimeUnit .MILLISECONDS ;
5351import static java .util .concurrent .TimeUnit .SECONDS ;
52+ import static org .apache .hc .core5 .http2 .HttpVersionPolicy .FORCE_HTTP_2 ;
5453import static org .junit .jupiter .api .Assertions .assertThrows ;
5554import static org .junit .jupiter .api .Assertions .assertTrue ;
5655import static org .junit .jupiter .api .Assumptions .assumeFalse ;
5756
5857public class TestAsyncTlsHandshakeTimeout {
59- private static final Duration EXPECTED_TIMEOUT = Duration .ofMillis (500 );
60-
61- @ Tag ("slow" )
6258 @ Timeout (5 )
6359 @ ParameterizedTest
64- @ ValueSource (strings = { "false" , "true" })
65- void testTimeout (final boolean sendServerHello ) throws Exception {
66- // There is a bug in Java 11: after the handshake times out, the SSLSocket implementation performs a blocking
67- // read on the socket to wait for close_notify or alert. This operation blocks until the read times out,
68- // which means that TLS handshakes take twice as long to time out on Java 11. Without a workaround, the only
69- // option is to skip the timeout duration assertions on Java 11.
70- assumeFalse (determineJRELevel () == 11 , "TLS handshake timeouts are buggy on Java 11" );
60+ @ CsvSource ({
61+ "10,0,false," ,
62+ "10,0,true," ,
63+ "0,10,false," ,
64+ "0,10,true," ,
65+ // handshakeTimeout overrides connectTimeout
66+ "30000,10,false," ,
67+ "30000,10,true," ,
68+ // ALPN and HTTP/2
69+ "0,10,false,FORCE_HTTP_2" ,
70+ "0,10,true,FORCE_HTTP_2" ,
71+ "0,10,false,NEGOTIATE" ,
72+ "0,10,true,NEGOTIATE" ,
73+ })
74+ void testTimeout (
75+ final int connTimeout ,
76+ final int handshakeTimeout ,
77+ final boolean sendServerHello ,
78+ final HttpVersionPolicy httpVersionPolicy
79+ ) throws Exception {
80+ final Builder connectionConfig = ConnectionConfig .custom ().setSocketTimeout (300 , SECONDS );
81+ final TlsConfig .Builder tlsConfig = TlsConfig .custom ();
82+ if (connTimeout > 0 ) {
83+ connectionConfig .setConnectTimeout (connTimeout , MILLISECONDS );
84+ }
85+ if (handshakeTimeout > 0 ) {
86+ tlsConfig .setHandshakeTimeout (handshakeTimeout , MILLISECONDS );
87+ }
88+ if (httpVersionPolicy != null ) {
89+ tlsConfig .setVersionPolicy (httpVersionPolicy );
90+ }
7191
92+ final AtomicReference <Exception > uncaughtException = new AtomicReference <>();
7293 final PoolingAsyncClientConnectionManager connMgr = PoolingAsyncClientConnectionManagerBuilder .create ()
73- .setDefaultConnectionConfig (ConnectionConfig .custom ()
74- .setConnectTimeout (5 , SECONDS )
75- .setSocketTimeout (5 , SECONDS )
76- .build ())
94+ .setDefaultConnectionConfig (connectionConfig .build ())
7795 .setTlsStrategy (new DefaultClientTlsStrategy (SSLTestContexts .createClientSSLContext ()))
78- .setDefaultTlsConfig (TlsConfig .custom ()
79- .setHandshakeTimeout (EXPECTED_TIMEOUT .toMillis (), MILLISECONDS )
80- .build ())
96+ .setDefaultTlsConfig (tlsConfig .build ())
8197 .build ();
8298 try (
8399 final TlsHandshakeTimeoutServer server = new TlsHandshakeTimeoutServer (sendServerHello );
84100 final CloseableHttpAsyncClient client = HttpAsyncClientBuilder .create ()
101+ .setIoReactorExceptionCallback (uncaughtException ::set )
85102 .setIOReactorConfig (IOReactorConfig .custom ()
86- .setSelectInterval (TimeValue .ofMilliseconds (50 ))
103+ .setSelectInterval (TimeValue .ofMilliseconds (10 ))
87104 .build ())
88105 .setConnectionManager (connMgr )
89106 .build ()
@@ -94,21 +111,16 @@ void testTimeout(final boolean sendServerHello) throws Exception {
94111 final SimpleHttpRequest request = SimpleHttpRequest .create ("GET" , "https://127.0.0.1:" + server .getPort ());
95112 assertTimeout (request , client );
96113 }
114+ final Exception ex = uncaughtException .get ();
115+ if (ex != null ) {
116+ assumeFalse (httpVersionPolicy == FORCE_HTTP_2 , "Known bug" );
117+ throw ex ;
118+ }
97119 }
98120
99121 private static void assertTimeout (final SimpleHttpRequest request , final CloseableHttpAsyncClient client ) {
100- final long startTime = System .nanoTime ();
101122 final Throwable ex = assertThrows (ExecutionException .class ,
102123 () -> client .execute (request , null ).get ()).getCause ();
103- final Duration actualTime = Duration .of (System .nanoTime () - startTime , ChronoUnit .NANOS );
104- assertTrue (actualTime .toMillis () > EXPECTED_TIMEOUT .toMillis () / 2 ,
105- format ("Handshake attempt timed out too soon (only %,d out of %,d ms)" ,
106- actualTime .toMillis (),
107- EXPECTED_TIMEOUT .toMillis ()));
108- assertTrue (actualTime .toMillis () < EXPECTED_TIMEOUT .toMillis () * 2 ,
109- format ("Handshake attempt timed out too late (%,d out of %,d ms)" ,
110- actualTime .toMillis (),
111- EXPECTED_TIMEOUT .toMillis ()));
112- assertTrue (ex .getMessage ().contains (EXPECTED_TIMEOUT .toMillis () + " MILLISECONDS" ), ex .getMessage ());
124+ assertTrue (ex .getMessage ().contains ("10 MILLISECONDS" ), ex .getMessage ());
113125 }
114126}
0 commit comments