package com.linkedin.databus2.core.container.netty; /* * * Copyright 2013 LinkedIn Corp. All rights reserved * * 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. * */ import java.io.IOException; import java.nio.channels.ClosedChannelException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import org.apache.log4j.Logger; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelUpstreamHandler; import org.jboss.netty.channel.UpstreamMessageEvent; import org.jboss.netty.handler.codec.http.HttpChunk; import org.jboss.netty.handler.codec.http.HttpChunkTrailer; import org.jboss.netty.handler.codec.http.HttpMethod; import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.codec.http.QueryStringDecoder; import com.linkedin.databus2.core.container.DatabusHttpHeaders; import com.linkedin.databus2.core.container.ExtendedReadTimeoutHandler; import com.linkedin.databus2.core.container.request.DatabusRequest; /** * */ public class HttpRequestHandler extends SimpleChannelUpstreamHandler { public static final String MODULE = HttpRequestHandler.class.getName(); public static final Logger LOG = Logger.getLogger(MODULE); private final ServerContainer _serverContainer; private final ExtendedReadTimeoutHandler _readTimeoutHandler; private HttpRequest request; private boolean readingChunks = false; private DatabusRequest dbusRequest; private ArrayList<ChannelBuffer> body = new ArrayList<ChannelBuffer>(); public HttpRequestHandler(ServerContainer serverContainer, ExtendedReadTimeoutHandler readTimeoutHandler) { super(); _serverContainer = serverContainer; _readTimeoutHandler = readTimeoutHandler; } @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { //FIXME DDS-305: Rework the netty stats collector to use event-based stats aggregation /*NettyStats nettyStats = _configManager.getNettyStats(); CallCompletion callCompletion = nettyStats.isEnabled() ? nettyStats.getRequestHandler_messageRecieved().startCall() : null;*/ try { if (!readingChunks) { request = (HttpRequest) e.getMessage(); ctx.sendUpstream(e); QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.getUri()); String queryPath = queryStringDecoder.getPath(); int slashPos = queryPath.indexOf('/', 1); if (slashPos < 0) { slashPos = queryPath.length(); } String cmdName = queryPath.substring(1, slashPos); ServerContainer.RuntimeConfig config = _serverContainer.getContainerRuntimeConfigMgr() .getReadOnlyConfig(); if (LOG.isDebugEnabled()) { LOG.debug("Got command: " + cmdName); } dbusRequest = new DatabusRequest(cmdName, request.getMethod(), e.getRemoteAddress(), config); if (LOG.isDebugEnabled()) { LOG.debug("Starting processing command [" + dbusRequest.getId() + "] " + dbusRequest.getName() ); } Properties requestProps = dbusRequest.getParams(); if (slashPos < queryPath.length()) { requestProps.put(DatabusRequest.PATH_PARAM_NAME, queryPath.substring(slashPos + 1)); } for (Map.Entry<String, String> h: request.getHeaders()) { handleHttpHeader(h); } Map<String, List<String>> params = queryStringDecoder.getParameters(); if (!params.isEmpty()) { for (Entry<String, List<String>> p: params.entrySet()) { String key = p.getKey(); List<String> vals = p.getValue(); if (vals.size() == 1) { requestProps.put(key, vals.get(0)); } else { requestProps.put(key, vals); } for (String val : vals) { LOG.trace("PARAM: " + key + " = " + val); } } } if (request.isChunked()) { if (null != _readTimeoutHandler) { readingChunks = true; _readTimeoutHandler.start(ctx.getPipeline().getContext(_readTimeoutHandler)); } } else { ChannelBuffer content = request.getContent(); handleRequestContentChunk(content); writeResponse(ctx, e); } } else if (e.getMessage() instanceof HttpChunk) { HttpChunk chunk = (HttpChunk) e.getMessage(); if (chunk.isLast()) { readingChunks = false; LOG.trace("END OF CONTENT"); HttpChunkTrailer trailer = (HttpChunkTrailer) chunk; for (Map.Entry<String, String> h: trailer.getHeaders()) { handleHttpHeader(h); } writeResponse(ctx, e); } else { ChannelBuffer content = chunk.getContent(); handleRequestContentChunk(content); } } ctx.sendUpstream(e); //FIXME DDS-305: Rework the netty stats collector to use event-based stats aggregation /*if (null != callCompletion) { callCompletion.endCall(); }*/ } catch (Exception ex) { LOG.error("HttpRequestHandler.messageReceived error", ex); //FIXME DDS-305: Rework the netty stats collector to use event-based stats aggregation /*if (null != callCompletion) { callCompletion.endCallWithError(ex); }*/ } } private void writeResponse(ChannelHandlerContext ctx, MessageEvent e) { try { if (HttpMethod.POST != dbusRequest.getRequestType()) { dbusRequest.getParams().put(DatabusRequest.DATA_PARAM_NAME, body); } else { //FIXME -- parse the body } //done with processing the request -- turn off the read time out if (null != _readTimeoutHandler && _readTimeoutHandler.isStarted()) { _readTimeoutHandler.stop(); } ctx.sendUpstream(new UpstreamMessageEvent(e.getChannel(), dbusRequest, e.getRemoteAddress())); } catch (Exception ex) { LOG.error("HttpRequestHandler.writeResponse error", ex); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { Throwable cause = e.getCause(); if (cause instanceof OutOfMemoryError) { LOG.error("Running out of memory. Initiating a shutdown on the server container"); _serverContainer.shutdownAsynchronously(); } boolean logError = true; if (cause instanceof ClosedChannelException) { //ignore logError = false; } else if (cause instanceof IOException) { logError = ! (cause.getMessage().contains("Connection reset by peer")); } if (logError) { LOG.error("exception detected: " + cause.getMessage(), cause); } if (e.getChannel().isOpen()) e.getChannel().close(); } private void handleRequestContentChunk(ChannelBuffer chunk) { if (chunk.readable()) { body.add(chunk); if (LOG.isTraceEnabled()) { LOG.trace("CHUNK: " + chunk.readableBytes()); } } } private void handleHttpHeader(Map.Entry<String, String> h) { Properties requestProps = dbusRequest.getParams(); String headerKey = h.getKey().toLowerCase(); LOG.trace("HEADER: " + h.getKey() + " = " + h.getValue()); if (headerKey.startsWith(DatabusHttpHeaders.DATABUS_HTTP_HEADER_PREFIX)) { String headerParamName = headerKey.substring(DatabusHttpHeaders.DATABUS_HTTP_HEADER_PREFIX.length()); requestProps.put(headerParamName, h.getValue()); } } }