io.jsync.http.impl.DefaultHttpServer.java Source code

Java tutorial

Introduction

Here is the source code for io.jsync.http.impl.DefaultHttpServer.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.jsync.http.impl;

import io.jsync.AsyncResult;
import io.jsync.Handler;
import io.jsync.VoidHandler;
import io.jsync.http.HttpServer;
import io.jsync.http.HttpServerRequest;
import io.jsync.http.ServerWebSocket;
import io.jsync.http.impl.cgbystrom.FlashPolicyHandler;
import io.jsync.http.impl.ws.DefaultWebSocketFrame;
import io.jsync.http.impl.ws.WebSocketFrameInternal;
import io.jsync.impl.AsyncInternal;
import io.jsync.impl.Closeable;
import io.jsync.impl.DefaultContext;
import io.jsync.impl.DefaultFutureResult;
import io.jsync.logging.Logger;
import io.jsync.logging.impl.LoggerFactory;
import io.jsync.net.impl.*;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.ChannelGroupFuture;
import io.netty.channel.group.ChannelGroupFutureListener;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.GlobalEventExecutor;

import javax.net.ssl.SSLContext;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import static io.jsync.http.WebSocketFrame.FrameType.PONG;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

/**
 * @author <a href="http://tfox.org">Tim Fox</a>
 */
public class DefaultHttpServer implements HttpServer, Closeable {

    private static final Logger log = LoggerFactory.getLogger(DefaultHttpServer.class);

    final AsyncInternal async;
    final TCPSSLHelper tcpHelper = new TCPSSLHelper();
    final Map<Channel, ServerConnection> connectionMap = new ConcurrentHashMap<>();
    private final DefaultContext actualCtx;
    private final AsyncEventLoopGroup availableWorkers = new AsyncEventLoopGroup();
    private Handler<HttpServerRequest> requestHandler;
    private Handler<ServerWebSocket> wsHandler;
    private ChannelGroup serverChannelGroup;
    private boolean listening;
    private String serverOrigin;
    private boolean compressionSupported;
    private int maxWebSocketFrameSize = 65536;
    private Set<String> webSocketSubProtocols = Collections.unmodifiableSet(Collections.<String>emptySet());
    private ChannelFuture bindFuture;
    private ServerID id;
    private DefaultHttpServer actualServer;
    private int actualPort = 0;
    private HandlerManager<HttpServerRequest> reqHandlerManager = new HandlerManager<>(availableWorkers);
    private HandlerManager<ServerWebSocket> wsHandlerManager = new HandlerManager<>(availableWorkers);

    public DefaultHttpServer(AsyncInternal async) {
        this.async = async;
        actualCtx = async.getOrCreateContext();
        actualCtx.addCloseHook(this);
        tcpHelper.setReuseAddress(true);
    }

    @Override
    public HttpServer requestHandler(Handler<HttpServerRequest> requestHandler) {
        if (listening) {
            throw new IllegalStateException("Please set handler before server is listening");
        }
        this.requestHandler = requestHandler;
        return this;
    }

    @Override
    public Handler<HttpServerRequest> requestHandler() {
        return requestHandler;
    }

    @Override
    public HttpServer websocketHandler(Handler<ServerWebSocket> wsHandler) {
        if (listening) {
            throw new IllegalStateException("Please set handler before server is listening");
        }
        this.wsHandler = wsHandler;
        return this;
    }

    @Override
    public Handler<ServerWebSocket> websocketHandler() {
        return wsHandler;
    }

    public HttpServer listen(int port) {
        listen(port, "0.0.0.0", null);
        return this;
    }

    public HttpServer listen(int port, String host) {
        listen(port, host, null);
        return this;
    }

    public HttpServer listen(int port, Handler<AsyncResult<HttpServer>> listenHandler) {
        listen(port, "0.0.0.0", listenHandler);
        return this;
    }

    public HttpServer listen(int port, String host, final Handler<AsyncResult<HttpServer>> listenHandler) {
        if (requestHandler == null && wsHandler == null) {
            throw new IllegalStateException("Set request or websocket handler first");
        }
        if (listening) {
            throw new IllegalStateException("Listen already called");
        }
        listening = true;

        synchronized (async.sharedHttpServers()) {
            id = new ServerID(port, host);

            serverOrigin = (isSSL() ? "https" : "http") + "://" + host + ":" + port;

            DefaultHttpServer shared = async.sharedHttpServers().get(id);
            if (shared == null || port == 0) {
                serverChannelGroup = new DefaultChannelGroup("async-acceptor-channels",
                        GlobalEventExecutor.INSTANCE);
                ServerBootstrap bootstrap = new ServerBootstrap();
                bootstrap.group(availableWorkers);
                bootstrap.channel(NioServerSocketChannel.class);
                tcpHelper.applyConnectionOptions(bootstrap);
                tcpHelper.checkSSL(async);
                bootstrap.childHandler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        if (tcpHelper.isSSL()) {
                            pipeline.addLast("ssl", tcpHelper.createSslHandler(async, false));
                        }
                        pipeline.addLast("flashpolicy", new FlashPolicyHandler());
                        pipeline.addLast("httpDecoder", new HttpRequestDecoder(4096, 8192, 8192, false));
                        pipeline.addLast("httpEncoder", new AsyncHttpResponseEncoder());
                        if (compressionSupported) {
                            pipeline.addLast("deflater", new HttpChunkContentCompressor());
                        }
                        if (tcpHelper.isSSL() || compressionSupported) {
                            // only add ChunkedWriteHandler when SSL is enabled otherwise it is not needed as FileRegion is used.
                            pipeline.addLast("chunkedWriter", new ChunkedWriteHandler()); // For large file / sendfile support
                        }
                        pipeline.addLast("handler", new ServerHandler());
                    }
                });

                addHandlers(this);
                try {
                    bindFuture = bootstrap.bind(new InetSocketAddress(InetAddress.getByName(host), port));
                    bindFuture.addListener(new ChannelFutureListener() {
                        @Override
                        public void operationComplete(ChannelFuture channelFuture) throws Exception {
                            if (!channelFuture.isSuccess()) {
                                async.sharedHttpServers().remove(id);
                            } else {
                                Channel channel = channelFuture.channel();
                                InetSocketAddress address = (InetSocketAddress) channel.localAddress();
                                DefaultHttpServer.this.actualPort = address.getPort();
                                serverChannelGroup.add(channel);
                            }
                        }
                    });
                } catch (final Throwable t) {
                    // Make sure we send the exception back through the handler (if any)
                    if (listenHandler != null) {
                        async.runOnContext(new VoidHandler() {
                            @Override
                            protected void handle() {
                                listenHandler.handle(new DefaultFutureResult<HttpServer>(t));
                            }
                        });
                    } else {
                        // No handler - log so user can see failure
                        actualCtx.reportException(t);
                    }
                    listening = false;
                    return this;
                }
                async.sharedHttpServers().put(id, this);
                actualServer = this;
            } else {
                // Server already exists with that host/port - we will use that
                actualServer = shared;
                this.actualPort = shared.actualPort;
                addHandlers(actualServer);
            }
            actualServer.bindFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(final ChannelFuture future) throws Exception {
                    if (listenHandler != null) {
                        final AsyncResult<HttpServer> res;
                        if (future.isSuccess()) {
                            res = new DefaultFutureResult<HttpServer>(DefaultHttpServer.this);
                        } else {
                            res = new DefaultFutureResult<>(future.cause());
                            listening = false;
                        }
                        actualCtx.execute(future.channel().eventLoop(), new Runnable() {
                            @Override
                            public void run() {
                                listenHandler.handle(res);
                            }
                        });
                    } else if (!future.isSuccess()) {
                        listening = false;
                        // No handler - log so user can see failure
                        actualCtx.reportException(future.cause());
                    }
                }
            });
        }
        return this;
    }

    private void addHandlers(DefaultHttpServer server) {
        if (requestHandler != null) {
            server.reqHandlerManager.addHandler(requestHandler, actualCtx);
        }
        if (wsHandler != null) {
            server.wsHandlerManager.addHandler(wsHandler, actualCtx);
        }
    }

    @Override
    public void close() {
        close(null);
    }

    @Override
    public void close(final Handler<AsyncResult<Void>> done) {
        if (!listening) {
            executeCloseDone(actualCtx, done, null);
            return;
        }
        listening = false;

        synchronized (async.sharedHttpServers()) {

            if (actualServer != null) {

                if (requestHandler != null) {
                    actualServer.reqHandlerManager.removeHandler(requestHandler, actualCtx);
                }
                if (wsHandler != null) {
                    actualServer.wsHandlerManager.removeHandler(wsHandler, actualCtx);
                }

                if (actualServer.reqHandlerManager.hasHandlers() || actualServer.wsHandlerManager.hasHandlers()) {
                    // The actual server still has handlers so we don't actually close it
                    if (done != null) {
                        executeCloseDone(actualCtx, done, null);
                    }
                } else {
                    // No Handlers left so close the actual server
                    // The done handler needs to be executed on the context that calls close, NOT the context
                    // of the actual server
                    actualServer.actualClose(actualCtx, done);
                }
            }
        }
        requestHandler = null;
        wsHandler = null;
        actualCtx.removeCloseHook(this);
    }

    @Override
    public HttpServer setSSL(boolean ssl) {
        checkListening();
        tcpHelper.setSSL(ssl);
        return this;
    }

    @Override
    public HttpServer setSSLContext(SSLContext sslContext) {
        checkListening();
        tcpHelper.setExternalSSLContext(sslContext);
        return this;
    }

    @Override
    public HttpServer setKeyStorePath(String path) {
        checkListening();
        tcpHelper.setKeyStorePath(path);
        return this;
    }

    @Override
    public HttpServer setKeyStorePassword(String pwd) {
        checkListening();
        tcpHelper.setKeyStorePassword(pwd);
        return this;
    }

    @Override
    public HttpServer setTrustStorePath(String path) {
        checkListening();
        tcpHelper.setTrustStorePath(path);
        return this;
    }

    @Override
    public HttpServer setTrustStorePassword(String pwd) {
        checkListening();
        tcpHelper.setTrustStorePassword(pwd);
        return this;
    }

    @Override
    public HttpServer setClientAuthRequired(boolean required) {
        checkListening();
        tcpHelper.setClientAuthRequired(required);
        return this;
    }

    @Override
    public HttpServer setTCPNoDelay(boolean tcpNoDelay) {
        checkListening();
        tcpHelper.setTCPNoDelay(tcpNoDelay);
        return this;
    }

    @Override
    public HttpServer setSendBufferSize(int size) {
        checkListening();
        tcpHelper.setSendBufferSize(size);
        return this;
    }

    @Override
    public HttpServer setReceiveBufferSize(int size) {
        checkListening();
        tcpHelper.setReceiveBufferSize(size);
        return this;
    }

    @Override
    public HttpServer setTCPKeepAlive(boolean keepAlive) {
        checkListening();
        tcpHelper.setTCPKeepAlive(keepAlive);
        return this;
    }

    @Override
    public HttpServer setReuseAddress(boolean reuse) {
        checkListening();
        tcpHelper.setReuseAddress(reuse);
        return this;
    }

    @Override
    public HttpServer setSoLinger(int linger) {
        checkListening();
        tcpHelper.setSoLinger(linger);
        return this;
    }

    @Override
    public HttpServer setTrafficClass(int trafficClass) {
        checkListening();
        tcpHelper.setTrafficClass(trafficClass);
        return this;
    }

    @Override
    public HttpServer setAcceptBacklog(int backlog) {
        checkListening();
        tcpHelper.setAcceptBacklog(backlog);
        return this;
    }

    @Override
    public boolean isTCPNoDelay() {
        return tcpHelper.isTCPNoDelay();
    }

    @Override
    public int getSendBufferSize() {

        return tcpHelper.getSendBufferSize();
    }

    @Override
    public int getReceiveBufferSize() {
        return tcpHelper.getReceiveBufferSize();
    }

    @Override
    public boolean isTCPKeepAlive() {
        return tcpHelper.isTCPKeepAlive();
    }

    @Override
    public boolean isReuseAddress() {
        return tcpHelper.isReuseAddress();
    }

    @Override
    public int getSoLinger() {
        return tcpHelper.getSoLinger();
    }

    @Override
    public int getTrafficClass() {
        return tcpHelper.getTrafficClass();
    }

    @Override
    public int getAcceptBacklog() {
        return tcpHelper.getAcceptBacklog();
    }

    @Override
    public boolean isSSL() {
        return tcpHelper.isSSL();
    }

    @Override
    public String getKeyStorePath() {
        return tcpHelper.getKeyStorePath();
    }

    @Override
    public String getKeyStorePassword() {
        return tcpHelper.getKeyStorePassword();
    }

    @Override
    public String getTrustStorePath() {
        return tcpHelper.getTrustStorePath();
    }

    @Override
    public String getTrustStorePassword() {
        return tcpHelper.getTrustStorePassword();
    }

    @Override
    public boolean isClientAuthRequired() {
        return tcpHelper.getClientAuth() == TCPSSLHelper.ClientAuth.REQUIRED;
    }

    @Override
    public HttpServer setUsePooledBuffers(boolean pooledBuffers) {
        checkListening();
        tcpHelper.setUsePooledBuffers(pooledBuffers);
        return this;
    }

    @Override
    public boolean isUsePooledBuffers() {
        return tcpHelper.isUsePooledBuffers();
    }

    @Override
    public HttpServer setCompressionSupported(boolean compressionSupported) {
        checkListening();
        this.compressionSupported = compressionSupported;
        return this;
    }

    @Override
    public boolean isCompressionSupported() {
        return compressionSupported;
    }

    @Override
    public HttpServer setMaxWebSocketFrameSize(int maxSize) {
        maxWebSocketFrameSize = maxSize;
        return this;
    }

    @Override
    public int getMaxWebSocketFrameSize() {
        return maxWebSocketFrameSize;
    }

    @Override
    public HttpServer setWebSocketSubProtocols(String... subProtocols) {
        if (subProtocols == null || subProtocols.length == 0) {
            webSocketSubProtocols = Collections.unmodifiableSet(Collections.<String>emptySet());
        } else {
            webSocketSubProtocols = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(subProtocols)));
        }
        return this;
    }

    @Override
    public int port() {
        return actualPort;
    }

    @Override
    public Set<String> getWebSocketSubProtocols() {
        return webSocketSubProtocols;
    }

    private void actualClose(final DefaultContext closeContext, final Handler<AsyncResult<Void>> done) {
        if (id != null) {
            async.sharedHttpServers().remove(id);
        }

        for (ServerConnection conn : connectionMap.values()) {
            conn.close();
        }

        // We need to reset it since sock.internalClose() above can call into the close handlers of sockets on the same thread
        // which can cause context id for the thread to change!

        async.setContext(closeContext);

        final CountDownLatch latch = new CountDownLatch(1);

        ChannelGroupFuture fut = serverChannelGroup.close();
        fut.addListener(new ChannelGroupFutureListener() {
            public void operationComplete(ChannelGroupFuture channelGroupFuture) throws Exception {
                latch.countDown();
            }
        });

        // Always sync
        try {
            latch.await(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
        }

        executeCloseDone(closeContext, done, fut.cause());
    }

    private void executeCloseDone(final DefaultContext closeContext, final Handler<AsyncResult<Void>> done,
            final Exception e) {
        if (done != null) {
            closeContext.execute(new Runnable() {
                public void run() {
                    done.handle(new DefaultFutureResult<Void>(e));
                }
            });
        }
    }

    private void checkListening() {
        if (listening) {
            throw new IllegalStateException("Can't set property when server is listening");
        }
    }

    public class ServerHandler extends AsyncHttpHandler<ServerConnection> {
        private boolean closeFrameSent;

        public ServerHandler() {
            super(async, DefaultHttpServer.this.connectionMap);
        }

        private void sendError(CharSequence err, HttpResponseStatus status, Channel ch) {
            FullHttpResponse resp = new DefaultFullHttpResponse(HTTP_1_1, status);
            if (status.code() == METHOD_NOT_ALLOWED.code()) {
                // SockJS requires this
                resp.headers().set(io.jsync.http.HttpHeaders.ALLOW, io.jsync.http.HttpHeaders.GET);
            }
            if (err != null) {
                resp.content().writeBytes(err.toString().getBytes(CharsetUtil.UTF_8));
                HttpUtil.setContentLength(resp, err.length());
            } else {
                HttpUtil.setContentLength(resp, 0);
            }

            ch.writeAndFlush(resp);
        }

        FullHttpRequest wsRequest;

        @Override
        protected void doMessageReceived(ServerConnection conn, ChannelHandlerContext ctx, Object msg)
                throws Exception {
            Channel ch = ctx.channel();

            if (msg instanceof HttpRequest) {
                final HttpRequest request = (HttpRequest) msg;

                if (log.isTraceEnabled())
                    log.trace("Server received request: " + request.uri());

                if (HttpUtil.is100ContinueExpected(request)) {
                    ch.writeAndFlush(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
                }

                if (wsHandlerManager.hasHandlers() && request.headers().contains(io.jsync.http.HttpHeaders.UPGRADE,
                        io.jsync.http.HttpHeaders.WEBSOCKET, true)) {
                    // As a fun part, Firefox 6.0.2 supports Websockets protocol '7'. But,
                    // it doesn't send a normal 'Connection: Upgrade' header. Instead it
                    // sends: 'Connection: keep-alive, Upgrade'. Brilliant.
                    String connectionHeader = request.headers().get(io.jsync.http.HttpHeaders.CONNECTION);
                    if (connectionHeader == null || !connectionHeader.toLowerCase().contains("upgrade")) {
                        sendError("\"Connection\" must be \"Upgrade\".", BAD_REQUEST, ch);
                        return;
                    }

                    if (request.method() != HttpMethod.GET) {
                        sendError(null, METHOD_NOT_ALLOWED, ch);
                        return;
                    }

                    if (wsRequest == null) {
                        if (request instanceof FullHttpRequest) {
                            handshake((FullHttpRequest) request, ch, ctx);
                        } else {
                            wsRequest = new DefaultFullHttpRequest(request.protocolVersion(), request.method(),
                                    request.uri());
                            wsRequest.headers().set(request.headers());
                        }
                    }
                } else {
                    //HTTP request
                    if (conn == null) {
                        HandlerHolder<HttpServerRequest> reqHandler = reqHandlerManager
                                .chooseHandler(ch.eventLoop());
                        if (reqHandler != null) {
                            conn = new ServerConnection(DefaultHttpServer.this, ch, reqHandler.context,
                                    serverOrigin);
                            conn.requestHandler(reqHandler.handler);
                            connectionMap.put(ch, conn);
                            conn.handleMessage(msg);
                        }
                    } else {
                        conn.handleMessage(msg);
                    }
                }
            } else if (msg instanceof WebSocketFrameInternal) {
                //Websocket frame
                WebSocketFrameInternal wsFrame = (WebSocketFrameInternal) msg;
                switch (wsFrame.type()) {
                case BINARY:
                case CONTINUATION:
                case TEXT:
                    if (conn != null) {
                        conn.handleMessage(msg);
                    }
                    break;
                case PING:
                    // Echo back the content of the PING frame as PONG frame as specified in RFC 6455 Section 5.5.2
                    ch.writeAndFlush(new DefaultWebSocketFrame(PONG, wsFrame.getBinaryData()));
                    break;
                case CLOSE:
                    if (!closeFrameSent) {
                        // Echo back close frame and close the connection once it was written.
                        // This is specified in the WebSockets RFC 6455 Section  5.4.1
                        ch.writeAndFlush(wsFrame).addListener(ChannelFutureListener.CLOSE);
                        closeFrameSent = true;
                    }
                    break;
                }
            } else if (msg instanceof HttpContent) {
                if (wsRequest != null) {
                    wsRequest.content().writeBytes(((HttpContent) msg).content());
                    if (msg instanceof LastHttpContent) {
                        FullHttpRequest req = wsRequest;
                        wsRequest = null;
                        handshake(req, ch, ctx);
                        return;
                    }
                }
                if (conn != null) {
                    conn.handleMessage(msg);
                }
            } else {
                throw new IllegalStateException("Invalid message " + msg);
            }
        }

        private String getWebSocketLocation(ChannelPipeline pipeline, FullHttpRequest req) throws Exception {
            String prefix;
            if (pipeline.get(SslHandler.class) == null) {
                prefix = "ws://";
            } else {
                prefix = "wss://";
            }
            URI uri = new URI(req.uri());
            String path = uri.getRawPath();
            String loc = prefix + req.headers().get(HttpHeaderNames.HOST) + path;
            String query = uri.getRawQuery();
            if (query != null) {
                loc += "?" + query;
            }
            return loc;
        }

        private void handshake(final FullHttpRequest request, final Channel ch, ChannelHandlerContext ctx)
                throws Exception {
            final WebSocketServerHandshaker shake;
            String subProtocols = null;
            Set<String> webSocketSubProtocols = DefaultHttpServer.this.webSocketSubProtocols;
            if (!webSocketSubProtocols.isEmpty()) {
                StringBuilder sb = new StringBuilder();
                Iterator<String> protocols = webSocketSubProtocols.iterator();
                while (protocols.hasNext()) {
                    sb.append(protocols.next());
                    if (protocols.hasNext()) {
                        sb.append(',');
                    }
                }
                subProtocols = sb.toString();
            }
            WebSocketServerHandshakerFactory factory = new WebSocketServerHandshakerFactory(
                    getWebSocketLocation(ch.pipeline(), request), subProtocols, false, maxWebSocketFrameSize);
            shake = factory.newHandshaker(request);

            if (shake == null) {
                log.error("Unrecognised websockets handshake");
                WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ch);
                return;
            }
            HandlerHolder<ServerWebSocket> firstHandler = null;
            HandlerHolder<ServerWebSocket> wsHandler = wsHandlerManager.chooseHandler(ch.eventLoop());
            while (true) {
                if (wsHandler == null || firstHandler == wsHandler) {
                    break;
                }

                URI theURI;
                try {
                    theURI = new URI(request.uri());
                } catch (URISyntaxException e2) {
                    throw new IllegalArgumentException("Invalid uri " + request.uri()); //Should never happen
                }

                final ServerConnection wsConn = new ServerConnection(DefaultHttpServer.this, ch, wsHandler.context,
                        serverOrigin);
                wsConn.wsHandler(wsHandler.handler);

                Runnable connectRunnable = new Runnable() {
                    public void run() {
                        connectionMap.put(ch, wsConn);
                        try {
                            shake.handshake(ch, request);
                        } catch (WebSocketHandshakeException e) {
                            wsConn.handleException(e);
                        } catch (Exception e) {
                            log.error("Failed to generate shake response", e);
                        }
                    }
                };

                final DefaultServerWebSocket ws = new DefaultServerWebSocket(async, theURI.toString(),
                        theURI.getPath(), theURI.getQuery(), new HttpHeadersAdapter(request.headers()), wsConn,
                        connectRunnable);
                wsConn.handleWebsocketConnect(ws);
                if (ws.isRejected()) {
                    firstHandler = wsHandler;
                } else {
                    ChannelHandler handler = ctx.pipeline().get(HttpChunkContentCompressor.class);
                    if (handler != null) {
                        // remove compressor as its not needed anymore once connection was upgraded to websockets
                        ctx.pipeline().remove(handler);
                    }
                    ws.connectNow();
                    return;
                }
            }
            ch.writeAndFlush(new DefaultFullHttpResponse(HTTP_1_1, BAD_GATEWAY));
        }
    }

}