package ddth.dasp.hetty.front; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang3.exception.ExceptionUtils; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.handler.codec.http.HttpChunk; import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.codec.http.HttpResponseStatus; import org.jboss.netty.handler.timeout.IdleStateAwareChannelHandler; import org.jboss.netty.handler.timeout.IdleStateEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ddth.dasp.hetty.HettyConstants; import ddth.dasp.hetty.message.IMessageFactory; import ddth.dasp.hetty.message.IRequest; import ddth.dasp.hetty.qnt.IQueueWriter; import ddth.dasp.hetty.utils.HettyControlPanelHttp; import ddth.dasp.hetty.utils.HettyUtils; public class HettyHttpHandler extends IdleStateAwareChannelHandler { private static Logger LOGGER = LoggerFactory.getLogger(HettyHttpHandler.class); private HttpRequest currentRequest; private ByteArrayOutputStream currentRequestContent = new ByteArrayOutputStream(4096); private IQueueWriter queueWriter; private IMessageFactory messageFactory; public HettyHttpHandler(IQueueWriter queueWriter, IMessageFactory messageFactory) { this.queueWriter = queueWriter; this.messageFactory = messageFactory; } /** * {@inheritDoc} */ @Override public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { Channel channel = e.getChannel(); HettyUtils.ALL_CHANNELS.add(channel); Map<String, Object> channelLocalData = new ConcurrentHashMap<String, Object>(); HettyUtils.CHANNEL_LOCAL.set(channel, channelLocalData); HettyUtils.resetChannelLocalAttribute(channel); } protected void validateHttpChunk(HttpChunk httpChunk) { if (currentRequest == null || currentRequest.getContent() == null) { throw new IllegalStateException("No chunk started!"); } } private String lookupQueueName(String host, Object queueConfig) { if (queueConfig instanceof Map<?, ?>) { Object obj = ((Map<?, ?>) queueConfig).get(host); if (obj == null) { obj = ((Map<?, ?>) queueConfig).get("*"); } return lookupQueueName(host, obj); } else { return queueConfig != null ? queueConfig.toString() : null; } } protected void handleRequest(HttpRequest httpRequest, byte[] requestContent, Channel userChannel) throws Exception { String uri = httpRequest.getUri(); if (uri.startsWith("/hetty/") || uri.startsWith("/hetty?") || uri.equals("/hetty")) { IRequest request = messageFactory.buildRequest(httpRequest, userChannel.getId(), requestContent); HettyControlPanelHttp.handleRequest(request, requestContent, userChannel); } else { IRequest request = messageFactory.buildRequest(httpRequest, userChannel.getId(), requestContent); String host = request.getDomain(); String queueName = lookupQueueName(host, HettyConnServer.getHostQueueNameMapping()); if (queueName != null) { HettyUtils.setChannelLocalAttribute(userChannel, HettyConstants.CHA_REQUEST_ID, request.getId()); if (!queueWriter.queueWrite(queueName, request.serialize())) { HettyUtils.responseText(userChannel, HttpResponseStatus.BAD_GATEWAY, "No request queue for [" + host + "], or queue is full!"); } } else { HettyUtils.responseText(userChannel, HttpResponseStatus.BAD_REQUEST, "Host [" + host + "] is not mapped!"); } } } /** * {@inheritDoc} */ @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { Object message = e.getMessage(); boolean processMessage = false; if (message instanceof HttpChunk) { validateHttpChunk((HttpChunk) message); HttpChunk httpChunk = (HttpChunk) message; currentRequestContent.write(httpChunk.getContent().array()); ChannelBuffer compositeBuffer = ChannelBuffers.wrappedBuffer( currentRequest.getContent(), httpChunk.getContent()); currentRequest.setContent(compositeBuffer); processMessage = httpChunk.isLast(); } else if (message instanceof HttpRequest) { HttpRequest httpRequest = (HttpRequest) message; currentRequestContent.write(httpRequest.getContent().array()); currentRequest = httpRequest; processMessage = !currentRequest.isChunked(); } if (processMessage) { Channel channel = e.getChannel(); HettyUtils.setChannelLocalAttribute(channel, HettyConstants.CHA_HAS_REQUEST, Boolean.TRUE); try { handleRequest(currentRequest, currentRequestContent.toByteArray(), channel); } catch (Exception ex) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); try { ExceptionUtils.printRootCauseStackTrace(ex, pw); HettyUtils.responseText(channel, HttpResponseStatus.INTERNAL_SERVER_ERROR, sw.toString()); } finally { pw.close(); } } } } /** * {@inheritDoc} */ @Override public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e) { Channel channel = e.getChannel(); Map<String, Object> channelLocalData = HettyUtils.CHANNEL_LOCAL.get(channel); if (channelLocalData != null && !channelLocalData.containsKey(HettyConstants.CHA_ERROR_SENT)) { channelLocalData.put(HettyConstants.CHA_ERROR_SENT, Boolean.TRUE); String msg = "Timeout [" + e.getState() + "]: " + channel; HettyUtils.responseText(channel, HttpResponseStatus.GATEWAY_TIMEOUT, msg); if (LOGGER.isDebugEnabled()) { LOGGER.debug(msg); } } else { channel.close(); } } /** * {@inheritDoc} */ @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { Throwable t = e.getCause(); LOGGER.error(t.getMessage(), t); Channel channel = e.getChannel(); channel.close(); } }