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 static org.jboss.netty.handler.codec.http.HttpHeaders.isKeepAlive;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.COOKIE;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SET_COOKIE;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.OK;
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.handler.codec.http.Cookie;
import org.jboss.netty.handler.codec.http.CookieDecoder;
import org.jboss.netty.handler.codec.http.CookieEncoder;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import com.linkedin.databus.core.DbusConstants;
import com.linkedin.databus.core.data_model.PhysicalPartition;
import com.linkedin.databus.core.util.DbusHttpUtils;
import com.linkedin.databus2.core.container.DatabusHttpHeaders;
import com.linkedin.databus2.core.container.monitoring.mbean.ContainerStatisticsCollector;
import com.linkedin.databus2.core.container.request.DatabusRequest;
import com.linkedin.databus2.core.container.request.RequestProcessorRegistry;
/**
* Expects DatabusRequest objects and runs them
* @author cbotev
*
*/
public class DatabusRequestExecutionHandler extends SimpleChannelUpstreamHandler
{
public static final String MODULE = DatabusRequestExecutionHandler.class.getName();
public static final Logger LOG = Logger.getLogger(MODULE);
private final RequestProcessorRegistry _processorRegistry;
private final ServerContainer _serverContainer;
private DatabusRequest _dbusRequest;
private HttpRequest _httpRequest;
public DatabusRequestExecutionHandler(ServerContainer serverContainer)
{
_serverContainer = serverContainer;
_processorRegistry = _serverContainer.getProcessorRegistry();
}
@Override
public void channelClosed(
ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
super.channelClosed(ctx, e);
ctx.setAttachment(null);
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception
{
if (e.getMessage() instanceof HttpRequest)
{
_httpRequest = (HttpRequest)e.getMessage();
ctx.sendUpstream(e);
}
if (e.getMessage() instanceof DatabusRequest)
{
_dbusRequest = (DatabusRequest)e.getMessage();
// If there is a physical partition stashed away, then restore it into the request now.
if (ctx.getAttachment() != null && ctx.getAttachment() instanceof PhysicalPartition)
{
_dbusRequest.setCursorPartition((PhysicalPartition)(ctx.getAttachment()));
}
//FIXME DDS-305: Rework the netty stats collector to use event-based stats aggregation
/*NettyStats nettyStats = _configManager.getNettyStats();
boolean nettyStatsEnabled = nettyStats.isEnabled();
CallCompletion callCompletion = nettyStatsEnabled ?
nettyStats.getRequestHandler_writeResponse().startCall() :
null;
CallCompletion processRequestCompletion = null;*/
try
{
if (LOG.isDebugEnabled())
{
LOG.debug("Creating response for command [" + _dbusRequest.getId() + "] " +
_dbusRequest.getName());
}
// Decide whether to close the connection or not.
boolean keepAlive = isKeepAlive(_httpRequest);
HttpResponse response = generateEmptyResponse();
if (LOG.isDebugEnabled())
{
//We are debugging -- let's add some more info to the response
response.addHeader(DatabusHttpHeaders.DATABUS_REQUEST_ID_HEADER,
Long.toString(_dbusRequest.getId()));
}
// Write the response.
ChunkedBodyWritableByteChannel responseChannel = null;
try
{
responseChannel = new ChunkedBodyWritableByteChannel(e.getChannel(), response);
_dbusRequest.setResponseContent(responseChannel);
if (LOG.isDebugEnabled())
{
LOG.debug("About to run command [" + _dbusRequest.getId() + "] " + _dbusRequest.getName());
}
//FIXME DDS-305: Rework the netty stats collector to use event-based stats aggregation
/*if (nettyStatsEnabled)
{
processRequestCompletion = nettyStats.getRequestHandler_processRequest().startCall();
}*/
Future<DatabusRequest> responseFuture = _processorRegistry.run(_dbusRequest);
ServerContainer.RuntimeConfig config = _dbusRequest.getConfig();
int timeoutMs = config.getRequestProcessingBudgetMs();
boolean done = responseFuture.isDone();
while (!done)
{
try
{
responseFuture.get(timeoutMs, TimeUnit.MILLISECONDS);
done = true;
ctx.setAttachment(_dbusRequest.getCursorPartition());
}
catch (InterruptedException ie)
{
done = responseFuture.isDone();
}
catch (Exception ex)
{
done = true;
_dbusRequest.setError(ex);
// On any error, clear any context saved. We will start afresh in a new request.
ctx.setAttachment(null);
//FIXME DDS-305: Rework the netty stats collector to use event-based stats aggregation
/*if (null != processRequestCompletion)
{
processRequestCompletion.endCallWithError(ex);
processRequestCompletion = null;
}*/
}
}
}
finally
{
if (null != responseChannel)
{
if (LOG.isDebugEnabled())
{
//Add some more debugging info
long curTimeMs = System.currentTimeMillis();
responseChannel.addMetadata(DatabusHttpHeaders.DATABUS_REQUEST_LATENCY_HEADER,
Long.toString(curTimeMs - _dbusRequest.getCreateTimestampMs()));
}
responseChannel.close();
}
if (null != _dbusRequest.getResponseThrowable())
{
ContainerStatisticsCollector statsCollector = _serverContainer.getContainerStatsCollector();
if (null != statsCollector)
{
statsCollector.registerContainerError(_dbusRequest.getResponseThrowable());
}
}
}
//FIXME DDS-305: Rework the netty stats collector to use event-based stats aggregation
/*if (null != processRequestCompletion)
{
processRequestCompletion.endCall();
}*/
if (LOG.isDebugEnabled())
{
LOG.debug("Done runing command [" + _dbusRequest.getId() + "] " + _dbusRequest.getName());
}
// Close the non-keep-alive or hard-failed connection after the write operation is done.
if (!keepAlive || null == responseChannel)
{
e.getChannel().close();
}
//FIXME DDS-305: Rework the netty stats collector to use event-based stats aggregation
/*if (null != callCompletion)
{
callCompletion.endCall();
}*/
}
catch (RuntimeException ex)
{
LOG.error("HttpRequestHandler.writeResponse error", ex);
//FIXME DDS-305: Rework the netty stats collector to use event-based stats aggregation
/*if (null != callCompletion)
{
callCompletion.endCallWithError(ex);
}*/
ContainerStatisticsCollector statsCollector = _serverContainer.getContainerStatsCollector();
if (null != statsCollector) statsCollector.registerContainerError(ex);
}
}
else
{
//Pass on everything else
ctx.sendUpstream(e);
}
}
private HttpResponse generateEmptyResponse()
{
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
//response.setContent(ChannelBuffers.wrappedBuffer(responseBody));
response.setHeader(CONTENT_TYPE, "text/plain; charset=UTF-8");
response.setHeader("Access-Control-Allow-Origin", "*");
setTrackingInfo(response);
// Encode the cookie.
String cookieString = _httpRequest.getHeader(COOKIE);
if (cookieString != null)
{
CookieDecoder cookieDecoder = new CookieDecoder();
Set<Cookie> cookies = cookieDecoder.decode(cookieString);
if(!cookies.isEmpty()) {
// Reset the cookies if necessary.
CookieEncoder cookieEncoder = new CookieEncoder(true);
for (Cookie cookie : cookies) {
cookieEncoder.addCookie(cookie);
}
response.addHeader(SET_COOKIE, cookieEncoder.encode());
}
}
return response;
}
/**
* Set headers to allow clients to trace information of relays across VIPs
* @param response
*/
private void setTrackingInfo(HttpResponse response)
{
String hostname = DbusConstants.getMachineName();
if (null == hostname || DbusConstants.UNKNOWN_HOST.equals(hostname))
{
hostname = DbusHttpUtils.getLocalHostName();
}
response.setHeader(DatabusHttpHeaders.DBUS_SERVER_HOST_HDR, hostname);
String serviceId = DbusConstants.getServiceIdentifier();
if (! DbusConstants.UNKNOWN_SERVICE_ID.equals(serviceId))
{
response.setHeader(DatabusHttpHeaders.DBUS_SERVER_SERVICE_HDR,
DbusConstants.getServiceIdentifier());
}
}
}