示例#1
0
/**
 * An asynchronous, pooling, HTTP 1.1 client
 *
 * <p>
 *
 * <p>An {@code HttpClient} maintains a pool of connections to a specific host, at a specific port.
 * The HTTP connections can act as pipelines for HTTP requests.
 *
 * <p>It is used as a factory for {@link HttpClientRequest} instances which encapsulate the actual
 * HTTP requests. It is also used as a factory for HTML5 {@link WebSocket websockets}.
 *
 * <p>The client is thread-safe and can be safely shared my different event loops.
 *
 * @author <a href="http://tfox.org">Tim Fox</a>
 */
public class HttpClient extends NetClientBase {

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

  private ClientBootstrap bootstrap;
  private NioClientSocketChannelFactory channelFactory;
  private Map<Channel, ClientConnection> connectionMap = new ConcurrentHashMap();
  private Handler<Exception> exceptionHandler;
  private int port = 80;
  private String host = "localhost";
  private final ConnectionPool<ClientConnection> pool =
      new ConnectionPool<ClientConnection>() {
        protected void connect(Handler<ClientConnection> connectHandler, long contextID) {
          internalConnect(connectHandler, contextID);
        }
      };
  private boolean keepAlive = true;

  /** Create an {@code HttpClient} instance */
  public HttpClient() {
    super();
  }

  /** Set the exception handler for the {@code HttpClient} */
  public void exceptionHandler(Handler<Exception> handler) {
    this.exceptionHandler = handler;
  }

  /**
   * Set the maximum pool size to the value specified by {@code maxConnections}
   *
   * <p>The client will maintain up to {@code maxConnections} HTTP connections in an internal pool
   *
   * <p>
   *
   * @return A reference to this, so multiple invocations can be chained together.
   */
  public HttpClient setMaxPoolSize(int maxConnections) {
    pool.setMaxPoolSize(maxConnections);
    return this;
  }

  /** Returns the maximum number of connections in the pool */
  public int getMaxPoolSize() {
    return pool.getMaxPoolSize();
  }

  /**
   * If {@code keepAlive} is {@code true} then, after the request has ended the connection will be
   * returned to the pool where it can be used by another request. In this manner, many HTTP
   * requests can be pipe-lined over an HTTP connection. Keep alive connections will not be closed
   * until the {@link #close() close()} method is invoked.
   *
   * <p>If {@code keepAlive} is {@code false} then a new connection will be created for each request
   * and it won't ever go in the pool, the connection will closed after the response has been
   * received. Even with no keep alive, the client will not allow more than {@link #getMaxPoolSize()
   * getMaxPoolSize()} connections to be created at any one time.
   *
   * <p>
   *
   * @return A reference to this, so multiple invocations can be chained together.
   */
  public HttpClient setKeepAlive(boolean keepAlive) {
    this.keepAlive = keepAlive;
    return this;
  }

  /** {@inheritDoc} */
  public HttpClient setSSL(boolean ssl) {
    return (HttpClient) super.setSSL(ssl);
  }

  /** {@inheritDoc} */
  public HttpClient setKeyStorePath(String path) {
    return (HttpClient) super.setKeyStorePath(path);
  }

  /** {@inheritDoc} */
  public HttpClient setKeyStorePassword(String pwd) {
    return (HttpClient) super.setKeyStorePassword(pwd);
  }

  /** {@inheritDoc} */
  public HttpClient setTrustStorePath(String path) {
    return (HttpClient) super.setTrustStorePath(path);
  }

  /** {@inheritDoc} */
  public HttpClient setTrustStorePassword(String pwd) {
    return (HttpClient) super.setTrustStorePassword(pwd);
  }

  /** {@inheritDoc} */
  public HttpClient setTrustAll(boolean trustAll) {
    return (HttpClient) super.setTrustAll(trustAll);
  }

  /**
   * Set the port that the client will attempt to connect to on the server to {@code port}. The
   * default value is {@code 80}
   *
   * <p>
   *
   * @return A reference to this, so multiple invocations can be chained together.
   */
  public HttpClient setPort(int port) {
    this.port = port;
    return this;
  }

  /**
   * Set the host that the client will attempt to connect to, to {@code host}. The default value is
   * {@code localhost}
   *
   * <p>
   *
   * @return A reference to this, so multiple invocations can be chained together.
   */
  public HttpClient setHost(String host) {
    this.host = host;
    return this;
  }

  /**
   * Attempt to connect an HTML5 websocket to the specified URI
   *
   * <p>This version of the method defaults to the Hybi-10 version of the websockets protocol The
   * connect is done asynchronously and {@code wsConnect} is called back with the result
   */
  public void connectWebsocket(final String uri, final Handler<WebSocket> wsConnect) {
    connectWebsocket(uri, WebSocketVersion.HYBI_17, wsConnect);
  }

  /**
   * Attempt to connect an HTML5 websocket to the specified URI
   *
   * <p>This version of the method allows you to specify the websockets version using the {@code
   * wsVersion parameter} The connect is done asynchronously and {@code wsConnect} is called back
   * with the result
   */
  public void connectWebsocket(
      final String uri, final WebSocketVersion wsVersion, final Handler<WebSocket> wsConnect) {
    getConnection(
        new Handler<ClientConnection>() {
          public void handle(final ClientConnection conn) {
            conn.toWebSocket(uri, wsConnect, wsVersion);
          }
        },
        Vertx.instance.getContextID());
  }

  /**
   * This is a quick version of the {@link #get(String, org.vertx.java.core.Handler) get()} method
   * where you do not want to do anything with the request before sending.
   *
   * <p>Normally with any of the HTTP methods you create the request then when you are ready to send
   * it you call {@link HttpClientRequest#end()} on it. With this method the request is immediately
   * sent.
   *
   * <p>When an HTTP response is received from the server the {@code responseHandler} is called
   * passing in the response.
   */
  public void getNow(String uri, Handler<HttpClientResponse> responseHandler) {
    getNow(uri, null, responseHandler);
  }

  /**
   * This method works in the same manner as {@link #getNow(String, org.vertx.java.core.Handler)},
   * except that it allows you specify a set of {@code headers} that will be sent with the request.
   */
  public void getNow(
      String uri,
      Map<String, ? extends Object> headers,
      Handler<HttpClientResponse> responseHandler) {
    HttpClientRequest req = get(uri, responseHandler);
    if (headers != null) {
      req.putAllHeaders(headers);
    }
    req.end();
  }

  /**
   * This method returns an {@link HttpClientRequest} instance which represents an HTTP OPTIONS
   * request with the specified {@code uri}.
   *
   * <p>When an HTTP response is received from the server the {@code responseHandler} is called
   * passing in the response.
   */
  public HttpClientRequest options(String uri, Handler<HttpClientResponse> responseHandler) {
    return request("OPTIONS", uri, responseHandler);
  }

  /**
   * This method returns an {@link HttpClientRequest} instance which represents an HTTP GET request
   * with the specified {@code uri}.
   *
   * <p>When an HTTP response is received from the server the {@code responseHandler} is called
   * passing in the response.
   */
  public HttpClientRequest get(String uri, Handler<HttpClientResponse> responseHandler) {
    return request("GET", uri, responseHandler);
  }

  /**
   * This method returns an {@link HttpClientRequest} instance which represents an HTTP HEAD request
   * with the specified {@code uri}.
   *
   * <p>When an HTTP response is received from the server the {@code responseHandler} is called
   * passing in the response.
   */
  public HttpClientRequest head(String uri, Handler<HttpClientResponse> responseHandler) {
    return request("HEAD", uri, responseHandler);
  }

  /**
   * This method returns an {@link HttpClientRequest} instance which represents an HTTP POST request
   * with the specified {@code uri}.
   *
   * <p>When an HTTP response is received from the server the {@code responseHandler} is called
   * passing in the response.
   */
  public HttpClientRequest post(String uri, Handler<HttpClientResponse> responseHandler) {
    return request("POST", uri, responseHandler);
  }

  /**
   * This method returns an {@link HttpClientRequest} instance which represents an HTTP PUT request
   * with the specified {@code uri}.
   *
   * <p>When an HTTP response is received from the server the {@code responseHandler} is called
   * passing in the response.
   */
  public HttpClientRequest put(String uri, Handler<HttpClientResponse> responseHandler) {
    return request("PUT", uri, responseHandler);
  }

  /**
   * This method returns an {@link HttpClientRequest} instance which represents an HTTP DELETE
   * request with the specified {@code uri}.
   *
   * <p>When an HTTP response is received from the server the {@code responseHandler} is called
   * passing in the response.
   */
  public HttpClientRequest delete(String uri, Handler<HttpClientResponse> responseHandler) {
    return request("DELETE", uri, responseHandler);
  }

  /**
   * This method returns an {@link HttpClientRequest} instance which represents an HTTP TRACE
   * request with the specified {@code uri}.
   *
   * <p>When an HTTP response is received from the server the {@code responseHandler} is called
   * passing in the response.
   */
  public HttpClientRequest trace(String uri, Handler<HttpClientResponse> responseHandler) {
    return request("TRACE", uri, responseHandler);
  }

  /**
   * This method returns an {@link HttpClientRequest} instance which represents an HTTP CONNECT
   * request with the specified {@code uri}.
   *
   * <p>When an HTTP response is received from the server the {@code responseHandler} is called
   * passing in the response.
   */
  public HttpClientRequest connect(String uri, Handler<HttpClientResponse> responseHandler) {
    return request("CONNECT", uri, responseHandler);
  }

  /**
   * This method returns an {@link HttpClientRequest} instance which represents an HTTP request with
   * the specified {@code uri}. The specific HTTP method (e.g. GET, POST, PUT etc) is specified
   * using the parameter {@code method}
   *
   * <p>When an HTTP response is received from the server the {@code responseHandler} is called
   * passing in the response.
   */
  public HttpClientRequest request(
      String method, String uri, Handler<HttpClientResponse> responseHandler) {
    final Long cid = Vertx.instance.getContextID();
    if (cid == null) {
      throw new IllegalStateException("Requests must be made from inside an event loop");
    }
    return new HttpClientRequest(this, method, uri, responseHandler, cid, Thread.currentThread());
  }

  /** Close the HTTP client. This will cause any pooled HTTP connections to be closed. */
  public void close() {
    pool.close();
    for (ClientConnection conn : connectionMap.values()) {
      conn.internalClose();
    }
  }

  /** {@inheritDoc} */
  public HttpClient setTcpNoDelay(boolean tcpNoDelay) {
    return (HttpClient) super.setTcpNoDelay(tcpNoDelay);
  }

  /** {@inheritDoc} */
  public HttpClient setSendBufferSize(int size) {
    return (HttpClient) super.setSendBufferSize(size);
  }

  /** {@inheritDoc} */
  public HttpClient setReceiveBufferSize(int size) {
    return (HttpClient) super.setReceiveBufferSize(size);
  }

  /** {@inheritDoc} */
  public HttpClient setTCPKeepAlive(boolean keepAlive) {
    return (HttpClient) super.setTCPKeepAlive(keepAlive);
  }

  /** {@inheritDoc} */
  public HttpClient setReuseAddress(boolean reuse) {
    return (HttpClient) super.setReuseAddress(reuse);
  }

  /** {@inheritDoc} */
  public HttpClient setSoLinger(boolean linger) {
    return (HttpClient) super.setSoLinger(linger);
  }

  /** {@inheritDoc} */
  public HttpClient setTrafficClass(int trafficClass) {
    return (HttpClient) super.setTrafficClass(trafficClass);
  }

  void getConnection(Handler<ClientConnection> handler, long contextID) {
    pool.getConnection(handler, contextID);
  }

  void returnConnection(final ClientConnection conn) {
    if (!conn.keepAlive) {
      // Close it
      conn.internalClose();
    } else {
      pool.returnConnection(conn);
    }
  }

  private void internalConnect(
      final Handler<ClientConnection> connectHandler, final long contextID) {

    if (bootstrap == null) {
      channelFactory =
          new NioClientSocketChannelFactory(
              VertxInternal.instance.getAcceptorPool(), VertxInternal.instance.getWorkerPool());
      bootstrap = new ClientBootstrap(channelFactory);

      checkSSL();

      bootstrap.setPipelineFactory(
          new ChannelPipelineFactory() {
            public ChannelPipeline getPipeline() throws Exception {
              ChannelPipeline pipeline = Channels.pipeline();
              if (ssl) {
                SSLEngine engine = context.createSSLEngine();
                engine.setUseClientMode(true); // We are on the client side of the connection
                pipeline.addLast("ssl", new SslHandler(engine));
              }
              pipeline.addLast("encoder", new HttpRequestEncoder());
              pipeline.addLast("decoder", new HttpResponseDecoder());
              pipeline.addLast("handler", new ClientHandler());
              return pipeline;
            }
          });
    }

    // Client connections share context with caller
    channelFactory.setWorker(VertxInternal.instance.getWorkerForContextID(contextID));
    bootstrap.setOptions(connectionOptions);
    ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port));
    future.addListener(
        new ChannelFutureListener() {
          public void operationComplete(ChannelFuture channelFuture) throws Exception {
            if (channelFuture.isSuccess()) {

              final NioSocketChannel ch = (NioSocketChannel) channelFuture.getChannel();

              runOnCorrectThread(
                  ch,
                  new Runnable() {
                    public void run() {
                      final ClientConnection conn =
                          new ClientConnection(
                              HttpClient.this,
                              ch,
                              host + ":" + port,
                              ssl,
                              keepAlive,
                              contextID,
                              Thread.currentThread());
                      conn.closedHandler(
                          new SimpleHandler() {
                            public void handle() {
                              pool.connectionClosed();
                            }
                          });
                      connectionMap.put(ch, conn);
                      VertxInternal.instance.setContextID(contextID);
                      connectHandler.handle(conn);
                    }
                  });
            } else {
              Throwable t = channelFuture.getCause();
              if (t instanceof Exception && exceptionHandler != null) {
                exceptionHandler.handle((Exception) t);
              } else {
                log.error("Unhandled exception", t);
              }
            }
          }
        });
  }

  private class ClientHandler extends SimpleChannelUpstreamHandler {

    @Override
    public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) {
      final NioSocketChannel ch = (NioSocketChannel) e.getChannel();
      final ClientConnection conn = connectionMap.remove(ch);
      if (conn != null) {
        runOnCorrectThread(
            ch,
            new Runnable() {
              public void run() {
                conn.handleClosed();
              }
            });
      }
    }

    @Override
    public void channelInterestChanged(ChannelHandlerContext ctx, ChannelStateEvent e)
        throws Exception {
      final NioSocketChannel ch = (NioSocketChannel) e.getChannel();
      final ClientConnection conn = connectionMap.get(ch);
      runOnCorrectThread(
          ch,
          new Runnable() {
            public void run() {
              conn.handleInterestedOpsChanged();
            }
          });
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
      final NioSocketChannel ch = (NioSocketChannel) e.getChannel();
      final ClientConnection conn = connectionMap.get(ch);
      final Throwable t = e.getCause();
      if (conn != null && t instanceof Exception) {
        runOnCorrectThread(
            ch,
            new Runnable() {
              public void run() {
                conn.handleException((Exception) t);
              }
            });
      } else {
        log.error("Unhandled exception", t);
      }
    }

    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {

      Channel ch = e.getChannel();
      ClientConnection conn = connectionMap.get(ch);
      Object msg = e.getMessage();
      if (msg instanceof HttpResponse) {
        HttpResponse response = (HttpResponse) msg;

        conn.handleResponse(response);
        ChannelBuffer content = response.getContent();
        if (content.readable()) {
          conn.handleResponseChunk(new Buffer(content));
        }
        if (!response.isChunked()) {
          conn.handleResponseEnd();
        }
      } else if (msg instanceof HttpChunk) {
        HttpChunk chunk = (HttpChunk) msg;
        if (chunk.getContent().readable()) {
          Buffer buff = new Buffer(chunk.getContent());
          conn.handleResponseChunk(buff);
        }
        if (chunk.isLast()) {
          if (chunk instanceof HttpChunkTrailer) {
            HttpChunkTrailer trailer = (HttpChunkTrailer) chunk;
            conn.handleResponseEnd(trailer);
          } else {
            conn.handleResponseEnd();
          }
        }
      } else if (msg instanceof WebSocketFrame) {
        WebSocketFrame frame = (WebSocketFrame) msg;
        conn.handleWsFrame(frame);
      } else {
        throw new IllegalStateException("Invalid object " + e.getMessage());
      }
    }
  }
}
示例#2
0
/**
 * TODO
 *
 * @author <a href="http://tfox.org">Tim Fox</a>
 */
public class RedisReconnectTest extends TestBase {

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

  @Test
  public void testConnectionFailure() throws Exception {

    final CountDownLatch latch = new CountDownLatch(1);

    VertxInternal.instance.go(
        new Runnable() {
          public void run() {
            RedisPool pool = new RedisPool();

            pool.setMaxPoolSize(1);
            pool.setReconnectAttempts(1000);

            final RedisConnection conn = pool.connection();

            log.info("Executing a set");
            conn.set(Buffer.create("key1"), Buffer.create("val1"))
                .handler(
                    new CompletionHandler<Void>() {
                      public void handle(Future<Void> future) {
                        log.info("Result of set returned");
                      }
                    })
                .execute();

            log.info("Now sleeping. Please kill redis");

            Vertx.instance.setTimer(
                10000,
                new Handler<Long>() {
                  public void handle(Long timerID) {

                    log.info("Executing a get");
                    conn.get(Buffer.create("key1"))
                        .handler(
                            new CompletionHandler<Buffer>() {
                              public void handle(Future<Buffer> future) {
                                log.info("Result of get returned " + future.result());

                                conn.close();
                                latch.countDown();
                              }
                            })
                        .execute();
                  }
                });
          }
        });

    assert (latch.await(1000000, TimeUnit.SECONDS));
  }

  @Test
  public void testConnectionFailureWhenInPool() throws Exception {

    final CountDownLatch latch = new CountDownLatch(1);

    VertxInternal.instance.go(
        new Runnable() {
          public void run() {
            final RedisPool pool = new RedisPool();

            pool.setMaxPoolSize(1);
            pool.setReconnectAttempts(1000000);

            RedisConnection conn1 = pool.connection();

            log.info("Executing a set");
            conn1
                .set(Buffer.create("key1"), Buffer.create("val1"))
                .handler(
                    new CompletionHandler<Void>() {
                      public void handle(Future<Void> future) {
                        log.info("Result of set returned");
                      }
                    })
                .execute();

            conn1.close();

            log.info("Now sleeping. Please kill redis");

            Vertx.instance.setTimer(
                10000,
                new Handler<Long>() {
                  public void handle(Long timerID) {

                    final RedisConnection conn2 = pool.connection();

                    log.info("Executing a get");
                    conn2
                        .get(Buffer.create("key1"))
                        .handler(
                            new CompletionHandler<Buffer>() {
                              public void handle(Future<Buffer> future) {
                                log.info("Result of get returned " + future.result());

                                conn2.close();
                                latch.countDown();
                              }
                            })
                        .execute();
                  }
                });
          }
        });

    assert (latch.await(100000, TimeUnit.SECONDS));
  }
}
示例#3
0
/**
 * This class allows you to do route requests based on the HTTP verb and the request URI, in a
 * manner similar to <a href="http://www.sinatrarb.com/">Sinatra</a> or <a
 * href="http://expressjs.com/">Express</a>.
 *
 * <p>RouteMatcher also lets you extract paramaters from the request URI either a simple pattern or
 * using regular expressions for more complex matches. Any parameters extracted will be added to the
 * requests parameters which will be available to you in your request handler.
 *
 * <p>It's particularly useful when writing REST-ful web applications.
 *
 * <p>To use a simple pattern to extract parameters simply prefix the parameter name in the pattern
 * with a ':' (colon).
 *
 * <p>For example:
 *
 * <pre>
 *   RouteMatcher rm = new RouteMatcher();
 *
 *   handler1 = ...
 *
 *   rm.get("/animals/:animal_name/:colour", handler1);
 * </pre>
 *
 * <p>In the above example, if a GET request with a uri of '/animals/dog/black' was received at the
 * server, handler1 would be called with request parameter 'animal' set to 'dog', and 'colour' set
 * to 'black'
 *
 * <p>Different handlers can be specified for each of the HTTP verbs, GET, POST, PUT, DELETE etc.
 *
 * <p>For more complex matches regular expressions can be used in the pattern. When regular
 * expressions are used, the extracted parameters do not have a name, so they are put into the HTTP
 * request with names of param0, param1, param2 etc.
 *
 * <p>Multiple matches can be specified for each HTTP verb. In the case there are more than one
 * matching patterns for a particular request, the first matching one will be used.
 *
 * @author <a href="http://tfox.org">Tim Fox</a>
 */
public class RouteMatcher implements Handler<HttpServerRequest> {

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

  private List<PatternBinding> getBindings = new ArrayList<>();
  private List<PatternBinding> putBindings = new ArrayList<>();
  private List<PatternBinding> postBindings = new ArrayList<>();
  private List<PatternBinding> deleteBindings = new ArrayList<>();
  private List<PatternBinding> optionsBindings = new ArrayList<>();

  @Override
  public void handle(HttpServerRequest request) {
    switch (request.method) {
      case "GET":
        route(request, getBindings);
        break;
      case "PUT":
        route(request, putBindings);
        break;
      case "POST":
        route(request, postBindings);
        break;
      case "DELETE":
        route(request, deleteBindings);
        break;
      case "OPTIONS":
        route(request, optionsBindings);
        break;
    }
  }

  /**
   * Specify a handler that will be called for a matching HTTP GET
   *
   * @param pattern The simple pattern
   * @param handler The handler to call
   */
  public void get(String pattern, Handler<HttpServerRequest> handler) {
    addPattern(pattern, handler, getBindings);
  }

  /**
   * Specify a handler that will be called for a matching HTTP PUT
   *
   * @param pattern The simple pattern
   * @param handler The handler to call
   */
  public void put(String pattern, Handler<HttpServerRequest> handler) {
    addPattern(pattern, handler, putBindings);
  }

  /**
   * Specify a handler that will be called for a matching HTTP POST
   *
   * @param pattern The simple pattern
   * @param handler The handler to call
   */
  public void post(String pattern, Handler<HttpServerRequest> handler) {
    addPattern(pattern, handler, postBindings);
  }

  /**
   * Specify a handler that will be called for a matching HTTP DELETE
   *
   * @param pattern The simple pattern
   * @param handler The handler to call
   */
  public void delete(String pattern, Handler<HttpServerRequest> handler) {
    addPattern(pattern, handler, deleteBindings);
  }

  /**
   * Specify a handler that will be called for a matching HTTP OPTIONS
   *
   * @param pattern The simple pattern
   * @param handler The handler to call
   */
  public void options(String pattern, Handler<HttpServerRequest> handler) {
    addPattern(pattern, handler, optionsBindings);
  }

  /**
   * Specify a handler that will be called for all HTTP methods
   *
   * @param pattern The simple pattern
   * @param handler The handler to call
   */
  public void all(String pattern, Handler<HttpServerRequest> handler) {
    addPattern(pattern, handler, getBindings);
    addPattern(pattern, handler, putBindings);
    addPattern(pattern, handler, postBindings);
    addPattern(pattern, handler, deleteBindings);
    addPattern(pattern, handler, optionsBindings);
  }

  /**
   * Specify a handler that will be called for a matching HTTP GET
   *
   * @param regex A regular expression
   * @param handler The handler to call
   */
  public void getWithRegEx(String regex, Handler<HttpServerRequest> handler) {
    addRegEx(regex, handler, getBindings);
  }

  /**
   * Specify a handler that will be called for a matching HTTP PUT
   *
   * @param regex A regular expression
   * @param handler The handler to call
   */
  public void putWithRegEx(String regex, Handler<HttpServerRequest> handler) {
    addRegEx(regex, handler, putBindings);
  }

  /**
   * Specify a handler that will be called for a matching HTTP POST
   *
   * @param regex A regular expression
   * @param handler The handler to call
   */
  public void postWithRegEx(String regex, Handler<HttpServerRequest> handler) {
    addRegEx(regex, handler, postBindings);
  }

  /**
   * Specify a handler that will be called for a matching HTTP DELETE
   *
   * @param regex A regular expression
   * @param handler The handler to call
   */
  public void deleteWithRegEx(String regex, Handler<HttpServerRequest> handler) {
    addRegEx(regex, handler, deleteBindings);
  }

  /**
   * Specify a handler that will be called for all HTTP methods
   *
   * @param regex A regular expression
   * @param handler The handler to call
   */
  public void allWithRegEx(String regex, Handler<HttpServerRequest> handler) {
    addRegEx(regex, handler, getBindings);
    addRegEx(regex, handler, putBindings);
    addRegEx(regex, handler, postBindings);
    addRegEx(regex, handler, deleteBindings);
    addRegEx(regex, handler, optionsBindings);
  }

  /**
   * Specify a handler that will be called for a matching HTTP OPTIONS
   *
   * @param regex A regular expression
   * @param handler The handler to call
   */
  public void optionsWithRegEx(String regex, Handler<HttpServerRequest> handler) {
    addRegEx(regex, handler, optionsBindings);
  }

  private void addPattern(
      String input, Handler<HttpServerRequest> handler, List<PatternBinding> bindings) {
    // We need to search for any :<token name> tokens in the String and replace them with named
    // capture groups
    Matcher m = Pattern.compile(":([A-Za-z][A-Za-z0-9]*)").matcher(input);
    StringBuffer sb = new StringBuffer();
    Set<String> groups = new HashSet<>();
    while (m.find()) {
      String group = m.group().substring(1);
      if (groups.contains(group)) {
        throw new IllegalArgumentException(
            "Cannot use identifier " + group + " more than once in pattern string");
      }
      m.appendReplacement(sb, "(?<$1>[^\\/]+)");
      groups.add(group);
    }
    m.appendTail(sb);
    String regex = sb.toString();
    PatternBinding binding = new PatternBinding(Pattern.compile(regex), groups, handler);
    bindings.add(binding);
  }

  private void addRegEx(
      String input, Handler<HttpServerRequest> handler, List<PatternBinding> bindings) {
    PatternBinding binding = new PatternBinding(Pattern.compile(input), null, handler);
    bindings.add(binding);
  }

  private void route(HttpServerRequest request, List<PatternBinding> bindings) {
    for (PatternBinding binding : bindings) {
      Matcher m = binding.pattern.matcher(request.path);
      if (m.matches()) {
        Map<String, String> params = new HashMap<>(m.groupCount());
        if (binding.paramNames != null) {
          // Named params
          for (String param : binding.paramNames) {
            params.put(param, m.group(param));
          }
        } else {
          // Un-named params
          for (int i = 0; i < m.groupCount(); i++) {
            params.put("param" + i, m.group(i + 1));
          }
        }
        request.getParams().putAll(params);
        binding.handler.handle(request);
        return;
      }
    }
    // If we get here it wasn't routed
    request.response.statusCode = 404;
    request.response.end();
  }

  private static class PatternBinding {
    final Pattern pattern;
    final Handler<HttpServerRequest> handler;
    final Set<String> paramNames;

    private PatternBinding(
        Pattern pattern, Set<String> paramNames, Handler<HttpServerRequest> handler) {
      this.pattern = pattern;
      this.paramNames = paramNames;
      this.handler = handler;
    }
  }
}
示例#4
0
/**
 * Encapsulates a client-side HTTP request.
 *
 * <p>
 *
 * <p>Instances of this class are created by an {@link HttpClient} instance, via one of the methods
 * corresponding to the specific HTTP methods, or the generic {@link HttpClient#request} method
 *
 * <p>
 *
 * <p>Once an instance of this class has been obtained, headers can be set on it, and data can be
 * written to its body, if required. Once you are ready to send the request, the {@link #end()}
 * method must called.
 *
 * <p>
 *
 * <p>Nothing is sent until the request has been internally assigned an HTTP connection. The {@link
 * HttpClient} instance will return an instance of this class immediately, even if there are no HTTP
 * connections available in the pool. Any requests sent before a connection is assigned will be
 * queued internally and actually sent when an HTTP connection becomes available from the pool.
 *
 * <p>
 *
 * <p>The headers of the request are actually sent either when the {@link #end()} method is called,
 * or, when the first part of the body is written, whichever occurs first.
 *
 * <p>
 *
 * <p>This class supports both chunked and non-chunked HTTP.
 *
 * <p>
 *
 * <p>This class can only be used from the event loop that created it.
 *
 * <p>
 *
 * <p>An example of using this class is as follows:
 *
 * <p>
 *
 * <pre>
 *
 * HttpClientRequest req = httpClient.post("/some-url", new EventHandler<HttpClientResponse>() {
 *   public void onEvent(HttpClientResponse response) {
 *     System.out.println("Got response: " + response.statusCode);
 *   }
 * });
 *
 * req.putHeader("some-header", "hello");
 * req.putHeader("Content-Length", 5);
 * req.write(Buffer.create(new byte[]{1, 2, 3, 4, 5}));
 * req.write(Buffer.create(new byte[]{6, 7, 8, 9, 10}));
 * req.end();
 *
 * </pre>
 *
 * @author <a href="http://tfox.org">Tim Fox</a>
 */
public class HttpClientRequest implements WriteStream {

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

  HttpClientRequest(
      final HttpClient client,
      final String method,
      final String uri,
      final Handler<HttpClientResponse> respHandler,
      final long contextID,
      final Thread th) {
    this(client, method, uri, respHandler, contextID, th, false);
  }

  // Raw request - used by websockets
  // Raw requests won't have any headers set automatically, like Content-Length and Connection
  HttpClientRequest(
      final HttpClient client,
      final String method,
      final String uri,
      final Handler<HttpClientResponse> respHandler,
      final long contextID,
      final Thread th,
      final ClientConnection conn) {
    this(client, method, uri, respHandler, contextID, th, true);
    this.conn = conn;
    conn.setCurrentRequest(this);
  }

  private HttpClientRequest(
      final HttpClient client,
      final String method,
      final String uri,
      final Handler<HttpClientResponse> respHandler,
      final long contextID,
      final Thread th,
      final boolean raw) {
    this.client = client;
    this.request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.valueOf(method), uri);
    this.chunked = false;
    this.respHandler = respHandler;
    this.contextID = contextID;
    this.th = th;
    this.raw = raw;
  }

  private final HttpClient client;
  private final HttpRequest request;
  private final Handler<HttpClientResponse> respHandler;
  private Handler<Void> continueHandler;
  private final long contextID;
  private final boolean raw;
  final Thread th;

  private boolean chunked;
  private ClientConnection conn;
  private Handler<Void> drainHandler;
  private Handler<Exception> exceptionHandler;
  private boolean headWritten;
  private boolean completed;
  private LinkedList<PendingChunk> pendingChunks;
  private int pendingMaxSize = -1;
  private boolean connecting;
  private boolean writeHead;
  private long written;
  private long contentLength = 0;

  /**
   * If {@code chunked} is {@code true}, this request will use HTTP chunked encoding, and each call
   * to write to the body will correspond to a new HTTP chunk sent on the wire. If chunked encoding
   * is used the HTTP header {@code Transfer-Encoding} with a value of {@code Chunked} will be
   * automatically inserted in the request.
   *
   * <p>If {@code chunked} is {@code false}, this request will not use HTTP chunked encoding, and
   * therefore if any data is written the body of the request, the total size of that data must be
   * set in the {@code Content-Length} header <b>before</b> any data is written to the request body.
   * If no data is written, then a {@code Content-Length} header with a value of {@code 0} will be
   * automatically inserted when the request is sent.
   *
   * <p>
   *
   * @return A reference to this, so multiple method calls can be chained.
   */
  public HttpClientRequest setChunked(boolean chunked) {
    check();
    if (written > 0) {
      throw new IllegalStateException("Cannot set chunked after data has been written on request");
    }
    this.chunked = chunked;
    return this;
  }

  /**
   * Inserts a header into the request. The {@link Object#toString()} method will be called on
   * {@code value} to determine the String value to actually use for the header value.
   *
   * <p>
   *
   * @return A reference to this, so multiple method calls can be chained.
   */
  public HttpClientRequest putHeader(String key, Object value) {
    check();
    request.setHeader(key, value);
    checkContentLengthChunked(key, value);
    return this;
  }

  /**
   * Inserts all the specified headers into the request. The {@link Object#toString()} method will
   * be called on the header values {@code value} to determine the String value to actually use for
   * the header value.
   *
   * <p>
   *
   * @return A reference to this, so multiple method calls can be chained.
   */
  public HttpClientRequest putAllHeaders(Map<String, ? extends Object> m) {
    check();
    for (Map.Entry<String, ? extends Object> entry : m.entrySet()) {
      request.setHeader(entry.getKey(), entry.getValue().toString());
      checkContentLengthChunked(entry.getKey(), entry.getValue());
    }
    return this;
  }

  /** Write a {@link Buffer} to the request body. */
  public void writeBuffer(Buffer chunk) {
    check();
    write(chunk.getChannelBuffer(), null);
  }

  /**
   * Write a {@link Buffer} to the request body.
   *
   * <p>
   *
   * @return A reference to this, so multiple method calls can be chained.
   */
  public HttpClientRequest write(Buffer chunk) {
    check();
    return write(chunk.getChannelBuffer(), null);
  }

  /**
   * Write a {@link String} to the request body, encoded in UTF-8.
   *
   * <p>
   *
   * @return A reference to this, so multiple method calls can be chained.
   */
  public HttpClientRequest write(String chunk) {
    check();
    return write(Buffer.create(chunk).getChannelBuffer(), null);
  }

  /**
   * Write a {@link String} to the request body, encoded using the encoding {@code enc}.
   *
   * <p>
   *
   * @return A reference to this, so multiple method calls can be chained.
   */
  public HttpClientRequest write(String chunk, String enc) {
    check();
    return write(Buffer.create(chunk, enc).getChannelBuffer(), null);
  }

  /**
   * Write a {@link Buffer} to the request body. The {@code doneHandler} is called after the buffer
   * is actually written to the wire.
   *
   * <p>
   *
   * @return A reference to this, so multiple method calls can be chained.
   */
  public HttpClientRequest write(Buffer chunk, Handler<Void> doneHandler) {
    check();
    return write(chunk.getChannelBuffer(), doneHandler);
  }

  /**
   * Write a {@link String} to the request body, encoded in UTF-8. The {@code doneHandler} is called
   * after the buffer is actually written to the wire.
   *
   * <p>
   *
   * @return A reference to this, so multiple method calls can be chained.
   */
  public HttpClientRequest write(String chunk, Handler<Void> doneHandler) {
    checkThread();
    checkComplete();
    return write(Buffer.create(chunk).getChannelBuffer(), doneHandler);
  }

  /**
   * Write a {@link String} to the request body, encoded with encoding {@code enc}. The {@code
   * doneHandler} is called after the buffer is actually written to the wire.
   *
   * <p>
   *
   * @return A reference to this, so multiple method calls can be chained.
   */
  public HttpClientRequest write(String chunk, String enc, Handler<Void> doneHandler) {
    check();
    return write(Buffer.create(chunk, enc).getChannelBuffer(), doneHandler);
  }

  /**
   * Data is queued until it is actually sent. To set the point at which the queue is considered
   * "full" call this method specifying the {@code maxSize} in bytes.
   *
   * <p>This method is used by the {@link org.vertx.java.core.streams.Pump} class to pump data
   * between different streams and perform flow control.
   */
  public void setWriteQueueMaxSize(int maxSize) {
    check();
    if (conn != null) {
      conn.setWriteQueueMaxSize(maxSize);
    } else {
      pendingMaxSize = maxSize;
    }
  }

  /**
   * If the amount of data that is currently queued is greater than the write queue max size see
   * {@link #setWriteQueueMaxSize(int)} then the request queue is considered full.
   *
   * <p>Data can still be written to the request even if the write queue is deemed full, however it
   * should be used as indicator to stop writing and push back on the source of the data, otherwise
   * you risk running out of available RAM.
   *
   * <p>This method is used by the {@link org.vertx.java.core.streams.Pump} class to pump data
   * between different streams and perform flow control.
   *
   * @return {@code true} if the write queue is full, {@code false} otherwise
   */
  public boolean writeQueueFull() {
    check();
    if (conn != null) {
      return conn.writeQueueFull();
    } else {
      return false;
    }
  }

  /**
   * This method sets a drain handler {@code handler} on the request. The drain handler will be
   * called when write queue is no longer full and it is safe to write to it again.
   *
   * <p>The drain handler is actually called when the write queue size reaches <b>half</b> the write
   * queue max size to prevent thrashing. This method is used as part of a flow control strategy,
   * e.g. it is used by the {@link org.vertx.java.core.streams.Pump} class to pump data between
   * different streams.
   *
   * @param handler
   */
  public void drainHandler(Handler<Void> handler) {
    check();
    this.drainHandler = handler;
    if (conn != null) {
      conn.handleInterestedOpsChanged(); // If the channel is already drained, we want to call it
      // immediately
    }
  }

  /**
   * Set {@code handler} as an exception handler on the request. Any exceptions that occur, either
   * at connection setup time or later will be notified by calling the handler. If the request has
   * no handler than any exceptions occurring will be output to {@link System#err}
   */
  public void exceptionHandler(Handler<Exception> handler) {
    check();
    this.exceptionHandler = handler;
  }

  /**
   * If you send an HTTP request with the header {@code Expect} set to the value {@code
   * 100-continue} and the server responds with an interim HTTP response with a status code of
   * {@code 100} and a continue handler has been set using this method, then the {@code handler}
   * will be called.
   *
   * <p>You can then continue to write data to the request body and later end it. This is normally
   * used in conjunction with the {@link #sendHead()} method to force the request header to be
   * written before the request has ended.
   */
  public void continueHandler(Handler<Void> handler) {
    check();
    this.continueHandler = handler;
  }

  /**
   * Forces the head of the request to be written before {@link #end()} is called on the request.
   * This is normally used to implement HTTP 100-continue handling, see {@link
   * #continueHandler(org.vertx.java.core.Handler)} for more information.
   *
   * @return A reference to this, so multiple method calls can be chained.
   */
  public HttpClientRequest sendHead() {
    check();
    if (conn != null) {
      if (!headWritten) {
        writeHead();
        headWritten = true;
      }
    } else {
      connect();
      writeHead = true;
    }
    return this;
  }

  /** Same as {@link #end(Buffer)} but writes a String with the default encoding */
  public void end(String chunk) {
    end(Buffer.create(chunk));
  }

  /** Same as {@link #end(Buffer)} but writes a String with the specified encoding */
  public void end(String chunk, String enc) {
    end(Buffer.create(chunk, enc));
  }

  /**
   * Same as {@link #end()} but writes some data to the request body before ending. If the request
   * is not chunked and no other data has been written then the Content-Length header will be
   * automatically set
   */
  public void end(Buffer chunk) {
    if (!chunked && contentLength == 0) {
      contentLength = chunk.length();
      request.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(contentLength));
    }
    write(chunk);
    end();
  }

  /**
   * Ends the request. If no data has been written to the request body, and {@link #sendHead()} has
   * not been called then the actual request won't get written until this method gets called.
   *
   * <p>Once the request has ended, it cannot be used any more, and if keep alive is true the
   * underlying connection will be returned to the {@link HttpClient} pool so it can be assigned to
   * another request.
   */
  public void end() {
    check();
    completed = true;
    if (conn != null) {
      if (!headWritten) {
        // No body
        writeHead();
      } else if (chunked) {
        // Body written - we use HTTP chunking so must send an empty buffer
        writeEndChunk();
      }
      conn.endRequest();
    } else {
      connect();
    }
  }

  void handleDrained() {
    checkThread();
    if (drainHandler != null) {
      drainHandler.handle(null);
    }
  }

  void handleException(Exception e) {
    checkThread();
    if (exceptionHandler != null) {
      exceptionHandler.handle(e);
    } else {
      log.error("Unhandled exception", e);
    }
  }

  void handleResponse(HttpClientResponse resp) {
    try {
      if (resp.statusCode == 100) {
        if (continueHandler != null) {
          continueHandler.handle(null);
        }
      } else {
        respHandler.handle(resp);
      }
    } catch (Throwable t) {
      if (t instanceof Exception) {
        handleException((Exception) t);
      } else {
        log.error("Unhandled exception", t);
      }
    }
  }

  private void checkContentLengthChunked(String key, Object value) {
    if (key.equals(HttpHeaders.Names.CONTENT_LENGTH)) {
      contentLength = Integer.parseInt(value.toString());
    } else if (key.equals(HttpHeaders.Names.TRANSFER_ENCODING)
        && value.equals(HttpHeaders.Values.CHUNKED)) {
      chunked = true;
    }
  }

  private void connect() {
    if (!connecting) {
      // We defer actual connection until the first part of body is written or end is called
      // This gives the user an opportunity to set an exception handler before connecting so
      // they can capture any exceptions on connection
      client.getConnection(
          new Handler<ClientConnection>() {
            public void handle(ClientConnection conn) {
              connected(conn);
            }
          },
          contextID);

      connecting = true;
    }
  }

  private void connected(ClientConnection conn) {
    checkThread();

    conn.setCurrentRequest(this);

    this.conn = conn;

    // If anything was written or the request ended before we got the connection, then
    // we need to write it now

    if (pendingMaxSize != -1) {
      conn.setWriteQueueMaxSize(pendingMaxSize);
    }

    if (pendingChunks != null || writeHead || completed) {
      writeHead();
      headWritten = true;
    }

    if (pendingChunks != null) {
      for (PendingChunk chunk : pendingChunks) {
        sendChunk(chunk.chunk, chunk.doneHandler);
      }
    }
    if (completed) {
      if (chunked) {
        writeEndChunk();
      }
      conn.endRequest();
    }
  }

  private void writeHead() {
    request.setChunked(chunked);
    if (!raw) {
      request.setHeader(HttpHeaders.Names.HOST, conn.hostHeader);

      if (chunked) {
        request.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
      } else if (contentLength == 0) {
        // request.setHeader(HttpHeaders.Names.CONTENT_LENGTH, "0");
      }
    }
    conn.write(request);
  }

  private HttpClientRequest write(ChannelBuffer buff, Handler<Void> doneHandler) {

    written += buff.readableBytes();

    if (!raw && !chunked && written > contentLength) {
      throw new IllegalStateException(
          "You must set the Content-Length header to be the total size of the message "
              + "payload BEFORE sending any data if you are not using HTTP chunked encoding. "
              + "Current written: "
              + written
              + " Current Content-Length: "
              + contentLength);
    }

    if (conn == null) {
      if (pendingChunks == null) {
        pendingChunks = new LinkedList<>();
      }
      pendingChunks.add(new PendingChunk(buff, doneHandler));
      connect();
    } else {
      if (!headWritten) {
        writeHead();
        headWritten = true;
      }
      sendChunk(buff, doneHandler);
    }
    return this;
  }

  private void sendChunk(ChannelBuffer buff, Handler<Void> doneHandler) {
    Object write = chunked ? new DefaultHttpChunk(buff) : buff;
    ChannelFuture writeFuture = conn.write(write);
    if (doneHandler != null) {
      conn.addFuture(doneHandler, writeFuture);
    }
  }

  private void writeEndChunk() {
    conn.write(new DefaultHttpChunk(ChannelBuffers.EMPTY_BUFFER));
  }

  private void check() {
    checkThread();
    checkComplete();
  }

  private void checkComplete() {
    if (completed) {
      throw new IllegalStateException("Request already complete");
    }
  }

  private void checkThread() {
    // All ops must always be invoked on same thread
    if (Thread.currentThread() != th) {
      throw new IllegalStateException(
          "Invoked with wrong thread, actual: " + Thread.currentThread() + " expected: " + th);
    }
  }

  private static class PendingChunk {
    final ChannelBuffer chunk;
    final Handler<Void> doneHandler;

    private PendingChunk(ChannelBuffer chunk, Handler<Void> doneHandler) {
      this.chunk = chunk;
      this.doneHandler = doneHandler;
    }
  }
}
示例#5
0
/** @author <a href="http://tfox.org">Tim Fox</a> */
public class SharedNetTest extends TestBase {

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

  @Test
  public void testConnectionDistribution() throws Exception {
    final int dataLength = 10;
    final int connections = 10;
    final int serverLoops = 1;
    final int serversPerLoop = 2;
    final CountDownLatch serverCloseLatch = new CountDownLatch(serverLoops * serversPerLoop);
    final CountDownLatch listenLatch = new CountDownLatch(serverLoops * serversPerLoop);

    final Set<Long> serverHandlers = SharedData.getSet("servers");
    final Set<Integer> connectedServers = SharedData.getSet("connected");

    for (int i = 0; i < serverLoops; i++) {
      VertxInternal.instance.go(
          new Runnable() {
            public void run() {
              final ContextChecker checker = new ContextChecker();

              for (int j = 0; j < serversPerLoop; j++) {
                final NetServer server = new NetServer();

                final long actorID =
                    Vertx.instance.registerHandler(
                        new Handler<String>() {
                          public void handle(String msg) {
                            checker.check();
                            server.close(
                                new SimpleHandler() {
                                  public void handle() {
                                    checker.check();
                                    serverCloseLatch.countDown();
                                    //  log.info("closed server");
                                  }
                                });
                          }
                        });

                serverHandlers.add(actorID);

                server
                    .connectHandler(
                        new Handler<NetSocket>() {
                          public void handle(final NetSocket sock) {

                            checker.check();

                            sock.dataHandler(
                                new Handler<Buffer>() {
                                  public void handle(Buffer data) {
                                    checker.check();
                                    sock.write(data); // Send it back to client
                                  }
                                });

                            connectedServers.add(System.identityHashCode(server));
                          }
                        })
                    .listen(8181);

                listenLatch.countDown();
              }
            }
          });
    }

    listenLatch.await(5, TimeUnit.SECONDS);

    VertxInternal.instance.go(
        new Runnable() {
          public void run() {
            final NetClient client = new NetClient();

            final ContextChecker checker = new ContextChecker();

            final long actorID =
                Vertx.instance.registerHandler(
                    new Handler<String>() {
                      int count;

                      public void handle(String msg) {
                        checker.check();
                        count++;
                        if (count == connections) {
                          // All client connections closed - now tell the servers to shutdown
                          client.close();
                          for (Long sactorID : serverHandlers) {
                            Vertx.instance.sendToHandler(sactorID, "foo");
                          }
                        }
                      }
                    });

            for (int i = 0; i < connections; i++) {

              client.connect(
                  8181,
                  new Handler<NetSocket>() {
                    public void handle(final NetSocket sock) {
                      final Buffer buff = Buffer.create(0);
                      sock.dataHandler(
                          new Handler<Buffer>() {
                            public void handle(Buffer data) {
                              checker.check();
                              buff.appendBuffer(data);
                              if (buff.length() == dataLength) {
                                sock.close();
                              }
                            }
                          });
                      sock.closedHandler(
                          new SimpleHandler() {
                            public void handle() {
                              checker.check();
                              Vertx.instance.sendToHandler(actorID, "foo");
                            }
                          });
                      Buffer sendBuff = Utils.generateRandomBuffer(dataLength);
                      sock.write(sendBuff);
                    }
                  });
            }
          }
        });

    azzert(serverCloseLatch.await(5, TimeUnit.SECONDS));
    azzert(connectedServers.size() == serverLoops * serversPerLoop);

    SharedData.removeSet("servers");
    SharedData.removeSet("connected");

    throwAssertions();
  }
}
示例#6
0
/**
 * Encapsulates a server that understands TCP or SSL.
 *
 * <p>Instances of this class can only be used from the event loop that created it. When connections
 * are accepted by the server they are supplied to the user in the form of a {@link NetSocket}
 * instance that is passed via an instance of {@link org.vertx.java.core.Handler} which is supplied
 * to the server via the {@link #connectHandler} method.
 *
 * @author <a href="http://tfox.org">Tim Fox</a>
 */
public class NetServer extends NetServerBase {

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

  // For debug only
  public static int numServers() {
    return servers.size();
  }

  private static final Map<ServerID, NetServer> servers = new HashMap<>();

  private Map<Channel, NetSocket> socketMap = new ConcurrentHashMap();
  private Handler<NetSocket> connectHandler;
  private ChannelGroup serverChannelGroup;
  private boolean listening;
  private ServerID id;
  private NetServer actualServer;
  private NetServerWorkerPool availableWorkers = new NetServerWorkerPool();
  private HandlerManager<NetSocket> handlerManager = new HandlerManager<>(availableWorkers);

  /** Create a new NetServer instance. */
  public NetServer() {
    super();
  }

  /**
   * Supply a connect handler for this server. The server can only have at most one connect handler
   * at any one time. As the server accepts TCP or SSL connections it creates an instance of {@link
   * NetSocket} and passes it to the connect handler.
   *
   * @return a reference to this so multiple method calls can be chained together
   */
  public NetServer connectHandler(Handler<NetSocket> connectHandler) {
    checkThread();
    this.connectHandler = connectHandler;
    return this;
  }

  /** {@inheritDoc} */
  public NetServer setSSL(boolean ssl) {
    checkThread();
    return (NetServer) super.setSSL(ssl);
  }

  /** {@inheritDoc} */
  public NetServer setKeyStorePath(String path) {
    checkThread();
    return (NetServer) super.setKeyStorePath(path);
  }

  /** {@inheritDoc} */
  public NetServer setKeyStorePassword(String pwd) {
    checkThread();
    return (NetServer) super.setKeyStorePassword(pwd);
  }

  /** {@inheritDoc} */
  public NetServer setTrustStorePath(String path) {
    checkThread();
    return (NetServer) super.setTrustStorePath(path);
  }

  /** {@inheritDoc} */
  public NetServer setTrustStorePassword(String pwd) {
    checkThread();
    return (NetServer) super.setTrustStorePassword(pwd);
  }

  /** {@inheritDoc} */
  public NetServer setClientAuthRequired(boolean required) {
    checkThread();
    return (NetServer) super.setClientAuthRequired(required);
  }

  /** {@inheritDoc} */
  public NetServer setTcpNoDelay(boolean tcpNoDelay) {
    checkThread();
    return (NetServer) super.setTcpNoDelay(tcpNoDelay);
  }

  /** {@inheritDoc} */
  public NetServer setSendBufferSize(int size) {
    checkThread();
    return (NetServer) super.setSendBufferSize(size);
  }

  /** {@inheritDoc} */
  public NetServer setReceiveBufferSize(int size) {
    checkThread();
    return (NetServer) super.setReceiveBufferSize(size);
  }

  /** {@inheritDoc} */
  public NetServer setTCPKeepAlive(boolean keepAlive) {
    checkThread();
    return (NetServer) super.setTCPKeepAlive(keepAlive);
  }

  /** {@inheritDoc} */
  public NetServer setReuseAddress(boolean reuse) {
    checkThread();
    return (NetServer) super.setReuseAddress(reuse);
  }

  /** {@inheritDoc} */
  public NetServer setSoLinger(boolean linger) {
    checkThread();
    return (NetServer) super.setSoLinger(linger);
  }

  /** {@inheritDoc} */
  public NetServer setTrafficClass(int trafficClass) {
    checkThread();
    return (NetServer) super.setTrafficClass(trafficClass);
  }

  /**
   * Instruct the server to listen for incoming connections on the specified {@code port} and all
   * available interfaces.
   *
   * @return a reference to this so multiple method calls can be chained together
   */
  public NetServer listen(int port) {
    return listen(port, "0.0.0.0");
  }

  /**
   * Instruct the server to listen for incoming connections on the specified {@code port} and {@code
   * host}. {@code host} can be a host name or an IP address.
   *
   * @return a reference to this so multiple method calls can be chained together
   */
  public NetServer listen(int port, String host) {
    checkThread();
    if (connectHandler == null) {
      throw new IllegalStateException("Set connect handler first");
    }
    if (listening) {
      throw new IllegalStateException("Listen already called");
    }
    listening = true;

    synchronized (servers) {
      id = new ServerID(port, host);
      NetServer shared = servers.get(id);
      if (shared == null) {
        serverChannelGroup = new DefaultChannelGroup("vertx-acceptor-channels");

        ChannelFactory factory =
            new NioServerSocketChannelFactory(
                VertxInternal.instance.getAcceptorPool(), availableWorkers);
        ServerBootstrap bootstrap = new ServerBootstrap(factory);

        checkSSL();

        bootstrap.setPipelineFactory(
            new ChannelPipelineFactory() {
              public ChannelPipeline getPipeline() {
                ChannelPipeline pipeline = Channels.pipeline();
                if (ssl) {
                  SSLEngine engine = context.createSSLEngine();
                  engine.setUseClientMode(false);
                  switch (clientAuth) {
                    case REQUEST:
                      {
                        engine.setWantClientAuth(true);
                        break;
                      }
                    case REQUIRED:
                      {
                        engine.setNeedClientAuth(true);
                        break;
                      }
                    case NONE:
                      {
                        engine.setNeedClientAuth(false);
                        break;
                      }
                  }
                  pipeline.addLast("ssl", new SslHandler(engine));
                }
                pipeline.addLast(
                    "chunkedWriter",
                    new ChunkedWriteHandler()); // For large file / sendfile support
                pipeline.addLast("handler", new ServerHandler());
                return pipeline;
              }
            });

        bootstrap.setOptions(connectionOptions);

        try {
          // TODO - currently bootstrap.bind is blocking - need to make it non blocking by not using
          // bootstrap directly
          Channel serverChannel =
              bootstrap.bind(new InetSocketAddress(InetAddress.getByName(host), port));
          serverChannelGroup.add(serverChannel);
          log.trace("Net server listening on " + host + ":" + port);
        } catch (UnknownHostException e) {
          e.printStackTrace();
        }
        servers.put(id, this);
        actualServer = this;
      } else {
        // Server already exists with that host/port - we will use that
        checkConfigs(actualServer, this);
        actualServer = shared;
      }
      actualServer.handlerManager.addHandler(connectHandler);
    }

    return this;
  }

  /** Close the server. This will close any currently open connections. */
  public void close() {
    close(null);
  }

  /**
   * Close the server. This will close any currently open connections. The event handler {@code
   * done} will be called when the close is complete.
   */
  public void close(final Handler<Void> done) {
    checkThread();

    if (!listening) return;
    listening = false;

    synchronized (servers) {
      actualServer.handlerManager.removeHandler(connectHandler);

      if (actualServer.handlerManager.hasHandlers()) {
        // The actual server still has handlers so we don't actually close it
        if (done != null) {
          executeCloseDone(contextID, done);
        }
      } 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(contextID, done);
      }
    }
  }

  private void actualClose(final long closeContextID, final Handler<Void> done) {
    if (id != null) {
      servers.remove(id);
    }

    for (NetSocket sock : socketMap.values()) {
      sock.internalClose();
    }

    // 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!

    VertxInternal.instance.setContextID(closeContextID);

    ChannelGroupFuture fut = serverChannelGroup.close();
    if (done != null) {
      fut.addListener(
          new ChannelGroupFutureListener() {
            public void operationComplete(ChannelGroupFuture channelGroupFuture) throws Exception {
              executeCloseDone(closeContextID, done);
            }
          });
    }
  }

  private void checkConfigs(NetServer currentServer, NetServer newServer) {
    // TODO check configs are the same
  }

  private void executeCloseDone(final long closeContextID, final Handler<Void> done) {
    VertxInternal.instance.executeOnContext(
        closeContextID,
        new Runnable() {
          public void run() {
            VertxInternal.instance.setContextID(closeContextID);
            done.handle(null);
          }
        });
  }

  private class ServerHandler extends SimpleChannelHandler {

    @Override
    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {

      final NioSocketChannel ch = (NioSocketChannel) e.getChannel();
      NioWorker worker = ch.getWorker();

      // Choose a handler
      final HandlerHolder handler = handlerManager.chooseHandler(worker);

      if (handler == null) {
        // Ignore
        return;
      }

      VertxInternal.instance.executeOnContext(
          handler.contextID,
          new Runnable() {
            public void run() {
              VertxInternal.instance.setContextID(handler.contextID);
              NetSocket sock = new NetSocket(ch, handler.contextID, Thread.currentThread());
              socketMap.put(ch, sock);
              handler.handler.handle(sock);
            }
          });
    }

    @Override
    public void channelInterestChanged(ChannelHandlerContext ctx, ChannelStateEvent e)
        throws Exception {
      final NioSocketChannel ch = (NioSocketChannel) e.getChannel();
      final NetSocket sock = socketMap.get(ch);
      ChannelState state = e.getState();
      if (state == ChannelState.INTEREST_OPS) {
        VertxInternal.instance.executeOnContext(
            sock.getContextID(),
            new Runnable() {
              public void run() {
                sock.handleInterestedOpsChanged();
              }
            });
      }
    }

    @Override
    public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) {
      final NioSocketChannel ch = (NioSocketChannel) e.getChannel();
      final NetSocket sock = socketMap.remove(ch);
      if (sock != null) {
        VertxInternal.instance.executeOnContext(
            sock.getContextID(),
            new Runnable() {
              public void run() {
                sock.handleClosed();
              }
            });
      }
    }

    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
      Channel ch = e.getChannel();
      NetSocket sock = socketMap.get(ch);
      if (sock != null) {
        ChannelBuffer buff = (ChannelBuffer) e.getMessage();
        sock.handleDataReceived(new Buffer(buff.slice()));
      }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
      final NioSocketChannel ch = (NioSocketChannel) e.getChannel();
      final NetSocket sock = socketMap.get(ch);
      ch.close();
      final Throwable t = e.getCause();
      if (sock != null && t instanceof Exception) {
        VertxInternal.instance.executeOnContext(
            sock.getContextID(),
            new Runnable() {
              public void run() {
                sock.handleException((Exception) t);
              }
            });
      } else {
        t.printStackTrace();
      }
    }
  }
}