package com.cloudhopper.jetty;
/*
* #%L
* ch-jetty
* %%
* Copyright (C) 2012 - 2013 Cloudhopper by Twitter
* %%
* 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.
* #L%
*/
import com.cloudhopper.commons.util.CountingRejectedExecutionHandler;
import com.cloudhopper.commons.util.NamingThreadFactory;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import javax.servlet.Servlet;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author joelauer (twitter: @jjlauer or <a href="http://twitter.com/jjlauer" target=window>http://twitter.com/jjlauer</a>)
*/
public class JettyHttpServer {
private static final Logger logger = LoggerFactory.getLogger(JettyHttpServer.class);
private HttpServerConfiguration configuration;
// internal instance of the Jetty http server
private Server server;
private HandlerCollection handlers;
private ContextHandlerCollection contexts;
private ServletContextHandler rootServletContext;
public JettyHttpServer(HttpServerConfiguration configuration, Server server, HandlerCollection handlers, ContextHandlerCollection contexts, ServletContextHandler rootServletContext) {
this.configuration = configuration;
this.server = server;
this.handlers = handlers;
this.contexts = contexts;
this.rootServletContext = rootServletContext;
}
Server getServer() {
return this.server;
}
public void join() throws InterruptedException {
this.server.join();
}
/**
* Starts the HTTP server. If an exception is thrown during startup, Jetty
* usually still runs, but this method will catch that and make sure it's
* shutdown before re-throwing the exception.
* @throws Exception Thrown if there is an error during start
*/
public void start() throws Exception {
// verify server is NOT null
if (server == null) {
throw new NullPointerException("Internal server instance was null, server not configured perhaps?");
}
logger.info("HttpServer [{}] version [{}] using Jetty [{}]", configuration.safeGetName(), com.cloudhopper.jetty.Version.getLongVersion(),
server.getVersion());
logger.info("HttpServer [{}] on [{}] starting...", configuration.safeGetName(), configuration.getPortString());
// try to start jetty server -- if it fails, it'll actually keep running
// so if an exception occurs, we'll make to stop it afterwards and rethrow the exception
try {
server.start();
} catch (Exception e) {
// make sure we stop the server
try { this.stop(); } catch (Exception ex) { }
throw e;
}
logger.info("HttpServer [{}] on [{}] started", configuration.safeGetName(), configuration.getPortString());
}
/**
* Stops the HTTP server.
* @throws Exception Thrown if there is an error during stop
*/
public void stop() throws Exception {
// verify server isn't null
if (server == null) {
throw new NullPointerException("Internal server instance was null, server already stopped perhaps?");
}
logger.info("HttpServer [{}] on [{}] stopping...", configuration.safeGetName(), configuration.getPortString());
server.stop();
logger.info("HttpServer [{}] on [{}] stopped", configuration.safeGetName(), configuration.getPortString());
}
/**
* Adds a directory for resources such as files containing html, images, etc.
* to be served up automatically be this server.<br>
* CAUTION: A resource base can only be added BEFORE the server is started.
* @param resourceBaseDir The directory containing resources (basically the
* web root)
*/
public void addResourceBase(String resourceBaseDir) {
// handles resources like images, files, etc..
logger.info("HttpServer [{}] adding resource base dir [{}]", configuration.safeGetName(), resourceBaseDir);
ResourceHandler resourceHandler = new ResourceHandler();
resourceHandler.setResourceBase(resourceBaseDir);
handlers.addHandler(resourceHandler);
}
/**
* Adds a servlet to this server.
* @param servlet The servlet to add
* @param uri The uri mapping (relative to root context /) to trigger this
* servlet to be executed. Wildcards are permitted.
*/
public void addServlet(Servlet servlet, String uri) {
// create a holder and then add it to the mapping
ServletHolder servletHolder = new ServletHolder(servlet);
rootServletContext.addServlet(servletHolder, uri);
}
private ThreadPoolExecutor getThreadPool() {
return server.getThreadPool() instanceof JettyExecutorThreadPool ? ((JettyExecutorThreadPool)server.getThreadPool()).getExecutor() : null;
}
// pass thru lifecycle state
public boolean isFailed() { return server.isFailed(); }
public boolean isRunning() { return server.isRunning(); }
public boolean isStarted() { return server.isStarted(); }
public boolean isStarting() { return server.isStarting(); }
public boolean isStopped() { return server.isStopped(); }
public boolean isStopping() { return server.isStopping(); }
// some safe stats for monitoring
public int getBusyThreads() {
return getThreadPool() == null ? -1 : getThreadPool().getActiveCount();
}
public int getCurrentThreads() {
return getThreadPool() == null ? -1 : getThreadPool().getPoolSize();
}
public int getIdleThreads() {
return getThreadPool() == null ? -1 :
(getThreadPool().getPoolSize() - getThreadPool().getActiveCount());
}
public int getMaxThreads() {
return getThreadPool() == null ? -1 : getThreadPool().getMaximumPoolSize();
}
public int getMinThreads() {
return getThreadPool() == null ? -1 : getThreadPool().getCorePoolSize();
}
public int getMostThreads() {
return getThreadPool() == null ? -1 : getThreadPool().getLargestPoolSize();
}
public int getFreeThreads() {
return getThreadPool() == null ? -1 :
(getThreadPool().getMaximumPoolSize() - getThreadPool().getActiveCount());
}
public long getRejectedConnections() {
return getThreadPool() == null ? -1 :
((getThreadPool().getRejectedExecutionHandler() != null &&
getThreadPool().getRejectedExecutionHandler() instanceof CountingRejectedExecutionHandler) ?
((CountingRejectedExecutionHandler)getThreadPool().getRejectedExecutionHandler()).getRejected() : -1);
}
// this should only be used as an estimate
public int getMaxQueuedConnections() {
return getThreadPool() == null ? -1 :
((getThreadPool().getQueue() instanceof ArrayBlockingQueue) ?
((ArrayBlockingQueue)getThreadPool().getQueue()).size() +
((ArrayBlockingQueue)getThreadPool().getQueue()).remainingCapacity() : -1);
}
public int getQueuedConnections() {
return getThreadPool() == null ? -1 : getThreadPool().getQueue().size();
}
public int getOpenConnections() {
if (this.server == null) return -1;
// fairly tough to calculate since we'll need to tally all connectors
// this should only be used as an estimate
int openConnections = 0;
for (Connector connector : server.getConnectors()) {
openConnections += connector.getConnectedEndPoints().size();
}
return openConnections;
}
}