io.vertx.test.core.Http2ServerTest.java Source code

Java tutorial

Introduction

Here is the source code for io.vertx.test.core.Http2ServerTest.java

Source

/*
 * 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.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http2.AbstractHttp2ConnectionHandlerBuilder;
import io.netty.handler.codec.http2.DefaultHttp2Connection;
import io.netty.handler.codec.http2.DefaultHttp2Headers;
import io.netty.handler.codec.http2.Http2Connection;
import io.netty.handler.codec.http2.Http2ConnectionDecoder;
import io.netty.handler.codec.http2.Http2ConnectionEncoder;
import io.netty.handler.codec.http2.Http2ConnectionHandler;
import io.netty.handler.codec.http2.Http2Error;
import io.netty.handler.codec.http2.Http2EventAdapter;
import io.netty.handler.codec.http2.Http2Exception;
import io.netty.handler.codec.http2.Http2Flags;
import io.netty.handler.codec.http2.Http2FrameAdapter;
import io.netty.handler.codec.http2.Http2Headers;
import io.netty.handler.codec.http2.Http2Settings;
import io.netty.handler.codec.http2.Http2Stream;
import io.netty.handler.ssl.ApplicationProtocolNames;
import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;
import io.netty.handler.ssl.SslHandler;
import io.vertx.core.AsyncResult;
import io.vertx.core.Context;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpConnection;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.http.HttpVersion;
import io.vertx.core.http.StreamResetException;
import io.vertx.core.http.impl.HttpUtils;
import io.vertx.core.impl.VertxInternal;
import io.vertx.core.net.NetSocket;
import io.vertx.core.net.impl.SSLHelper;
import io.vertx.core.streams.ReadStream;
import io.vertx.core.streams.WriteStream;
import io.vertx.test.core.tls.Trust;
import org.junit.Test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ClosedChannelException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;

import static io.vertx.test.core.TestUtils.assertIllegalStateException;

/**
 * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
 */
public class Http2ServerTest extends Http2TestBase {

    private static Http2Headers headers(String method, String scheme, String path) {
        return new DefaultHttp2Headers().method(method).scheme(scheme).path(path);
    }

    private static Http2Headers GET(String scheme, String path) {
        return headers("GET", scheme, path);
    }

    private static Http2Headers GET(String path) {
        return headers("GET", "https", path);
    }

    private static Http2Headers POST(String path) {
        return headers("POST", "https", path);
    }

    class TestClient {

        final Http2Settings settings = new Http2Settings();

        public class Connection {
            public final Channel channel;
            public final ChannelHandlerContext context;
            public final Http2Connection connection;
            public final Http2ConnectionEncoder encoder;
            public final Http2ConnectionDecoder decoder;

            public Connection(ChannelHandlerContext context, Http2Connection connection,
                    Http2ConnectionEncoder encoder, Http2ConnectionDecoder decoder) {
                this.channel = context.channel();
                this.context = context;
                this.connection = connection;
                this.encoder = encoder;
                this.decoder = decoder;
            }

            public int nextStreamId() {
                return connection.local().incrementAndGetNextStreamId();
            }
        }

        class TestClientHandler extends Http2ConnectionHandler {

            private final Consumer<Connection> requestHandler;
            private boolean handled;

            public TestClientHandler(Consumer<Connection> requestHandler, Http2ConnectionDecoder decoder,
                    Http2ConnectionEncoder encoder, Http2Settings initialSettings) {
                super(decoder, encoder, initialSettings);
                this.requestHandler = requestHandler;
            }

            @Override
            public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
                super.handlerAdded(ctx);
                if (ctx.channel().isActive()) {
                    checkHandle(ctx);
                }
            }

            @Override
            public void channelActive(ChannelHandlerContext ctx) throws Exception {
                super.channelActive(ctx);
                checkHandle(ctx);
            }

            private void checkHandle(ChannelHandlerContext ctx) {
                if (!handled) {
                    handled = true;
                    Connection conn = new Connection(ctx, connection(), encoder(), decoder());
                    requestHandler.accept(conn);
                }
            }
        }

        class TestClientHandlerBuilder
                extends AbstractHttp2ConnectionHandlerBuilder<TestClientHandler, TestClientHandlerBuilder> {

            private final Consumer<Connection> requestHandler;

            public TestClientHandlerBuilder(Consumer<Connection> requestHandler) {
                this.requestHandler = requestHandler;
            }

            @Override
            protected TestClientHandler build(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder,
                    Http2Settings initialSettings) throws Exception {
                return new TestClientHandler(requestHandler, decoder, encoder, initialSettings);
            }

            public TestClientHandler build(Http2Connection conn) {
                connection(conn);
                initialSettings(settings);
                frameListener(new Http2EventAdapter() {
                    @Override
                    public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
                            boolean endOfStream) throws Http2Exception {
                        return super.onDataRead(ctx, streamId, data, padding, endOfStream);
                    }
                });
                return super.build();
            }
        }

        protected ChannelInitializer channelInitializer(int port, String host, Consumer<Connection> handler) {
            return new ChannelInitializer<Channel>() {
                @Override
                protected void initChannel(Channel ch) throws Exception {
                    SSLHelper sslHelper = new SSLHelper(new HttpClientOptions().setUseAlpn(true).setSsl(true), null,
                            Trust.SERVER_JKS.get());
                    SslHandler sslHandler = sslHelper
                            .setApplicationProtocols(Arrays.asList(HttpVersion.HTTP_2, HttpVersion.HTTP_1_1))
                            .createSslHandler((VertxInternal) vertx, host, port);
                    ch.pipeline().addLast(sslHandler);
                    ch.pipeline().addLast(new ApplicationProtocolNegotiationHandler("whatever") {
                        @Override
                        protected void configurePipeline(ChannelHandlerContext ctx, String protocol) {
                            if (ApplicationProtocolNames.HTTP_2.equals(protocol)) {
                                ChannelPipeline p = ctx.pipeline();
                                Http2Connection connection = new DefaultHttp2Connection(false);
                                TestClientHandlerBuilder clientHandlerBuilder = new TestClientHandlerBuilder(
                                        handler);
                                TestClientHandler clientHandler = clientHandlerBuilder.build(connection);
                                p.addLast(clientHandler);
                                return;
                            }
                            ctx.close();
                            throw new IllegalStateException("unknown protocol: " + protocol);
                        }
                    });
                }
            };
        }

        public ChannelFuture connect(int port, String host, Consumer<Connection> handler) {
            Bootstrap bootstrap = new Bootstrap();
            NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
            eventLoopGroups.add(eventLoopGroup);
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.group(eventLoopGroup);
            bootstrap.handler(channelInitializer(port, host, handler));
            return bootstrap.connect(new InetSocketAddress(host, port));
        }
    }

    private List<EventLoopGroup> eventLoopGroups = new ArrayList<>();

    @Override
    public void setUp() throws Exception {
        eventLoopGroups.clear();
        super.setUp();
    }

    @Override
    protected void tearDown() throws Exception {
        super.tearDown();
        for (EventLoopGroup eventLoopGroup : eventLoopGroups) {
            eventLoopGroup.shutdownGracefully(0, 10, TimeUnit.SECONDS);
        }
    }

    @Test
    public void testConnectionHandler() throws Exception {
        waitFor(2);
        Context ctx = vertx.getOrCreateContext();
        server.close();
        server.connectionHandler(conn -> {
            assertOnIOContext(ctx);
            complete();
        });
        server.requestHandler(req -> fail());
        startServer(ctx);
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            vertx.runOnContext(v -> {
                complete();
            });
        });
        fut.sync();
        await();
    }

    @Test
    public void testServerInitialSettings() throws Exception {
        io.vertx.core.http.Http2Settings settings = TestUtils.randomHttp2Settings();
        server.close();
        server = vertx.createHttpServer(serverOptions.setInitialSettings(settings));
        server.requestHandler(req -> fail());
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            request.decoder.frameListener(new Http2FrameAdapter() {
                @Override
                public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings newSettings)
                        throws Http2Exception {
                    vertx.runOnContext(v -> {
                        assertEquals((Long) settings.getHeaderTableSize(), newSettings.headerTableSize());
                        assertEquals((Long) settings.getMaxConcurrentStreams(), newSettings.maxConcurrentStreams());
                        assertEquals((Integer) settings.getInitialWindowSize(), newSettings.initialWindowSize());
                        assertEquals((Integer) settings.getMaxFrameSize(), newSettings.maxFrameSize());
                        assertEquals((Long) settings.getMaxHeaderListSize(), newSettings.maxHeaderListSize());
                        assertEquals(settings.get('\u0007'), newSettings.get('\u0007'));
                        testComplete();
                    });
                }
            });
        });
        fut.sync();
        await();
    }

    @Test
    public void testServerSettings() throws Exception {
        waitFor(2);
        io.vertx.core.http.Http2Settings expectedSettings = TestUtils.randomHttp2Settings();
        expectedSettings.setHeaderTableSize((int) io.vertx.core.http.Http2Settings.DEFAULT_HEADER_TABLE_SIZE);
        server.close();
        server = vertx.createHttpServer(serverOptions);
        Context otherContext = vertx.getOrCreateContext();
        server.connectionHandler(conn -> {
            otherContext.runOnContext(v -> {
                conn.updateSettings(expectedSettings, ar -> {
                    assertSame(otherContext, Vertx.currentContext());
                    io.vertx.core.http.Http2Settings ackedSettings = conn.settings();
                    assertEquals(expectedSettings.getMaxHeaderListSize(), ackedSettings.getMaxHeaderListSize());
                    assertEquals(expectedSettings.getMaxFrameSize(), ackedSettings.getMaxFrameSize());
                    assertEquals(expectedSettings.getInitialWindowSize(), ackedSettings.getInitialWindowSize());
                    assertEquals(expectedSettings.getMaxConcurrentStreams(),
                            ackedSettings.getMaxConcurrentStreams());
                    assertEquals(expectedSettings.getHeaderTableSize(), ackedSettings.getHeaderTableSize());
                    assertEquals(expectedSettings.get('\u0007'), ackedSettings.get(7));
                    complete();
                });
            });
        });
        server.requestHandler(req -> {
            fail();
        });
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            request.decoder.frameListener(new Http2FrameAdapter() {
                AtomicInteger count = new AtomicInteger();

                @Override
                public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings newSettings)
                        throws Http2Exception {
                    vertx.runOnContext(v -> {
                        switch (count.getAndIncrement()) {
                        case 0:
                            // Initial settings
                            break;
                        case 1:
                            // Server sent settings
                            assertEquals((Long) expectedSettings.getMaxHeaderListSize(),
                                    newSettings.maxHeaderListSize());
                            assertEquals((Integer) expectedSettings.getMaxFrameSize(), newSettings.maxFrameSize());
                            assertEquals((Integer) expectedSettings.getInitialWindowSize(),
                                    newSettings.initialWindowSize());
                            assertEquals((Long) expectedSettings.getMaxConcurrentStreams(),
                                    newSettings.maxConcurrentStreams());
                            assertEquals(null, newSettings.headerTableSize());
                            complete();
                            break;
                        default:
                            fail();
                        }
                    });
                }
            });
        });
        fut.sync();
        await();
    }

    @Test
    public void testClientSettings() throws Exception {
        Context ctx = vertx.getOrCreateContext();
        io.vertx.core.http.Http2Settings initialSettings = TestUtils.randomHttp2Settings();
        io.vertx.core.http.Http2Settings updatedSettings = TestUtils.randomHttp2Settings();
        Future<Void> settingsRead = Future.future();
        AtomicInteger count = new AtomicInteger();
        server.connectionHandler(conn -> {
            io.vertx.core.http.Http2Settings settings = conn.remoteSettings();
            assertEquals(true, settings.isPushEnabled());

            // Netty bug ?
            // Nothing has been yet received so we should get Integer.MAX_VALUE
            // assertEquals(Integer.MAX_VALUE, settings.getMaxHeaderListSize());

            assertEquals(io.vertx.core.http.Http2Settings.DEFAULT_MAX_FRAME_SIZE, settings.getMaxFrameSize());
            assertEquals(io.vertx.core.http.Http2Settings.DEFAULT_INITIAL_WINDOW_SIZE,
                    settings.getInitialWindowSize());
            assertEquals((Long) (long) Integer.MAX_VALUE, (Long) (long) settings.getMaxConcurrentStreams());
            assertEquals(io.vertx.core.http.Http2Settings.DEFAULT_HEADER_TABLE_SIZE, settings.getHeaderTableSize());
            conn.remoteSettingsHandler(update -> {
                assertOnIOContext(ctx);
                switch (count.getAndIncrement()) {
                case 0:
                    assertEquals(initialSettings.isPushEnabled(), update.isPushEnabled());
                    assertEquals(initialSettings.getMaxHeaderListSize(), update.getMaxHeaderListSize());
                    assertEquals(initialSettings.getMaxFrameSize(), update.getMaxFrameSize());
                    assertEquals(initialSettings.getInitialWindowSize(), update.getInitialWindowSize());
                    assertEquals(initialSettings.getMaxConcurrentStreams(), update.getMaxConcurrentStreams());
                    assertEquals(initialSettings.getHeaderTableSize(), update.getHeaderTableSize());
                    assertEquals(initialSettings.get('\u0007'), update.get(7));
                    settingsRead.complete();
                    break;
                case 1:
                    assertEquals(updatedSettings.isPushEnabled(), update.isPushEnabled());
                    assertEquals(updatedSettings.getMaxHeaderListSize(), update.getMaxHeaderListSize());
                    assertEquals(updatedSettings.getMaxFrameSize(), update.getMaxFrameSize());
                    assertEquals(updatedSettings.getInitialWindowSize(), update.getInitialWindowSize());
                    assertEquals(updatedSettings.getMaxConcurrentStreams(), update.getMaxConcurrentStreams());
                    assertEquals(updatedSettings.getHeaderTableSize(), update.getHeaderTableSize());
                    assertEquals(updatedSettings.get('\u0007'), update.get(7));
                    testComplete();
                    break;
                }
            });
        });
        server.requestHandler(req -> {
            fail();
        });
        startServer(ctx);
        TestClient client = new TestClient();
        client.settings.putAll(HttpUtils.fromVertxSettings(initialSettings));
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            settingsRead.setHandler(ar -> {
                request.encoder.writeSettings(request.context, HttpUtils.fromVertxSettings(updatedSettings),
                        request.context.newPromise());
                request.context.flush();
            });
        });
        fut.sync();
        await();
    }

    @Test
    public void testGet() throws Exception {
        String expected = TestUtils.randomAlphaString(1000);
        AtomicBoolean requestEnded = new AtomicBoolean();
        Context ctx = vertx.getOrCreateContext();
        server.requestHandler(req -> {
            assertOnIOContext(ctx);
            req.endHandler(v -> {
                assertOnIOContext(ctx);
                requestEnded.set(true);
            });
            HttpServerResponse resp = req.response();
            assertEquals(HttpMethod.GET, req.method());
            assertEquals(DEFAULT_HTTPS_HOST_AND_PORT, req.host());
            assertEquals("/", req.path());
            assertEquals(DEFAULT_HTTPS_HOST_AND_PORT, req.getHeader(":authority"));
            assertTrue(req.isSSL());
            assertEquals("https", req.getHeader(":scheme"));
            assertEquals("/", req.getHeader(":path"));
            assertEquals("GET", req.getHeader(":method"));
            assertEquals("foo_request_value", req.getHeader("Foo_request"));
            assertEquals("bar_request_value", req.getHeader("bar_request"));
            assertEquals(2, req.headers().getAll("juu_request").size());
            assertEquals("juu_request_value_1", req.headers().getAll("juu_request").get(0));
            assertEquals("juu_request_value_2", req.headers().getAll("juu_request").get(1));
            assertEquals(Collections.singletonList("cookie_1; cookie_2; cookie_3"), req.headers().getAll("cookie"));
            resp.putHeader("content-type", "text/plain");
            resp.putHeader("Foo_response", "foo_response_value");
            resp.putHeader("bar_response", "bar_response_value");
            resp.putHeader("juu_response",
                    (List<String>) Arrays.asList("juu_response_value_1", "juu_response_value_2"));
            resp.end(expected);
        });
        startServer(ctx);
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            request.decoder.frameListener(new Http2EventAdapter() {
                @Override
                public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
                        int streamDependency, short weight, boolean exclusive, int padding, boolean endStream)
                        throws Http2Exception {
                    vertx.runOnContext(v -> {
                        assertEquals(id, streamId);
                        assertEquals("200", headers.status().toString());
                        assertEquals("text/plain", headers.get("content-type").toString());
                        assertEquals("foo_response_value", headers.get("foo_response").toString());
                        assertEquals("bar_response_value", headers.get("bar_response").toString());
                        assertEquals(2, headers.getAll("juu_response").size());
                        assertEquals("juu_response_value_1", headers.getAll("juu_response").get(0).toString());
                        assertEquals("juu_response_value_2", headers.getAll("juu_response").get(1).toString());
                        assertFalse(endStream);
                    });
                }

                @Override
                public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
                        boolean endOfStream) throws Http2Exception {
                    String actual = data.toString(StandardCharsets.UTF_8);
                    vertx.runOnContext(v -> {
                        assertEquals(id, streamId);
                        assertEquals(expected, actual);
                        assertTrue(endOfStream);
                        testComplete();
                    });
                    return super.onDataRead(ctx, streamId, data, padding, endOfStream);
                }
            });
            Http2Headers headers = GET("/").authority(DEFAULT_HTTPS_HOST_AND_PORT);
            headers.set("foo_request", "foo_request_value");
            headers.set("bar_request", "bar_request_value");
            headers.set("juu_request", "juu_request_value_1", "juu_request_value_2");
            headers.set("cookie", Arrays.asList("cookie_1", "cookie_2", "cookie_3"));
            request.encoder.writeHeaders(request.context, id, headers, 0, true, request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testStatusMessage() throws Exception {
        server.requestHandler(req -> {
            HttpServerResponse resp = req.response();
            resp.setStatusCode(404);
            assertEquals("Not Found", resp.getStatusMessage());
            resp.setStatusMessage("whatever");
            assertEquals("whatever", resp.getStatusMessage());
            testComplete();
        });
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            request.encoder.writeHeaders(request.context, id, GET("/"), 0, true, request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testURI() throws Exception {
        server.requestHandler(req -> {
            assertEquals("/some/path", req.path());
            assertEquals("foo=foo_value&bar=bar_value_1&bar=bar_value_2", req.query());
            assertEquals("/some/path?foo=foo_value&bar=bar_value_1&bar=bar_value_2", req.uri());
            assertEquals("http://whatever.com/some/path?foo=foo_value&bar=bar_value_1&bar=bar_value_2",
                    req.absoluteURI());
            assertEquals("/some/path?foo=foo_value&bar=bar_value_1&bar=bar_value_2", req.getHeader(":path"));
            assertEquals("whatever.com", req.host());
            MultiMap params = req.params();
            Set<String> names = params.names();
            assertEquals(2, names.size());
            assertTrue(names.contains("foo"));
            assertTrue(names.contains("bar"));
            assertEquals("foo_value", params.get("foo"));
            assertEquals(Collections.singletonList("foo_value"), params.getAll("foo"));
            assertEquals("bar_value_2", params.get("bar"));
            assertEquals(Arrays.asList("bar_value_1", "bar_value_2"), params.getAll("bar"));
            testComplete();
        });
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            Http2Headers headers = new DefaultHttp2Headers().method("GET").scheme("http").authority("whatever.com")
                    .path("/some/path?foo=foo_value&bar=bar_value_1&bar=bar_value_2");
            request.encoder.writeHeaders(request.context, id, headers, 0, true, request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testHeadersEndHandler() throws Exception {
        Context ctx = vertx.getOrCreateContext();
        server.requestHandler(req -> {
            HttpServerResponse resp = req.response();
            resp.setChunked(true);
            resp.putHeader("some", "some-header");
            resp.headersEndHandler(v -> {
                assertOnIOContext(ctx);
                assertFalse(resp.headWritten());
                resp.putHeader("extra", "extra-header");
            });
            resp.write("something");
            assertTrue(resp.headWritten());
            resp.end();
        });
        startServer(ctx);
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            request.decoder.frameListener(new Http2EventAdapter() {
                @Override
                public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
                        int streamDependency, short weight, boolean exclusive, int padding, boolean endStream)
                        throws Http2Exception {
                    vertx.runOnContext(v -> {
                        assertEquals("some-header", headers.get("some").toString());
                        assertEquals("extra-header", headers.get("extra").toString());
                        testComplete();
                    });
                }
            });
            int id = request.nextStreamId();
            request.encoder.writeHeaders(request.context, id, GET("/"), 0, true, request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testBodyEndHandler() throws Exception {
        server.requestHandler(req -> {
            HttpServerResponse resp = req.response();
            resp.setChunked(true);
            AtomicInteger count = new AtomicInteger();
            resp.bodyEndHandler(v -> {
                assertEquals(0, count.getAndIncrement());
                assertTrue(resp.ended());
            });
            resp.write("something");
            assertEquals(0, count.get());
            resp.end();
            assertEquals(1, count.get());
            testComplete();
        });
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            request.encoder.writeHeaders(request.context, id, GET("/"), 0, true, request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testPost() throws Exception {
        Context ctx = vertx.getOrCreateContext();
        Buffer expectedContent = TestUtils.randomBuffer(1000);
        Buffer postContent = Buffer.buffer();
        server.requestHandler(req -> {
            assertOnIOContext(ctx);
            req.handler(buff -> {
                assertOnIOContext(ctx);
                postContent.appendBuffer(buff);
            });
            req.endHandler(v -> {
                assertOnIOContext(ctx);
                req.response().putHeader("content-type", "text/plain").end("");
                assertEquals(expectedContent, postContent);
                testComplete();
            });
        });
        startServer(ctx);
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            request.encoder.writeHeaders(request.context, id, POST("/").set("content-type", "text/plain"), 0, false,
                    request.context.newPromise());
            request.encoder.writeData(request.context, id, expectedContent.getByteBuf(), 0, true,
                    request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testPostFileUpload() throws Exception {
        Context ctx = vertx.getOrCreateContext();
        server.requestHandler(req -> {
            Buffer tot = Buffer.buffer();
            req.setExpectMultipart(true);
            req.uploadHandler(upload -> {
                assertOnIOContext(ctx);
                assertEquals("file", upload.name());
                assertEquals("tmp-0.txt", upload.filename());
                assertEquals("image/gif", upload.contentType());
                upload.handler(tot::appendBuffer);
                upload.endHandler(v -> {
                    assertEquals(tot, Buffer.buffer("some-content"));
                    testComplete();
                });
            });
            req.endHandler(v -> {
                assertEquals(0, req.formAttributes().size());
                req.response().putHeader("content-type", "text/plain").end("done");
            });
        });
        startServer(ctx);

        String contentType = "multipart/form-data; boundary=a4e41223-a527-49b6-ac1c-315d76be757e";
        String contentLength = "225";
        String body = "--a4e41223-a527-49b6-ac1c-315d76be757e\r\n"
                + "Content-Disposition: form-data; name=\"file\"; filename=\"tmp-0.txt\"\r\n"
                + "Content-Type: image/gif; charset=utf-8\r\n" + "Content-Length: 12\r\n" + "\r\n"
                + "some-content\r\n" + "--a4e41223-a527-49b6-ac1c-315d76be757e--\r\n";

        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            request.encoder.writeHeaders(request.context, id,
                    POST("/form").set("content-type", contentType).set("content-length", contentLength), 0, false,
                    request.context.newPromise());
            request.encoder.writeData(request.context, id, Buffer.buffer(body).getByteBuf(), 0, true,
                    request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testInvalidPostFileUpload() throws Exception {
        server.requestHandler(req -> {
            req.setExpectMultipart(true);
            AtomicInteger errCount = new AtomicInteger();
            req.exceptionHandler(err -> {
                errCount.incrementAndGet();
            });
            req.endHandler(v -> {
                assertTrue(errCount.get() > 0);
                testComplete();
            });
        });
        startServer();

        String contentType = "multipart/form-data; boundary=a4e41223-a527-49b6-ac1c-315d76be757e";
        String contentLength = "225";
        String body = "--a4e41223-a527-49b6-ac1c-315d76be757e\r\n"
                + "Content-Disposition: form-data; name=\"file\"; filename=\"tmp-0.txt\"\r\n"
                + "Content-Type: image/gif; charset=ABCD\r\n" + "Content-Length: 12\r\n" + "\r\n"
                + "some-content\r\n" + "--a4e41223-a527-49b6-ac1c-315d76be757e--\r\n";

        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            request.encoder.writeHeaders(request.context, id,
                    POST("/form").set("content-type", contentType).set("content-length", contentLength), 0, false,
                    request.context.newPromise());
            request.encoder.writeData(request.context, id, Buffer.buffer(body).getByteBuf(), 0, true,
                    request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testConnect() throws Exception {
        server.requestHandler(req -> {
            assertEquals(HttpMethod.CONNECT, req.method());
            assertEquals("whatever.com", req.host());
            assertNull(req.path());
            assertNull(req.query());
            assertNull(req.scheme());
            assertNull(req.uri());
            assertNull(req.absoluteURI());
            testComplete();
        });
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            Http2Headers headers = new DefaultHttp2Headers().method("CONNECT").authority("whatever.com");
            request.encoder.writeHeaders(request.context, id, headers, 0, true, request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testServerRequestPauseResume() throws Exception {
        testStreamPauseResume(req -> req);
    }

    private void testStreamPauseResume(Function<HttpServerRequest, ReadStream<Buffer>> streamProvider)
            throws Exception {
        Buffer expected = Buffer.buffer();
        String chunk = TestUtils.randomAlphaString(1000);
        AtomicBoolean done = new AtomicBoolean();
        AtomicBoolean paused = new AtomicBoolean();
        Buffer received = Buffer.buffer();
        server.requestHandler(req -> {
            ReadStream<Buffer> stream = streamProvider.apply(req);
            vertx.setPeriodic(1, timerID -> {
                if (paused.get()) {
                    vertx.cancelTimer(timerID);
                    done.set(true);
                    // Let some time to accumulate some more buffers
                    vertx.setTimer(100, id -> {
                        stream.resume();
                    });
                }
            });
            stream.handler(received::appendBuffer);
            stream.endHandler(v -> {
                assertEquals(expected, received);
                testComplete();
            });
            stream.pause();
        });
        startServer();

        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            request.encoder.writeHeaders(request.context, id, POST("/form").set("content-type", "text/plain"), 0,
                    false, request.context.newPromise());
            request.context.flush();
            Http2Stream stream = request.connection.stream(id);
            class Anonymous {
                void send() {
                    boolean writable = request.encoder.flowController().isWritable(stream);
                    if (writable) {
                        Buffer buf = Buffer.buffer(chunk);
                        expected.appendBuffer(buf);
                        request.encoder.writeData(request.context, id, buf.getByteBuf(), 0, false,
                                request.context.newPromise());
                        request.context.flush();
                        request.context.executor().execute(this::send);
                    } else {
                        request.encoder.writeData(request.context, id, Unpooled.EMPTY_BUFFER, 0, true,
                                request.context.newPromise());
                        request.context.flush();
                        paused.set(true);
                    }
                }
            }
            new Anonymous().send();
        });
        fut.sync();
        await();
    }

    @Test
    public void testServerResponseWritability() throws Exception {
        testStreamWritability(req -> {
            HttpServerResponse resp = req.response();
            resp.putHeader("content-type", "text/plain");
            resp.setChunked(true);
            return resp;
        });
    }

    private void testStreamWritability(Function<HttpServerRequest, WriteStream<Buffer>> streamProvider)
            throws Exception {
        Context ctx = vertx.getOrCreateContext();
        String content = TestUtils.randomAlphaString(1024);
        StringBuilder expected = new StringBuilder();
        Future<Void> whenFull = Future.future();
        AtomicBoolean drain = new AtomicBoolean();
        server.requestHandler(req -> {
            WriteStream<Buffer> stream = streamProvider.apply(req);
            vertx.setPeriodic(1, timerID -> {
                if (stream.writeQueueFull()) {
                    stream.drainHandler(v -> {
                        assertOnIOContext(ctx);
                        expected.append("last");
                        stream.end(Buffer.buffer("last"));
                    });
                    vertx.cancelTimer(timerID);
                    drain.set(true);
                    whenFull.complete();
                } else {
                    expected.append(content);
                    Buffer buf = Buffer.buffer(content);
                    stream.write(buf);
                }
            });
        });
        startServer(ctx);

        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            AtomicInteger toAck = new AtomicInteger();
            int id = request.nextStreamId();
            Http2ConnectionEncoder encoder = request.encoder;
            encoder.writeHeaders(request.context, id, GET("/"), 0, true, request.context.newPromise());
            request.decoder.frameListener(new Http2FrameAdapter() {

                StringBuilder received = new StringBuilder();

                @Override
                public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
                        boolean endOfStream) throws Http2Exception {
                    received.append(data.toString(StandardCharsets.UTF_8));
                    int delta = super.onDataRead(ctx, streamId, data, padding, endOfStream);
                    if (endOfStream) {
                        vertx.runOnContext(v -> {
                            assertEquals(expected.toString(), received.toString());
                            testComplete();
                        });
                        return delta;
                    } else {
                        if (drain.get()) {
                            return delta;
                        } else {
                            toAck.getAndAdd(delta);
                            return 0;
                        }
                    }
                }
            });
            whenFull.setHandler(ar -> {
                request.context.executor().execute(() -> {
                    try {
                        request.decoder.flowController().consumeBytes(request.connection.stream(id),
                                toAck.intValue());
                        request.context.flush();
                    } catch (Http2Exception e) {
                        e.printStackTrace();
                        fail(e);
                    }
                });
            });
        });

        fut.sync();

        await();
    }

    @Test
    public void testTrailers() throws Exception {
        server.requestHandler(req -> {
            HttpServerResponse resp = req.response();
            resp.setChunked(true);
            resp.write("some-content");
            resp.putTrailer("Foo", "foo_value");
            resp.putTrailer("bar", "bar_value");
            resp.putTrailer("juu", (List<String>) Arrays.asList("juu_value_1", "juu_value_2"));
            resp.end();
        });
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            request.decoder.frameListener(new Http2EventAdapter() {
                int count;

                @Override
                public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
                        int streamDependency, short weight, boolean exclusive, int padding, boolean endStream)
                        throws Http2Exception {
                    switch (count++) {
                    case 0:
                        vertx.runOnContext(v -> {
                            assertFalse(endStream);
                        });
                        break;
                    case 1:
                        vertx.runOnContext(v -> {
                            assertEquals("foo_value", headers.get("foo").toString());
                            assertEquals(1, headers.getAll("foo").size());
                            assertEquals("foo_value", headers.getAll("foo").get(0).toString());
                            assertEquals("bar_value", headers.getAll("bar").get(0).toString());
                            assertEquals(2, headers.getAll("juu").size());
                            assertEquals("juu_value_1", headers.getAll("juu").get(0).toString());
                            assertEquals("juu_value_2", headers.getAll("juu").get(1).toString());
                            assertTrue(endStream);
                            testComplete();
                        });
                        break;
                    default:
                        vertx.runOnContext(v -> {
                            fail();
                        });
                        break;
                    }
                }
            });
            int id = request.nextStreamId();
            request.encoder.writeHeaders(request.context, id, GET("/"), 0, true, request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testServerResetClientStream() throws Exception {
        server.requestHandler(req -> {
            req.handler(buf -> {
                req.response().reset(8);
            });
        });
        startServer();

        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            request.decoder.frameListener(new Http2EventAdapter() {
                @Override
                public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode)
                        throws Http2Exception {
                    vertx.runOnContext(v -> {
                        assertEquals(8, errorCode);
                        testComplete();
                    });
                }
            });
            Http2ConnectionEncoder encoder = request.encoder;
            encoder.writeHeaders(request.context, id, GET("/"), 0, false, request.context.newPromise());
            encoder.writeData(request.context, id, Buffer.buffer("hello").getByteBuf(), 0, false,
                    request.context.newPromise());
        });

        fut.sync();

        await();
    }

    @Test
    public void testClientResetServerStream() throws Exception {
        Context ctx = vertx.getOrCreateContext();
        Future<Void> bufReceived = Future.future();
        AtomicInteger resetCount = new AtomicInteger();
        server.requestHandler(req -> {
            req.handler(buf -> {
                bufReceived.complete();
            });
            req.exceptionHandler(err -> {
                assertOnIOContext(ctx);
                assertTrue(err instanceof StreamResetException);
                assertEquals(10L, ((StreamResetException) err).getCode());
                assertEquals(0, resetCount.getAndIncrement());
            });
            req.response().exceptionHandler(err -> {
                assertOnIOContext(ctx);
                assertTrue(err instanceof StreamResetException);
                assertEquals(10L, ((StreamResetException) err).getCode());
                assertEquals(1, resetCount.getAndIncrement());
            });
            req.endHandler(v -> {
                assertOnIOContext(ctx);
                assertEquals(2, resetCount.get());
                testComplete();
            });
        });
        startServer(ctx);

        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            Http2ConnectionEncoder encoder = request.encoder;
            encoder.writeHeaders(request.context, id, GET("/"), 0, false, request.context.newPromise());
            encoder.writeData(request.context, id, Buffer.buffer("hello").getByteBuf(), 0, false,
                    request.context.newPromise());
            bufReceived.setHandler(ar -> {
                encoder.writeRstStream(request.context, id, 10, request.context.newPromise());
                request.context.flush();
            });
        });

        fut.sync();

        await();
    }

    @Test
    public void testConnectionClose() throws Exception {
        Context ctx = vertx.getOrCreateContext();
        server.requestHandler(req -> {
            HttpConnection conn = req.connection();
            conn.closeHandler(v -> {
                assertSame(ctx, Vertx.currentContext());
                testComplete();
            });
            req.response().putHeader("Content-Type", "text/plain").end();
        });
        startServer(ctx);

        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            Http2ConnectionEncoder encoder = request.encoder;
            encoder.writeHeaders(request.context, id, GET("/"), 0, true, request.context.newPromise());
            request.decoder.frameListener(new Http2FrameAdapter() {
                @Override
                public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
                        int streamDependency, short weight, boolean exclusive, int padding, boolean endStream)
                        throws Http2Exception {
                    request.context.close();
                }
            });
        });
        fut.sync();
        await();
    }

    @Test
    public void testPushPromise() throws Exception {
        testPushPromise(GET("/").authority("whatever.com"), (resp, handler) -> {
            resp.push(HttpMethod.GET, "/wibble", handler);
        }, headers -> {
            assertEquals("GET", headers.method().toString());
            assertEquals("https", headers.scheme().toString());
            assertEquals("/wibble", headers.path().toString());
            assertEquals("whatever.com", headers.authority().toString());
        });
    }

    @Test
    public void testPushPromiseHeaders() throws Exception {
        testPushPromise(GET("/").authority("whatever.com"), (resp, handler) -> {
            resp.push(HttpMethod.GET, "/wibble", MultiMap.caseInsensitiveMultiMap().set("foo", "foo_value")
                    .set("bar", Arrays.<CharSequence>asList("bar_value_1", "bar_value_2")), handler);
        }, headers -> {
            assertEquals("GET", headers.method().toString());
            assertEquals("https", headers.scheme().toString());
            assertEquals("/wibble", headers.path().toString());
            assertEquals(null, headers.authority());
            assertEquals("foo_value", headers.get("foo").toString());
            assertEquals(Arrays.asList("bar_value_1", "bar_value_2"),
                    headers.getAll("bar").stream().map(CharSequence::toString).collect(Collectors.toList()));
        });
    }

    @Test
    public void testPushPromiseNoAuthority() throws Exception {
        Http2Headers get = GET("/");
        get.remove("authority");
        testPushPromise(get, (resp, handler) -> {
            resp.push(HttpMethod.GET, "/wibble", handler);
        }, headers -> {
            assertEquals("GET", headers.method().toString());
            assertEquals("https", headers.scheme().toString());
            assertEquals("/wibble", headers.path().toString());
            assertEquals(DEFAULT_HTTPS_HOST_AND_PORT, headers.authority().toString());
        });
    }

    @Test
    public void testPushPromiseOverrideAuthority() throws Exception {
        testPushPromise(GET("/").authority("whatever.com"), (resp, handler) -> {
            resp.push(HttpMethod.GET, "override.com", "/wibble", handler);
        }, headers -> {
            assertEquals("GET", headers.method().toString());
            assertEquals("https", headers.scheme().toString());
            assertEquals("/wibble", headers.path().toString());
            assertEquals("override.com", headers.authority().toString());
        });
    }

    @Test
    public void testPushPromiseOverrideAuthorityWithNull() throws Exception {
        testPushPromise(GET("/").authority("whatever.com"), (resp, handler) -> {
            resp.push(HttpMethod.GET, null, "/wibble", handler);
        }, headers -> {
            assertEquals("GET", headers.method().toString());
            assertEquals("https", headers.scheme().toString());
            assertEquals("/wibble", headers.path().toString());
            assertEquals(null, headers.authority());
        });
    }

    private void testPushPromise(Http2Headers requestHeaders,
            BiConsumer<HttpServerResponse, Handler<AsyncResult<HttpServerResponse>>> pusher,
            Consumer<Http2Headers> headerChecker) throws Exception {
        Context ctx = vertx.getOrCreateContext();
        server.requestHandler(req -> {
            Handler<AsyncResult<HttpServerResponse>> handler = ar -> {
                assertSame(ctx, Vertx.currentContext());
                assertTrue(ar.succeeded());
                HttpServerResponse response = ar.result();
                response./*putHeader("content-type", "application/plain").*/end("the_content");
                assertIllegalStateException(() -> response.push(HttpMethod.GET, "/wibble2", resp -> {
                }));
            };
            pusher.accept(req.response(), handler);
        });
        startServer(ctx);
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            Http2ConnectionEncoder encoder = request.encoder;
            encoder.writeHeaders(request.context, id, requestHeaders, 0, true, request.context.newPromise());
            Map<Integer, Http2Headers> pushed = new HashMap<>();
            request.decoder.frameListener(new Http2FrameAdapter() {
                @Override
                public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
                        Http2Headers headers, int padding) throws Http2Exception {
                    pushed.put(promisedStreamId, headers);
                }

                @Override
                public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
                        boolean endOfStream) throws Http2Exception {
                    int delta = super.onDataRead(ctx, streamId, data, padding, endOfStream);
                    String content = data.toString(StandardCharsets.UTF_8);
                    vertx.runOnContext(v -> {
                        assertEquals(Collections.singleton(streamId), pushed.keySet());
                        assertEquals("the_content", content);
                        Http2Headers pushedHeaders = pushed.get(streamId);
                        headerChecker.accept(pushedHeaders);
                        testComplete();
                    });
                    return delta;
                }
            });
        });
        fut.sync();
        await();
    }

    @Test
    public void testResetActivePushPromise() throws Exception {
        Context ctx = vertx.getOrCreateContext();
        server.requestHandler(req -> {
            req.response().push(HttpMethod.GET, "/wibble", ar -> {
                assertTrue(ar.succeeded());
                assertOnIOContext(ctx);
                HttpServerResponse response = ar.result();
                response.exceptionHandler(err -> {
                    testComplete();
                });
                response.setChunked(true).write("some_content");
            });
        });
        startServer(ctx);
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            Http2ConnectionEncoder encoder = request.encoder;
            encoder.writeHeaders(request.context, id, GET("/"), 0, true, request.context.newPromise());
            request.decoder.frameListener(new Http2FrameAdapter() {
                @Override
                public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
                        boolean endOfStream) throws Http2Exception {
                    request.encoder.writeRstStream(ctx, streamId, Http2Error.CANCEL.code(), ctx.newPromise());
                    request.context.flush();
                    return super.onDataRead(ctx, streamId, data, padding, endOfStream);
                }
            });
        });
        fut.sync();
        await();
    }

    @Test
    public void testQueuePushPromise() throws Exception {
        Context ctx = vertx.getOrCreateContext();
        int numPushes = 10;
        Set<String> pushSent = new HashSet<>();
        server.requestHandler(req -> {
            req.response().setChunked(true).write("abc");
            for (int i = 0; i < numPushes; i++) {
                int val = i;
                String path = "/wibble" + val;
                req.response().push(HttpMethod.GET, path, ar -> {
                    assertTrue(ar.succeeded());
                    assertSame(ctx, Vertx.currentContext());
                    pushSent.add(path);
                    vertx.setTimer(10, id -> {
                        ar.result().end("wibble-" + val);
                    });
                });
            }
        });
        startServer(ctx);
        TestClient client = new TestClient();
        client.settings.maxConcurrentStreams(3);
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            Http2ConnectionEncoder encoder = request.encoder;
            encoder.writeHeaders(request.context, id, GET("/"), 0, true, request.context.newPromise());
            request.decoder.frameListener(new Http2FrameAdapter() {
                int count = numPushes;
                Set<String> pushReceived = new HashSet<>();

                @Override
                public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
                        Http2Headers headers, int padding) throws Http2Exception {
                    pushReceived.add(headers.path().toString());
                }

                @Override
                public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
                        boolean endOfStream) throws Http2Exception {
                    if (count-- == 0) {
                        vertx.runOnContext(v -> {
                            assertEquals(numPushes, pushSent.size());
                            assertEquals(pushReceived, pushSent);
                            testComplete();
                        });
                    }
                    return super.onDataRead(ctx, streamId, data, padding, endOfStream);
                }
            });
        });
        fut.sync();
        await();
    }

    @Test
    public void testResetPendingPushPromise() throws Exception {
        Context ctx = vertx.getOrCreateContext();
        server.requestHandler(req -> {
            req.response().push(HttpMethod.GET, "/wibble", ar -> {
                assertFalse(ar.succeeded());
                assertOnIOContext(ctx);
                testComplete();
            });
        });
        startServer(ctx);
        TestClient client = new TestClient();
        client.settings.maxConcurrentStreams(0);
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            Http2ConnectionEncoder encoder = request.encoder;
            encoder.writeHeaders(request.context, id, GET("/"), 0, true, request.context.newPromise());
            request.decoder.frameListener(new Http2FrameAdapter() {
                @Override
                public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
                        Http2Headers headers, int padding) throws Http2Exception {
                    request.encoder.writeRstStream(request.context, promisedStreamId, Http2Error.CANCEL.code(),
                            request.context.newPromise());
                    request.context.flush();
                }
            });
        });
        fut.sync();
        await();
    }

    @Test
    public void testMissingMethodPseudoHeader() throws Exception {
        testMalformedRequestHeaders(new DefaultHttp2Headers().scheme("http").path("/"));
    }

    @Test
    public void testMissingSchemePseudoHeader() throws Exception {
        testMalformedRequestHeaders(new DefaultHttp2Headers().method("GET").path("/"));
    }

    @Test
    public void testMissingPathPseudoHeader() throws Exception {
        testMalformedRequestHeaders(new DefaultHttp2Headers().method("GET").scheme("http"));
    }

    @Test
    public void testInvalidAuthority() throws Exception {
        testMalformedRequestHeaders(new DefaultHttp2Headers().method("GET").scheme("http")
                .authority("foo@" + DEFAULT_HTTPS_HOST_AND_PORT).path("/"));
    }

    @Test
    public void testConnectInvalidPath() throws Exception {
        testMalformedRequestHeaders(
                new DefaultHttp2Headers().method("CONNECT").path("/").authority(DEFAULT_HTTPS_HOST_AND_PORT));
    }

    @Test
    public void testConnectInvalidScheme() throws Exception {
        testMalformedRequestHeaders(
                new DefaultHttp2Headers().method("CONNECT").scheme("http").authority(DEFAULT_HTTPS_HOST_AND_PORT));
    }

    @Test
    public void testConnectInvalidAuthority() throws Exception {
        testMalformedRequestHeaders(
                new DefaultHttp2Headers().method("CONNECT").authority("foo@" + DEFAULT_HTTPS_HOST_AND_PORT));
    }

    private void testMalformedRequestHeaders(Http2Headers headers) throws Exception {
        server.requestHandler(req -> fail());
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            Http2ConnectionEncoder encoder = request.encoder;
            encoder.writeHeaders(request.context, id, headers, 0, true, request.context.newPromise());
            request.decoder.frameListener(new Http2FrameAdapter() {
                @Override
                public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode)
                        throws Http2Exception {
                    vertx.runOnContext(v -> {
                        testComplete();
                    });
                }
            });
        });
        fut.sync();
        await();
    }

    @Test
    public void testRequestHandlerFailure() throws Exception {
        testHandlerFailure(false, (err, server) -> {
            server.requestHandler(req -> {
                throw err;
            });
        });
    }

    @Test
    public void testRequestEndHandlerFailure() throws Exception {
        testHandlerFailure(false, (err, server) -> {
            server.requestHandler(req -> {
                req.endHandler(v -> {
                    throw err;
                });
            });
        });
    }

    @Test
    public void testRequestEndHandlerFailureWithData() throws Exception {
        testHandlerFailure(true, (err, server) -> {
            server.requestHandler(req -> {
                req.endHandler(v -> {
                    throw err;
                });
            });
        });
    }

    @Test
    public void testRequestDataHandlerFailure() throws Exception {
        testHandlerFailure(true, (err, server) -> {
            server.requestHandler(req -> {
                req.handler(buf -> {
                    System.out.println("throwing from data");
                    throw err;
                });
            });
        });
    }

    private void testHandlerFailure(boolean data, BiConsumer<RuntimeException, HttpServer> configurator)
            throws Exception {
        RuntimeException failure = new RuntimeException();
        io.vertx.core.http.Http2Settings settings = TestUtils.randomHttp2Settings();
        server.close();
        server = vertx.createHttpServer(serverOptions.setInitialSettings(settings));
        configurator.accept(failure, server);
        Context ctx = vertx.getOrCreateContext();
        ctx.exceptionHandler(err -> {
            assertSame(err, failure);
            testComplete();
        });
        startServer(ctx);
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            request.encoder.writeHeaders(request.context, id, GET("/"), 0, !data, request.context.newPromise());
            if (data) {
                request.encoder.writeData(request.context, id, Buffer.buffer("hello").getByteBuf(), 0, true,
                        request.context.newPromise());
            }
        });
        fut.sync();
        await();
    }

    private static File createTempFile(Buffer buffer) throws Exception {
        File f = File.createTempFile("vertx", ".bin");
        f.deleteOnExit();
        try (FileOutputStream out = new FileOutputStream(f)) {
            out.write(buffer.getBytes());
        }
        return f;
    }

    @Test
    public void testSendFile() throws Exception {
        Buffer expected = Buffer.buffer(TestUtils.randomAlphaString(1000 * 1000));
        File tmp = createTempFile(expected);
        testSendFile(expected, tmp.getAbsolutePath(), 0, expected.length());
    }

    @Test
    public void testSendFileRange() throws Exception {
        Buffer expected = Buffer.buffer(TestUtils.randomAlphaString(1000 * 1000));
        File tmp = createTempFile(expected);
        int from = 200 * 1000;
        int to = 700 * 1000;
        testSendFile(expected.slice(from, to), tmp.getAbsolutePath(), from, to - from);
    }

    @Test
    public void testSendEmptyFile() throws Exception {
        Buffer expected = Buffer.buffer();
        File tmp = createTempFile(expected);
        testSendFile(expected, tmp.getAbsolutePath(), 0, expected.length());
    }

    private void testSendFile(Buffer expected, String path, long offset, long length) throws Exception {
        waitFor(2);
        server.requestHandler(req -> {
            HttpServerResponse resp = req.response();
            resp.bodyEndHandler(v -> {
                assertEquals(resp.bytesWritten(), length);
                complete();
            });
            resp.sendFile(path, offset, length);
        });
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            request.decoder.frameListener(new Http2EventAdapter() {
                Buffer buffer = Buffer.buffer();
                Http2Headers responseHeaders;

                @Override
                public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
                        int streamDependency, short weight, boolean exclusive, int padding, boolean endStream)
                        throws Http2Exception {
                    responseHeaders = headers;
                }

                @Override
                public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
                        boolean endOfStream) throws Http2Exception {
                    buffer.appendBuffer(Buffer.buffer(data.duplicate()));
                    if (endOfStream) {
                        vertx.runOnContext(v -> {
                            assertEquals("" + length, responseHeaders.get("content-length").toString());
                            assertEquals(expected, buffer);
                            complete();
                        });
                    }
                    return data.readableBytes() + padding;
                }
            });
            int id = request.nextStreamId();
            request.encoder.writeHeaders(request.context, id, GET("/"), 0, true, request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testStreamError() throws Exception {
        waitFor(5);
        Future<Void> when = Future.future();
        Context ctx = vertx.getOrCreateContext();
        server.requestHandler(req -> {
            req.exceptionHandler(err -> {
                // Called twice : reset + close
                assertEquals(ctx, Vertx.currentContext());
                complete();
            });
            req.response().exceptionHandler(err -> {
                assertEquals(ctx, Vertx.currentContext());
                complete();
            });
            req.response().closeHandler(v -> {
                assertEquals(ctx, Vertx.currentContext());
                complete();
            });
            req.response().endHandler(v -> {
                assertEquals(ctx, Vertx.currentContext());
                complete();
            });
            when.complete();
        });
        startServer(ctx);
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            Http2ConnectionEncoder encoder = request.encoder;
            encoder.writeHeaders(request.context, id, GET("/"), 0, false, request.context.newPromise());
            request.context.flush();
            when.setHandler(ar -> {
                // Send a corrupted frame on purpose to check we get the corresponding error in the request exception handler
                // the error is : greater padding value 0c -> 1F
                // ChannelFuture a = encoder.frameWriter().writeData(request.context, id, Buffer.buffer("hello").getByteBuf(), 12, false, request.context.newPromise());
                // normal frame    : 00 00 12 00 08 00 00 00 03 0c 68 65 6c 6c 6f 00 00 00 00 00 00 00 00 00 00 00 00
                // corrupted frame : 00 00 12 00 08 00 00 00 03 1F 68 65 6c 6c 6f 00 00 00 00 00 00 00 00 00 00 00 00
                request.channel.write(Buffer.buffer(new byte[] { 0x00, 0x00, 0x12, 0x00, 0x08, 0x00, 0x00, 0x00,
                        (byte) (id & 0xFF), 0x1F, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }).getByteBuf());
                request.context.flush();
            });
        });
        fut.sync();
        await();
    }

    @Test
    public void testPromiseStreamError() throws Exception {
        Context ctx = vertx.getOrCreateContext();
        waitFor(3);
        Future<Void> when = Future.future();
        server.requestHandler(req -> {
            req.response().push(HttpMethod.GET, "/wibble", ar -> {
                assertTrue(ar.succeeded());
                assertOnIOContext(ctx);
                when.complete();
                HttpServerResponse resp = ar.result();
                resp.exceptionHandler(err -> {
                    assertSame(ctx, Vertx.currentContext());
                    complete();
                });
                resp.closeHandler(v -> {
                    assertSame(ctx, Vertx.currentContext());
                    complete();
                });
                resp.endHandler(v -> {
                    assertSame(ctx, Vertx.currentContext());
                    complete();
                });
                resp.setChunked(true).write("whatever"); // Transition to half-closed remote
            });
        });
        startServer(ctx);
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            request.decoder.frameListener(new Http2EventAdapter() {
                @Override
                public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
                        Http2Headers headers, int padding) throws Http2Exception {
                    when.setHandler(ar -> {
                        Http2ConnectionEncoder encoder = request.encoder;
                        encoder.frameWriter().writeHeaders(request.context, promisedStreamId, GET("/"), 0, false,
                                request.context.newPromise());
                        request.context.flush();
                    });
                }
            });
            int id = request.nextStreamId();
            Http2ConnectionEncoder encoder = request.encoder;
            encoder.writeHeaders(request.context, id, GET("/"), 0, false, request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testConnectionDecodeError() throws Exception {
        Context ctx = vertx.getOrCreateContext();
        waitFor(6);
        Future<Void> when = Future.future();
        server.requestHandler(req -> {
            req.exceptionHandler(err -> {
                // Called twice : reset + close
                assertSame(ctx, Vertx.currentContext());
                complete();
            });
            req.response().exceptionHandler(err -> {
                // Called once : reset
                assertSame(ctx, Vertx.currentContext());
                complete();
            });
            req.response().closeHandler(v -> {
                // Called once : close
                assertSame(ctx, Vertx.currentContext());
                complete();
            });
            req.response().endHandler(v -> {
                // Called once : close
                assertSame(ctx, Vertx.currentContext());
                complete();
            });
            req.connection().exceptionHandler(err -> {
                assertSame(ctx, Vertx.currentContext());
                complete();
            });
            when.complete();
        });
        startServer(ctx);
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            Http2ConnectionEncoder encoder = request.encoder;
            when.setHandler(ar -> {
                encoder.frameWriter().writeRstStream(request.context, 10, 0, request.context.newPromise());
                request.context.flush();
            });
            encoder.writeHeaders(request.context, id, GET("/"), 0, false, request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testServerSendGoAwayNoError() throws Exception {
        waitFor(2);
        AtomicReference<HttpServerRequest> first = new AtomicReference<>();
        AtomicInteger status = new AtomicInteger();
        AtomicInteger closed = new AtomicInteger();
        AtomicBoolean done = new AtomicBoolean();
        Context ctx = vertx.getOrCreateContext();
        Handler<HttpServerRequest> requestHandler = req -> {
            if (first.compareAndSet(null, req)) {
                req.exceptionHandler(err -> {
                    assertTrue(done.get());
                });
                req.response().exceptionHandler(err -> {
                    assertTrue(done.get());
                });
            } else {
                assertEquals(0, status.getAndIncrement());
                req.exceptionHandler(err -> {
                    closed.incrementAndGet();
                });
                req.response().exceptionHandler(err -> {
                    closed.incrementAndGet();
                });
                HttpConnection conn = req.connection();
                conn.shutdownHandler(v -> {
                    assertTrue(done.get());
                });
                conn.closeHandler(v -> {
                    assertTrue(done.get());
                });
                ctx.runOnContext(v1 -> {
                    conn.goAway(0, first.get().response().streamId());
                    vertx.setTimer(300, timerID -> {
                        assertEquals(1, status.getAndIncrement());
                        done.set(true);
                        complete();
                    });
                });
            }
        };
        testServerSendGoAway(requestHandler, 0);
    }

    @Test
    public void testServerSendGoAwayInteralError() throws Exception {
        waitFor(3);
        AtomicReference<HttpServerRequest> first = new AtomicReference<>();
        AtomicInteger status = new AtomicInteger();
        AtomicInteger closed = new AtomicInteger();
        Handler<HttpServerRequest> requestHandler = req -> {
            if (first.compareAndSet(null, req)) {
                req.exceptionHandler(err -> {
                    fail();
                });
                req.response().closeHandler(err -> {
                    closed.incrementAndGet();
                });
                req.response().endHandler(err -> {
                    closed.incrementAndGet();
                });
            } else {
                assertEquals(0, status.getAndIncrement());
                req.exceptionHandler(err -> {
                    closed.incrementAndGet();
                });
                req.response().closeHandler(err -> {
                    closed.incrementAndGet();
                });
                req.response().endHandler(err -> {
                    closed.incrementAndGet();
                });
                HttpConnection conn = req.connection();
                conn.closeHandler(v -> {
                    assertEquals(5, closed.get());
                    assertEquals(1, status.get());
                    complete();
                });
                conn.shutdownHandler(v -> {
                    assertEquals(1, status.get());
                    complete();
                });
                conn.goAway(2, first.get().response().streamId());
            }
        };
        testServerSendGoAway(requestHandler, 2);
    }

    @Test
    public void testShutdownWithTimeout() throws Exception {
        waitFor(2);
        AtomicInteger closed = new AtomicInteger();
        AtomicReference<HttpServerRequest> first = new AtomicReference<>();
        AtomicInteger status = new AtomicInteger();
        Handler<HttpServerRequest> requestHandler = req -> {
            if (first.compareAndSet(null, req)) {
                req.exceptionHandler(err -> {
                    fail();
                });
                req.response().closeHandler(err -> {
                    closed.incrementAndGet();
                });
                req.response().endHandler(err -> {
                    closed.incrementAndGet();
                });
            } else {
                assertEquals(0, status.getAndIncrement());
                req.exceptionHandler(err -> {
                    fail();
                });
                req.response().closeHandler(err -> {
                    closed.incrementAndGet();
                });
                req.response().endHandler(err -> {
                    closed.incrementAndGet();
                });
                HttpConnection conn = req.connection();
                conn.closeHandler(v -> {
                    assertEquals(4, closed.get());
                    assertEquals(1, status.getAndIncrement());
                    complete();
                });
                conn.shutdown(300);
            }
        };
        testServerSendGoAway(requestHandler, 0);
    }

    @Test
    public void testShutdown() throws Exception {
        waitFor(2);
        AtomicReference<HttpServerRequest> first = new AtomicReference<>();
        AtomicInteger status = new AtomicInteger();
        Handler<HttpServerRequest> requestHandler = req -> {
            if (first.compareAndSet(null, req)) {
                req.exceptionHandler(err -> {
                    fail();
                });
                req.response().exceptionHandler(err -> {
                    fail();
                });
            } else {
                assertEquals(0, status.getAndIncrement());
                req.exceptionHandler(err -> {
                    fail();
                });
                req.response().exceptionHandler(err -> {
                    fail();
                });
                HttpConnection conn = req.connection();
                conn.closeHandler(v -> {
                    assertEquals(2, status.getAndIncrement());
                    complete();
                });
                conn.shutdown();
                vertx.setTimer(300, timerID -> {
                    assertEquals(1, status.getAndIncrement());
                    first.get().response().end();
                    req.response().end();
                });
            }
        };
        testServerSendGoAway(requestHandler, 0);
    }

    private void testServerSendGoAway(Handler<HttpServerRequest> requestHandler, int expectedError)
            throws Exception {
        server.requestHandler(requestHandler);
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            request.decoder.frameListener(new Http2EventAdapter() {
                @Override
                public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode,
                        ByteBuf debugData) throws Http2Exception {
                    vertx.runOnContext(v -> {
                        assertEquals(expectedError, errorCode);
                        complete();
                    });
                }
            });
            Http2ConnectionEncoder encoder = request.encoder;
            int id1 = request.nextStreamId();
            encoder.writeHeaders(request.context, id1, GET("/"), 0, true, request.context.newPromise());
            int id2 = request.nextStreamId();
            encoder.writeHeaders(request.context, id2, GET("/"), 0, true, request.context.newPromise());
            request.context.flush();

        });
        fut.sync();
        await();
    }

    @Test
    public void testServerClose() throws Exception {
        waitFor(2);
        AtomicInteger status = new AtomicInteger();
        Handler<HttpServerRequest> requestHandler = req -> {
            HttpConnection conn = req.connection();
            conn.shutdownHandler(v -> {
                assertEquals(0, status.getAndIncrement());
            });
            conn.closeHandler(v -> {
                assertEquals(1, status.getAndIncrement());
                complete();
            });
            conn.close();
        };
        server.requestHandler(requestHandler);
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            request.channel.closeFuture().addListener(v1 -> {
                vertx.runOnContext(v2 -> {
                    complete();
                });
            });
            request.decoder.frameListener(new Http2EventAdapter() {
                @Override
                public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode,
                        ByteBuf debugData) throws Http2Exception {
                    vertx.runOnContext(v -> {
                        assertEquals(0, errorCode);
                    });
                }
            });
            Http2ConnectionEncoder encoder = request.encoder;
            int id = request.nextStreamId();
            encoder.writeHeaders(request.context, id, GET("/"), 0, true, request.context.newPromise());
            request.context.flush();

        });
        fut.sync();
        await();
    }

    @Test
    public void testClientSendGoAwayNoError() throws Exception {
        Future<Void> abc = Future.future();
        Context ctx = vertx.getOrCreateContext();
        Handler<HttpServerRequest> requestHandler = req -> {
            HttpConnection conn = req.connection();
            AtomicInteger numShutdown = new AtomicInteger();
            AtomicBoolean completed = new AtomicBoolean();
            conn.shutdownHandler(v -> {
                assertOnIOContext(ctx);
                numShutdown.getAndIncrement();
                vertx.setTimer(100, timerID -> {
                    // Delay so we can check the connection is not closed
                    completed.set(true);
                    testComplete();
                });
            });
            conn.goAwayHandler(ga -> {
                assertOnIOContext(ctx);
                assertEquals(0, numShutdown.get());
                req.response().end();
            });
            conn.closeHandler(v -> {
                assertTrue(completed.get());
            });
            abc.complete();
        };
        server.requestHandler(requestHandler);
        startServer(ctx);
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            Http2ConnectionEncoder encoder = request.encoder;
            int id = request.nextStreamId();
            encoder.writeHeaders(request.context, id, GET("/"), 0, true, request.context.newPromise());
            request.context.flush();
            abc.setHandler(ar -> {
                encoder.writeGoAway(request.context, id, 0, Unpooled.EMPTY_BUFFER, request.context.newPromise());
                request.context.flush();
            });
        });
        fut.sync();
        await();
    }

    @Test
    public void testClientSendGoAwayInternalError() throws Exception {
        Future<Void> abc = Future.future();
        Context ctx = vertx.getOrCreateContext();
        Handler<HttpServerRequest> requestHandler = req -> {
            HttpConnection conn = req.connection();
            AtomicInteger status = new AtomicInteger();
            conn.goAwayHandler(ga -> {
                assertOnIOContext(ctx);
                assertEquals(0, status.getAndIncrement());
                req.response().end();
            });
            conn.shutdownHandler(v -> {
                assertOnIOContext(ctx);
                assertEquals(1, status.getAndIncrement());
            });
            conn.closeHandler(v -> {
                assertEquals(2, status.getAndIncrement());
                testComplete();
            });
            abc.complete();
        };
        server.requestHandler(requestHandler);
        startServer(ctx);
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            Http2ConnectionEncoder encoder = request.encoder;
            int id = request.nextStreamId();
            encoder.writeHeaders(request.context, id, GET("/"), 0, true, request.context.newPromise());
            request.context.flush();
            abc.setHandler(ar -> {
                encoder.writeGoAway(request.context, id, 3, Unpooled.EMPTY_BUFFER, request.context.newPromise());
                request.context.flush();
            });
        });
        fut.sync();
        await();
    }

    @Test
    public void testShutdownOverride() throws Exception {
        AtomicLong shutdown = new AtomicLong();
        Handler<HttpServerRequest> requestHandler = req -> {
            HttpConnection conn = req.connection();
            shutdown.set(System.currentTimeMillis());
            conn.shutdown(10000);
            vertx.setTimer(300, v -> {
                conn.shutdown(300);
            });
        };
        server.requestHandler(requestHandler);
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            request.channel.closeFuture().addListener(v1 -> {
                vertx.runOnContext(v2 -> {
                    assertTrue(shutdown.get() - System.currentTimeMillis() < 1200);
                    testComplete();
                });
            });
            Http2ConnectionEncoder encoder = request.encoder;
            int id = request.nextStreamId();
            encoder.writeHeaders(request.context, id, GET("/"), 0, true, request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testRequestResponseLifecycle() throws Exception {
        waitFor(2);
        server.requestHandler(req -> {
            req.endHandler(v -> {
                assertIllegalStateException(() -> req.setExpectMultipart(false));
                assertIllegalStateException(() -> req.handler(buf -> {
                }));
                assertIllegalStateException(() -> req.uploadHandler(upload -> {
                }));
                assertIllegalStateException(() -> req.endHandler(v2 -> {
                }));
                complete();
            });
            HttpServerResponse resp = req.response();
            resp.setChunked(true).write(Buffer.buffer("whatever"));
            assertTrue(resp.headWritten());
            assertIllegalStateException(() -> resp.setChunked(false));
            assertIllegalStateException(() -> resp.setStatusCode(100));
            assertIllegalStateException(() -> resp.setStatusMessage("whatever"));
            assertIllegalStateException(() -> resp.putHeader("a", "b"));
            assertIllegalStateException(() -> resp.putHeader("a", (CharSequence) "b"));
            assertIllegalStateException(() -> resp.putHeader("a", (Iterable<String>) Arrays.asList("a", "b")));
            assertIllegalStateException(() -> resp.putHeader("a", (Arrays.<CharSequence>asList("a", "b"))));
            assertIllegalStateException(resp::writeContinue);
            resp.end();
            assertIllegalStateException(() -> resp.write("a"));
            assertIllegalStateException(() -> resp.write("a", "UTF-8"));
            assertIllegalStateException(() -> resp.write(Buffer.buffer("a")));
            assertIllegalStateException(resp::end);
            assertIllegalStateException(() -> resp.end("a"));
            assertIllegalStateException(() -> resp.end("a", "UTF-8"));
            assertIllegalStateException(() -> resp.end(Buffer.buffer("a")));
            assertIllegalStateException(() -> resp.sendFile("the-file.txt"));
            assertIllegalStateException(() -> resp.reset(0));
            assertIllegalStateException(() -> resp.closeHandler(v -> {
            }));
            assertIllegalStateException(() -> resp.endHandler(v -> {
            }));
            assertIllegalStateException(() -> resp.drainHandler(v -> {
            }));
            assertIllegalStateException(() -> resp.exceptionHandler(err -> {
            }));
            assertIllegalStateException(resp::writeQueueFull);
            assertIllegalStateException(() -> resp.setWriteQueueMaxSize(100));
            assertIllegalStateException(() -> resp.putTrailer("a", "b"));
            assertIllegalStateException(() -> resp.putTrailer("a", (CharSequence) "b"));
            assertIllegalStateException(() -> resp.putTrailer("a", (Iterable<String>) Arrays.asList("a", "b")));
            assertIllegalStateException(() -> resp.putTrailer("a", (Arrays.<CharSequence>asList("a", "b"))));
            assertIllegalStateException(() -> resp.push(HttpMethod.GET, "/whatever", ar -> {
            }));
            complete();
        });
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            request.encoder.writeHeaders(request.context, id, GET("/"), 0, true, request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testResponseCompressionDisabled() throws Exception {
        waitFor(2);
        String expected = TestUtils.randomAlphaString(1000);
        server.requestHandler(req -> {
            req.response().end(expected);
        });
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            request.decoder.frameListener(new Http2EventAdapter() {
                @Override
                public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
                        int streamDependency, short weight, boolean exclusive, int padding, boolean endStream)
                        throws Http2Exception {
                    vertx.runOnContext(v -> {
                        assertEquals(null, headers.get(HttpHeaderNames.CONTENT_ENCODING));
                        complete();
                    });
                }

                @Override
                public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
                        boolean endOfStream) throws Http2Exception {
                    String s = data.toString(StandardCharsets.UTF_8);
                    vertx.runOnContext(v -> {
                        assertEquals(expected, s);
                        complete();
                    });
                    return super.onDataRead(ctx, streamId, data, padding, endOfStream);
                }
            });
            int id = request.nextStreamId();
            request.encoder.writeHeaders(request.context, id, GET("/").add("accept-encoding", "gzip"), 0, true,
                    request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testResponseCompressionEnabled() throws Exception {
        waitFor(2);
        String expected = TestUtils.randomAlphaString(1000);
        server.close();
        server = vertx.createHttpServer(serverOptions.setCompressionSupported(true));
        server.requestHandler(req -> {
            req.response().end(expected);
        });
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            request.decoder.frameListener(new Http2EventAdapter() {
                @Override
                public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
                        int streamDependency, short weight, boolean exclusive, int padding, boolean endStream)
                        throws Http2Exception {
                    vertx.runOnContext(v -> {
                        assertEquals("gzip", headers.get(HttpHeaderNames.CONTENT_ENCODING).toString());
                        complete();
                    });
                }

                @Override
                public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
                        boolean endOfStream) throws Http2Exception {
                    byte[] bytes = new byte[data.readableBytes()];
                    data.readBytes(bytes);
                    vertx.runOnContext(v -> {
                        String decoded;
                        try {
                            GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(bytes));
                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
                            while (true) {
                                int i = in.read();
                                if (i == -1) {
                                    break;
                                }
                                baos.write(i);
                                ;
                            }
                            decoded = baos.toString();
                        } catch (IOException e) {
                            fail(e);
                            return;
                        }
                        assertEquals(expected, decoded);
                        complete();
                    });
                    return super.onDataRead(ctx, streamId, data, padding, endOfStream);
                }
            });
            int id = request.nextStreamId();
            request.encoder.writeHeaders(request.context, id, GET("/").add("accept-encoding", "gzip"), 0, true,
                    request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testRequestCompressionEnabled() throws Exception {
        String expected = TestUtils.randomAlphaString(1000);
        byte[] expectedGzipped = TestUtils.compressGzip(expected);
        server.close();
        server = vertx.createHttpServer(serverOptions.setDecompressionSupported(true));
        server.requestHandler(req -> {
            StringBuilder postContent = new StringBuilder();
            req.handler(buff -> {
                postContent.append(buff.toString());
            });
            req.endHandler(v -> {
                req.response().putHeader("content-type", "text/plain").end("");
                assertEquals(expected, postContent.toString());
                testComplete();
            });
        });
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            request.encoder.writeHeaders(request.context, id, POST("/").add("content-encoding", "gzip"), 0, false,
                    request.context.newPromise());
            request.encoder.writeData(request.context, id, Buffer.buffer(expectedGzipped).getByteBuf(), 0, true,
                    request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void test100ContinueHandledManually() throws Exception {
        server.requestHandler(req -> {
            assertEquals("100-continue", req.getHeader("expect"));
            HttpServerResponse resp = req.response();
            resp.writeContinue();
            req.bodyHandler(body -> {
                assertEquals("the-body", body.toString());
                resp.putHeader("wibble", "wibble-value").end();
            });
        });
        test100Continue();
    }

    @Test
    public void test100ContinueHandledAutomatically() throws Exception {
        server.close();
        server = vertx.createHttpServer(serverOptions.setHandle100ContinueAutomatically(true));
        server.requestHandler(req -> {
            HttpServerResponse resp = req.response();
            req.bodyHandler(body -> {
                assertEquals("the-body", body.toString());
                resp.putHeader("wibble", "wibble-value").end();
            });
        });
        test100Continue();
    }

    private void test100Continue() throws Exception {
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            request.decoder.frameListener(new Http2EventAdapter() {
                int count = 0;

                @Override
                public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
                        int streamDependency, short weight, boolean exclusive, int padding, boolean endStream)
                        throws Http2Exception {
                    switch (count++) {
                    case 0:
                        vertx.runOnContext(v -> {
                            assertEquals("100", headers.status().toString());
                        });
                        request.encoder.writeData(request.context, id, Buffer.buffer("the-body").getByteBuf(), 0,
                                true, request.context.newPromise());
                        request.context.flush();
                        break;
                    case 1:
                        vertx.runOnContext(v -> {
                            assertEquals("200", headers.status().toString());
                            assertEquals("wibble-value", headers.get("wibble").toString());
                            testComplete();
                        });
                        break;
                    default:
                        vertx.runOnContext(v -> {
                            fail();
                        });
                    }
                }
            });
            request.encoder.writeHeaders(request.context, id, GET("/").add("expect", "100-continue"), 0, false,
                    request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void test100ContinueRejectedManually() throws Exception {
        server.requestHandler(req -> {
            req.response().setStatusCode(405).end();
            req.handler(buf -> {
                fail();
            });
        });
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            request.decoder.frameListener(new Http2EventAdapter() {
                int count = 0;

                @Override
                public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
                        int streamDependency, short weight, boolean exclusive, int padding, boolean endStream)
                        throws Http2Exception {
                    switch (count++) {
                    case 0:
                        vertx.runOnContext(v -> {
                            assertEquals("405", headers.status().toString());
                            vertx.setTimer(100, v2 -> {
                                testComplete();
                            });
                        });
                        break;
                    default:
                        vertx.runOnContext(v -> {
                            fail();
                        });
                    }
                }
            });
            request.encoder.writeHeaders(request.context, id, GET("/").add("expect", "100-continue"), 0, false,
                    request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testNetSocketConnect() throws Exception {
        waitFor(2);
        server.requestHandler(req -> {
            NetSocket socket = req.netSocket();
            AtomicInteger status = new AtomicInteger();
            socket.handler(buff -> {
                switch (status.getAndIncrement()) {
                case 0:
                    assertEquals(Buffer.buffer("some-data"), buff);
                    socket.write(buff);
                    break;
                case 1:
                    assertEquals(Buffer.buffer("last-data"), buff);
                    break;
                default:
                    fail();
                    break;
                }
            });
            socket.endHandler(v -> {
                assertEquals(2, status.getAndIncrement());
                socket.write(Buffer.buffer("last-data"));
            });
            socket.closeHandler(v -> {
                assertEquals(3, status.getAndIncrement());
                complete();
            });
        });

        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            request.decoder.frameListener(new Http2EventAdapter() {
                @Override
                public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
                        int streamDependency, short weight, boolean exclusive, int padding, boolean endStream)
                        throws Http2Exception {
                    vertx.runOnContext(v -> {
                        assertEquals("200", headers.status().toString());
                        assertFalse(endStream);
                    });
                    request.encoder.writeData(request.context, id, Buffer.buffer("some-data").getByteBuf(), 0,
                            false, request.context.newPromise());
                    request.context.flush();
                }

                StringBuilder received = new StringBuilder();

                @Override
                public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
                        boolean endOfStream) throws Http2Exception {
                    String s = data.toString(StandardCharsets.UTF_8);
                    received.append(s);
                    if (received.toString().equals("some-data")) {
                        received.setLength(0);
                        vertx.runOnContext(v -> {
                            assertFalse(endOfStream);
                        });
                        request.encoder.writeData(request.context, id, Buffer.buffer("last-data").getByteBuf(), 0,
                                true, request.context.newPromise());
                    } else if (endOfStream) {
                        vertx.runOnContext(v -> {
                            assertEquals("last-data", received.toString());
                            complete();
                        });
                    }
                    return data.readableBytes() + padding;
                }
            });
            request.encoder.writeHeaders(request.context, id, GET("/"), 0, false, request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testNetSocketSendFile() throws Exception {
        Buffer expected = Buffer.buffer(TestUtils.randomAlphaString(1000 * 1000));
        File tmp = createTempFile(expected);
        testNetSocketSendFile(expected, tmp.getAbsolutePath(), 0, expected.length());
    }

    @Test
    public void testNetSocketSendFileRange() throws Exception {
        Buffer expected = Buffer.buffer(TestUtils.randomAlphaString(1000 * 1000));
        File tmp = createTempFile(expected);
        int from = 200 * 1000;
        int to = 700 * 1000;
        testNetSocketSendFile(expected.slice(from, to), tmp.getAbsolutePath(), from, to - from);
    }

    private void testNetSocketSendFile(Buffer expected, String path, long offset, long length) throws Exception {
        server.requestHandler(req -> {
            NetSocket socket = req.netSocket();
            socket.sendFile(path, offset, length, ar -> {
                assertTrue(ar.succeeded());
                socket.end();
            });
        });
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            request.decoder.frameListener(new Http2EventAdapter() {
                @Override
                public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
                        int streamDependency, short weight, boolean exclusive, int padding, boolean endStream)
                        throws Http2Exception {
                    vertx.runOnContext(v -> {
                        assertEquals("200", headers.status().toString());
                        assertFalse(endStream);
                    });
                }

                Buffer received = Buffer.buffer();

                @Override
                public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
                        boolean endOfStream) throws Http2Exception {
                    received.appendBuffer(Buffer.buffer(data.copy()));
                    if (endOfStream) {
                        vertx.runOnContext(v -> {
                            assertEquals(received, expected);
                            testComplete();
                        });
                    }
                    return data.readableBytes() + padding;
                }
            });
            request.encoder.writeHeaders(request.context, id, GET("/"), 0, true, request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testServerCloseNetSocket() throws Exception {
        waitFor(2);
        AtomicInteger status = new AtomicInteger();
        server.requestHandler(req -> {
            NetSocket socket = req.netSocket();
            socket.handler(buff -> {
                switch (status.getAndIncrement()) {
                case 0:
                    assertEquals(Buffer.buffer("some-data"), buff);
                    socket.write(buff);
                    socket.close();
                    break;
                case 1:
                    assertEquals(Buffer.buffer("last-data"), buff);
                    break;
                default:
                    fail();
                    break;
                }
            });
            socket.endHandler(v -> {
                assertEquals(2, status.getAndIncrement());
            });
            socket.closeHandler(v -> {
                assertEquals(3, status.getAndIncrement());
                complete();
            });
        });

        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            request.decoder.frameListener(new Http2EventAdapter() {
                int count = 0;

                @Override
                public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
                        int streamDependency, short weight, boolean exclusive, int padding, boolean endStream)
                        throws Http2Exception {
                    int c = count++;
                    vertx.runOnContext(v -> {
                        assertEquals(0, c);
                    });
                    request.encoder.writeData(request.context, id, Buffer.buffer("some-data").getByteBuf(), 0,
                            false, request.context.newPromise());
                    request.context.flush();
                }

                StringBuilder received = new StringBuilder();

                @Override
                public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
                        boolean endOfStream) throws Http2Exception {
                    String s = data.toString(StandardCharsets.UTF_8);
                    received.append(s);
                    if (endOfStream) {
                        request.encoder.writeData(request.context, id, Buffer.buffer("last-data").getByteBuf(), 0,
                                true, request.context.newPromise());
                        vertx.runOnContext(v -> {
                            assertEquals("some-data", received.toString());
                            complete();
                        });
                    }
                    return data.readableBytes() + padding;
                }
            });
            request.encoder.writeHeaders(request.context, id, GET("/"), 0, false, request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testNetSocketHandleReset() throws Exception {
        server.requestHandler(req -> {
            NetSocket socket = req.netSocket();
            AtomicInteger status = new AtomicInteger();
            socket.exceptionHandler(err -> {
                assertTrue(err instanceof StreamResetException);
                StreamResetException ex = (StreamResetException) err;
                assertEquals(0, ex.getCode());
                assertEquals(0, status.getAndIncrement());
            });
            socket.endHandler(v -> {
                fail();
            });
            socket.closeHandler(v -> {
                assertEquals(1, status.getAndIncrement());
                testComplete();
            });
        });
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            request.decoder.frameListener(new Http2EventAdapter() {
                int count = 0;

                @Override
                public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
                        int streamDependency, short weight, boolean exclusive, int padding, boolean endStream)
                        throws Http2Exception {
                    int c = count++;
                    vertx.runOnContext(v -> {
                        assertEquals(0, c);
                    });
                    request.encoder.writeRstStream(ctx, streamId, 0, ctx.newPromise());
                    request.context.flush();
                }
            });
            request.encoder.writeHeaders(request.context, id, GET("/"), 0, false, request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testNetSocketPauseResume() throws Exception {
        testStreamPauseResume(HttpServerRequest::netSocket);
    }

    @Test
    public void testNetSocketWritability() throws Exception {
        testStreamWritability(HttpServerRequest::netSocket);
    }

    @Test
    public void testUnknownFrame() throws Exception {
        Buffer expectedSend = TestUtils.randomBuffer(500);
        Buffer expectedRecv = TestUtils.randomBuffer(500);
        Context ctx = vertx.getOrCreateContext();
        server.requestHandler(req -> {
            req.customFrameHandler(frame -> {
                assertOnIOContext(ctx);
                assertEquals(10, frame.type());
                assertEquals(253, frame.flags());
                assertEquals(expectedSend, frame.payload());
                req.response().writeCustomFrame(12, 134, expectedRecv).end();
            });
        });
        startServer(ctx);
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            request.decoder.frameListener(new Http2EventAdapter() {
                int status = 0;

                @Override
                public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
                        int streamDependency, short weight, boolean exclusive, int padding, boolean endStream)
                        throws Http2Exception {
                    int s = status++;
                    vertx.runOnContext(v -> {
                        assertEquals(0, s);
                    });
                }

                @Override
                public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId,
                        Http2Flags flags, ByteBuf payload) {
                    int s = status++;
                    Buffer recv = Buffer.buffer(payload.copy());
                    vertx.runOnContext(v -> {
                        assertEquals(1, s);
                        assertEquals(12, frameType);
                        assertEquals(134, flags.value());
                        assertEquals(expectedRecv, recv);
                    });
                }

                @Override
                public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
                        boolean endOfStream) throws Http2Exception {
                    int len = data.readableBytes();
                    int s = status++;
                    vertx.runOnContext(v -> {
                        assertEquals(2, s);
                        assertEquals(0, len);
                        assertTrue(endOfStream);
                        testComplete();
                    });
                    return data.readableBytes() + padding;
                }
            });
            request.encoder.writeHeaders(request.context, id, GET("/"), 0, false, request.context.newPromise());
            request.encoder.writeFrame(request.context, (byte) 10, id, new Http2Flags((short) 253),
                    expectedSend.getByteBuf(), request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testUpgradeToClearText() throws Exception {
        server.close();
        server = vertx.createHttpServer(
                serverOptions.setHost(DEFAULT_HTTP_HOST).setPort(DEFAULT_HTTP_PORT).setUseAlpn(false).setSsl(false)
                        .setInitialSettings(new io.vertx.core.http.Http2Settings().setMaxConcurrentStreams(20000)));
        server.requestHandler(req -> {
            assertEquals(HttpVersion.HTTP_2, req.version());
            assertEquals(10000, req.connection().remoteSettings().getMaxConcurrentStreams());
            assertFalse(req.isSSL());
            req.response().end();
        });
        startServer();
        client = vertx.createHttpClient(clientOptions.setUseAlpn(false).setSsl(false)
                .setInitialSettings(new io.vertx.core.http.Http2Settings().setMaxConcurrentStreams(10000)));
        HttpClientRequest req = client.get(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, "/somepath");
        req.handler(resp -> {
            assertEquals(HttpVersion.HTTP_2, resp.version());
            assertEquals(20000, req.connection().remoteSettings().getMaxConcurrentStreams());
            testComplete();
        }).exceptionHandler(this::fail).end();
        await();
    }

    @Test
    public void testPushPromiseClearText() throws Exception {
        waitFor(2);
        server.close();
        server = vertx.createHttpServer(serverOptions.setHost(DEFAULT_HTTP_HOST).setPort(DEFAULT_HTTP_PORT)
                .setUseAlpn(false).setSsl(false));
        server.requestHandler(req -> {
            req.response().push(HttpMethod.GET, "/resource", ar -> {
                assertTrue(ar.succeeded());
                ar.result().end("the-pushed-response");
            });
            req.response().end();
        });
        startServer();
        client = vertx.createHttpClient(clientOptions.setUseAlpn(false).setSsl(false));
        HttpClientRequest req = client.get(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, "/somepath");
        req.handler(resp -> {
            assertEquals(HttpVersion.HTTP_2, resp.version());
            complete();
        }).exceptionHandler(this::fail).pushHandler(pushedReq -> {
            assertEquals("http", pushedReq.headers().get(":scheme"));
            pushedReq.handler(pushResp -> {
                pushResp.bodyHandler(buff -> {
                    assertEquals("the-pushed-response", buff.toString());
                    complete();
                });
            });
        }).end();
        await();
    }

    @Test
    public void testUpgradeToClearTextInvalidConnectionHeader() throws Exception {
        testUpgradeFailure(vertx.getOrCreateContext(),
                client -> client.get(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, "/somepath").putHeader("Upgrade", "h2c")
                        .putHeader("Connection", "Upgrade").putHeader("HTTP2-Settings", ""));
    }

    @Test
    public void testUpgradeToClearTextMalformedSettings() throws Exception {
        testUpgradeFailure(vertx.getOrCreateContext(),
                client -> client.get(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, "/somepath").putHeader("Upgrade", "h2c")
                        .putHeader("Connection", "Upgrade,HTTP2-Settings")
                        .putHeader("HTTP2-Settings", "incorrect-settings"));
    }

    @Test
    public void testUpgradeToClearTextInvalidSettings() throws Exception {
        Buffer buffer = Buffer.buffer();
        buffer.appendUnsignedShort(5).appendUnsignedInt((0xFFFFFF + 1));
        String s = new String(Base64.getUrlEncoder().encode(buffer.getBytes()), StandardCharsets.UTF_8);
        testUpgradeFailure(vertx.getOrCreateContext(),
                client -> client.get(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, "/somepath").putHeader("Upgrade", "h2c")
                        .putHeader("Connection", "Upgrade,HTTP2-Settings").putHeader("HTTP2-Settings", s));
    }

    @Test
    public void testUpgradeToClearTextMissingSettings() throws Exception {
        testUpgradeFailure(vertx.getOrCreateContext(),
                client -> client.get(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, "/somepath").putHeader("Upgrade", "h2c")
                        .putHeader("Connection", "Upgrade,HTTP2-Settings"));
    }

    @Test
    public void testUpgradeToClearTextWorkerContext() throws Exception {
        testUpgradeFailure(createWorker(),
                client -> client.get(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, "/somepath").putHeader("Upgrade", "h2c")
                        .putHeader("Connection", "Upgrade,HTTP2-Settings").putHeader("HTTP2-Settings", ""));
    }

    private void testUpgradeFailure(Context context, Function<HttpClient, HttpClientRequest> doRequest)
            throws Exception {
        server.close();
        server = vertx.createHttpServer(serverOptions.setHost(DEFAULT_HTTP_HOST).setPort(DEFAULT_HTTP_PORT)
                .setUseAlpn(false).setSsl(false));
        server.requestHandler(req -> {
            fail();
        });
        startServer(context);
        client = vertx.createHttpClient(
                clientOptions.setProtocolVersion(HttpVersion.HTTP_1_1).setUseAlpn(false).setSsl(false));
        doRequest.apply(client).handler(resp -> {
            assertEquals(400, resp.statusCode());
            assertEquals(HttpVersion.HTTP_1_1, resp.version());
            testComplete();
        }).exceptionHandler(this::fail).end();
        await();
    }

    @Test
    public void testIdleTimeout() throws Exception {
        waitFor(5);
        server.close();
        server = vertx.createHttpServer(serverOptions.setIdleTimeout(2));
        server.requestHandler(req -> {
            req.exceptionHandler(err -> {
                assertTrue(err instanceof ClosedChannelException);
                complete();
            });
            req.response().closeHandler(v -> {
                complete();
            });
            req.response().endHandler(v -> {
                complete();
            });
            req.connection().closeHandler(v -> {
                complete();
            });
        });
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            int id = request.nextStreamId();
            request.decoder.frameListener(new Http2EventAdapter() {
            });
            request.encoder.writeHeaders(request.context, id, GET("/"), 0, false, request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        fut.channel().closeFuture().addListener(v1 -> {
            vertx.runOnContext(v2 -> {
                complete();
            });
        });
        await();
    }

    @Test
    public void testFallbackOnHttp1ForWorkerContext() throws Exception {
        server.requestHandler(req -> {
            assertEquals(HttpVersion.HTTP_1_1, req.version());
            req.response().end();
        });
        startServer(createWorker());
        client = vertx.createHttpClient(clientOptions);
        client.get(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/somepath", resp -> {
            assertEquals(HttpVersion.HTTP_1_1, resp.version());
            testComplete();
        }).end();
        await();
    }

    @Test
    public void testSendPing() throws Exception {
        waitFor(2);
        Buffer expected = TestUtils.randomBuffer(8);
        Context ctx = vertx.getOrCreateContext();
        server.close();
        server.connectionHandler(conn -> {
            conn.ping(expected, ar -> {
                assertSame(ctx, Vertx.currentContext());
                assertTrue(ar.succeeded());
                assertEquals(expected, ar.result());
                complete();
            });
        });
        server.requestHandler(req -> fail());
        startServer(ctx);
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            request.decoder.frameListener(new Http2EventAdapter() {
                @Override
                public void onPingRead(ChannelHandlerContext ctx, ByteBuf data) throws Http2Exception {
                    Buffer buffer = Buffer.buffer(data.copy());
                    vertx.runOnContext(v -> {
                        assertEquals(expected, buffer);
                        complete();
                    });
                }
            });
        });
        fut.sync();
        await();
    }

    @Test
    public void testReceivePing() throws Exception {
        Buffer expected = TestUtils.randomBuffer(8);
        Context ctx = vertx.getOrCreateContext();
        server.close();
        server.connectionHandler(conn -> {
            conn.pingHandler(buff -> {
                assertOnIOContext(ctx);
                assertEquals(expected, buff);
                testComplete();
            });
        });
        server.requestHandler(req -> fail());
        startServer(ctx);
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            request.encoder.writePing(request.context, false, expected.getByteBuf(), request.context.newPromise());
        });
        fut.sync();
        await();
    }

    @Test
    public void testPriorKnowledge() throws Exception {
        server.close();
        server = vertx
                .createHttpServer(new HttpServerOptions().setPort(DEFAULT_HTTP_PORT).setHost(DEFAULT_HTTP_HOST));
        server.requestHandler(req -> {
            req.response().end("Hello World");
        });
        startServer();
        TestClient client = new TestClient() {
            @Override
            protected ChannelInitializer channelInitializer(int port, String host, Consumer<Connection> handler) {
                return new ChannelInitializer() {
                    @Override
                    protected void initChannel(Channel ch) throws Exception {
                        ChannelPipeline p = ch.pipeline();
                        Http2Connection connection = new DefaultHttp2Connection(false);
                        TestClientHandlerBuilder clientHandlerBuilder = new TestClientHandlerBuilder(handler);
                        TestClientHandler clientHandler = clientHandlerBuilder.build(connection);
                        p.addLast(clientHandler);
                    }
                };
            }
        };
        ChannelFuture fut = client.connect(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, request -> {
            request.decoder.frameListener(new Http2EventAdapter() {
                @Override
                public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
                        int streamDependency, short weight, boolean exclusive, int padding, boolean endStream)
                        throws Http2Exception {
                    vertx.runOnContext(v -> {
                        testComplete();
                    });
                }
            });
            int id = request.nextStreamId();
            request.encoder.writeHeaders(request.context, id, GET("/"), 0, true, request.context.newPromise());
            request.context.flush();
        });
        fut.sync();
        await();
    }

    @Test
    public void testConnectionWindowSize() throws Exception {
        server.close();
        server = vertx.createHttpServer(createHttp2ServerOptions(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST)
                .setHttp2ConnectionWindowSize(65535 + 65535));
        server.requestHandler(req -> {
            req.response().end();
        });
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            request.decoder.frameListener(new Http2EventAdapter() {
                @Override
                public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement)
                        throws Http2Exception {
                    vertx.runOnContext(v -> {
                        assertEquals(65535, windowSizeIncrement);
                        testComplete();
                    });
                }
            });
        });
        fut.sync();
        await();
    }

    @Test
    public void testUpdateConnectionWindowSize() throws Exception {
        server.connectionHandler(conn -> {
            assertEquals(65535, conn.getWindowSize());
            conn.setWindowSize(65535 + 10000);
            assertEquals(65535 + 10000, conn.getWindowSize());
            conn.setWindowSize(65535 + 65535);
            assertEquals(65535 + 65535, conn.getWindowSize());
        }).requestHandler(req -> {
            req.response().end();
        });
        startServer();
        TestClient client = new TestClient();
        ChannelFuture fut = client.connect(DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, request -> {
            request.decoder.frameListener(new Http2EventAdapter() {
                @Override
                public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement)
                        throws Http2Exception {
                    vertx.runOnContext(v -> {
                        assertEquals(65535, windowSizeIncrement);
                        testComplete();
                    });
                }
            });
        });
        fut.sync();
        await();
    }
}