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.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteOrder; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.jboss.netty.bootstrap.ClientBootstrap; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelHandler; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.local.LocalAddress; import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; import org.jboss.netty.handler.codec.http.DefaultHttpChunk; import org.jboss.netty.handler.codec.http.DefaultHttpChunkTrailer; import org.jboss.netty.handler.codec.http.DefaultHttpRequest; import org.jboss.netty.handler.codec.http.DefaultHttpResponse; import org.jboss.netty.handler.codec.http.HttpChunk; import org.jboss.netty.handler.codec.http.HttpChunkTrailer; import org.jboss.netty.handler.codec.http.HttpClientCodec; import org.jboss.netty.handler.codec.http.HttpHeaders; import org.jboss.netty.handler.codec.http.HttpMethod; import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.codec.http.HttpResponse; import org.jboss.netty.handler.codec.http.HttpResponseStatus; import org.jboss.netty.handler.codec.http.HttpServerCodec; import org.jboss.netty.handler.codec.http.HttpVersion; import org.jboss.netty.handler.logging.LoggingHandler; import org.jboss.netty.handler.timeout.ReadTimeoutException; import org.jboss.netty.logging.InternalLogLevel; import org.jboss.netty.logging.InternalLoggerFactory; import org.jboss.netty.logging.Log4JLoggerFactory; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import com.linkedin.databus.client.netty.AbstractNettyHttpConnection.ChannelCloseListener; import com.linkedin.databus.client.netty.AbstractNettyHttpConnection.ConnectResultListener; import com.linkedin.databus.client.netty.AbstractNettyHttpConnection.SendRequestResultListener; import com.linkedin.databus.client.netty.GenericHttpResponseHandler.KeepAliveType; import com.linkedin.databus.client.netty.GenericHttpResponseHandler.MessageState; import com.linkedin.databus2.core.DatabusException; import com.linkedin.databus2.test.ConditionCheck; import com.linkedin.databus2.test.TestUtil; import com.linkedin.databus2.test.container.SimpleTestServerConnection; public class TestGenericHttpResponseHandler { static final ExecutorService BOSS_POOL = Executors.newCachedThreadPool(); static final ExecutorService IO_POOL = Executors.newCachedThreadPool(); static final int SERVER_ADDRESS_ID = 14455; static final LocalAddress SERVER_ADDRESS = new LocalAddress(SERVER_ADDRESS_ID); static SimpleTestServerConnection _dummyServer; static org.apache.log4j.Level _logLevel = org.apache.log4j.Level.INFO; @BeforeClass public void setUpClass() { TestUtil.setupLoggingWithTimestampedFile(true, "/tmp/TestGenericHttpResponseHandler_", ".log", Level.INFO); InternalLoggerFactory.setDefaultFactory(new Log4JLoggerFactory()); _dummyServer = new SimpleTestServerConnection(ByteOrder.BIG_ENDIAN, SimpleTestServerConnection.ServerType.NIO); _dummyServer.setPipelineFactory(new ChannelPipelineFactory() { @Override public ChannelPipeline getPipeline() throws Exception { return Channels.pipeline(new HttpServerCodec()); } }); _dummyServer.start(SERVER_ADDRESS_ID); _logLevel = org.apache.log4j.Level.INFO; } @AfterClass public void tearDownClass() { _dummyServer.stop(); BOSS_POOL.shutdownNow(); IO_POOL.shutdownNow(); } void setListeners(GenericHttpResponseHandler responseHandler, HttpResponseProcessor respProcessor, SendRequestResultListener requestProcessor, ChannelCloseListener channelCloseProcessor ) throws DatabusException { responseHandler.setResponseProcessor(respProcessor); responseHandler.setRequestListener(requestProcessor); responseHandler.setCloseListener(channelCloseProcessor); } @Test public void testHappyPathNoChunking() throws DatabusException { Logger log = Logger.getLogger("GenericHttpResponseHandler.testHappyPathNoChunking"); final GenericHttpResponseHandler responseHandler = new GenericHttpResponseHandler(KeepAliveType.KEEP_ALIVE); responseHandler.getLog().setLevel(_logLevel); TestHttpResponseProcessor respProcessor = new TestHttpResponseProcessor(log); TestConnectListener connectListener = new TestConnectListener(log); TestSendRequestListener requestListener = new TestSendRequestListener(log); TestCloseListener closeListener = new TestCloseListener(log); responseHandler.setConnectionListener(connectListener); Channel channel = createClientBootstrap(responseHandler); SocketAddress clientAddr = channel.getLocalAddress(); try { setListeners(responseHandler,respProcessor,requestListener,closeListener); channel.write(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/test")); //It seems that there is a race condition between the writeFuture succeeding //and the writeComplete message getting to the handler. Make sure that the //writeComplete has got to the handler before we do anything else with //the channel. final GenericHttpResponseHandler handler = getResponseHandler(channel); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { return handler._messageState.hasSentRequest(); } }, "request sent", 1000, log); HttpResponse resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); resp.setContent(null); resp.setHeader(HttpHeaders.Names.CONTENT_LENGTH, 0); sendServerResponse(clientAddr, resp, 1000); final List<String> callbacks = respProcessor.getCallbacks(); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { return 2 == callbacks.size(); } }, "waiting for response processed", 1000, null); final List<String> connectCallbacks = connectListener.getCallbacks(); final List<String> requestCallbacks = requestListener.getCallbacks(); final List<String> closeCallbacks = closeListener.getCallbacks(); stateSanityCheck(connectCallbacks,requestCallbacks,callbacks,closeCallbacks); Assert.assertEquals(callbacks.get(0), "startResponse"); Assert.assertEquals(callbacks.get(1), "finishResponse"); } finally { channel.close(); } } @Test public void testHappyPathWithCloseNoChunking() throws DatabusException { Logger log = Logger.getLogger("GenericHttpResponseHandler.testHappyPathWithCloseNoChunking"); final GenericHttpResponseHandler responseHandler = new GenericHttpResponseHandler(KeepAliveType.KEEP_ALIVE); responseHandler.getLog().setLevel(_logLevel); TestHttpResponseProcessor respProcessor = new TestHttpResponseProcessor(log); TestConnectListener connectListener = new TestConnectListener(log); TestSendRequestListener requestListener = new TestSendRequestListener(log); TestCloseListener closeListener = new TestCloseListener(log); responseHandler.setConnectionListener(connectListener); Channel channel = createClientBootstrap(responseHandler); SocketAddress clientAddr = channel.getLocalAddress(); try { setListeners(responseHandler,respProcessor,requestListener,closeListener); channel.write(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/test")); //It seems that there is a race condition between the writeFuture succeeding //and the writeComplete message getting to the handler. Make sure that the //writeComplete has got to the handler before we do anything else with //the channel. final GenericHttpResponseHandler handler = getResponseHandler(channel); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { return handler._messageState.hasSentRequest(); } }, "request sent", 1000, log); HttpResponse resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); resp.setContent(null); resp.setHeader(HttpHeaders.Names.CONTENT_LENGTH, 0); sendServerResponse(clientAddr, resp, 1000); TestUtil.sleep(200); sendServerClose(clientAddr, -1); final List<String> callbacks = respProcessor.getCallbacks(); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { return 2 == callbacks.size(); } }, "waiting for response processed", 1000, null); final List<String> connectCallbacks = connectListener.getCallbacks(); final List<String> requestCallbacks = requestListener.getCallbacks(); final List<String> closeCallbacks = closeListener.getCallbacks(); stateSanityCheck(connectCallbacks,requestCallbacks,callbacks,closeCallbacks); Assert.assertEquals(callbacks.get(0), "startResponse"); Assert.assertEquals(callbacks.get(1), "finishResponse"); //make sure that no new callbacks have showed up Assert.assertEquals(callbacks.size(), 2); } finally { channel.close(); } } @Test public void testReadTimeoutNoChunking() throws InterruptedException, DatabusException { final Logger log = Logger.getLogger("GenericHttpResponseHandler.testReadTimeoutNoChunking"); final GenericHttpResponseHandler responseHandler = new GenericHttpResponseHandler(KeepAliveType.KEEP_ALIVE); responseHandler.getLog().setLevel(_logLevel); log.info("start"); log.setLevel(_logLevel); TestHttpResponseProcessor respProcessor = new TestHttpResponseProcessor(log); TestConnectListener connectListener = new TestConnectListener(log); TestSendRequestListener requestListener = new TestSendRequestListener(log); TestCloseListener closeListener = new TestCloseListener(log); responseHandler.setConnectionListener(connectListener); Channel channel = createClientBootstrap(responseHandler); try { setListeners(responseHandler,respProcessor,requestListener,closeListener); ChannelFuture writeFuture = channel.write(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/test")); Assert.assertTrue(writeFuture.await(1000)); //It seems that there is a race condition between the writeFuture succeeding //and the writeComplete message getting to the handler. Make sure that the //writeComplete has got to the handler before we do anything else with //the channel. final GenericHttpResponseHandler handler = getResponseHandler(channel); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { return handler._messageState.hasSentRequest(); } }, "request sent", 1000, log); Channels.fireExceptionCaught(channel, new ReadTimeoutException()); channel.close(); final List<String> callbacks = respProcessor.getCallbacks(); final List<String> closeCallbacks = closeListener.getCallbacks(); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { log.debug("# callbacks:" + callbacks + ";expecting 1"); return 1 == callbacks.size() && 1==closeCallbacks.size(); } }, "waiting for response processed", 5000, null); final List<String> connectCallbacks = connectListener.getCallbacks(); final List<String> requestCallbacks = requestListener.getCallbacks(); stateSanityCheck(connectCallbacks,requestCallbacks,callbacks,closeCallbacks); Assert.assertTrue(callbacks.get(0).startsWith("channelException")); //Assert.assertEquals(callbacks.get(1), "channelClosed"); // we don't get channelClosed after exception anymore //make sure that no new callbacks have showed up Assert.assertEquals(callbacks.size(), 1); } finally { channel.close(); log.info("end"); } } @Test public void testHappyPathChunking() throws DatabusException { Logger log = Logger.getLogger("GenericHttpResponseHandler.testHappyPathChunking"); final GenericHttpResponseHandler responseHandler = new GenericHttpResponseHandler(KeepAliveType.KEEP_ALIVE); responseHandler.getLog().setLevel(_logLevel); TestHttpResponseProcessor respProcessor = new TestHttpResponseProcessor(log); TestConnectListener connectListener = new TestConnectListener(log); TestSendRequestListener requestListener = new TestSendRequestListener(log); TestCloseListener closeListener = new TestCloseListener(log); responseHandler.setConnectionListener(connectListener); Channel channel = createClientBootstrap(responseHandler); SocketAddress clientAddr = channel.getLocalAddress(); try { setListeners(responseHandler,respProcessor,requestListener,closeListener); channel.write(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/test")); //It seems that there is a race condition between the writeFuture succeeding //and the writeComplete message getting to the handler. Make sure that the //writeComplete has got to the handler before we do anything else with //the channel. final GenericHttpResponseHandler handler = getResponseHandler(channel); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { return handler._messageState.hasSentRequest(); } }, "request sent", 1000, log); HttpResponse resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); resp.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED); sendServerResponse(clientAddr, resp, 1000); HttpChunk chunk1 = new DefaultHttpChunk(ChannelBuffers.wrappedBuffer("chunk1".getBytes(Charset.defaultCharset()))); sendServerResponse(clientAddr, chunk1, 1000); HttpChunk chunk2 = new DefaultHttpChunk(ChannelBuffers.wrappedBuffer("chunk2".getBytes(Charset.defaultCharset()))); sendServerResponse(clientAddr, chunk2, 1000); sendServerResponse(clientAddr, new DefaultHttpChunkTrailer(), 1000); final List<String> callbacks = respProcessor.getCallbacks(); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { return callbacks.size() == 5; } }, "waiting for response processed", 1000, null); final List<String> connectCallbacks = connectListener.getCallbacks(); final List<String> requestCallbacks = requestListener.getCallbacks(); final List<String> closeCallbacks = closeListener.getCallbacks(); stateSanityCheck(connectCallbacks,requestCallbacks,callbacks,closeCallbacks); Assert.assertEquals(callbacks.get(0), "startResponse"); Assert.assertEquals(callbacks.get(1), "addChunk"); Assert.assertEquals(callbacks.get(2), "addChunk"); Assert.assertEquals(callbacks.get(3), "addTrailer"); Assert.assertEquals(callbacks.get(4), "finishResponse"); //make sure that no new callbacks have showed up Assert.assertEquals(callbacks.size(), 5); } finally { channel.close(); } } @Test public void testHappyPathWithCloseChunking() throws DatabusException { Logger log = Logger.getLogger("GenericHttpResponseHandler.testHappyPathWithCloseChunking"); final GenericHttpResponseHandler responseHandler = new GenericHttpResponseHandler(KeepAliveType.KEEP_ALIVE); responseHandler.getLog().setLevel(_logLevel); TestHttpResponseProcessor respProcessor = new TestHttpResponseProcessor(log); TestConnectListener connectListener = new TestConnectListener(log); TestSendRequestListener requestListener = new TestSendRequestListener(log); TestCloseListener closeListener = new TestCloseListener(log); responseHandler.setConnectionListener(connectListener); Channel channel = createClientBootstrap(responseHandler); SocketAddress clientAddr = channel.getLocalAddress(); try { setListeners(responseHandler,respProcessor,requestListener,closeListener); channel.write(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/test")); //It seems that there is a race condition between the writeFuture succeeding //and the writeComplete message getting to the handler. Make sure that the //writeComplete has got to the handler before we do anything else with //the channel. final GenericHttpResponseHandler handler = getResponseHandler(channel); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { return handler._messageState.hasSentRequest(); } }, "request sent", 1000, log); HttpResponse resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); resp.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED); sendServerResponse(clientAddr, resp, 1000); HttpChunk chunk1 = new DefaultHttpChunk(ChannelBuffers.wrappedBuffer("chunk1".getBytes(Charset.defaultCharset()))); sendServerResponse(clientAddr, chunk1, 1000); HttpChunk chunk2 = new DefaultHttpChunk(ChannelBuffers.wrappedBuffer("chunk2".getBytes(Charset.defaultCharset()))); sendServerResponse(clientAddr, chunk2, 1000); sendServerResponse(clientAddr, new DefaultHttpChunkTrailer(), 1000); TestUtil.sleep(200); sendServerClose(clientAddr, -1); final List<String> callbacks = respProcessor.getCallbacks(); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { return 5 == callbacks.size(); } }, "waiting for response processed", 1000, null); final List<String> connectCallbacks = connectListener.getCallbacks(); final List<String> requestCallbacks = requestListener.getCallbacks(); final List<String> closeCallbacks = closeListener.getCallbacks(); stateSanityCheck(connectCallbacks,requestCallbacks,callbacks,closeCallbacks); Assert.assertEquals(callbacks.get(0), "startResponse"); Assert.assertEquals(callbacks.get(1), "addChunk"); Assert.assertEquals(callbacks.get(2), "addChunk"); Assert.assertEquals(callbacks.get(3), "addTrailer"); Assert.assertEquals(callbacks.get(4), "finishResponse"); //make sure that no new callbacks have showed up Assert.assertEquals(callbacks.size(), 5); } finally { channel.close(); } } @Test public void testEarlyServerCloseChunking() throws DatabusException { Logger log = Logger.getLogger("GenericHttpResponseHandler.testEarlyServerCloseChunking"); final GenericHttpResponseHandler responseHandler = new GenericHttpResponseHandler(KeepAliveType.KEEP_ALIVE); responseHandler.getLog().setLevel(_logLevel); TestHttpResponseProcessor respProcessor = new TestHttpResponseProcessor(log); TestConnectListener connectListener = new TestConnectListener(log); TestSendRequestListener requestListener = new TestSendRequestListener(log); TestCloseListener closeListener = new TestCloseListener(log); responseHandler.setConnectionListener(connectListener); Channel channel = createClientBootstrap(responseHandler); Assert.assertTrue(channel.isConnected()); final SocketAddress clientAddr = channel.getLocalAddress(); try { setListeners(responseHandler,respProcessor,requestListener,closeListener); channel.write(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/test")); //It seems that there is a race condition between the writeFuture succeeding //and the writeComplete message getting to the handler. Make sure that the //writeComplete has got to the handler before we do anything else with //the channel. final GenericHttpResponseHandler handler = getResponseHandler(channel); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { return handler._messageState.hasSentRequest(); } }, "request sent", 1000, log); HttpResponse resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); resp.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED); TestUtil.assertWithBackoff(new ConditionCheck(){ @Override public boolean check() { return null != _dummyServer.getChildChannel(clientAddr); } }, "make sure we have all tracking populated for client connection", 1000, log); sendServerResponse(clientAddr, resp, 1000); HttpChunk chunk1 = new DefaultHttpChunk(ChannelBuffers.wrappedBuffer("chunk1".getBytes(Charset.defaultCharset()))); sendServerResponse(clientAddr, chunk1, 1000); TestUtil.sleep(200); sendServerClose(clientAddr, -1); final List<String> callbacks = respProcessor.getCallbacks(); final List<String> closeCallbacks = closeListener.getCallbacks(); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { return 3 == callbacks.size() && 1==closeCallbacks.size(); } }, "waiting for response processed", 1000, null); final List<String> connectCallbacks = connectListener.getCallbacks(); final List<String> requestCallbacks = requestListener.getCallbacks(); stateSanityCheck(connectCallbacks,requestCallbacks,callbacks,closeCallbacks); Assert.assertEquals(callbacks.get(0), "startResponse"); Assert.assertEquals(callbacks.get(1), "addChunk"); Assert.assertTrue(callbacks.get(2).startsWith("channelException")); // we get Exception, no ChannelClose //make sure that no new callbacks have showed up Assert.assertEquals(callbacks.size(), 3); } finally { channel.close(); } } @Test public void testReadTimeoutChunking() throws DatabusException { final Logger log = Logger.getLogger("GenericHttpResponseHandler.testReadTimeoutChunking"); log.info("start"); final GenericHttpResponseHandler responseHandler = new GenericHttpResponseHandler(KeepAliveType.KEEP_ALIVE); responseHandler.getLog().setLevel(_logLevel); TestHttpResponseProcessor respProcessor = new TestHttpResponseProcessor(log); TestConnectListener connectListener = new TestConnectListener(log); TestSendRequestListener requestListener = new TestSendRequestListener(log); TestCloseListener closeListener = new TestCloseListener(log); responseHandler.setConnectionListener(connectListener); Channel channel = createClientBootstrap(responseHandler); SocketAddress clientAddr = channel.getLocalAddress(); try { setListeners(responseHandler,respProcessor,requestListener,closeListener); channel.write(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/test")); //It seems that there is a race condition between the writeFuture succeeding //and the writeComplete message getting to the handler. Make sure that the //writeComplete has got to the handler before we do anything else with //the channel. final GenericHttpResponseHandler handler = getResponseHandler(channel); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { return handler._messageState.hasSentRequest(); } }, "request sent", 1000, log); HttpResponse resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); resp.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED); sendServerResponse(clientAddr, resp, 1000); HttpChunk chunk1 = new DefaultHttpChunk(ChannelBuffers.wrappedBuffer("chunk1".getBytes(Charset.defaultCharset()))); sendServerResponse(clientAddr, chunk1, 1000); final List<String> callbacks = respProcessor.getCallbacks(); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { return 2 == callbacks.size(); } }, "waiting for response processed", 1000, null); Assert.assertEquals(callbacks.get(0), "startResponse"); Assert.assertEquals(callbacks.get(1), "addChunk"); Channels.fireExceptionCaught(channel, new ReadTimeoutException()); channel.close(); final List<String> closeCallbacks = closeListener.getCallbacks(); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { return 3 == callbacks.size() && 1==closeCallbacks.size(); } }, "waiting for response processed", 1000, null); final List<String> connectCallbacks = connectListener.getCallbacks(); final List<String> requestCallbacks = requestListener.getCallbacks(); Assert.assertEquals(callbacks.get(0), "startResponse"); Assert.assertEquals(callbacks.get(1), "addChunk"); Assert.assertTrue(callbacks.get(2).startsWith("channelException")); //Assert.assertEquals(callbacks.get(3), "channelClosed"); // no more channelClosed after channel Exception //make sure that no new callbacks have showed up stateSanityCheck(connectCallbacks,requestCallbacks,callbacks,closeCallbacks); Assert.assertEquals(callbacks.size(), 3); } finally { channel.close(); log.info("end"); } } @Test public void testErrorServerResponseChunking() throws DatabusException { Logger log = Logger.getLogger("GenericHttpResponseHandler.testErrorServerResponseChunking"); final GenericHttpResponseHandler responseHandler = new GenericHttpResponseHandler(KeepAliveType.KEEP_ALIVE); responseHandler.getLog().setLevel(_logLevel); TestHttpResponseProcessor respProcessor = new TestHttpResponseProcessor(log); TestConnectListener connectListener = new TestConnectListener(log); TestSendRequestListener requestListener = new TestSendRequestListener(log); TestCloseListener closeListener = new TestCloseListener(log); responseHandler.setConnectionListener(connectListener); Channel channel = createClientBootstrap(responseHandler); SocketAddress clientAddr = channel.getLocalAddress(); try { setListeners(responseHandler,respProcessor,requestListener,closeListener); channel.write(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/test")); //It seems that there is a race condition between the writeFuture succeeding //and the writeComplete message getting to the handler. Make sure that the //writeComplete has got to the handler before we do anything else with //the channel. final GenericHttpResponseHandler handler = getResponseHandler(channel); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { return handler._messageState.hasSentRequest(); } }, "request sent", 1000, log); HttpResponse resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.SERVICE_UNAVAILABLE); resp.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED); sendServerResponse(clientAddr, resp, 2000); HttpChunk chunk1 = new DefaultHttpChunk(ChannelBuffers.wrappedBuffer("chunk1".getBytes(Charset.defaultCharset()))); sendServerResponse(clientAddr, chunk1, 1000); sendServerResponse(clientAddr, new DefaultHttpChunkTrailer(), 1000); final List<String> callbacks = respProcessor.getCallbacks(); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { return 4 == callbacks.size(); } }, "waiting for response processed", 1000, null); final List<String> connectCallbacks = connectListener.getCallbacks(); final List<String> requestCallbacks = requestListener.getCallbacks(); final List<String> closeCallbacks = closeListener.getCallbacks(); stateSanityCheck(connectCallbacks,requestCallbacks,callbacks,closeCallbacks); Assert.assertEquals(callbacks.get(0), "startResponse"); Assert.assertEquals(callbacks.get(1), "addChunk"); Assert.assertEquals(callbacks.get(2), "addTrailer"); Assert.assertEquals(callbacks.get(3), "finishResponse"); TestUtil.sleep(500); //make sure that no new callbacks have showed up Assert.assertEquals(callbacks.size(), 4); } finally { channel.close(); } } @Test public void testRequestError() throws DatabusException { Logger log = Logger.getLogger("GenericHttpResponseHandler.testRequestError"); TestHttpResponseProcessor respProcessor = new TestHttpResponseProcessor(log); TestConnectListener connectListener = new TestConnectListener(log); TestSendRequestListener requestListener = new TestSendRequestListener(log); TestCloseListener closeListener = new TestCloseListener(log); //Need this call to set respProcessor without triggering erroneous check final GenericHttpResponseHandler responseHandler = new GenericHttpResponseHandler(respProcessor,KeepAliveType.KEEP_ALIVE); responseHandler.getLog().setLevel(_logLevel); responseHandler.setRequestListener(requestListener); responseHandler.setConnectionListener(connectListener); responseHandler.setCloseListener(closeListener); Channel channel = createClientBootstrap(responseHandler); SocketAddress clientAddr = channel.getLocalAddress(); try { HttpResponse resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR); resp.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED); sendServerResponse(clientAddr, resp, 2000); channel.write(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/test")); final List<String> callbacks = respProcessor.getCallbacks(); final List<String> connectCallbacks = connectListener.getCallbacks(); final List<String> requestCallbacks = requestListener.getCallbacks(); final List<String> closeCallbacks = closeListener.getCallbacks(); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { return 1 == closeCallbacks.size(); } }, "waiting for close channel callback", 1000, null); //make sure that no new callbacks have showed up stateSanityCheck(connectCallbacks,requestCallbacks,callbacks,closeCallbacks); Assert.assertEquals(connectCallbacks.get(0),"onConnectSuccess"); Assert.assertEquals(requestCallbacks.size(),1); Assert.assertEquals(requestCallbacks.get(0),"onSendRequestFailure"); Assert.assertEquals(callbacks.size(), 0); Assert.assertEquals(closeCallbacks.size(),1); Assert.assertEquals(closeCallbacks.get(0),"onChannelClose"); } finally { channel.close(); } } @Test public void testRepeatedReadSuccess() throws DatabusException { Logger log = Logger.getLogger("GenericHttpResponseHandler.testRestRepeatedReadSuccess"); //global responseHandler; final GenericHttpResponseHandler responseHandler = new GenericHttpResponseHandler(KeepAliveType.KEEP_ALIVE); responseHandler.getLog().setLevel(_logLevel); TestConnectListener connectListener = new TestConnectListener(log); responseHandler.setConnectionListener(connectListener); Channel channel = createClientBootstrap(responseHandler); SocketAddress clientAddr = channel.getLocalAddress(); try { for (int i=0; i < 2;++i) { TestHttpResponseProcessor respProcessor = new TestHttpResponseProcessor(log); TestSendRequestListener requestListener = new TestSendRequestListener(log); TestCloseListener closeListener = new TestCloseListener(log); setListeners(responseHandler, respProcessor, requestListener,closeListener); channel.write(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/test")); TestUtil.sleep(1000); HttpResponse resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); resp.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED); sendServerResponse(clientAddr, resp, 2000); HttpChunk chunk1 = new DefaultHttpChunk(ChannelBuffers.wrappedBuffer("chunk1".getBytes(Charset.defaultCharset()))); sendServerResponse(clientAddr, chunk1, 1000); sendServerResponse(clientAddr, new DefaultHttpChunkTrailer(), 1000); final List<String> callbacks = respProcessor.getCallbacks(); final List<String> connectCallbacks = connectListener.getCallbacks(); final List<String> requestCallbacks = requestListener.getCallbacks(); final List<String> closeCallbacks = closeListener.getCallbacks(); TestUtil.sleep(500); stateSanityCheck(connectCallbacks,requestCallbacks,callbacks,closeCallbacks); Assert.assertEquals(1,connectCallbacks.size()); Assert.assertEquals(connectCallbacks.get(0),"onConnectSuccess"); Assert.assertEquals(requestCallbacks.size(),1); Assert.assertEquals(requestCallbacks.get(0),"onSendRequestSuccess"); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { return callbacks.size() == 4; } }, "waiting for response processed", 2000, null); Assert.assertEquals(callbacks.get(0), "startResponse"); Assert.assertEquals(callbacks.get(1), "addChunk"); Assert.assertEquals(callbacks.get(2), "addTrailer"); Assert.assertEquals(callbacks.get(3), "finishResponse"); } } finally { channel.close(); } } @Test public void testConnectFail() throws DatabusException { Logger log = Logger.getLogger("GenericHttpResponseHandler.testConnectFail"); TestHttpResponseProcessor respProcessor = new TestHttpResponseProcessor(log); TestConnectListener connectListener = new TestConnectListener(log); TestSendRequestListener requestListener = new TestSendRequestListener(log); TestCloseListener closeListener = new TestCloseListener(log); //Need this call to set respProcessor without triggering erroneous check final GenericHttpResponseHandler responseHandler = new GenericHttpResponseHandler(respProcessor,KeepAliveType.KEEP_ALIVE); responseHandler.setRequestListener(requestListener); responseHandler.setConnectionListener(connectListener); responseHandler.setCloseListener(closeListener); //use port 0 to generate connect fail ChannelFuture channelFuture = createChannelFuture(responseHandler,0); Channel channel = channelFuture.getChannel(); try { channel.write(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/test")); final List<String> respCallbacks = respProcessor.getCallbacks(); final List<String> connectCallbacks = connectListener.getCallbacks(); final List<String> requestCallbacks = requestListener.getCallbacks(); final List<String> closeChannelCallbacks = closeListener.getCallbacks(); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { return 1 == closeChannelCallbacks.size(); } }, "waiting for close channel callback", 1000, null); //make sure that no new callbacks have showed up stateSanityCheck(connectCallbacks,requestCallbacks,respCallbacks,closeChannelCallbacks); Assert.assertEquals(connectCallbacks.size(), 1); Assert.assertEquals(connectCallbacks.get(0),"onConnectFailure"); Assert.assertEquals(closeChannelCallbacks.size(),1); Assert.assertEquals(closeChannelCallbacks.get(0),"onChannelClose"); } finally { channel.close(); } } /** * * @param connectCallbacks * @param requestCallbacks * @param respCallbacks * @param closeChannelCallbacks * Invariants are tested to see if valid sequence of callbacks were called */ static void stateSanityCheck(List<String> connectCallbacks,List<String> requestCallbacks,List<String> respCallbacks ,List<String> closeChannelCallbacks) { System.out.println("connectCallbacks:" + connectCallbacks); System.out.println("requestCallbacks:" + requestCallbacks); System.out.println("responseCallbacks:" + respCallbacks); System.out.println("closeChannelCallbacks:" + closeChannelCallbacks); int totalConnectCallbacks = connectCallbacks.size(); int totalRequestCallbacks = requestCallbacks.size(); int totalRespCallbacks = respCallbacks.size(); int totalCloseChannelCallbacks = closeChannelCallbacks.size(); Assert.assertTrue((totalConnectCallbacks+totalRequestCallbacks+totalRespCallbacks)>0); if (!errorOccurred(respCallbacks)) { //Response was received Assert.assertTrue((totalConnectCallbacks > 0) && (totalConnectCallbacks <= totalRequestCallbacks)); //check if any exception state exists } else { //Some error happened; so close channel had to be called Assert.assertEquals(totalCloseChannelCallbacks,1); } } /** * * @param respCallbacks : callbacks in response callback object * @return false if a non-empty callback list with no callback that has substring 'Exception' was passed; true if the list was empty or a callback * which contained the string 'Exception'. */ private static boolean errorOccurred(List<String> respCallbacks) { if (respCallbacks.size() > 0) { for (String callback: respCallbacks) { if (callback.contains("Exception")) { return true; } } return false; } return true; } void sendServerResponse(SocketAddress clientAddr, Object response, long timeoutMillis) { Channel childChannel = _dummyServer.getChildChannel(clientAddr); Assert.assertNotEquals(childChannel, null); ChannelFuture writeFuture = childChannel.write(response); if (timeoutMillis > 0) { try { writeFuture.await(timeoutMillis); } catch (InterruptedException e) { //NOOP } Assert.assertTrue(writeFuture.isDone()); Assert.assertTrue(writeFuture.isSuccess()); } } void sendServerClose(SocketAddress clientAddr, long timeoutMillis) { Channel childChannel = _dummyServer.getChildChannel(clientAddr); Assert.assertNotEquals(childChannel, null); ChannelFuture closeFuture = childChannel.close(); if (timeoutMillis > 0) { try { closeFuture.await(timeoutMillis); } catch (InterruptedException e) { //NOOP } Assert.assertTrue(closeFuture.isDone()); Assert.assertTrue(closeFuture.isSuccess()); } } static Channel createClientBootstrap(GenericHttpResponseHandler responseHandler) { return createClientBootstrap(responseHandler, SERVER_ADDRESS_ID); } static ChannelFuture createChannelFuture(final GenericHttpResponseHandler responseHandler, int port) { ClientBootstrap client = new ClientBootstrap( new NioClientSocketChannelFactory(BOSS_POOL, IO_POOL)); client.setPipelineFactory(new ChannelPipelineFactory() { @Override public ChannelPipeline getPipeline() throws Exception { return Channels.pipeline(new LoggingHandler(InternalLogLevel.DEBUG), new HttpClientCodec(), new LoggingHandler(InternalLogLevel.DEBUG), responseHandler); } }); final ChannelFuture connectFuture = client.connect(new InetSocketAddress( "localhost", port)); return connectFuture; } static Channel createClientBootstrap(final GenericHttpResponseHandler responseHandler,int port) { final ChannelFuture connectFuture = createChannelFuture(responseHandler, port); TestUtil.assertWithBackoff(new ConditionCheck() { @Override public boolean check() { return (connectFuture.isDone() && connectFuture.isSuccess() && responseHandler.getMessageState()==MessageState.REQUEST_WAIT); } }, "waiting for connect success", 2000, null); Assert.assertTrue(connectFuture.isDone() && connectFuture.isSuccess()); return connectFuture.getChannel(); } static GenericHttpResponseHandler getResponseHandler(Channel clientChannel) { ChannelPipeline pipe = clientChannel.getPipeline(); ChannelHandler handler = pipe.get("3"); Assert.assertNotNull(handler, "unable to find handler. Did client pipeline factory change?"); Assert.assertTrue(handler instanceof GenericHttpResponseHandler, "expected GenericHttpResponseHandler; found: " + handler.getClass() + ". Did client pipeline factory change?"); return (GenericHttpResponseHandler)handler; } } class TestCloseListener implements ChannelCloseListener { private final List<String> _callbacks = new ArrayList<String>(); private final Logger _log; public List<String> getCallbacks() { return _callbacks; } public void clear() { _callbacks.clear(); } public TestCloseListener(Logger log) { _log=log; } @Override public void onChannelClose() { if (null != _log) _log.info("onChannelClose"); _callbacks.add("onChannelClose"); } } class TestConnectListener implements ConnectResultListener { private final List<String> _callbacks = new ArrayList<String>(); private final Logger _log; public TestConnectListener(Logger log) { _log=log; } public List<String> getCallbacks() { return _callbacks; } public void clear() { _callbacks.clear(); } @Override public void onConnectSuccess(Channel channel) { if (null != _log) _log.info("onConnectSuccess"); _callbacks.add("onConnectSuccess"); } @Override public void onConnectFailure(Throwable cause) { if (null != _log) _log.info("onConnectFailure"); _callbacks.add("onConnectFailure"); } } class TestSendRequestListener implements SendRequestResultListener { private final List<String> _callbacks = new ArrayList<String>(); private final Logger _log; public TestSendRequestListener(Logger log) { _log=log; } public List<String> getCallbacks() { return _callbacks; } public void clear() { _callbacks.clear(); } @Override public void onSendRequestSuccess(HttpRequest req) { if (null != _log) _log.info("onSendRequestSuccess"); _callbacks.add("onSendRequestSuccess"); } @Override public void onSendRequestFailure(HttpRequest req, Throwable cause) { if (null != _log) _log.info("onSendRequestFailure"); _callbacks.add("onSendRequestFailure"); } } class TestHttpResponseProcessor implements HttpResponseProcessor { private final List<String> _callbacks = new ArrayList<String>(); private final Logger _log; public TestHttpResponseProcessor(Logger log) { _log = log; } public List<String> getCallbacks() { return _callbacks; } @Override public void startResponse(HttpResponse response) throws Exception { if (null != _log) _log.info("startResponse"); _callbacks.add("startResponse"); } @Override public void addChunk(HttpChunk chunk) throws Exception { if (null != _log) _log.info("addChunk"); _callbacks.add("addChunk"); } @Override public void addTrailer(HttpChunkTrailer trailer) throws Exception { if (null != _log) _log.info("addTrailer"); _callbacks.add("addTrailer"); } @Override public void finishResponse() throws Exception { if (null != _log) _log.info("finishResponse"); _callbacks.add("finishResponse"); } @Override public void channelException(Throwable cause) { if (null != _log) _log.info("channelException: " + cause); _callbacks.add("channelException(" + cause.getClass().getSimpleName() + ")"); } public void clearCallbacks() { _callbacks.clear(); } }