package com.linkedin.databus.client.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.StringWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.codehaus.jackson.map.ObjectMapper;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.DefaultChannelPipeline;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpContentCompressor;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.HttpVersion;
import org.jboss.netty.util.CharsetUtil;
import org.jboss.netty.util.HashedWheelTimer;
import org.jboss.netty.util.Timer;
import org.testng.Assert;
import org.testng.annotations.Test;
import com.linkedin.databus.client.ChunkedBodyReadableByteChannel;
import com.linkedin.databus.client.DatabusHttpClientImpl;
import com.linkedin.databus.client.pub.ServerInfo;
import com.linkedin.databus.core.BufferInfoResponse;
import com.linkedin.databus.core.util.NamedThreadFactory;
import com.linkedin.databus.core.util.Utils;
import com.linkedin.databus2.core.container.ExtendedReadTimeoutHandler;
import com.linkedin.databus2.core.container.monitoring.mbean.ContainerStatisticsCollector;
import com.linkedin.databus2.test.TestUtil;
public class TestClientChannelClose
{
static
{
TestUtil.setupLoggingWithTimestampedFile(true, "/tmp/TestClientChannelClose_", ".log", Level.ERROR);
}
public static enum MsgState
{
OPERATION_COMPLETE,
CHANNEL_EXCEPTION_DECORATOR,
CHANNEL_EXCEPTION_NO_DECORATOR,
MSG_ENQUEUED,
REQUEST_FAILURE,
RESPONSE_FAILURE,
}
@Test
public void testTimeoutCase() throws Exception
{
int port = Utils.getAvailablePort(27781);
FakeRelay relay = new FakeRelay(port, 10, true , 100);
TestClientConnectionFactory factory = null;
try
{
relay.start();
List<MsgState> msgList = new ArrayList<MsgState>();
DatabusHttpClientImpl.Config conf = new DatabusHttpClientImpl.Config();
conf.getContainer().setReadTimeoutMs(54);
factory = new TestClientConnectionFactory(conf.build());
ServerInfo relayInfo = new ServerInfo("dummy", "dummy", new InetSocketAddress(InetAddress.getLocalHost(), port), "dummy");
for (int i = 0 ; i < 10 ; i++)
{
TestClientConnection conn = factory.createConnection(relayInfo, msgList);
conn.requestCall();
Thread.sleep(1000);
System.out.println("MsgList is :" + msgList);
//Assert.assertEquals(msgList.size(), 4, "Expected Size Check");
int numMsgEnqueued = 0;
for (MsgState s : msgList)
{
if (s == MsgState.MSG_ENQUEUED)
numMsgEnqueued++;
}
Assert.assertEquals(numMsgEnqueued, 1, "Expected NumEnqueued Msgs");
msgList.clear();
}
} finally {
if ( null != relay )
relay.shutdown();
if (null != factory)
factory.stop();
}
}
public static class TestClientConnectionFactory
{
private final ExecutorService _bossThreadPool;
private final ExecutorService _ioThreadPool;
private final Timer _timeoutTimer;
private final DatabusHttpClientImpl.StaticConfig _clientConfig;
private final ChannelFactory _channelFactory;
private final ChannelGroup _channelGroup; //provides automatic channel closure on shutdown
public TestClientConnectionFactory(
DatabusHttpClientImpl.StaticConfig clientConfig)
{
_bossThreadPool = Executors.newCachedThreadPool(new NamedThreadFactory("boss"));;
_ioThreadPool = Executors.newCachedThreadPool(new NamedThreadFactory("io"));;
_timeoutTimer = new HashedWheelTimer(5, TimeUnit.MILLISECONDS);
_clientConfig = clientConfig;
_channelFactory = new NioClientSocketChannelFactory(_bossThreadPool, _ioThreadPool);
_channelGroup = new DefaultChannelGroup();;
}
public TestClientConnection createConnection(ServerInfo relay, List<MsgState> msgList)
{
return new TestClientConnection(relay, new ClientBootstrap(_channelFactory), null, _timeoutTimer,
_clientConfig, _channelGroup, msgList,
15000, Logger.getLogger(TestClientConnectionFactory.class));
}
public void stop()
throws InterruptedException
{
_channelGroup.close().await();
_timeoutTimer.stop();
}
}
public static class TestHttpResponseProcessor
extends AbstractHttpResponseProcessorDecorator <ChunkedBodyReadableByteChannel>
{
private List<MsgState> msgList;
private final ExtendedReadTimeoutHandler _readTimeOutHandler;
public TestHttpResponseProcessor(List<MsgState> msgList, ExtendedReadTimeoutHandler readTimeOutHandler)
{
super(new ChunkedBodyReadableByteChannel());
this.msgList = msgList;
this._readTimeOutHandler = readTimeOutHandler;
}
@Override
public void finishResponse() throws Exception
{
super.finishResponse();
System.out.println("finished response for /test");
if (null != _readTimeOutHandler) _readTimeOutHandler.stop();
}
@Override
public void startResponse(HttpResponse response) throws Exception
{
LOG.info("Start Response !!");
try
{
Thread.sleep(200);
} catch (InterruptedException ie) {}
super.startResponse(response);
msgList.add(MsgState.MSG_ENQUEUED);
}
@Override
public void handleChannelException(Throwable cause)
{
if ((_responseStatus != ResponseStatus.CHUNKS_SEEN) &&
(_responseStatus != ResponseStatus.CHUNKS_FINISHED))
{
LOG.info("CHannel Exception : Message Enqueued !!");
msgList.add(MsgState.MSG_ENQUEUED);
}
if (null != _decorated)
{
System.out.println("Response Status is :" + _responseStatus);
msgList.add(MsgState.CHANNEL_EXCEPTION_DECORATOR);
_decorated.channelException(cause);
}
else
{
msgList.add(MsgState.CHANNEL_EXCEPTION_NO_DECORATOR);
LOG.error("channel exception but no decorated object:" + cause.getClass() + ":" +
cause.getMessage());
if (LOG.isDebugEnabled()) LOG.error(cause);
}
}
}
public static class TestClientConnection extends AbstractNettyHttpConnection
{
private ExtendedReadTimeoutHandler _readTimeOutHandler;
private final List<MsgState> _msgList;
public TestClientConnection(ServerInfo relay,
ClientBootstrap bootstrap,
ContainerStatisticsCollector containerStatsCollector,
Timer timeoutTimer,
DatabusHttpClientImpl.StaticConfig clientConfig,
ChannelGroup channelGroup,
List<MsgState> msgList,
long timeoutMs,
Logger log)
{
super(relay, bootstrap, null, timeoutTimer, timeoutMs, timeoutMs, channelGroup, 1, log);
_msgList = msgList;
}
public void requestCall()
{
final TestClientConnection me = this;
if (null == _channel || ! _channel.isConnected())
{
connectWithListener(new ConnectResultListener()
{
@Override
public void onConnectSuccess(Channel channel)
{
me.onConnected();
}
@Override
public void onConnectFailure(Throwable cause)
{
_msgList.add(MsgState.REQUEST_FAILURE);
}
});
}
else
{
onConnected();
}
}
void onConnected()
{
ChannelPipeline channelPipeline = _channel.getPipeline();
_readTimeOutHandler = (ExtendedReadTimeoutHandler)channelPipeline.get(GenericHttpClientPipelineFactory.READ_TIMEOUT_HANDLER_NAME);
_readTimeOutHandler.start(channelPipeline.getContext(_readTimeOutHandler));
TestHttpResponseProcessor testResponseProcessor =
new TestHttpResponseProcessor(_msgList,_readTimeOutHandler);
String uriString = "/test";
HttpRequest request = createEmptyRequest(uriString);
sendRequest(request, new AbstractNettyHttpConnection.SendRequestResultListener(){
@Override
public void onSendRequestSuccess(HttpRequest req)
{
_msgList.add(MsgState.REQUEST_FAILURE);
}
@Override
public void onSendRequestFailure(HttpRequest req, Throwable cause)
{
onResponseFailure(cause);
}
}, testResponseProcessor);
}
private void onResponseFailure(Throwable cause)
{
}
}
public static class FakeRelay extends Thread
{
int _port;
long _scn;
boolean _returnSCN;
ServerBootstrap _bootstrap;
int _timeout;
public FakeRelay(int port, long scn, boolean returnSCN, int timeout)
{
_port = port;
_scn = scn;
_returnSCN = returnSCN;
_timeout = timeout;
}
@Override
public void run()
{
_bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));
// Set up the event pipeline factory.
_bootstrap.setPipelineFactory(new HttpServerPipelineFactory());
// Bind and start to accept incoming connections.
_bootstrap.bind(new InetSocketAddress(_port));
}
void shutdown()
{
if(_bootstrap != null)
{
_bootstrap.releaseExternalResources();
}
}
class HttpServerPipelineFactory implements ChannelPipelineFactory
{
@Override
public ChannelPipeline getPipeline() throws Exception
{
// Create a default pipeline implementation.
ChannelPipeline pipeline = new DefaultChannelPipeline();
pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("encoder", new HttpResponseEncoder());
pipeline.addLast("deflater", new HttpContentCompressor());
pipeline.addLast("handler", new HttpRequestHandler());
return pipeline;
}
}
class HttpRequestHandler extends SimpleChannelUpstreamHandler
{
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception
{
System.out.println("Server : Request received");
BufferInfoResponse bfResponse = new BufferInfoResponse();
bfResponse.setMaxScn(_scn);
bfResponse.setMinScn(_scn - 5000);
bfResponse.setTimestampFirstEvent(System.currentTimeMillis() - 1000);
bfResponse.setTimestampLatestEvent(System.currentTimeMillis());
if(_timeout > 0)
{
//Thread.currentThread().sleep(_timeout);
}
if(_returnSCN)
{
ObjectMapper mapper = new ObjectMapper();
StringWriter sw = new StringWriter();
mapper.writeValue(sw, bfResponse);
HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
response.setContent(ChannelBuffers.copiedBuffer(sw.toString(), CharsetUtil.UTF_8));
response.setHeader(HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=UTF-8");
e.getChannel().write(response);
try
{
Thread.sleep(_timeout);
} catch (InterruptedException ie) {
}
e.getChannel().close();
}
else
{
HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR);
response.setHeader(HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=UTF-8");
e.getChannel().write(response);
e.getChannel().close();
}
}
}
}
}