/*
* Copyright 2012 Jason Miller
*
* Licensed 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 jj.http.server;
import static java.util.concurrent.TimeUnit.*;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.Future;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import jj.ServerStopping;
import jj.configuration.ConfigurationLoaded;
import jj.event.Listener;
import jj.event.Publisher;
import jj.event.Subscriber;
import jj.execution.TaskRunner;
import jj.logging.Emergency;
import jj.util.StringUtils;
/**
* <p>
* Manages the netty ServerBootstrap instance for HTTP serving.
*
* <p>
* Mainly, this class acts as an interface point to the configuration system,
* it listens for
*
* @author jason
*
*/
@Singleton
@Subscriber
class HttpServer {
private static ExecutorService executorService(final int threads, final UncaughtExceptionHandler uncaughtExceptionHandler) {
return Executors.newFixedThreadPool(threads, new ThreadFactory() {
private final AtomicInteger id = new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "JibbrJabbr HTTP Boss Handler " + id.incrementAndGet());
thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
return thread;
}
});
}
private final HttpServerNioEventLoopGroup ioEventLoopGroup;
private final HttpServerChannelInitializer initializer;
private final HttpServerSocketConfiguration configuration;
private final HttpServerSwitch httpServerSwitch;
private final Publisher publisher;
private final TaskRunner taskRunner;
private final Provider<ServerBootstrap> serverBootstrapProvider;
private final UncaughtExceptionHandler uncaughtExceptionHandler;
private ServerBootstrap serverBootstrap;
private volatile int configurationHashCode;
@Inject
HttpServer(
final HttpServerNioEventLoopGroup ioEventLoopGroup,
final HttpServerChannelInitializer initializer,
final HttpServerSocketConfiguration configuration,
final HttpServerSwitch httpServerSwitch,
final Publisher publisher,
final TaskRunner taskRunner,
final Provider<ServerBootstrap> serverBootstrapProvider,
final UncaughtExceptionHandler uncaughtExceptionHandler
) {
this.ioEventLoopGroup = ioEventLoopGroup;
this.initializer = initializer;
this.configuration = configuration;
this.httpServerSwitch = httpServerSwitch;
this.publisher = publisher;
this.taskRunner = taskRunner;
this.serverBootstrapProvider = serverBootstrapProvider;
this.uncaughtExceptionHandler = uncaughtExceptionHandler;
}
@Listener
void on(ConfigurationLoaded event) {
if (httpServerSwitch.on()) {
checkStart();
}
}
@Listener
void on(ServerStopping event) {
if (httpServerSwitch.on() && serverBootstrap != null) {
stop(serverBootstrap);
publisher.publish(new HttpServerStopped());
serverBootstrap = null;
}
}
private void checkStart() {
if (configurationHashCode != configuration.hashCode()) {
configurationHashCode = configuration.hashCode();
taskRunner.execute(new HttpServerTask("starting the http server") {
@Override
protected void run() throws Exception {
if (serverBootstrap != null) {
stop(serverBootstrap).addListener((future) -> {
serverBootstrap = null;
if (future.isSuccess()) {
publisher.publish(new HttpServerRestarting());
start();
} else {
publisher.publish(new Emergency("could not restart the HTTP server", future.cause()));
}
});
} else {
start();
}
}
});
}
}
private Future<?> stop(ServerBootstrap serverBootstrap) {
return serverBootstrap.group().shutdownGracefully(0, 250, MILLISECONDS).addListener((future) -> {
if (!future.isSuccess()) {} // uhhhh - no clue here really. can't imagine the failure mode yet
if (serverBootstrap.childGroup() != null) {
serverBootstrap.childGroup().shutdownGracefully(); // don't really care about waiting for these
}
});
}
private void start() throws Exception {
List<Binding> bindings = configuration.bindings();
if (!bindings.isEmpty()) {
serverBootstrap = bindPorts(makeServerBootstrap(bindings.size()), bindings);
publisher.publish(new HttpServerStarted());
} else {
serverBootstrap = null;
}
}
private ServerBootstrap makeServerBootstrap(int bindingCount) {
return serverBootstrapProvider.get()
.group(new NioEventLoopGroup(bindingCount, executorService(bindingCount, uncaughtExceptionHandler)), ioEventLoopGroup)
.channel(NioServerSocketChannel.class)
.childHandler(initializer)
.option(ChannelOption.SO_KEEPALIVE, configuration.keepAlive())
.option(ChannelOption.SO_REUSEADDR, configuration.reuseAddress())
.option(ChannelOption.TCP_NODELAY, configuration.tcpNoDelay())
.option(ChannelOption.SO_TIMEOUT, configuration.timeout())
.option(ChannelOption.SO_BACKLOG, configuration.backlog())
.option(ChannelOption.SO_RCVBUF, configuration.receiveBufferSize())
.option(ChannelOption.SO_SNDBUF, configuration.sendBufferSize());
}
private ServerBootstrap bindPorts(ServerBootstrap serverBootstrap, List<Binding> bindings) throws Exception {
try {
for (Binding binding : bindings) {
String host = binding.host();
int port = binding.port();
if (!StringUtils.isEmpty(host)) {
serverBootstrap.bind(host, port).sync();
} else {
serverBootstrap.bind(port).sync();
}
publisher.publish(new BindingHttpServer(binding));
}
return serverBootstrap;
} catch (Exception e) {
e.printStackTrace();
stop(serverBootstrap);
throw e;
}
}
}