package com.taobao.top.analysis.node.monitor;
import java.io.File;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.StringWriter;
import java.io.Writer;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.HttpStatus;
import org.apache.http.MethodNotSupportedException;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.nio.DefaultHttpServerIODispatch;
import org.apache.http.impl.nio.DefaultNHttpServerConnection;
import org.apache.http.impl.nio.DefaultNHttpServerConnectionFactory;
import org.apache.http.impl.nio.reactor.DefaultListeningIOReactor;
import org.apache.http.nio.NHttpConnection;
import org.apache.http.nio.NHttpConnectionFactory;
import org.apache.http.nio.NHttpServerConnection;
import org.apache.http.nio.entity.NFileEntity;
import org.apache.http.nio.entity.NStringEntity;
import org.apache.http.nio.protocol.BasicAsyncRequestConsumer;
import org.apache.http.nio.protocol.BasicAsyncResponseProducer;
import org.apache.http.nio.protocol.HttpAsyncExchange;
import org.apache.http.nio.protocol.HttpAsyncRequestConsumer;
import org.apache.http.nio.protocol.HttpAsyncRequestHandler;
import org.apache.http.nio.protocol.HttpAsyncRequestHandlerRegistry;
import org.apache.http.nio.protocol.HttpAsyncService;
import org.apache.http.nio.reactor.IOEventDispatch;
import org.apache.http.nio.reactor.ListeningIOReactor;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.params.SyncBasicHttpParams;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.ImmutableHttpProcessor;
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.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.runtime.resource.ResourceManagerImpl;
import com.taobao.top.analysis.config.MasterConfig;
import com.taobao.top.analysis.exception.AnalysisException;
import com.taobao.top.analysis.node.IComponent;
import com.taobao.top.analysis.node.component.MasterMonitor;
/**
* 使用apache http components的一个简单HttpServer
* @author sihai
*
*/
public class NHttpServer implements IComponent<MasterConfig>{
private static final Log logger = LogFactory.getLog(NHttpServer.class);
/**
* Master端配置
*/
private MasterConfig config;
/**
* Master的监控组件, 提供监控数据
*/
private MasterMonitor monitor;
/**
* 模板编码
*/
private String templateEncode = "utf-8";
/**
* 模板缓存
*/
private ConcurrentHashMap<String, Template> templateCache;
/**
*
*/
private boolean velocityOK = false;
/**
*
*/
private ListeningIOReactor ioReactor;
@Override
public MasterConfig getConfig() {
return config;
}
@Override
public void init() throws AnalysisException {
//
templateCache = new ConcurrentHashMap<String, Template>();
// 初始化模板引擎
try {
Velocity.setProperty(Velocity.RESOURCE_MANAGER_CLASS, ResourceManagerImpl.class.getName());
Velocity.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, config.getMonitorDocRoot());
Velocity.setProperty(Velocity.INPUT_ENCODING, "utf-8");
Velocity.setProperty(Velocity.OUTPUT_ENCODING, "utf-8");
Velocity.init();
velocityOK = true;
} catch (Exception e) {
velocityOK = false;
logger.error("Exception: init velocity:", e);
}
// init the server
// HTTP parameters for the server
HttpParams params = new SyncBasicHttpParams();
params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000)
.setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
.setParameter(CoreProtocolPNames.ORIGIN_SERVER, "HttpTest/1.1");
// Create HTTP protocol processing chain
HttpProcessor httpproc = new ImmutableHttpProcessor(new HttpResponseInterceptor[] {
// Use standard server-side protocol interceptors
new ResponseDate(),
new ResponseServer(),
new ResponseContent(),
new ResponseConnControl()
});
// Create request handler registry
HttpAsyncRequestHandlerRegistry reqistry = new HttpAsyncRequestHandlerRegistry();
// Register the default handler for all URIs
reqistry.register("*", new HttpFileHandler(new File(config.getMonitorDocRoot())));
// Create server-side HTTP protocol handler
HttpAsyncService protocolHandler = new HttpAsyncService(
httpproc, new DefaultConnectionReuseStrategy(), reqistry, params) {
@Override
public void connected(final NHttpServerConnection conn) {
System.out.println(conn + ": connection open");
super.connected(conn);
}
@Override
public void closed(final NHttpServerConnection conn) {
System.out.println(conn + ": connection closed");
super.closed(conn);
}
};
// Create HTTP connection factory
NHttpConnectionFactory<DefaultNHttpServerConnection> connFactory;
connFactory = new DefaultNHttpServerConnectionFactory(params);
// Create server-side I/O event dispatch
final IOEventDispatch ioEventDispatch = new DefaultHttpServerIODispatch(protocolHandler, connFactory);
// Create server-side I/O reactor
new Thread(new Runnable(){
@Override
public void run() {
try {
ioReactor = new DefaultListeningIOReactor();
// Listen of the given port
ioReactor.listen(new InetSocketAddress(config.getMonitorPort()));
// Ready to go!
ioReactor.execute(ioEventDispatch);
} catch (InterruptedIOException ex) {
System.err.println("Interrupted");
logger.error("NHttpServer failed", ex);
} catch (IOException e) {
System.err.println("I/O error: " + e.getMessage());
logger.error("NHttpServer failed", e);
}
}
}, "NHttp-Server-Thread").start();
}
@Override
public void releaseResource() {
templateCache.clear();
if(ioReactor != null) {
try {
ioReactor.shutdown();
} catch (IOException e) {
logger.error(e);
}
}
}
@Override
public void setConfig(MasterConfig config) {
this.config = config;
}
public MasterMonitor getMonitor() {
return monitor;
}
public void setMonitor(MasterMonitor monitor) {
this.monitor = monitor;
}
/**
*
* @author sihai
*
*/
private class HttpFileHandler implements HttpAsyncRequestHandler<HttpRequest> {
private final File docRoot;
public HttpFileHandler(final File docRoot) {
super();
this.docRoot = docRoot;
}
public HttpAsyncRequestConsumer<HttpRequest> processRequest(
final HttpRequest request,
final HttpContext context) {
// Buffer request content in memory for simplicity
return new BasicAsyncRequestConsumer();
}
public void handle(
final HttpRequest request,
final HttpAsyncExchange httpexchange,
final HttpContext context) throws HttpException, IOException {
HttpResponse response = httpexchange.getResponse();
handleInternal(request, response, context);
httpexchange.submitResponse(new BasicAsyncResponseProducer(response));
}
private void handleInternal(
final HttpRequest request,
final HttpResponse response,
final HttpContext context) throws HttpException, IOException {
String method = request.getRequestLine().getMethod().toUpperCase(Locale.ENGLISH);
if (!method.equals("GET") && !method.equals("HEAD") && !method.equals("POST")) {
throw new MethodNotSupportedException(method + " method not supported");
}
String target = URLDecoder.decode(request.getRequestLine().getUri(), "UTF-8");
if(target.endsWith(".jhtml")) {
if(velocityOK) {
Template template = templateCache.get(target);
if(template == null) {
try {
String vmFileName = target.replace(".jhtml", ".vm").replace("/", "\\").substring(1);
final File file = new File(this.docRoot, target.replace(".jhtml", ".vm"));
if(!file.exists()) {
response.setStatusCode(HttpStatus.SC_NOT_FOUND);
NStringEntity entity = new NStringEntity(
"<html><body><h1>File" + file.getPath() +
" not found</h1></body></html>",
ContentType.create("text/html", "UTF-8"));
response.setEntity(entity);
System.out.println("File " + file.getPath() + " not found");
} else {
template = Velocity.getTemplate(vmFileName, templateEncode);
//
templateCache.putIfAbsent(target, template);
}
} catch (Exception e) {
logger.error("Exception: get template", e);
}
}
// 渲染VM
VelocityContext cxt = new VelocityContext();
monitor.getData(cxt);
Writer writer = new StringWriter();
template.merge(cxt, writer);
response.setStatusCode(HttpStatus.SC_OK);
NStringEntity entity = new NStringEntity(writer.toString(), ContentType.create("text/html", "UTF-8"));
response.setEntity(entity);
} else {
response.setStatusCode(HttpStatus.SC_NOT_FOUND);
NStringEntity entity = new NStringEntity("Velocity not init succeed",
ContentType.create("text/html", "UTF-8"));
response.setEntity(entity);
System.out.println("Velocity not init succeed");
}
} else {
final File file = new File(this.docRoot, target);
if (!file.exists()) {
response.setStatusCode(HttpStatus.SC_NOT_FOUND);
NStringEntity entity = new NStringEntity(
"<html><body><h1>File" + file.getPath() +
" not found</h1></body></html>",
ContentType.create("text/html", "UTF-8"));
response.setEntity(entity);
System.out.println("File " + file.getPath() + " not found");
} else if (!file.canRead() || file.isDirectory()) {
response.setStatusCode(HttpStatus.SC_FORBIDDEN);
NStringEntity entity = new NStringEntity(
"<html><body><h1>Access denied</h1></body></html>",
ContentType.create("text/html", "UTF-8"));
response.setEntity(entity);
System.out.println("Cannot read file " + file.getPath());
} else {
NHttpConnection conn = (NHttpConnection) context.getAttribute(
ExecutionContext.HTTP_CONNECTION);
response.setStatusCode(HttpStatus.SC_OK);
NFileEntity body = new NFileEntity(file, ContentType.create("text/html"));
response.setEntity(body);
System.out.println(conn + ": serving file " + file.getPath());
}
}
}
}
}