/** * 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()); } } } }
/** * 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)); } }
/** * 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; } } }
/** * 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; } } }
/** @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(); } }
/** * 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(); } } } }