Package com.linkedin.databus.client.netty

Source Code of com.linkedin.databus.client.netty.TestGenericHttpResponseHandler

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();
  }

}
TOP

Related Classes of com.linkedin.databus.client.netty.TestGenericHttpResponseHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.