| /* | |
| * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| /* | |
| * @test | |
| * @bug 8288717 | |
| * @summary Tests that when the idleConnectionTimeoutEvent is configured in HTTP/2, | |
| * an HTTP/2 connection will close within the specified interval if there | |
| * are no active streams on the connection. | |
| * @library /test/lib /test/jdk/java/net/httpclient/lib | |
| * @build jdk.httpclient.test.lib.http2.Http2TestServer | |
| * | |
| * @run testng/othervm -Djdk.httpclient.HttpClient.log=errors -Djdk.httpclient.keepalive.timeout=1 | |
| * IdleConnectionTimeoutTest | |
| * @run testng/othervm -Djdk.httpclient.HttpClient.log=errors -Djdk.httpclient.keepalive.timeout=2 | |
| * IdleConnectionTimeoutTest | |
| * | |
| * @run testng/othervm -Djdk.httpclient.HttpClient.log=errors -Djdk.httpclient.keepalive.timeout.h2=1 | |
| * IdleConnectionTimeoutTest | |
| * @run testng/othervm -Djdk.httpclient.HttpClient.log=errors -Djdk.httpclient.keepalive.timeout.h2=2 | |
| * IdleConnectionTimeoutTest | |
| * | |
| * @run testng/othervm -Djdk.httpclient.HttpClient.log=errors -Djdk.httpclient.keepalive.timeout.h2=1 | |
| * -Djdk.httpclient.keepalive.timeout=2 | |
| * IdleConnectionTimeoutTest | |
| * | |
| * @run testng/othervm -Djdk.httpclient.HttpClient.log=errors IdleConnectionTimeoutTest | |
| * @run testng/othervm -Djdk.httpclient.HttpClient.log=errors -Djdk.httpclient.keepalive.timeout.h2=-1 | |
| * IdleConnectionTimeoutTest | |
| * @run testng/othervm -Djdk.httpclient.HttpClient.log=errors,trace -Djdk.httpclient.keepalive.timeout.h2=abc | |
| * IdleConnectionTimeoutTest | |
| */ | |
| import jdk.httpclient.test.lib.http2.BodyOutputStream; | |
| import jdk.httpclient.test.lib.http2.Http2TestExchangeImpl; | |
| import jdk.httpclient.test.lib.http2.Http2TestServerConnection; | |
| import jdk.internal.net.http.common.HttpHeadersBuilder; | |
| import org.testng.annotations.BeforeTest; | |
| import org.testng.annotations.Test; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.io.PrintStream; | |
| import java.net.URI; | |
| import java.net.http.HttpClient; | |
| import java.net.http.HttpHeaders; | |
| import java.net.http.HttpRequest; | |
| import java.net.http.HttpResponse; | |
| import java.util.concurrent.CompletableFuture; | |
| import jdk.httpclient.test.lib.http2.Http2TestServer; | |
| import jdk.httpclient.test.lib.http2.Http2TestExchange; | |
| import jdk.httpclient.test.lib.http2.Http2Handler; | |
| import javax.net.ssl.SSLSession; | |
| import static java.nio.charset.StandardCharsets.UTF_8; | |
| import static java.net.http.HttpClient.Version.HTTP_2; | |
| import static org.testng.Assert.assertEquals; | |
| public class IdleConnectionTimeoutTest { | |
| static Http2TestServer http2TestServer; | |
| URI timeoutUri; | |
| URI noTimeoutUri; | |
| final String IDLE_CONN_PROPERTY = "jdk.httpclient.keepalive.timeout.h2"; | |
| final String KEEP_ALIVE_PROPERTY = "jdk.httpclient.keepalive.timeout"; | |
| final String TIMEOUT_PATH = "/serverTimeoutHandler"; | |
| final String NO_TIMEOUT_PATH = "/noServerTimeoutHandler"; | |
| static final PrintStream testLog = System.err; | |
| @BeforeTest | |
| public void setup() throws Exception { | |
| http2TestServer = new Http2TestServer(false, 0); | |
| http2TestServer.addHandler(new ServerTimeoutHandler(), TIMEOUT_PATH); | |
| http2TestServer.addHandler(new ServerNoTimeoutHandler(), NO_TIMEOUT_PATH); | |
| http2TestServer.setExchangeSupplier(TestExchangeSupplier::new); | |
| http2TestServer.start(); | |
| int port = http2TestServer.getAddress().getPort(); | |
| timeoutUri = new URI("http://localhost:" + port + TIMEOUT_PATH); | |
| noTimeoutUri = new URI("http://localhost:" + port + NO_TIMEOUT_PATH); | |
| } | |
| /* | |
| If the InetSocketAddress of the first remote connection is not equal to the address of the | |
| second remote connection, then the idleConnectionTimeoutEvent has occurred and a new connection | |
| was made to carry out the second request by the client. | |
| */ | |
| @Test | |
| public void test() throws InterruptedException { | |
| String timeoutVal = System.getProperty(IDLE_CONN_PROPERTY); | |
| String keepAliveVal = System.getProperty(KEEP_ALIVE_PROPERTY); | |
| testLog.println("Test run for " + IDLE_CONN_PROPERTY + "=" + timeoutVal); | |
| int sleepTime = 0; | |
| HttpClient hc = HttpClient.newBuilder().version(HTTP_2).build(); | |
| HttpRequest hreq; | |
| HttpResponse<String> hresp; | |
| if (timeoutVal != null) { | |
| if (keepAliveVal != null) { | |
| // In this case, specified h2 timeout should override keep alive timeout. | |
| // Timeout should occur | |
| hreq = HttpRequest.newBuilder(timeoutUri).version(HTTP_2).GET().build(); | |
| sleepTime = 2000; | |
| hresp = runRequest(hc, hreq, sleepTime); | |
| assertEquals(hresp.statusCode(), 200, "idleConnectionTimeoutEvent was expected but did not occur"); | |
| } else if (timeoutVal.equals("1")) { | |
| // Timeout should occur | |
| hreq = HttpRequest.newBuilder(timeoutUri).version(HTTP_2).GET().build(); | |
| sleepTime = 2000; | |
| hresp = runRequest(hc, hreq, sleepTime); | |
| assertEquals(hresp.statusCode(), 200, "idleConnectionTimeoutEvent was expected but did not occur"); | |
| } else if (timeoutVal.equals("2")) { | |
| // Timeout should not occur | |
| hreq = HttpRequest.newBuilder(noTimeoutUri).version(HTTP_2).GET().build(); | |
| sleepTime = 1000; | |
| hresp = runRequest(hc, hreq, sleepTime); | |
| assertEquals(hresp.statusCode(), 200, "idleConnectionTimeoutEvent was not expected but occurred"); | |
| } else if (timeoutVal.equals("abc") || timeoutVal.equals("-1")) { | |
| // Timeout should not occur | |
| hreq = HttpRequest.newBuilder(noTimeoutUri).version(HTTP_2).GET().build(); | |
| hresp = runRequest(hc, hreq, sleepTime); | |
| assertEquals(hresp.statusCode(), 200, "idleConnectionTimeoutEvent was not expected but occurred"); | |
| } | |
| } else { | |
| // When no value is specified then no timeout should occur (default keep alive value of 600 used) | |
| hreq = HttpRequest.newBuilder(noTimeoutUri).version(HTTP_2).GET().build(); | |
| hresp = runRequest(hc, hreq, sleepTime); | |
| assertEquals(hresp.statusCode(), 200, "idleConnectionTimeoutEvent should not occur, no value was specified for this property"); | |
| } | |
| } | |
| private HttpResponse<String> runRequest(HttpClient hc, HttpRequest req, int sleepTime) throws InterruptedException { | |
| CompletableFuture<HttpResponse<String>> request = hc.sendAsync(req, HttpResponse.BodyHandlers.ofString(UTF_8)); | |
| HttpResponse<String> hresp = request.join(); | |
| assertEquals(hresp.statusCode(), 200); | |
| Thread.sleep(sleepTime); | |
| request = hc.sendAsync(req, HttpResponse.BodyHandlers.ofString(UTF_8)); | |
| return request.join(); | |
| } | |
| static class ServerTimeoutHandler implements Http2Handler { | |
| volatile Object firstConnection = null; | |
| @Override | |
| public void handle(Http2TestExchange exchange) throws IOException { | |
| if (exchange instanceof TestExchangeSupplier exch) { | |
| if (firstConnection == null) { | |
| firstConnection = exch.getTestConnection(); | |
| exch.sendResponseHeaders(200, 0); | |
| } else { | |
| var secondConnection = exch.getTestConnection(); | |
| if (firstConnection != secondConnection) { | |
| testLog.println("ServerTimeoutHandler: New Connection was used, idleConnectionTimeoutEvent fired." | |
| + " First Connection Hash: " + firstConnection + ", Second Connection Hash: " + secondConnection); | |
| exch.sendResponseHeaders(200, 0); | |
| } else { | |
| testLog.println("ServerTimeoutHandler: Same Connection was used, idleConnectionTimeoutEvent did not fire." | |
| + " First Connection Hash: " + firstConnection + ", Second Connection Hash: " + secondConnection); | |
| exch.sendResponseHeaders(400, 0); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| static class ServerNoTimeoutHandler implements Http2Handler { | |
| volatile Object firstConnection = null; | |
| @Override | |
| public void handle(Http2TestExchange exchange) throws IOException { | |
| if (exchange instanceof TestExchangeSupplier exch) { | |
| if (firstConnection == null) { | |
| firstConnection = exch.getTestConnection(); | |
| exch.sendResponseHeaders(200, 0); | |
| } else { | |
| var secondConnection = exch.getTestConnection(); | |
| if (firstConnection == secondConnection) { | |
| testLog.println("ServerTimeoutHandler: Same Connection was used, idleConnectionTimeoutEvent did not fire." | |
| + " First Connection Hash: " + firstConnection + ", Second Connection Hash: " + secondConnection); | |
| exch.sendResponseHeaders(200, 0); | |
| } else { | |
| testLog.println("ServerTimeoutHandler: Different Connection was used, idleConnectionTimeoutEvent fired." | |
| + " First Connection Hash: " + firstConnection + ", Second Connection Hash: " + secondConnection); | |
| exch.sendResponseHeaders(400, 0); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| static class TestExchangeSupplier extends Http2TestExchangeImpl { | |
| public TestExchangeSupplier(int streamid, String method, HttpHeaders reqheaders, HttpHeadersBuilder rspheadersBuilder, URI uri, InputStream is, SSLSession sslSession, BodyOutputStream os, Http2TestServerConnection conn, boolean pushAllowed) { | |
| super(streamid, method, reqheaders, rspheadersBuilder, uri, is, sslSession, os, conn, pushAllowed); | |
| } | |
| public Http2TestServerConnection getTestConnection() { | |
| return this.conn; | |
| } | |
| } | |
| } |