package ddth.dasp.hetty.back;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ddth.dasp.common.DaspGlobal;
import ddth.dasp.common.RequestLocal;
import ddth.dasp.common.logging.ProfileLogger;
import ddth.dasp.common.osgi.IOsgiBootstrap;
import ddth.dasp.hetty.HettyConstants;
import ddth.dasp.hetty.IRequestActionHandler;
import ddth.dasp.hetty.message.IMessageFactory;
import ddth.dasp.hetty.message.IRequest;
import ddth.dasp.hetty.message.IRequestParser;
import ddth.dasp.hetty.message.IResponse;
import ddth.dasp.hetty.message.protobuf.ResponseUtils;
import ddth.dasp.hetty.qnt.IQueueReader;
import ddth.dasp.hetty.qnt.ITopicPublisher;
public class HettyRequestHandlerServer {
private final Logger LOGGER = LoggerFactory.getLogger(HettyRequestHandlerServer.class);
private IQueueReader queueReader;
private String queueName = HettyConstants.DEFAULT_HETTY_QUEUE;
private ITopicPublisher topicPublisher;
private String topicName = HettyConstants.DEFAULT_HETTY_TOPIC;
private long readTimeoutMillisecs = 5000, writeTimeoutMillisecs = 5000;
private IRequestParser requestParser;
private IMessageFactory messageFactory;
private int numWorkers = Runtime.getRuntime().availableProcessors();
private WorkerThread[] workerThreads;
public HettyRequestHandlerServer() {
}
protected IMessageFactory getMessageFactory() {
return messageFactory;
}
public HettyRequestHandlerServer setMessageFactory(IMessageFactory messageFactory) {
this.messageFactory = messageFactory;
return this;
}
protected IQueueReader getQueueReader() {
return queueReader;
}
public HettyRequestHandlerServer setQueueReader(IQueueReader queueReader) {
this.queueReader = queueReader;
return this;
}
protected ITopicPublisher getTopicPublisher() {
return topicPublisher;
}
public HettyRequestHandlerServer setTopicPublisher(ITopicPublisher topicPublisher) {
this.topicPublisher = topicPublisher;
return this;
}
protected String getQueueName() {
return queueName;
}
public HettyRequestHandlerServer setQueueName(String queueName) {
this.queueName = queueName;
return this;
}
protected String getTopicName() {
return topicName;
}
public HettyRequestHandlerServer setTopicName(String topicName) {
this.topicName = topicName;
return this;
}
public long getReadTimeoutMillisecs() {
return readTimeoutMillisecs;
}
public HettyRequestHandlerServer setReadTimeoutMillisecs(long readTimeoutMillisecs) {
this.readTimeoutMillisecs = readTimeoutMillisecs;
return this;
}
public long getWriteTimeoutMillisecs() {
return writeTimeoutMillisecs;
}
public HettyRequestHandlerServer setWriteTimeoutMillisecs(long writeTimeoutMillisecs) {
this.writeTimeoutMillisecs = writeTimeoutMillisecs;
return this;
}
public IRequestParser getRequestParser() {
return requestParser;
}
public HettyRequestHandlerServer setRequestParser(IRequestParser requestParser) {
this.requestParser = requestParser;
return this;
}
public int getNumWorkers() {
return numWorkers;
}
public HettyRequestHandlerServer setNumWorkers(int numWorkers) {
this.numWorkers = numWorkers;
return this;
}
protected void handleRequest(IRequest request) throws Exception {
long queueTimeMs = (long) ((System.nanoTime() - request.getTimestampNano()) / 1E6);
if (queueTimeMs > 10000) {
// in queue more than 10 secs
String msg = "Request [" + request.getId() + ":" + request.getUri()
+ "] has stayed in queue too long [" + queueTimeMs + " ms]!";
IResponse response = ResponseUtils.newResponse(request).setStatus(408)
.addHeader("Content-Type", "text/html; charset=UTF-8").setContent(msg);
topicPublisher.publish(topicName, response, writeTimeoutMillisecs,
TimeUnit.MILLISECONDS);
LOGGER.warn(msg);
} else {
internalHandleRequest(request);
}
}
/**
* Actually handles the request. Called by {@link #handleRequest(IRequest)},
* sub-class may override this method to implement its own logic.
*
* @param request
* @throws Exception
*/
protected void internalHandleRequest(IRequest request) throws Exception {
String module = requestParser.getModule(request);
String action = requestParser.getAction(request);
Map<String, String> filter = new HashMap<String, String>();
filter.put(IRequestActionHandler.FILTER_KEY_MODULE, !StringUtils.isBlank(module) ? module
: "_");
filter.put(IRequestActionHandler.FILTER_KEY_ACTION, !StringUtils.isBlank(action) ? action
: "_");
IOsgiBootstrap osgiBootstrap = DaspGlobal.getOsgiBootstrap();
IRequestActionHandler handler = osgiBootstrap.getService(IRequestActionHandler.class,
filter);
if (handler == null) {
// fallback 1: lookup for wildcard handler
// note: do not use "*" for filtering as OSGi will match "any"
// service, which is not what we want!
filter.put(IRequestActionHandler.FILTER_KEY_ACTION, "_");
handler = osgiBootstrap.getService(IRequestActionHandler.class, filter);
}
if (handler == null) {
// fallback 2: lookup for non-action handler
filter.remove(IRequestActionHandler.FILTER_KEY_ACTION);
handler = osgiBootstrap.getService(IRequestActionHandler.class, filter);
}
if (handler != null) {
handler.handleRequest(request, topicPublisher, topicName);
} else {
IResponse response = ResponseUtils.response404(request);
topicPublisher.publish(topicName, response, writeTimeoutMillisecs,
TimeUnit.MILLISECONDS);
}
}
public void destroy() {
for (WorkerThread workerThread : workerThreads) {
try {
workerThread.finish();
} catch (Exception e) {
}
}
}
public void start() {
workerThreads = new WorkerThread[numWorkers];
for (int i = 1; i <= numWorkers; i++) {
WorkerThread t = new WorkerThread(this, queueReader, queueName, messageFactory,
topicPublisher, topicName, readTimeoutMillisecs);
workerThreads[i - 1] = t;
t.start();
}
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Hetty request handler workers: " + numWorkers + " / Read timeout: "
+ readTimeoutMillisecs + " / Write timeout: " + writeTimeoutMillisecs);
}
}
private static class WorkerThread extends Thread {
private Logger LOGGER = LoggerFactory.getLogger(WorkerThread.class);
private static int COUNTER = 1;
private HettyRequestHandlerServer requestHandler;
private IQueueReader queueReader;
private String queueName;
private IMessageFactory messageFactory;
private ITopicPublisher topicPublisher;
private String topicName;
private long readTimeoutMillisecs;
private boolean done = false;
public WorkerThread(HettyRequestHandlerServer requestHandler, IQueueReader queueReader,
String queueName, IMessageFactory messageFactory, ITopicPublisher topicPublisher,
String topicName, long readTimeoutMillisecs) {
setName("HRH-Worker/" + queueName + "/" + topicName + "/" + (COUNTER++));
setDaemon(true);
this.requestHandler = requestHandler;
this.queueReader = queueReader;
this.queueName = queueName;
this.messageFactory = messageFactory;
this.topicPublisher = topicPublisher;
this.topicName = topicName;
this.readTimeoutMillisecs = readTimeoutMillisecs;
}
private Object poll() {
Object obj = null;
try {
obj = queueReader.queueRead(queueName, readTimeoutMillisecs, TimeUnit.MILLISECONDS);
} catch (Exception e) {
obj = null;
}
if (obj == null) {
try {
Thread.sleep(1);
// Thread.sleep(System.currentTimeMillis() & 0x7);
Thread.yield();
} catch (Exception e) {
}
}
return obj;
}
private RequestLocal initRequestLocal() {
RequestLocal requestLocal = RequestLocal.get();
if (requestLocal == null) {
requestLocal = new RequestLocal();
RequestLocal.set(requestLocal);
}
return requestLocal;
}
private void doneRequestLocal() {
try {
RequestLocal.remove();
} catch (Exception e) {
}
}
private Object deserialize(Object obj) {
try {
if (obj instanceof byte[]) {
ProfileLogger.push("deserialize_request");
try {
obj = messageFactory.deserializeRequest((byte[]) obj);
} finally {
ProfileLogger.pop();
}
}
return obj;
} catch (Exception e) {
LOGGER.warn(((byte[]) obj).length + ": " + e.getMessage());
return null;
}
}
public void finish() {
done = true;
interrupt();
}
public void run() {
while (!done && !interrupted()) {
Object obj = poll();
initRequestLocal();
try {
ProfileLogger.push("start_request_handler_worker");
try {
obj = deserialize(obj);
if (obj instanceof IRequest) {
requestHandler.handleRequest((IRequest) obj);
}
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
if (obj instanceof IRequest) {
try {
IResponse response = ResponseUtils.response500((IRequest) obj,
e.getMessage(), e);
topicPublisher.publish(topicName, response);
} catch (Exception ex) {
LOGGER.error(ex.getMessage(), ex);
}
}
} finally {
ProfileLogger.pop();
}
} finally {
doneRequestLocal();
}
}
}
}
}