Java tutorial
/* * Copyright (c) 2011-2013 The original author or authors * ------------------------------------------------------ * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. */ package io.vertx.core.http.impl; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.handler.codec.http.*; import io.netty.handler.ssl.SslHandler; import io.netty.handler.timeout.IdleStateHandler; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.MultiMap; import io.vertx.core.VertxException; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.*; import io.vertx.core.http.HttpMethod; import io.vertx.core.http.impl.ws.WebSocketFrameImpl; import io.vertx.core.http.impl.ws.WebSocketFrameInternal; import io.vertx.core.impl.Closeable; import io.vertx.core.impl.ContextImpl; import io.vertx.core.impl.VertxInternal; import io.vertx.core.logging.Logger; import io.vertx.core.logging.LoggerFactory; import io.vertx.core.net.impl.KeyStoreHelper; import io.vertx.core.net.impl.PartialPooledByteBufAllocator; import io.vertx.core.net.impl.SSLHelper; import io.vertx.core.spi.metrics.HttpClientMetrics; import io.vertx.core.spi.metrics.Metrics; import io.vertx.core.spi.metrics.MetricsProvider; import javax.net.ssl.SSLHandshakeException; import java.net.InetSocketAddress; import java.net.MalformedURLException; import java.net.URL; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; /** * * This class is thread-safe * * @author <a href="http://tfox.org">Tim Fox</a> */ public class HttpClientImpl implements HttpClient, MetricsProvider { private static final Logger log = LoggerFactory.getLogger(HttpClientImpl.class); private final VertxInternal vertx; private final HttpClientOptions options; private final Map<Channel, ClientConnection> connectionMap = new ConcurrentHashMap<>(); private final ContextImpl creatingContext; private final ConnectionManager pool; private final Closeable closeHook; private final SSLHelper sslHelper; private final HttpClientMetrics metrics; private volatile boolean closed; public HttpClientImpl(VertxInternal vertx, HttpClientOptions options) { this.vertx = vertx; this.options = new HttpClientOptions(options); this.sslHelper = new SSLHelper(options, KeyStoreHelper.create(vertx, options.getKeyCertOptions()), KeyStoreHelper.create(vertx, options.getTrustOptions())); this.creatingContext = vertx.getContext(); closeHook = completionHandler -> { HttpClientImpl.this.close(); completionHandler.handle(Future.succeededFuture()); }; if (creatingContext != null) { if (creatingContext.isMultiThreadedWorkerContext()) { throw new IllegalStateException("Cannot use HttpClient in a multi-threaded worker verticle"); } creatingContext.addCloseHook(closeHook); } pool = new ConnectionManager(options.getMaxPoolSize(), options.isKeepAlive(), options.isPipelining()) { protected void connect(String host, int port, Handler<ClientConnection> connectHandler, Handler<Throwable> connectErrorHandler, ContextImpl context, ConnectionLifeCycleListener listener) { internalConnect(context, port, host, connectHandler, connectErrorHandler, listener); } }; this.metrics = vertx.metricsSPI().createMetrics(this, options); } @Override public HttpClient websocket(int port, String host, String requestURI, Handler<WebSocket> wsConnect) { websocketStream(port, host, requestURI, null, null).handler(wsConnect); return this; } @Override public HttpClient websocket(String host, String requestURI, Handler<WebSocket> wsConnect) { return websocket(options.getDefaultPort(), host, requestURI, wsConnect); } @Override public HttpClient websocket(int port, String host, String requestURI, MultiMap headers, Handler<WebSocket> wsConnect) { websocketStream(port, host, requestURI, headers, null).handler(wsConnect); return this; } @Override public HttpClient websocket(String host, String requestURI, MultiMap headers, Handler<WebSocket> wsConnect) { return websocket(options.getDefaultPort(), host, requestURI, headers, wsConnect); } @Override public HttpClient websocket(int port, String host, String requestURI, MultiMap headers, WebsocketVersion version, Handler<WebSocket> wsConnect) { websocketStream(port, host, requestURI, headers, version, null).handler(wsConnect); return this; } @Override public HttpClient websocket(String host, String requestURI, MultiMap headers, WebsocketVersion version, Handler<WebSocket> wsConnect) { return websocket(options.getDefaultPort(), host, requestURI, headers, version, wsConnect); } @Override public HttpClient websocket(int port, String host, String requestURI, MultiMap headers, WebsocketVersion version, String subProtocols, Handler<WebSocket> wsConnect) { websocketStream(port, host, requestURI, headers, version, subProtocols).handler(wsConnect); return this; } @Override public HttpClient websocket(String host, String requestURI, MultiMap headers, WebsocketVersion version, String subProtocols, Handler<WebSocket> wsConnect) { return websocket(options.getDefaultPort(), host, requestURI, headers, version, subProtocols, wsConnect); } @Override public HttpClient websocket(String requestURI, Handler<WebSocket> wsConnect) { return websocket(options.getDefaultPort(), options.getDefaultHost(), requestURI, wsConnect); } @Override public HttpClient websocket(String requestURI, MultiMap headers, Handler<WebSocket> wsConnect) { return websocket(options.getDefaultPort(), options.getDefaultHost(), requestURI, headers, wsConnect); } @Override public HttpClient websocket(String requestURI, MultiMap headers, WebsocketVersion version, Handler<WebSocket> wsConnect) { return websocket(options.getDefaultPort(), options.getDefaultHost(), requestURI, headers, version, wsConnect); } @Override public HttpClient websocket(String requestURI, MultiMap headers, WebsocketVersion version, String subProtocols, Handler<WebSocket> wsConnect) { return websocket(options.getDefaultPort(), options.getDefaultHost(), requestURI, headers, version, subProtocols, wsConnect); } @Override public WebSocketStream websocketStream(int port, String host, String requestURI) { return websocketStream(port, host, requestURI, null, null); } @Override public WebSocketStream websocketStream(String host, String requestURI) { return websocketStream(options.getDefaultPort(), host, requestURI); } @Override public WebSocketStream websocketStream(int port, String host, String requestURI, MultiMap headers) { return websocketStream(port, host, requestURI, headers, null); } @Override public WebSocketStream websocketStream(String host, String requestURI, MultiMap headers) { return websocketStream(options.getDefaultPort(), host, requestURI, headers); } @Override public WebSocketStream websocketStream(int port, String host, String requestURI, MultiMap headers, WebsocketVersion version) { return websocketStream(port, host, requestURI, headers, version, null); } @Override public WebSocketStream websocketStream(String host, String requestURI, MultiMap headers, WebsocketVersion version) { return websocketStream(options.getDefaultPort(), host, requestURI, headers, version); } @Override public WebSocketStream websocketStream(int port, String host, String requestURI, MultiMap headers, WebsocketVersion version, String subProtocols) { return new WebSocketStreamImpl(port, host, requestURI, headers, version, subProtocols); } @Override public WebSocketStream websocketStream(String host, String requestURI, MultiMap headers, WebsocketVersion version, String subProtocols) { return websocketStream(options.getDefaultPort(), host, requestURI, headers, version, subProtocols); } @Override public WebSocketStream websocketStream(String requestURI) { return websocketStream(options.getDefaultPort(), options.getDefaultHost(), requestURI); } @Override public WebSocketStream websocketStream(String requestURI, MultiMap headers) { return websocketStream(options.getDefaultPort(), options.getDefaultHost(), requestURI, headers); } @Override public WebSocketStream websocketStream(String requestURI, MultiMap headers, WebsocketVersion version) { return websocketStream(options.getDefaultPort(), options.getDefaultHost(), requestURI, headers, version); } @Override public WebSocketStream websocketStream(String requestURI, MultiMap headers, WebsocketVersion version, String subProtocols) { return websocketStream(options.getDefaultPort(), options.getDefaultHost(), requestURI, headers, version, subProtocols); } @Override public HttpClientRequest requestAbs(HttpMethod method, String absoluteURI, Handler<HttpClientResponse> responseHandler) { Objects.requireNonNull(responseHandler, "no null responseHandler accepted"); return requestAbs(method, absoluteURI).handler(responseHandler); } @Override public HttpClientRequest request(HttpMethod method, int port, String host, String requestURI, Handler<HttpClientResponse> responseHandler) { Objects.requireNonNull(responseHandler, "no null responseHandler accepted"); return request(method, port, host, requestURI).handler(responseHandler); } @Override public HttpClientRequest request(HttpMethod method, String host, String requestURI, Handler<HttpClientResponse> responseHandler) { return request(method, options.getDefaultPort(), host, requestURI, responseHandler); } @Override public HttpClientRequest request(HttpMethod method, String requestURI) { return request(method, options.getDefaultPort(), options.getDefaultHost(), requestURI); } @Override public HttpClientRequest request(HttpMethod method, String requestURI, Handler<HttpClientResponse> responseHandler) { return request(method, options.getDefaultPort(), options.getDefaultHost(), requestURI, responseHandler); } @Override public HttpClientRequest requestAbs(HttpMethod method, String absoluteURI) { URL url = parseUrl(absoluteURI); int port = url.getPort(); if (port == -1) { String protocol = url.getProtocol(); char chend = protocol.charAt(protocol.length() - 1); if (chend == 'p') { port = 80; } else if (chend == 's') { port = 443; } } return doRequest(method, url.getHost(), port, url.getFile(), null); } @Override public HttpClientRequest request(HttpMethod method, int port, String host, String requestURI) { return doRequest(method, host, port, requestURI, null); } @Override public HttpClientRequest request(HttpMethod method, String host, String requestURI) { return request(method, options.getDefaultPort(), host, requestURI); } @Override public HttpClientRequest get(int port, String host, String requestURI) { return request(HttpMethod.GET, port, host, requestURI); } @Override public HttpClientRequest get(String host, String requestURI) { return get(options.getDefaultPort(), host, requestURI); } @Override public HttpClientRequest get(int port, String host, String requestURI, Handler<HttpClientResponse> responseHandler) { return request(HttpMethod.GET, port, host, requestURI, responseHandler); } @Override public HttpClientRequest get(String host, String requestURI, Handler<HttpClientResponse> responseHandler) { return get(options.getDefaultPort(), host, requestURI, responseHandler); } @Override public HttpClientRequest get(String requestURI) { return request(HttpMethod.GET, requestURI); } @Override public HttpClientRequest get(String requestURI, Handler<HttpClientResponse> responseHandler) { return request(HttpMethod.GET, requestURI, responseHandler); } @Override public HttpClientRequest getAbs(String absoluteURI) { return requestAbs(HttpMethod.GET, absoluteURI); } @Override public HttpClientRequest getAbs(String absoluteURI, Handler<HttpClientResponse> responseHandler) { return requestAbs(HttpMethod.GET, absoluteURI, responseHandler); } @Override public HttpClient getNow(int port, String host, String requestURI, Handler<HttpClientResponse> responseHandler) { get(port, host, requestURI, responseHandler).end(); return this; } @Override public HttpClient getNow(String host, String requestURI, Handler<HttpClientResponse> responseHandler) { return getNow(options.getDefaultPort(), host, requestURI, responseHandler); } @Override public HttpClient getNow(String requestURI, Handler<HttpClientResponse> responseHandler) { get(requestURI, responseHandler).end(); return this; } @Override public HttpClientRequest post(int port, String host, String requestURI) { return request(HttpMethod.POST, port, host, requestURI); } @Override public HttpClientRequest post(String host, String requestURI) { return post(options.getDefaultPort(), host, requestURI); } @Override public HttpClientRequest post(int port, String host, String requestURI, Handler<HttpClientResponse> responseHandler) { return request(HttpMethod.POST, port, host, requestURI, responseHandler); } @Override public HttpClientRequest post(String host, String requestURI, Handler<HttpClientResponse> responseHandler) { return post(options.getDefaultPort(), host, requestURI, responseHandler); } @Override public HttpClientRequest post(String requestURI) { return request(HttpMethod.POST, requestURI); } @Override public HttpClientRequest post(String requestURI, Handler<HttpClientResponse> responseHandler) { return request(HttpMethod.POST, requestURI, responseHandler); } @Override public HttpClientRequest postAbs(String absoluteURI) { return requestAbs(HttpMethod.POST, absoluteURI); } @Override public HttpClientRequest postAbs(String absoluteURI, Handler<HttpClientResponse> responseHandler) { return requestAbs(HttpMethod.POST, absoluteURI, responseHandler); } @Override public HttpClientRequest head(int port, String host, String requestURI) { return request(HttpMethod.HEAD, port, host, requestURI); } @Override public HttpClientRequest head(String host, String requestURI) { return head(options.getDefaultPort(), host, requestURI); } @Override public HttpClientRequest head(int port, String host, String requestURI, Handler<HttpClientResponse> responseHandler) { return request(HttpMethod.HEAD, port, host, requestURI, responseHandler); } @Override public HttpClientRequest head(String host, String requestURI, Handler<HttpClientResponse> responseHandler) { return head(options.getDefaultPort(), host, requestURI, responseHandler); } @Override public HttpClientRequest head(String requestURI) { return request(HttpMethod.HEAD, requestURI); } @Override public HttpClientRequest head(String requestURI, Handler<HttpClientResponse> responseHandler) { return request(HttpMethod.HEAD, requestURI, responseHandler); } @Override public HttpClientRequest headAbs(String absoluteURI) { return requestAbs(HttpMethod.HEAD, absoluteURI); } @Override public HttpClientRequest headAbs(String absoluteURI, Handler<HttpClientResponse> responseHandler) { return requestAbs(HttpMethod.HEAD, absoluteURI, responseHandler); } @Override public HttpClient headNow(int port, String host, String requestURI, Handler<HttpClientResponse> responseHandler) { head(port, host, requestURI, responseHandler).end(); return this; } @Override public HttpClient headNow(String host, String requestURI, Handler<HttpClientResponse> responseHandler) { return headNow(options.getDefaultPort(), host, requestURI, responseHandler); } @Override public HttpClient headNow(String requestURI, Handler<HttpClientResponse> responseHandler) { head(requestURI, responseHandler).end(); return this; } @Override public HttpClientRequest options(int port, String host, String requestURI) { return request(HttpMethod.OPTIONS, port, host, requestURI); } @Override public HttpClientRequest options(String host, String requestURI) { return options(options.getDefaultPort(), host, requestURI); } @Override public HttpClientRequest options(int port, String host, String requestURI, Handler<HttpClientResponse> responseHandler) { return request(HttpMethod.OPTIONS, port, host, requestURI, responseHandler); } @Override public HttpClientRequest options(String host, String requestURI, Handler<HttpClientResponse> responseHandler) { return options(options.getDefaultPort(), host, requestURI, responseHandler); } @Override public HttpClientRequest options(String requestURI) { return request(HttpMethod.OPTIONS, requestURI); } @Override public HttpClientRequest options(String requestURI, Handler<HttpClientResponse> responseHandler) { return request(HttpMethod.OPTIONS, requestURI, responseHandler); } @Override public HttpClientRequest optionsAbs(String absoluteURI) { return requestAbs(HttpMethod.OPTIONS, absoluteURI); } @Override public HttpClientRequest optionsAbs(String absoluteURI, Handler<HttpClientResponse> responseHandler) { return requestAbs(HttpMethod.OPTIONS, absoluteURI, responseHandler); } @Override public HttpClient optionsNow(int port, String host, String requestURI, Handler<HttpClientResponse> responseHandler) { options(port, host, requestURI, responseHandler).end(); return this; } @Override public HttpClient optionsNow(String host, String requestURI, Handler<HttpClientResponse> responseHandler) { return optionsNow(options.getDefaultPort(), host, requestURI, responseHandler); } @Override public HttpClient optionsNow(String requestURI, Handler<HttpClientResponse> responseHandler) { options(requestURI, responseHandler).end(); return this; } @Override public HttpClientRequest put(int port, String host, String requestURI) { return request(HttpMethod.PUT, port, host, requestURI); } @Override public HttpClientRequest put(String host, String requestURI) { return put(options.getDefaultPort(), host, requestURI); } @Override public HttpClientRequest put(int port, String host, String requestURI, Handler<HttpClientResponse> responseHandler) { return request(HttpMethod.PUT, port, host, requestURI, responseHandler); } @Override public HttpClientRequest put(String host, String requestURI, Handler<HttpClientResponse> responseHandler) { return put(options.getDefaultPort(), host, requestURI, responseHandler); } @Override public HttpClientRequest put(String requestURI) { return request(HttpMethod.PUT, requestURI); } @Override public HttpClientRequest put(String requestURI, Handler<HttpClientResponse> responseHandler) { return request(HttpMethod.PUT, requestURI, responseHandler); } @Override public HttpClientRequest putAbs(String absoluteURI) { return requestAbs(HttpMethod.PUT, absoluteURI); } @Override public HttpClientRequest putAbs(String absoluteURI, Handler<HttpClientResponse> responseHandler) { return requestAbs(HttpMethod.PUT, absoluteURI, responseHandler); } @Override public HttpClientRequest delete(int port, String host, String requestURI) { return request(HttpMethod.DELETE, port, host, requestURI); } @Override public HttpClientRequest delete(String host, String requestURI) { return delete(options.getDefaultPort(), host, requestURI); } @Override public HttpClientRequest delete(int port, String host, String requestURI, Handler<HttpClientResponse> responseHandler) { return request(HttpMethod.DELETE, port, host, requestURI, responseHandler); } @Override public HttpClientRequest delete(String host, String requestURI, Handler<HttpClientResponse> responseHandler) { return delete(options.getDefaultPort(), host, requestURI, responseHandler); } @Override public HttpClientRequest delete(String requestURI) { return request(HttpMethod.DELETE, requestURI); } @Override public HttpClientRequest delete(String requestURI, Handler<HttpClientResponse> responseHandler) { return request(HttpMethod.DELETE, requestURI, responseHandler); } @Override public HttpClientRequest deleteAbs(String absoluteURI) { return requestAbs(HttpMethod.DELETE, absoluteURI); } @Override public HttpClientRequest deleteAbs(String absoluteURI, Handler<HttpClientResponse> responseHandler) { return requestAbs(HttpMethod.DELETE, absoluteURI, responseHandler); } @Override public synchronized void close() { checkClosed(); pool.close(); for (ClientConnection conn : connectionMap.values()) { conn.close(); } if (creatingContext != null) { creatingContext.removeCloseHook(closeHook); } closed = true; metrics.close(); } @Override public boolean isMetricsEnabled() { return metrics != null && metrics.isEnabled(); } @Override public Metrics getMetrics() { return metrics; } HttpClientOptions getOptions() { return options; } void getConnection(int port, String host, Handler<ClientConnection> handler, Handler<Throwable> connectionExceptionHandler, ContextImpl context) { pool.getConnection(port, host, handler, connectionExceptionHandler, context); } /** * @return the vertx, for use in package related classes only. */ VertxInternal getVertx() { return vertx; } SSLHelper getSslHelper() { return sslHelper; } void removeChannel(Channel channel) { connectionMap.remove(channel); } HttpClientMetrics httpClientMetrics() { return metrics; } private void applyConnectionOptions(Bootstrap bootstrap) { bootstrap.option(ChannelOption.TCP_NODELAY, options.isTcpNoDelay()); if (options.getSendBufferSize() != -1) { bootstrap.option(ChannelOption.SO_SNDBUF, options.getSendBufferSize()); } if (options.getReceiveBufferSize() != -1) { bootstrap.option(ChannelOption.SO_RCVBUF, options.getReceiveBufferSize()); bootstrap.option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(options.getReceiveBufferSize())); } bootstrap.option(ChannelOption.SO_LINGER, options.getSoLinger()); if (options.getTrafficClass() != -1) { bootstrap.option(ChannelOption.IP_TOS, options.getTrafficClass()); } bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, options.getConnectTimeout()); bootstrap.option(ChannelOption.ALLOCATOR, PartialPooledByteBufAllocator.INSTANCE); bootstrap.option(ChannelOption.SO_KEEPALIVE, options.isTcpKeepAlive()); bootstrap.option(ChannelOption.SO_REUSEADDR, options.isReuseAddress()); } private void internalConnect(ContextImpl context, int port, String host, Handler<ClientConnection> connectHandler, Handler<Throwable> connectErrorHandler, ConnectionLifeCycleListener listener) { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(context.eventLoop()); bootstrap.channelFactory(new VertxNioSocketChannelFactory()); sslHelper.validate(vertx); bootstrap.handler(new ChannelInitializer<Channel>() { @Override protected void initChannel(Channel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); if (options.isSsl()) { pipeline.addLast("ssl", sslHelper.createSslHandler(vertx, true, host, port)); } pipeline.addLast("codec", new HttpClientCodec(4096, 8192, 8192, false, false)); if (options.isTryUseCompression()) { pipeline.addLast("inflater", new HttpContentDecompressor(true)); } if (options.getIdleTimeout() > 0) { pipeline.addLast("idle", new IdleStateHandler(0, 0, options.getIdleTimeout())); } pipeline.addLast("handler", new ClientHandler(vertx, context)); } }); applyConnectionOptions(bootstrap); ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port)); future.addListener((ChannelFuture channelFuture) -> { Channel ch = channelFuture.channel(); if (channelFuture.isSuccess()) { if (options.isSsl()) { // TCP connected, so now we must do the SSL handshake SslHandler sslHandler = ch.pipeline().get(SslHandler.class); io.netty.util.concurrent.Future<Channel> fut = sslHandler.handshakeFuture(); fut.addListener(fut2 -> { if (fut2.isSuccess()) { connected(context, port, host, ch, connectHandler, connectErrorHandler, listener); } else { connectionFailed(context, ch, connectErrorHandler, new SSLHandshakeException("Failed to create SSL connection"), listener); } }); } else { connected(context, port, host, ch, connectHandler, connectErrorHandler, listener); } } else { connectionFailed(context, ch, connectErrorHandler, channelFuture.cause(), listener); } }); } private URL parseUrl(String surl) { // Note - parsing a URL this way is slower than specifying host, port and relativeURI try { return new URL(surl); } catch (MalformedURLException e) { throw new VertxException("Invalid url: " + surl); } } private HttpClientRequest doRequest(HttpMethod method, String host, int port, String relativeURI, MultiMap headers) { Objects.requireNonNull(method, "no null method accepted"); Objects.requireNonNull(host, "no null host accepted"); Objects.requireNonNull(relativeURI, "no null relativeURI accepted"); checkClosed(); HttpClientRequest req = new HttpClientRequestImpl(this, method, host, port, relativeURI, vertx); if (headers != null) { req.headers().setAll(headers); } return req; } private synchronized void checkClosed() { if (closed) { throw new IllegalStateException("Client is closed"); } } private void connected(ContextImpl context, int port, String host, Channel ch, Handler<ClientConnection> connectHandler, Handler<Throwable> exceptionHandler, ConnectionLifeCycleListener listener) { context.executeFromIO( () -> createConn(context, port, host, ch, connectHandler, exceptionHandler, listener)); } private void createConn(ContextImpl context, int port, String host, Channel ch, Handler<ClientConnection> connectHandler, Handler<Throwable> exceptionHandler, ConnectionLifeCycleListener listener) { ClientConnection conn = new ClientConnection(vertx, HttpClientImpl.this, exceptionHandler, ch, options.isSsl(), host, port, context, listener, metrics); conn.closeHandler(v -> { // The connection has been closed - tell the pool about it, this allows the pool to create more // connections. Note the pool doesn't actually remove the connection, when the next person to get a connection // gets the closed on, they will check if it's closed and if so get another one. listener.connectionClosed(conn); }); connectionMap.put(ch, conn); connectHandler.handle(conn); } private void connectionFailed(ContextImpl context, Channel ch, Handler<Throwable> connectionExceptionHandler, Throwable t, ConnectionLifeCycleListener listener) { // If no specific exception handler is provided, fall back to the HttpClient's exception handler. // If that doesn't exist just log it Handler<Throwable> exHandler = connectionExceptionHandler == null ? log::error : connectionExceptionHandler; context.executeFromIO(() -> { listener.connectionClosed(null); try { ch.close(); } catch (Exception ignore) { } if (exHandler != null) { exHandler.handle(t); } else { log.error(t); } }); } private class ClientHandler extends VertxHttpHandler<ClientConnection> { private boolean closeFrameSent; private ContextImpl context; public ClientHandler(VertxInternal vertx, ContextImpl context) { super(HttpClientImpl.this.connectionMap); this.context = context; } @Override protected ContextImpl getContext(ClientConnection connection) { return context; } @Override protected void doMessageReceived(ClientConnection conn, ChannelHandlerContext ctx, Object msg) { if (conn == null) { return; } boolean valid = false; if (msg instanceof HttpResponse) { HttpResponse response = (HttpResponse) msg; conn.handleResponse(response); valid = true; } if (msg instanceof HttpContent) { HttpContent chunk = (HttpContent) msg; if (chunk.content().isReadable()) { Buffer buff = Buffer.buffer(chunk.content().slice()); conn.handleResponseChunk(buff); } if (chunk instanceof LastHttpContent) { conn.handleResponseEnd((LastHttpContent) chunk); } valid = true; } else if (msg instanceof WebSocketFrameInternal) { WebSocketFrameInternal frame = (WebSocketFrameInternal) msg; switch (frame.type()) { case BINARY: case CONTINUATION: case TEXT: conn.handleWsFrame(frame); break; case PING: // Echo back the content of the PING frame as PONG frame as specified in RFC 6455 Section 5.5.2 ctx.writeAndFlush(new WebSocketFrameImpl(FrameType.PONG, frame.getBinaryData())); break; case PONG: // Just ignore it break; case CLOSE: if (!closeFrameSent) { // Echo back close frame and close the connection once it was written. // This is specified in the WebSockets RFC 6455 Section 5.4.1 ctx.writeAndFlush(frame).addListener(ChannelFutureListener.CLOSE); closeFrameSent = true; } break; default: throw new IllegalStateException("Invalid type: " + frame.type()); } valid = true; } if (!valid) { throw new IllegalStateException("Invalid object " + msg); } } } private class WebSocketStreamImpl implements WebSocketStream { final int port; final String host; final String requestURI; final MultiMap headers; final WebsocketVersion version; final String subProtocols; private Handler<WebSocket> handler; private Handler<Throwable> exceptionHandler; private Handler<Void> endHandler; public WebSocketStreamImpl(int port, String host, String requestURI, MultiMap headers, WebsocketVersion version, String subProtocols) { this.port = port; this.host = host; this.requestURI = requestURI; this.headers = headers; this.version = version; this.subProtocols = subProtocols; } @Override public synchronized WebSocketStream exceptionHandler(Handler<Throwable> handler) { exceptionHandler = handler; return this; } @Override public synchronized WebSocketStream handler(Handler<WebSocket> handler) { if (this.handler == null && handler != null) { this.handler = handler; checkClosed(); ContextImpl context = vertx.getOrCreateContext(); Handler<Throwable> connectionExceptionHandler = exceptionHandler; if (connectionExceptionHandler == null) { connectionExceptionHandler = log::error; } Handler<WebSocket> wsConnect; if (endHandler != null) { Handler<Void> endCallback = endHandler; wsConnect = ws -> { handler.handle(ws); endCallback.handle(null); }; } else { wsConnect = handler; } getConnection(port, host, conn -> { if (!conn.isClosed()) { conn.toWebSocket(requestURI, headers, version, subProtocols, options.getMaxWebsocketFrameSize(), wsConnect); } else { websocket(port, host, requestURI, headers, version, subProtocols, wsConnect); } }, connectionExceptionHandler, context); } return this; } @Override public synchronized WebSocketStream endHandler(Handler<Void> endHandler) { this.endHandler = endHandler; return this; } @Override public WebSocketStream pause() { return this; } @Override public WebSocketStream resume() { return this; } } @Override protected void finalize() throws Throwable { // Make sure this gets cleaned up if there are no more references to it // so as not to leave connections and resources dangling until the system is shutdown // which could make the JVM run out of file handles. close(); super.finalize(); } }