Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.vertx.core.http.impl.HttpClientImpl Maven / Gradle / Ivy
/*
* 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.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.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BooleanSupplier;
/**
*
* This class is thread-safe
*
* @author Tim Fox
*/
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 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(), options.getMaxWaitQueueSize()) {
protected void connect(String host, int port, Handler connectHandler, Handler 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 wsConnect) {
websocketStream(port, host, requestURI, null, null).handler(wsConnect);
return this;
}
public HttpClient websocket(int port, String host, String requestURI, Handler wsConnect, Handler failureHandler){
websocketStream(port, host, requestURI, null, null).exceptionHandler(failureHandler).handler(wsConnect);
return this;
}
@Override
public HttpClient websocket(String host, String requestURI, Handler wsConnect) {
return websocket(options.getDefaultPort(), host, requestURI, wsConnect);
}
@Override
public HttpClient websocket(String host, String requestURI, Handler wsConnect, Handler failureHandler) {
return websocket(options.getDefaultPort(), host, requestURI, wsConnect, failureHandler);
}
@Override
public HttpClient websocket(int port, String host, String requestURI, MultiMap headers, Handler wsConnect) {
websocketStream(port, host, requestURI, headers, null).handler(wsConnect);
return this;
}
@Override
public HttpClient websocket(int port, String host, String requestURI, MultiMap headers, Handler wsConnect, Handler failureHandler) {
websocketStream(port, host, requestURI, headers, null).exceptionHandler(failureHandler).handler(wsConnect);
return this;
}
@Override
public HttpClient websocket(String host, String requestURI, MultiMap headers, Handler wsConnect) {
return websocket(options.getDefaultPort(), host, requestURI, headers, wsConnect);
}
@Override
public HttpClient websocket(String host, String requestURI, MultiMap headers, Handler wsConnect, Handler failureHandler) {
return websocket(options.getDefaultPort(), host, requestURI, headers, wsConnect, failureHandler);
}
@Override
public HttpClient websocket(int port, String host, String requestURI, MultiMap headers, WebsocketVersion version, Handler wsConnect) {
websocketStream(port, host, requestURI, headers, version, null).handler(wsConnect);
return this;
}
@Override
public HttpClient websocket(int port, String host, String requestURI, MultiMap headers, WebsocketVersion version
, Handler wsConnect, Handler failureHandler) {
websocketStream(port, host, requestURI, headers, version, null).exceptionHandler(failureHandler).handler(wsConnect);
return this;
}
@Override
public HttpClient websocket(String host, String requestURI, MultiMap headers, WebsocketVersion version, Handler wsConnect) {
return websocket(options.getDefaultPort(), host, requestURI, headers, version, wsConnect);
}
@Override
public HttpClient websocket(String host, String requestURI, MultiMap headers, WebsocketVersion version
, Handler wsConnect, Handler failureHandler) {
return websocket(options.getDefaultPort(), host, requestURI, headers, version, wsConnect, failureHandler);
}
@Override
public HttpClient websocket(int port, String host, String requestURI, MultiMap headers, WebsocketVersion version,
String subProtocols, Handler wsConnect) {
websocketStream(port, host, requestURI, headers, version, subProtocols).handler(wsConnect);
return this;
}
@Override
public HttpClient websocket(int port, String host, String requestURI, MultiMap headers, WebsocketVersion version,
String subProtocols, Handler wsConnect, Handler failureHandler) {
websocketStream(port, host, requestURI, headers, version, subProtocols).exceptionHandler(failureHandler).handler(wsConnect);
return this;
}
@Override
public HttpClient websocket(String host, String requestURI, MultiMap headers, WebsocketVersion version, String subProtocols, Handler wsConnect) {
return websocket(options.getDefaultPort(), host, requestURI, headers, version, subProtocols, wsConnect);
}
@Override
public HttpClient websocket(String host, String requestURI, MultiMap headers, WebsocketVersion version, String subProtocols
, Handler wsConnect, Handler failureHandler) {
return websocket(options.getDefaultPort(), host, requestURI, headers, version, subProtocols, wsConnect, failureHandler);
}
@Override
public HttpClient websocket(String requestURI, Handler wsConnect) {
return websocket(options.getDefaultPort(), options.getDefaultHost(), requestURI, wsConnect);
}
@Override
public HttpClient websocket(String requestURI, Handler wsConnect, Handler failureHandler) {
return websocket(options.getDefaultPort(), options.getDefaultHost(), requestURI, wsConnect, failureHandler);
}
@Override
public HttpClient websocket(String requestURI, MultiMap headers, Handler wsConnect) {
return websocket(options.getDefaultPort(), options.getDefaultHost(), requestURI, headers, wsConnect);
}
@Override
public HttpClient websocket(String requestURI, MultiMap headers, Handler wsConnect, Handler failureHandler) {
return websocket(options.getDefaultPort(), options.getDefaultHost(), requestURI, headers, wsConnect, failureHandler);
}
@Override
public HttpClient websocket(String requestURI, MultiMap headers, WebsocketVersion version, Handler wsConnect) {
return websocket(options.getDefaultPort(), options.getDefaultHost(), requestURI, headers, version, wsConnect);
}
@Override
public HttpClient websocket(String requestURI, MultiMap headers, WebsocketVersion version, Handler wsConnect, Handler failureHandler) {
return websocket(options.getDefaultPort(), options.getDefaultHost(), requestURI, headers, version, wsConnect, failureHandler);
}
@Override
public HttpClient websocket(String requestURI, MultiMap headers, WebsocketVersion version, String subProtocols, Handler wsConnect) {
return websocket(options.getDefaultPort(), options.getDefaultHost(), requestURI, headers, version, subProtocols, wsConnect);
}
@Override
public HttpClient websocket(String requestURI, MultiMap headers, WebsocketVersion version, String subProtocols
, Handler wsConnect, Handler failureHandler) {
return websocket(options.getDefaultPort(), options.getDefaultHost(), requestURI, headers, version, subProtocols
, wsConnect, failureHandler);
}
@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 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 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 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 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 responseHandler) {
return request(HttpMethod.GET, port, host, requestURI, responseHandler);
}
@Override
public HttpClientRequest get(String host, String requestURI, Handler 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 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 responseHandler) {
return requestAbs(HttpMethod.GET, absoluteURI, responseHandler);
}
@Override
public HttpClient getNow(int port, String host, String requestURI, Handler responseHandler) {
get(port, host, requestURI, responseHandler).end();
return this;
}
@Override
public HttpClient getNow(String host, String requestURI, Handler responseHandler) {
return getNow(options.getDefaultPort(), host, requestURI, responseHandler);
}
@Override
public HttpClient getNow(String requestURI, Handler 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 responseHandler) {
return request(HttpMethod.POST, port, host, requestURI, responseHandler);
}
@Override
public HttpClientRequest post(String host, String requestURI, Handler 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 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 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 responseHandler) {
return request(HttpMethod.HEAD, port, host, requestURI, responseHandler);
}
@Override
public HttpClientRequest head(String host, String requestURI, Handler 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 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 responseHandler) {
return requestAbs(HttpMethod.HEAD, absoluteURI, responseHandler);
}
@Override
public HttpClient headNow(int port, String host, String requestURI, Handler responseHandler) {
head(port, host, requestURI, responseHandler).end();
return this;
}
@Override
public HttpClient headNow(String host, String requestURI, Handler responseHandler) {
return headNow(options.getDefaultPort(), host, requestURI, responseHandler);
}
@Override
public HttpClient headNow(String requestURI, Handler 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 responseHandler) {
return request(HttpMethod.OPTIONS, port, host, requestURI, responseHandler);
}
@Override
public HttpClientRequest options(String host, String requestURI, Handler 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 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 responseHandler) {
return requestAbs(HttpMethod.OPTIONS, absoluteURI, responseHandler);
}
@Override
public HttpClient optionsNow(int port, String host, String requestURI, Handler responseHandler) {
options(port, host, requestURI, responseHandler).end();
return this;
}
@Override
public HttpClient optionsNow(String host, String requestURI, Handler responseHandler) {
return optionsNow(options.getDefaultPort(), host, requestURI, responseHandler);
}
@Override
public HttpClient optionsNow(String requestURI, Handler 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 responseHandler) {
return request(HttpMethod.PUT, port, host, requestURI, responseHandler);
}
@Override
public HttpClientRequest put(String host, String requestURI, Handler 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 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 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 responseHandler) {
return request(HttpMethod.DELETE, port, host, requestURI, responseHandler);
}
@Override
public HttpClientRequest delete(String host, String requestURI, Handler 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 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 responseHandler) {
return requestAbs(HttpMethod.DELETE, absoluteURI, responseHandler);
}
@Override
public synchronized void close() {
synchronized (this) {
checkClosed();
closed = true;
}
if (creatingContext != null) {
creatingContext.removeCloseHook(closeHook);
}
pool.close();
for (ClientConnection conn : connectionMap.values()) {
conn.close();
}
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 handler, Handler connectionExceptionHandler,
ContextImpl context) {
pool.getConnection(port, host, handler, connectionExceptionHandler, context, () -> false);
}
void getConnection(int port, String host, Handler handler, Handler connectionExceptionHandler,
ContextImpl context, BooleanSupplier canceled) {
pool.getConnection(port, host, handler, connectionExceptionHandler, context, canceled);
}
/**
* @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()));
}
if (options.getSoLinger() != -1) {
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 clientContext, int port, String host, Handler connectHandler,
Handler connectErrorHandler, ConnectionLifeCycleListener listener) {
ContextImpl context;
if (clientContext == null) {
// Embedded
context = vertx.getOrCreateContext();
} else {
context = clientContext;
}
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(context.nettyEventLoop());
bootstrap.channelFactory(new VertxNioSocketChannelFactory());
sslHelper.validate(vertx);
bootstrap.handler(new ChannelInitializer() {
@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, options.getMaxChunkSize(), 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 fut = sslHandler.handshakeFuture();
fut.addListener(fut2 -> {
if (fut2.isSuccess()) {
connected(context, port, host, ch, connectHandler, connectErrorHandler, listener);
} else {
SSLHandshakeException sslException = new SSLHandshakeException("Failed to create SSL connection");
Optional.ofNullable(fut2.cause()).ifPresent(sslException::initCause);
connectionFailed(context, ch, connectErrorHandler, sslException,
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 connectHandler,
Handler 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 connectHandler,
Handler 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 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 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 {
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 handler;
private Handler exceptionHandler;
private Handler 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 handler) {
exceptionHandler = handler;
return this;
}
@Override
public synchronized WebSocketStream handler(Handler handler) {
if (this.handler == null && handler != null) {
this.handler = handler;
checkClosed();
ContextImpl context = vertx.getOrCreateContext();
Handler connectionExceptionHandler = exceptionHandler;
if (connectionExceptionHandler == null) {
connectionExceptionHandler = log::error;
}
Handler wsConnect;
if (endHandler != null) {
Handler 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 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();
}
}