com.king.platform.net.http.netty.ChannelManager Maven / Gradle / Ivy
// Copyright (C) king.com Ltd 2015
// https://github.com/king/king-http-client
// Author: Magnus Gustafsson
// License: Apache 2.0, https://raw.github.com/king/king-http-client/LICENSE-APACHE
package com.king.platform.net.http.netty;
import com.king.platform.net.http.ConfKeys;
import com.king.platform.net.http.netty.eventbus.*;
import com.king.platform.net.http.netty.pool.ChannelPool;
import com.king.platform.net.http.netty.response.NettyHttpClientResponse;
import com.king.platform.net.http.netty.util.TimeProvider;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpContentDecompressor;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.Timer;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import org.slf4j.Logger;
import java.util.concurrent.TimeUnit;
import static org.slf4j.LoggerFactory.getLogger;
public class ChannelManager {
private final Logger logger = getLogger(getClass());
private final EventLoopGroup eventLoopGroup;
private final TimeProvider timeProvider;
private final ConfMap confMap;
private final ChannelPool channelPool;
private final Bootstrap plainBootstrap;
private final Bootstrap secureBootstrap;
private Timer nettyTimer;
public ChannelManager(NioEventLoopGroup nioEventLoop, final HttpClientHandler httpClientHandler, Timer nettyTimer, TimeProvider timeProvider, ChannelPool
channelPool, final ConfMap confMap, RootEventBus rootEventBus) {
this.eventLoopGroup = nioEventLoop;
this.nettyTimer = nettyTimer;
this.timeProvider = timeProvider;
this.channelPool = channelPool;
this.confMap = confMap;
plainBootstrap = new Bootstrap().channel(NioSocketChannel.class).group(eventLoopGroup);
plainBootstrap.handler(new ChannelInitializer() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
addLoggingIfDesired(pipeline, confMap.get(ConfKeys.NETTY_TRACE_LOGS));
pipeline.addLast("http-codec", newHttpClientCodec());
pipeline.addLast("inflater", new HttpContentDecompressor());
pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
pipeline.addLast("httpClientHandler", httpClientHandler);
}
});
secureBootstrap = new Bootstrap().channel(NioSocketChannel.class).group(eventLoopGroup);
secureBootstrap.handler(new ChannelInitializer() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
SslInitializer sslInitializer = new SslInitializer(new SSLFactory(confMap.get(ConfKeys.SSL_ALLOW_ALL_CERTIFICATES)), confMap.get(ConfKeys
.SSL_HANDSHAKE_TIMEOUT_MILLIS));
pipeline.addLast(SslInitializer.NAME, sslInitializer);
addLoggingIfDesired(pipeline, confMap.get(ConfKeys.NETTY_TRACE_LOGS));
pipeline.addLast("http-codec", newHttpClientCodec());
pipeline.addLast("inflater", new HttpContentDecompressor());
pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
pipeline.addLast("httpClientHandler", httpClientHandler);
}
});
secureBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, confMap.get(ConfKeys.CONNECT_TIMEOUT_MILLIS));
plainBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, confMap.get(ConfKeys.CONNECT_TIMEOUT_MILLIS));
NettyChannelOptions nettyChannelOptions = confMap.get(ConfKeys.NETTY_CHANNEL_OPTIONS);
for (ChannelOption channelOption : nettyChannelOptions.keys()) {
plainBootstrap.option(channelOption, nettyChannelOptions.get(channelOption));
secureBootstrap.option(channelOption, nettyChannelOptions.get(channelOption));
}
rootEventBus.subscribePermanently(Event.ERROR, new ErrorCallback());
rootEventBus.subscribePermanently(Event.COMPLETED, new CompletedCallback());
rootEventBus.subscribePermanently(Event.EXECUTE_REQUEST, new ExecuteRequestCallback());
}
private void addLoggingIfDesired(ChannelPipeline pipeline, boolean desired) {
if (desired) {
pipeline.addLast("logging", new LoggingHandler(LogLevel.TRACE));
}
}
private HttpClientCodec newHttpClientCodec() {
return new HttpClientCodec(confMap.get(ConfKeys.HTTP_CODEC_MAX_INITIAL_LINE_LENGTH), confMap.get(ConfKeys.HTTP_CODEC_MAX_HEADER_SIZE), confMap.get
(ConfKeys.HTTP_CODEC_MAX_CHUNK_SIZE));
}
public void sendOnChannel(final HttpRequestContext httpRequestContext, RequestEventBus requestEventBus) {
ServerInfo serverInfo = httpRequestContext.getServerInfo();
logger.trace("Sending request {} to server {}", httpRequestContext, serverInfo);
requestEventBus.triggerEvent(Event.onConnecting);
boolean keepAlive = httpRequestContext.isKeepAlive();
if (keepAlive && channelPool.isActive()) {
final Channel channel = channelPool.get(serverInfo);
if (channel != null) {
logger.trace("Got old channel {} for request {}", channel, httpRequestContext);
requestEventBus.triggerEvent(Event.REUSED_CONNECTION, serverInfo);
requestEventBus.triggerEvent(Event.onConnected);
sendOnChannel(channel, httpRequestContext, requestEventBus);
} else {
logger.trace("Sending on a new channel for request {}", httpRequestContext);
sendOnNewChannel(httpRequestContext, requestEventBus);
}
} else {
logger.trace("Sending on a new channel for request {}", httpRequestContext);
sendOnNewChannel(httpRequestContext, requestEventBus);
}
}
private void sendOnChannel(final Channel channel, final HttpRequestContext httpRequestContext, final RequestEventBus requestEventBus) {
httpRequestContext.attachedToChannel(channel);
scheduleTimeOutTasks(requestEventBus, httpRequestContext, httpRequestContext.getTotalRequestTimeoutMillis(), httpRequestContext.getIdleTimeoutMillis
());
ChannelFuture channelFuture = channel.writeAndFlush(httpRequestContext);
channelFuture.addListener(new GenericFutureListener>() {
@Override
public void operationComplete(Future super Void> future) throws Exception {
if (!future.isSuccess()) {
requestEventBus.triggerEvent(Event.ERROR, httpRequestContext, future.cause());
}
}
});
logger.trace("Wrote {} to channel {}", httpRequestContext, channel);
}
private void scheduleTimeOutTasks(RequestEventBus requestEventBus, HttpRequestContext httpRequestContext, int totalRequestTimeoutMillis, int idleTimeoutMillis) {
if (totalRequestTimeoutMillis > 0) {
TotalRequestTimeoutTimerTask totalRequestTimeoutTimerTask = new TotalRequestTimeoutTimerTask(requestEventBus, httpRequestContext);
TimeoutTimerHandler timeoutTimerHandler = new TimeoutTimerHandler(nettyTimer, requestEventBus);
timeoutTimerHandler.scheduleTimeout(totalRequestTimeoutTimerTask, totalRequestTimeoutMillis, TimeUnit.MILLISECONDS);
}
if (idleTimeoutMillis != 0 && (idleTimeoutMillis < totalRequestTimeoutMillis || totalRequestTimeoutMillis == 0)) {
TimeoutTimerHandler timeoutTimerHandler = new TimeoutTimerHandler(nettyTimer, requestEventBus);
IdleTimeoutTimerTask idleTimeoutTimerTask = new IdleTimeoutTimerTask(httpRequestContext, timeoutTimerHandler, idleTimeoutMillis,
totalRequestTimeoutMillis, timeProvider, requestEventBus);
timeoutTimerHandler.scheduleTimeout(idleTimeoutTimerTask, idleTimeoutMillis, TimeUnit.MILLISECONDS);
}
}
private void sendOnNewChannel(final HttpRequestContext httpRequestContext, final RequestEventBus requestEventBus) {
final ServerInfo serverInfo = httpRequestContext.getServerInfo();
ChannelFuture channelFuture = connect(serverInfo);
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
requestEventBus.triggerEvent(Event.CREATED_CONNECTION, serverInfo);
requestEventBus.triggerEvent(Event.onConnected);
Channel channel = future.channel();
channel.attr(ServerInfo.ATTRIBUTE_KEY).set(serverInfo);
logger.trace("Opened a new channel {}, for request {}", channel, httpRequestContext);
sendOnChannel(channel, httpRequestContext, requestEventBus);
} else {
logger.trace("Failed to opened a new channel for request {}", httpRequestContext);
Throwable cause = future.cause();
requestEventBus.triggerEvent(Event.ERROR, httpRequestContext, cause);
}
}
});
}
private ChannelFuture connect(ServerInfo serverInfo) {
Bootstrap bootstrap = plainBootstrap;
if (serverInfo.isSecure()) {
bootstrap = secureBootstrap;
logger.trace("Connecting using secureBootstrap");
} else {
logger.trace("Connecting using plainBootstrap");
}
return bootstrap.connect(serverInfo.getHost(), serverInfo.getPort());
}
private class ErrorCallback implements EventBusCallback2 {
@Override
public void onEvent(Event2 event, HttpRequestContext httpRequestContext, Throwable throwable) {
ServerInfo serverInfo = httpRequestContext.getServerInfo();
Channel channel = httpRequestContext.getAndDetachChannel();
if (channel != null) {
channelPool.discard(serverInfo, channel);
channel.close();
}
httpRequestContext.getRequestEventBus().triggerEvent(Event.CLOSED_CONNECTION, serverInfo);
}
}
private class CompletedCallback implements EventBusCallback1 {
@Override
public void onEvent(Event1 event, HttpRequestContext httpRequestContext) {
RequestEventBus requestEventBus = httpRequestContext.getRequestEventBus();
Channel channel = httpRequestContext.getAndDetachChannel();
ServerInfo serverInfo = httpRequestContext.getServerInfo();
if (!channelPool.isActive()) {
channel.close();
requestEventBus.triggerEvent(Event.CLOSED_CONNECTION, serverInfo);
return;
}
boolean keepAlive = httpRequestContext.isKeepAlive();
NettyHttpClientResponse nettyHttpClientResponse = httpRequestContext.getNettyHttpClientResponse();
if (nettyHttpClientResponse == null || nettyHttpClientResponse.getHttpHeaders() == null) {
keepAlive = false;
} else {
String connection = nettyHttpClientResponse.getHttpHeaders().get(HttpHeaders.Names.CONNECTION);
if (connection != null && HttpHeaders.Values.CLOSE.equalsIgnoreCase(connection)) {
keepAlive = false;
}
}
if (keepAlive) {
channelPool.offer(serverInfo, channel);
requestEventBus.triggerEvent(Event.POOLED_CONNECTION, serverInfo);
} else {
channelPool.discard(serverInfo, channel);
channel.close();
requestEventBus.triggerEvent(Event.CLOSED_CONNECTION, serverInfo);
}
}
}
private class ExecuteRequestCallback implements EventBusCallback1 {
@Override
public void onEvent(Event1 event, HttpRequestContext httpRequestContext) {
sendOnChannel(httpRequestContext, httpRequestContext.getRequestEventBus());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy