/*
* Copyright (c) 2009, TamaCat.org
* All rights reserved.
*/
package org.tamacat.httpd.core;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.lang.management.ManagementFactory;
import java.net.ServerSocket;
import java.rmi.registry.LocateRegistry;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.protocol.HttpRequestHandlerRegistry;
import org.apache.http.protocol.ResponseConnControl;
import org.apache.http.protocol.ResponseContent;
import org.apache.http.protocol.ResponseDate;
import org.apache.http.protocol.ResponseServer;
import org.tamacat.httpd.config.ServerConfig;
import org.tamacat.httpd.config.ServiceConfig;
import org.tamacat.httpd.config.ServiceUrl;
import org.tamacat.httpd.config.HostServiceConfig;
import org.tamacat.httpd.config.ServiceConfigParser;
import org.tamacat.httpd.jmx.BasicCounter;
import org.tamacat.httpd.jmx.JMXReloadableHttpd;
import org.tamacat.httpd.ssl.SSLContextCreator;
import org.tamacat.log.Log;
import org.tamacat.log.LogFactory;
import org.tamacat.util.ExceptionUtils;
import org.tamacat.util.PropertyUtils;
import org.tamacat.util.StringUtils;
/**
* <p>It is implements of the multi-thread server.
*/
public class HttpEngine implements JMXReloadableHttpd, Runnable {
static final Log LOG = LogFactory.getLog(HttpEngine.class);
private String propertiesName = "server.properties";
private ServerConfig serverConfig;
private ObjectName objectName;
private DefaultHttpService service;
private SSLContextCreator sslContextCreator;
private ServerSocket serversocket;
private HttpParamsBuilder paramsBuilder;
private HttpProcessorBuilder procBuilder;
private ExecutorService executors;
private BasicCounter counter = new BasicCounter();
private List<HttpResponseInterceptor> interceptors
= new ArrayList<HttpResponseInterceptor>();
private boolean isMXServerStarted;
private ClassLoader loader;
/**
* <p>This method called by {@link #start}.
*/
protected void init() {
if (serverConfig == null) {
Properties props = PropertyUtils.getProperties(propertiesName);
serverConfig = new ServerConfig(props);
}
paramsBuilder = new HttpParamsBuilder();
paramsBuilder.socketTimeout(serverConfig.getSocketTimeout())
.socketBufferSize(serverConfig.getSocketBufferSize())
.originServer(serverConfig.getParam("ServerName"));
procBuilder = new HttpProcessorBuilder();
//default interceptors
procBuilder.addInterceptor(new ResponseDate());
procBuilder.addInterceptor(new ResponseServer());
procBuilder.addInterceptor(new ResponseContent());
procBuilder.addInterceptor(new ResponseConnControl());
//add interceptors
for (HttpResponseInterceptor interceptor : interceptors) {
procBuilder.addInterceptor(interceptor);
}
service = new DefaultHttpService(
procBuilder, new DefaultConnectionReuseStrategy(),
new DefaultHttpResponseFactory(), null, null,
paramsBuilder.buildParams());
service.setClassLoader(getClassLoader());
if (isMXServerStarted == false) {
registerMXServer();
}
String componentsXML = serverConfig.getParam("components.file", "components.xml");
HttpHandlerFactory factory = new DefaultHttpHandlerFactory(
componentsXML, getClass().getClassLoader());
HostRequestHandlerResolver hostResolver = new HostRequestHandlerResolver();
HostServiceConfig hostConfig = new ServiceConfigParser(serverConfig).getConfig();
for (String host : hostConfig.getHosts()) {
HttpRequestHandlerRegistry registry = new HttpRequestHandlerRegistry();
ServiceConfig serviceConfig = hostConfig.getServiceConfig(host);
for (ServiceUrl serviceUrl : serviceConfig.getServiceUrlList()) {
HttpHandler handler = factory.getHttpHandler(serviceUrl);
if (handler != null) {
LOG.info(serviceUrl.getPath() + " - " + handler.getClass().getName());
registry.register(serviceUrl.getPath() + "*", handler);
} else {
LOG.warn(serviceUrl.getPath() + " HttpHandler is not found.");
}
}
hostResolver.setHostRequestHandlerResolver(host, registry);
}
service.setHostHandlerResolver(hostResolver);
}
/**
* <p>Start the http server.
*/
@Override
public void startHttpd() {
//Initalize engine.
init();
try {
//setup the server port.
int port = serverConfig.getPort();
if (serverConfig.useHttps()) {
serversocket = createSecureServerSocket(port);
if (serverConfig.useClientAuth() && serversocket instanceof SSLServerSocket) {
((SSLServerSocket)serversocket).setNeedClientAuth(true);
}
} else {
serversocket = new ServerSocket(port);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
//set the maximun worker threads.
int maxThreads = serverConfig.getMaxThreads();
LOG.info("MaxServerThreads: " + maxThreads);
String threadName = serverConfig.getParam("WorkerThreadName", "httpd");
//create the ExecutorService.
executors = new ThreadExecutorFactory(threadName).getExecutorService(maxThreads);
LOG.info("Listen: " + serverConfig.getPort());
while (!Thread.interrupted()) {
try {
//socket accept -> execute WorkerThrad.
executors.execute(new WorkerThread(
service, serversocket.accept(), paramsBuilder.buildParams(), counter)
);
} catch (InterruptedIOException e) {
counter.error();
LOG.error(e.getMessage());
break;
} catch (IOException e) {
counter.error();
LOG.error(e.getMessage());
if (serversocket.isClosed()) { //for stop()
break;
}
} catch (Exception e) {
counter.error();
LOG.error(e.getMessage(), e);
}
}
executors.shutdown();
}
@Override
public void stopHttpd() {
try {
if (serversocket != null) serversocket.close();
} catch (IOException e) {
LOG.error(e.getMessage(), e);
} finally {
if (executors != null) executors.shutdown();
}
}
@Override
public void restartHttpd() {
for (;;) {
if (counter.getActiveConnections() == 0) {
stopHttpd();
startHttpd();
break;
} else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* <p>Set the {@link SSLContextCreator},
* when customize the configration of https (SSL/TSL).
* When I did not set it, it is generated by a {@code createSecureServerSocket}.
* @param sslContextCreator
*/
public void setSSLContextCreator(SSLContextCreator sslContextCreator) {
this.sslContextCreator = sslContextCreator;
}
/**
* <p>Create the secure {@link ServerSocket}.
* @param port HTTPS listen port.
* @return created the {@link ServerSocket}
* @throws IOException
*/
protected ServerSocket createSecureServerSocket(int port) throws IOException {
if (sslContextCreator == null) {
sslContextCreator = new SSLContextCreator(serverConfig);
}
SSLContext ctx = sslContextCreator.getSSLContext();
return ctx.getServerSocketFactory().createServerSocket(port);
}
/**
* <p>Add the response interceptor.
* @param interceptor
* @since 0.5
*/
public void setHttpResponseInterceptor(HttpResponseInterceptor interceptor) {
interceptors.add(interceptor);
}
//install
//http://ws-jmx-connector.dev.java.net/files/documents/4956/114781/jsr262-ri.jar
//https://jax-ws.dev.java.net/2.1.1/JAXWS2.1.1_20070501.jar
public void registerMXServer() {
try {
//"service:jmx:rmi:///jndi/rmi://localhost/httpd";
//"ws", "localhost", 9999, "/admin"
String jmxUrl = serverConfig.getParam("JMX.server-url");
if (!isMXServerStarted && StringUtils.isNotEmpty(jmxUrl)) {
String name = serverConfig.getParam(
"JMX.objectname","org.tamacat.httpd:type=HttpEngine");
int rmiPort = serverConfig.getParam("JMX.rmi.port", -1);
if (rmiPort > 0) {
LocateRegistry.createRegistry(rmiPort);
}
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
objectName = new ObjectName(name);
server.registerMBean(this, objectName);
JMXConnectorServer sv = JMXConnectorServerFactory.newJMXConnectorServer(
new JMXServiceURL(jmxUrl), null, server);
sv.start();
isMXServerStarted = true;
}
counter.register();
} catch (Exception e) {
LOG.error(e.getMessage());
LOG.warn(ExceptionUtils.getStackTrace(e));
}
}
@Override
public void unregisterMXServer() {
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
try {
server.unregisterMBean(objectName);
} catch (Exception e) {
LOG.error(e.getMessage());
LOG.warn(ExceptionUtils.getStackTrace(e));
}
}
@Override
public void reload() {
init();
LOG.info("reloaded.");
}
@Override
public int getMaxServerThreads() {
return serverConfig.getMaxThreads();
}
@Override
public void setMaxServerThreads(int max) {
serverConfig.setParam("MaxServerThreads",String.valueOf(max));
}
@Override
public void run() {
startHttpd();
}
public String getPropertiesName() {
return propertiesName;
}
public void setPropertiesName(String propertiesName) {
this.propertiesName = propertiesName;
}
public ServerConfig getServerConfig() {
return serverConfig;
}
public void setServerConfig(ServerConfig serverConfig) {
this.serverConfig = serverConfig;
}
public void setClassLoader(ClassLoader loader) {
this.loader = loader;
}
public ClassLoader getClassLoader() {
if (loader == null) return Thread.currentThread().getContextClassLoader();
else return loader;
}
}