/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.wso2.carbon.transport.http.netty.listener;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.ssl.SslContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.carbon.messaging.BufferFactory;
import org.wso2.carbon.messaging.handler.HandlerExecutor;
import org.wso2.carbon.transport.http.netty.common.Constants;
import org.wso2.carbon.transport.http.netty.common.Util;
import org.wso2.carbon.transport.http.netty.common.ssl.SSLHandlerFactory;
import org.wso2.carbon.transport.http.netty.config.ListenerConfiguration;
import org.wso2.carbon.transport.http.netty.config.TransportProperty;
import org.wso2.carbon.transport.http.netty.config.TransportsConfiguration;
import org.wso2.carbon.transport.http.netty.internal.HTTPTransportContextHolder;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.net.ssl.SSLException;
/**
* {@code ServerConnectorController} is the heart of the HTTP Server Connector.
* <p>
* This is responsible for creating the bootstrap and allow bind/unbind to interfaces
*/
public class ServerConnectorController {
private static final Logger log = LoggerFactory.getLogger(ServerConnectorController.class);
private ServerBootstrap bootstrap;
private HTTPServerChannelInitializer handler;
private TransportsConfiguration transportsConfiguration;
private boolean initialized = false;
public ServerConnectorController(TransportsConfiguration transportsConfiguration) {
this.transportsConfiguration = transportsConfiguration;
}
public void start() {
Set<TransportProperty> transportPropertiesSet = transportsConfiguration.getTransportProperties();
Map<String, Object> transportProperties = new HashMap<>();
if (transportPropertiesSet != null && !transportPropertiesSet.isEmpty()) {
transportProperties = transportPropertiesSet.stream().collect(
Collectors.toMap(TransportProperty::getName, TransportProperty::getValue));
}
// Create Bootstrap Configuration from listener parameters
ServerBootstrapConfiguration.createBootStrapConfiguration(transportProperties);
ServerBootstrapConfiguration serverBootstrapConfiguration = ServerBootstrapConfiguration.getInstance();
// Create Boss Group - boss group is for accepting channels
EventLoopGroup bossGroup = HTTPTransportContextHolder.getInstance().getBossGroup();
if (bossGroup == null) {
int bossGroupSize =
Util.getIntProperty(transportProperties,
Constants.SERVER_BOOTSTRAP_BOSS_GROUP_SIZE,
Runtime.getRuntime().availableProcessors());
bossGroup = new NioEventLoopGroup(bossGroupSize);
HTTPTransportContextHolder.getInstance().setBossGroup(bossGroup);
}
// Create Worker Group - worker group is for processing IO
EventLoopGroup workerGroup = HTTPTransportContextHolder.getInstance().getWorkerGroup();
if (workerGroup == null) {
int workerGroupSize =
Util.getIntProperty(transportProperties,
Constants.SERVER_BOOTSTRAP_WORKER_GROUP_SIZE,
Runtime.getRuntime().availableProcessors() * 2);
workerGroup = new NioEventLoopGroup(workerGroupSize);
HTTPTransportContextHolder.getInstance().setWorkerGroup(workerGroup);
}
// Set Handler Executor
HTTPTransportContextHolder.getInstance().setHandlerExecutor(new HandlerExecutor());
bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class);
// Register Channel initializer
handler = new HTTPServerChannelInitializer();
handler.setupConnectionManager(transportProperties);
bootstrap.childHandler(handler);
int bufferSize =
Util.getIntProperty(transportProperties, Constants.OUTPUT_CONTENT_BUFFER_SIZE, 0);
if (bufferSize != 0) {
BufferFactory.createInstance(bufferSize);
}
// Set other bootstrap parameters
bootstrap.option(ChannelOption.SO_BACKLOG, serverBootstrapConfiguration.getSoBackLog());
log.debug("Netty Server Socket BACKLOG " + serverBootstrapConfiguration.getSoBackLog());
bootstrap.childOption(ChannelOption.TCP_NODELAY, serverBootstrapConfiguration.isTcpNoDelay());
log.debug("Netty Server Socket TCP_NODELAY " + serverBootstrapConfiguration.isTcpNoDelay());
bootstrap.option(ChannelOption.SO_KEEPALIVE, serverBootstrapConfiguration.isKeepAlive());
log.debug("Netty Server Socket SO_KEEPALIVE " + serverBootstrapConfiguration.isKeepAlive());
bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, serverBootstrapConfiguration.getConnectTimeOut());
log.debug(" Netty Server Socket CONNECT_TIMEOUT_MILLIS " + serverBootstrapConfiguration.getConnectTimeOut());
bootstrap.option(ChannelOption.SO_SNDBUF, serverBootstrapConfiguration.getSendBufferSize());
log.debug("Netty Server Socket SO_SNDBUF " + serverBootstrapConfiguration.getSendBufferSize());
bootstrap.option(ChannelOption.SO_RCVBUF, serverBootstrapConfiguration.getReceiveBufferSize());
log.debug("Netty Server Socket SO_RCVBUF " + serverBootstrapConfiguration.getReceiveBufferSize());
bootstrap.childOption(ChannelOption.SO_RCVBUF, serverBootstrapConfiguration.getReceiveBufferSize());
log.debug("Netty Server Socket SO_RCVBUF " + serverBootstrapConfiguration.getReceiveBufferSize());
bootstrap.childOption(ChannelOption.SO_SNDBUF, serverBootstrapConfiguration.getSendBufferSize());
log.debug("Netty Server Socket SO_SNDBUF " + serverBootstrapConfiguration.getSendBufferSize());
initialized = true;
}
public void stop() {
shutdownEventLoops();
}
private void shutdownEventLoops() {
try {
EventLoopGroup bossGroup = HTTPTransportContextHolder.getInstance().getBossGroup();
if (bossGroup != null) {
bossGroup.shutdownGracefully().sync();
HTTPTransportContextHolder.getInstance().setBossGroup(null);
}
EventLoopGroup workerGroup = HTTPTransportContextHolder.getInstance().getWorkerGroup();
if (workerGroup != null) {
workerGroup.shutdownGracefully().sync();
HTTPTransportContextHolder.getInstance().setWorkerGroup(null);
}
log.info("HTTP transport event loops stopped successfully");
} catch (InterruptedException e) {
log.error("Error while shutting down event loops " + e.getMessage());
}
}
public boolean bindInterface(HTTPServerConnector serverConnector) {
if (!initialized) {
log.error("ServerConnectorController is not initialized");
return false;
}
try {
ListenerConfiguration listenerConfiguration = serverConnector.getListenerConfiguration();
SslContext http2sslContext = null;
// Create HTTP/2 ssl context during interface binding.
if (listenerConfiguration.isHttp2() && listenerConfiguration.getSslConfig() != null) {
http2sslContext = new SSLHandlerFactory(listenerConfiguration.getSslConfig())
.createHttp2TLSContext();
}
handler.registerListenerConfig(listenerConfiguration, http2sslContext);
ChannelFuture future = bootstrap.bind(new InetSocketAddress(listenerConfiguration.getHost(),
listenerConfiguration.getPort())).sync();
serverConnector.setChannelFuture(future);
if (future.isSuccess()) {
String msg = "Started listener " +
listenerConfiguration.getScheme() + "-" + listenerConfiguration.getPort();
log.info(msg);
if (listenerConfiguration.getSslConfig() == null) {
log.info("HTTP Interface " + listenerConfiguration.getId() + " starting on host " +
listenerConfiguration.getHost() + " and port " + listenerConfiguration.getPort());
} else {
log.info("HTTPS Interface " + listenerConfiguration.getId() + " starting on host " +
listenerConfiguration.getHost() + " and port " + listenerConfiguration.getPort());
}
return true;
} else {
log.error("Cannot bind port for host " + listenerConfiguration.getHost() + " port "
+ listenerConfiguration.getPort());
}
} catch (InterruptedException e) {
log.error(e.getMessage(), e);
} catch (SSLException e) {
log.error("Error occurred while configuring HTTP/2 SSL context for host "
+ serverConnector.getListenerConfiguration().getHost() + " port "
+ serverConnector.getListenerConfiguration().getPort());
}
return false;
}
public boolean unBindInterface(HTTPServerConnector serverConnector) {
if (!initialized) {
log.error("ServerConnectorController is not initialized");
return false;
}
ListenerConfiguration listenerConfiguration = serverConnector.getListenerConfiguration();
handler.unRegisterListenerConfig(listenerConfiguration);
//Remove cached channels and close them.
ChannelFuture future = serverConnector.getChannelFuture();
if (future != null) {
future.channel().close();
if (listenerConfiguration.getSslConfig() == null) {
log.info("HTTP Listener stopped on listening interface " +
listenerConfiguration.getId() + " attached to host " +
listenerConfiguration.getHost() + " and port " + listenerConfiguration.getPort());
} else {
log.info("HTTPS Listener stopped on listening interface " +
listenerConfiguration.getId() + " attached to host " + listenerConfiguration.getHost() +
" and port " + listenerConfiguration.getPort());
}
return true;
}
return false;
}
}