/* * Copyright (c) 2011-2013 The original author or authors * ------------------------------------------------------ * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. */ package io.vertx.test.core; import io.netty.channel.Channel; import io.netty.channel.socket.nio.NioSocketChannel; import io.vertx.core.Context; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.Http2Settings; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpConnection; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.http.HttpServerResponse; import io.vertx.core.http.HttpVersion; import io.vertx.core.http.StreamResetException; import io.vertx.core.http.impl.Http2ServerConnection; import io.vertx.core.net.OpenSSLEngineOptions; import io.vertx.test.core.tls.Cert; import org.junit.Test; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; /** * @author <a href="mailto:julien@julienviet.com">Julien Viet</a> */ public class Http2Test extends HttpTest { @Override public void setUp() throws Exception { super.setUp(); client = vertx.createHttpClient(createBaseClientOptions()); server = vertx.createHttpServer(createBaseServerOptions().setHandle100ContinueAutomatically(true)); } @Override protected HttpServerOptions createBaseServerOptions() { return Http2TestBase.createHttp2ServerOptions(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST); } @Override protected HttpClientOptions createBaseClientOptions() { return Http2TestBase.createHttp2ClientOptions(); } @Override public void testCloseHandlerNotCalledWhenConnectionClosedAfterEnd() throws Exception { testCloseHandlerNotCalledWhenConnectionClosedAfterEnd(1); } // Extra test @Test public void testServerResponseWriteBufferFromOtherThread() throws Exception { server.requestHandler(req -> { runAsync(() -> { req.response().write("hello ").end("world"); }); }).listen(onSuccess(v -> { client.get(8080, "localhost", "/somepath", resp -> { assertEquals(200, resp.statusCode()); resp.bodyHandler(buff -> { assertEquals(Buffer.buffer("hello world"), buff); testComplete(); }); }).exceptionHandler(this::fail).end(); })); await(); } @Test public void testServerResponseResetFromOtherThread() throws Exception { server.requestHandler(req -> { runAsync(() -> { req.response().reset(0); }); }).listen(onSuccess(v -> { client.get(8080, "localhost", "/somepath", resp -> { fail(); }).exceptionHandler(err -> { assertTrue(err instanceof StreamResetException); testComplete(); }).sendHead(); })); await(); } void runAsync(Runnable runnable) { new Thread(() -> { try { runnable.run(); } catch (Exception e) { fail(e); } }).start(); } @Test public void testClientRequestWriteFromOtherThread() throws Exception { CountDownLatch latch1 = new CountDownLatch(1); CountDownLatch latch2 = new CountDownLatch(1); server.requestHandler(req -> { latch2.countDown(); req.endHandler(v -> { req.response().end(); }); }).listen(onSuccess(v -> { latch1.countDown(); })); awaitLatch(latch1); HttpClientRequest req = client.get(8080, "localhost", "/somepath", resp -> { assertEquals(200, resp.statusCode()); testComplete(); }).setChunked(true).sendHead(); awaitLatch(latch2); // The next write won't be buffered req.write("hello ").end("world"); await(); } @Test public void testServerOpenSSL() throws Exception { HttpServerOptions opts = new HttpServerOptions() .setPort(DEFAULT_HTTPS_PORT) .setHost(DEFAULT_HTTPS_HOST) .setUseAlpn(true) .setSsl(true) .addEnabledCipherSuite("TLS_RSA_WITH_AES_128_CBC_SHA") // Non Diffie-helman -> debuggable in wireshark .setPemKeyCertOptions(Cert.SERVER_PEM.get()).setSslEngineOptions(new OpenSSLEngineOptions()); server.close(); client.close(); client = vertx.createHttpClient(createBaseClientOptions()); server = vertx.createHttpServer(opts); server.requestHandler(req -> { req.response().end(); }); CountDownLatch latch = new CountDownLatch(1); System.out.println("starting"); try { server.listen(onSuccess(v -> latch.countDown())); } catch (Throwable e) { e.printStackTrace(); } System.out.println("listening"); awaitLatch(latch); client.get(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/somepath", resp -> { assertEquals(200, resp.statusCode()); testComplete(); }).exceptionHandler(this::fail).end(); await(); } @Test public void testServerStreamPausedWhenConnectionIsPaused() throws Exception { CountDownLatch fullLatch = new CountDownLatch(1); CompletableFuture<Void> resumeLatch = new CompletableFuture<>(); server.requestHandler(req -> { HttpServerResponse resp = req.response(); switch (req.path()) { case "/0": { vertx.setPeriodic(1, timerID -> { if (resp.writeQueueFull()) { vertx.cancelTimer(timerID); fullLatch.countDown(); } else { resp.write(Buffer.buffer(TestUtils.randomAlphaString(512))); } }); break; } case "/1": { assertTrue(resp.writeQueueFull()); resp.drainHandler(v -> { resp.end(); }); resumeLatch.complete(null); break; } } }); startServer(); client.getNow(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, "/0", resp -> { resp.pause(); Context ctx = vertx.getOrCreateContext(); resumeLatch.thenAccept(v1 -> { ctx.runOnContext(v2 -> { resp.endHandler(v -> { testComplete(); }); resp.resume(); }); }); }); awaitLatch(fullLatch); client.getNow(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, "/1", resp -> { resp.endHandler(v -> { complete(); }); }); resumeLatch.get(20, TimeUnit.SECONDS); // Make sure it completes await(); } @Test public void testClientStreamPausedWhenConnectionIsPaused() throws Exception { waitFor(2); Buffer buffer = TestUtils.randomBuffer(512); CompletableFuture<Void> resumeLatch = new CompletableFuture<>(); server.requestHandler(req -> { switch (req.path()) { case "/0": { req.pause(); resumeLatch.thenAccept(v -> { req.resume(); }); req.endHandler(v -> { req.response().end(); }); break; } case "/1": { req.bodyHandler(v -> { assertEquals(v, buffer); req.response().end(); }); break; } } }); startServer(); HttpClientRequest req1 = client.get(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, "/0", resp -> { complete(); }).setChunked(true); while (!req1.writeQueueFull()) { req1.write(Buffer.buffer(TestUtils.randomAlphaString(512))); Thread.sleep(1); } HttpClientRequest req2 = client.get(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, "/1", resp -> { complete(); }).setChunked(true); assertFalse(req2.writeQueueFull()); req2.sendHead(v -> { assertTrue(req2.writeQueueFull()); resumeLatch.complete(null); }); resumeLatch.get(20, TimeUnit.SECONDS); assertWaitUntil(() -> !req2.writeQueueFull()); req1.end(); req2.end(buffer); await(); } @Test public void testResetClientRequestNotYetSent() throws Exception { waitFor(2); server.close(); server = vertx.createHttpServer(createBaseServerOptions().setInitialSettings(new Http2Settings().setMaxConcurrentStreams(1))); AtomicInteger numReq = new AtomicInteger(); server.requestHandler(req -> { assertEquals(0, numReq.getAndIncrement()); req.response().end(); complete(); }); startServer(); HttpClientRequest post = client.post(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, DEFAULT_TEST_URI, resp -> { fail(); }); post.setChunked(true).write(TestUtils.randomBuffer(1024)); assertTrue(post.reset()); client.getNow(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, DEFAULT_TEST_URI, resp -> { assertEquals(1, numReq.get()); complete(); }); await(); } @Test public void testDiscardConnectionWhenChannelBecomesInactive() throws Exception { AtomicInteger count = new AtomicInteger(); server.requestHandler(req -> { if (count.getAndIncrement() == 0) { Http2ServerConnection a = (Http2ServerConnection) req.connection(); NioSocketChannel channel = (NioSocketChannel) a.channel(); channel.shutdown(); } else { req.response().end(); } }); startServer(); AtomicBoolean closed = new AtomicBoolean(); client.get(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, DEFAULT_TEST_URI, resp -> { fail(); }).connectionHandler(conn -> conn.closeHandler(v -> closed.set(true))).end(); assertWaitUntil(closed::get); client.get(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, DEFAULT_TEST_URI, resp -> { testComplete(); }).exceptionHandler(err -> { fail(); }).end(); await(); } }