Package com.linkedin.databus.client.netty

Source Code of com.linkedin.databus.client.netty.AbstractNettyHttpConnection$ChannelCloseListener

/*
*
* 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.
*
*/
package com.linkedin.databus.client.netty;

import java.nio.channels.ClosedChannelException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.log4j.Logger;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
import org.jboss.netty.handler.codec.http.HttpChunkTrailer;
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.HttpVersion;
import org.jboss.netty.handler.timeout.WriteTimeoutException;
import org.jboss.netty.util.Timer;

import com.linkedin.databus.client.ChunkedBodyReadableByteChannel;
import com.linkedin.databus.client.DatabusServerConnection;
import com.linkedin.databus.client.DatabusSourcesConnection;
import com.linkedin.databus.client.pub.ServerInfo;
import com.linkedin.databus.core.DbusConstants;
import com.linkedin.databus.core.DbusEventFactory;
import com.linkedin.databus.core.DbusPrettyLogUtils;
import com.linkedin.databus.core.util.DbusHttpUtils;
import com.linkedin.databus2.core.DatabusException;
import com.linkedin.databus2.core.container.DatabusHttpHeaders;
import com.linkedin.databus2.core.container.ExtendedReadTimeoutHandler;
import com.linkedin.databus2.core.container.monitoring.mbean.ContainerStatisticsCollector;

/**
* Common functionality between {@link NettyHttpDatabusRelayConnection} and
* {@link NettyHttpDatabusBootstrapConnection}.
*
*/
public class AbstractNettyHttpConnection implements DatabusServerConnection
{
  /** Connection state */
  static enum State
  {
    INIT,
    CONNECTING,
    CONNECTED,
    CLOSING,
    CLOSED,
    ERROR
  }

  //State transitions
  // (INIT, connect()) --> CONNECTING
  // (INIT, close()) --> CLOSING
  // (CONNECTING, connect_error) --> INIT
  // (CONNECTING, connect_success) --> CONNECTED
  // (CONNECTING, close()) --> CLOSING
  // (CONNECTED, close()) --> CLOSING
  // (CLOSING, close()) --> CLOSING
  // (CLOSING, close_complete) --> CLOSED
  // (CLOSED, close()) --> CLOSED
  // (CLOSED, close_complete) --> CLOSED
  // (CLOSED, connect()) --> CONNECTING
  // Attempt for any other transition will result in going to state ERROR

  private final Lock _mutex = new ReentrantLock();
  private final Condition _connectionClosed = _mutex.newCondition();
  private final GenericHttpClientPipelineFactory _pipelineFactory;
  protected final ClientBootstrap _bootstrap;
  protected final ServerInfo _server;
  private final Logger _log;
  final private int _protocolVersion;
  protected volatile Channel _channel;
  private int _connectRetriesLeft;
  private State _state;
  private final GenericHttpResponseHandler _handler;
  String _hostHdr;
  String _svcHdr;

  public AbstractNettyHttpConnection(ServerInfo server,
                                     ClientBootstrap bootstrap,
                                     ContainerStatisticsCollector containerStatsCollector,
                                     Timer timeoutTimer,
                                     long writeTimeoutMs,
                                     long readTimeoutMs,
                                     ChannelGroup channelGroup,
                                     int protocolVersion,
                                     Logger log)
  {
    super();
    _log = null != log ? log : Logger.getLogger(AbstractNettyHttpConnection.class);

    _server = server;
    _bootstrap = bootstrap;
    _protocolVersion = protocolVersion;
    _bootstrap.setOption("connectTimeoutMillis", DatabusSourcesConnection.CONNECT_TIMEOUT_MS);

    _handler = new GenericHttpResponseHandler(GenericHttpResponseHandler.KeepAliveType.KEEP_ALIVE, null);
    _pipelineFactory = new GenericHttpClientPipelineFactory(
        _handler,
        containerStatsCollector,
        timeoutTimer,
        writeTimeoutMs,
        readTimeoutMs,
        channelGroup);
    _bootstrap.setPipelineFactory(_pipelineFactory);
    _channel = null;
    _state = State.INIT;
  }

  @Override
  public int getProtocolVersion()
  {
    return _protocolVersion;
  }


  /** Closes the connection. Note: this method will block until the connection is actually closed */
  @Override
  public void close()
  {
    _log.info("closing connection to: " + _server.getAddress());
    final State newState = switchToClosing();
    if (State.CLOSING != newState && State.CLOSED != newState)
    {
      return;
    }

    if (null == _channel || !_channel.isConnected())
    {
      switchToClosed();
    }
    else
    {
      _channel.close();
      awaitForCloseUninterruptibly();
    }
  }

  @Override
  public String getRemoteHost()
  {
    String host = getHostHdr();
    return null != host ? host : DbusConstants.UNKNOWN_HOST;
  }

  @Override
  public String getRemoteService()
  {
    String service = getSvcHdr();
    return null != service ? service : DbusConstants.UNKNOWN_SERVICE_ID;
  }


  private void awaitForCloseUninterruptibly()
  {
    _mutex.lock();
    try
    {
      while (! isClosed())
      {
        _connectionClosed.awaitUninterruptibly();
      }
    }
    finally
    {
      _mutex.unlock();
    }
  }

  /**
   * @return the logger used by this instance
   */
  public Logger getLog()
  {
    return _log;
  }

  public boolean isInit()
  {
    _mutex.lock();
    try
    {
      return State.INIT == _state;
    }
    finally
    {
      _mutex.unlock();
    }
  }

  public boolean isClosingOrClosed()
  {
    _mutex.lock();
    try
    {
      return State.CLOSING == _state || State.CLOSED == _state;
    }
    finally
    {
      _mutex.unlock();
    }
  }

  public boolean isClosed()
  {
    _mutex.lock();
    try
    {
      return State.CLOSED == _state;
    }
    finally
    {
      _mutex.unlock();
    }
  }

  public boolean isClosing()
  {
    _mutex.lock();
    try
    {
      return State.CLOSING == _state;
    }
    finally
    {
      _mutex.unlock();
    }
  }

  public boolean isConnectingOrConnected()
  {
    _mutex.lock();
    try
    {
      return State.CONNECTING == _state || State.CONNECTED == _state;
    }
    finally
    {
      _mutex.unlock();
    }
  }

  public boolean isConnecting()
  {
    _mutex.lock();
    try
    {
      return State.CONNECTING == _state;
    }
    finally
    {
      _mutex.unlock();
    }
  }

  public boolean isConnected()
  {
    _mutex.lock();
    try
    {
      return State.CONNECTED == _state;
    }
    finally
    {
      _mutex.unlock();
    }
  }

  private void unexpectedTransition(State fromState, State toState)
  {
    _log.error("unexpected netty connection transition from " + fromState + " to " + toState);
    _state = State.ERROR;
  }

  /**
   * Attempt to switch to CLOSING state
   * @return the new state
   */
  private State switchToClosing()
  {
    _mutex.lock();
    try
    {
      switch (_state)
      {
      case CLOSING:
      case CLOSED: break; //NOOP
      default: _state = State.CLOSING;
      }
      return _state;
    }
    finally
    {
      _mutex.unlock();
    }
  }

  /**
   * Switch to CLOSED state
   * @return the new state
   */
  private State switchToClosed()
  {
    _mutex.lock();
    try
    {
      switch (_state)
      {
      case CLOSING:
      case CLOSED:
        _state = State.CLOSED;
        break;
      default: unexpectedTransition(_state, State.CLOSED); break;
      }
      return _state;
    }
    finally
    {
      _mutex.unlock();
    }
  }

  /**
   * Switch to CONNECTING state
   * @return the new state
   */
  private State switchToConnecting()
  {
    _mutex.lock();
    try
    {
      switch (_state)
      {
      case CLOSED:
      case INIT: _state = State.CONNECTING; break;
      default: unexpectedTransition(_state, State.CONNECTING); break;
      }
      return _state;
    }
    finally
    {
      _mutex.unlock();
    }
  }

  /**
   * Switch to CONNECTED state
   * @return the new state
   */
  private State switchToConnected()
  {
    _mutex.lock();
    try
    {
      switch (_state)
      {
      case CONNECTING: _state = State.CONNECTED; break;
      case CLOSING:
      case CLOSED: break; //NOOP
      default: unexpectedTransition(_state, State.CONNECTED); break;
      }
      return _state;
    }
    finally
    {
      _mutex.unlock();
    }
  }

  /**
   * Switch to INIT state
   * @return the new state
   */
  private State switchToInit()
  {
    _mutex.lock();
    try
    {
      switch (_state)
      {
      case CONNECTING: _state = State.INIT; break;
      default: unexpectedTransition(_state, State.INIT); break;
      }
      return _state;
    }
    finally
    {
      _mutex.unlock();
    }
  }

  /**
   * Checks if there is network connection is available. Note that if the connection is in a
   * transitional state (CONNECTING, CLOSING), the method will still return true to avoid race
   * conditions where the caller will try to re-establish the connection while these transitions are
   * in progress. If the caller tries an operation that is not allowed on the channel, e.g. send data
   * over a closed channel, netty should be able to detect that and throw a ClosedChannelException.   *
   */
  protected boolean hasConnection()
  {
    return null != _channel && State.INIT != _state && State.CLOSED != _state;
    //return null != _channel && _channel.isConnected();
  }

  /** For testing */
  State getNetworkState()
  {
    return _state;
  }

  protected void connectWithListener(ConnectResultListener listener)
  {

    if (State.CONNECTING != switchToConnecting())
    {
      listener.onConnectFailure(new RuntimeException("unable to connect"));
    }
    else
    {
      _connectRetriesLeft = DatabusSourcesConnection.MAX_CONNECT_RETRY_NUM;
      connectRetry(listener);
    }
  }

  private void connectRetry(ConnectResultListener listener)
  {
    _log.info("connecting: " + _server.toSimpleString());
    if (isClosingOrClosed())
    {
      listener.onConnectFailure(new ClosedChannelException());
      return;
    }
    else if (! isConnecting())
    {
      listener.onConnectFailure(new RuntimeException("unable to connect"));
    }
    else
    {
      //ChannelFuture future = _bootstrap.connect(_server.getAddress());
      //future.addListener(new MyConnectListener(listener));
      _handler.reset();// reset state to make sure new connection starts with a blank Handler state
      _handler.setConnectionListener(new AbstractNettyConnectListener(listener));
      _bootstrap.connect(_server.getAddress());
    }
  }

  protected void sendRequest(HttpRequest request,
                             SendRequestResultListener listener,
                             HttpResponseProcessor responseProcessor)
  {
    if (isClosingOrClosed())
    {
      listener.onSendRequestFailure(request, new ClosedChannelException());
    }
    else if (! isConnected())
    {
      listener.onSendRequestFailure(request, new RuntimeException("unable to send request"));
    }
    else
    {
      try {
        setResponseProcessor(responseProcessor, listener);
      } catch (DatabusException e) {
        listener.onSendRequestFailure(request, e.getCause());
        _channel.close();
        return;
      }

      // Send the HTTP request.
      if (_channel.isConnected())
      {
        //ChannelFuture future = _channel.write(request);
        //future.addListener(new MySendRequestListener(request, listener));
        _channel.write(request);
      }
      else
      {
        _log.error("disconnect on request: " + request.getUri());
        listener.onSendRequestFailure(request, new ClosedChannelException());
      }
    }
  }

  /**
   * Creates an empty HttpRequest object with pre-filled standard headers
   * @param uriString  the request URL
   * @return the request object
   */
  protected HttpRequest createEmptyRequest(String uriString)
  {
    HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uriString);
    request.setHeader(HttpHeaders.Names.HOST, _server.getAddress().toString());
    request.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
    request.setHeader(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP);
    String hostHdr = DbusHttpUtils.getLocalHostName();
    String svcHdr = DbusConstants.getServiceIdentifier();
    if (! hostHdr.isEmpty())
    {
        request.setHeader(DatabusHttpHeaders.DBUS_CLIENT_HOST_HDR, hostHdr);
    }
    if (! svcHdr.isEmpty())
    {
        request.setHeader(DatabusHttpHeaders.DBUS_CLIENT_SERVICE_HDR, svcHdr);
    }
    return request;
  }

  protected void setConnectListener(ConnectResultListener l) {
    _handler.setConnectionListener(l);
  }

  protected void setResponseProcessor(HttpResponseProcessor responseProcessor, SendRequestResultListener l)
      throws DatabusException
  {
    _handler.setResponseProcessor(responseProcessor);
    _handler.setRequestListener(l);

    /*
    if (null != _channel)
    {
      ChannelPipeline channelPipeline = _channel.getPipeline();
      channelPipeline.replace("handler", "handler", _handler);
    }
    */
    assert(_channel != null);
    assert(_channel.getPipeline().get("handler")!=null);
  }

  protected GenericHttpResponseHandler getHandler()
  {
    return _handler;
  }

  protected boolean shouldIgnoreWriteTimeoutException(Throwable cause)
  {
    // special case - DDSDBUS-1497
    // in case of WriteTimeoutException we will get close channel exception.
    // Since the timeout exception comes from a different thread (timer) we
    // may end up informing PullerThread twice - and causing it to create two new connections
    // Instead we just drop this exception and just react to close channel

    //If null == getHandler(), the error happened the connect attempt, i.e. the request was not
    //sent.
    boolean requestSent = null != getHandler() ? getHandler()._messageState.hasSentRequest()
                                               : false;
    if(requestSent && (cause instanceof WriteTimeoutException)) {
      _log.error("Got RequestFailure because of WriteTimeoutException. requestSent = " + requestSent);
      return true;
    }
    else
    {
      _log.error("The request has not been sent due to " + cause + " requestSent = " + requestSent);
      return false;
    }
  }

  protected void addConnectionTracking(HttpResponse response) throws Exception
  {
      boolean debugEnabled = _log.isDebugEnabled();
      _hostHdr = response.getHeader(DatabusHttpHeaders.DBUS_SERVER_HOST_HDR);
      _svcHdr  = response.getHeader(DatabusHttpHeaders.DBUS_SERVER_SERVICE_HDR);
      if (debugEnabled)
      {
          if (null != _hostHdr)
          {
            _log.debug("Received response for databus server host: " + _hostHdr);
          }
          if (null != _svcHdr)
          {
            _log.debug("Received response for databus server host: " + _svcHdr);
          }
      }
      return;
  }

  /** Internal listener to handle connect success and failures */
  private class AbstractNettyConnectListener implements ConnectResultListener
  {
    private final ConnectResultListener _listener;

    public AbstractNettyConnectListener(ConnectResultListener listener)
    {
      super();
      _listener = listener;
    }
    /**
     * @see com.linkedin.databus.client.netty.AbstractNettyHttpConnection.ConnectResultListener#onConnectSuccess(org.jboss.netty.channel.Channel)
     */
    @Override
    public void onConnectSuccess(Channel channel)
    {
      if (State.CONNECTED != switchToConnected())
      {
        _listener.onConnectFailure(new RuntimeException("unable to connect"));
      }
      else
      {
        _log.info("connected: " + _server.toSimpleString());
        //_channel = future.getChannel();
        //channel.getCloseFuture().addListener(new MyChannelCloseListener());
        _handler.setCloseListener(new NettyChannelCloseListener());
        _channel = channel;
        _listener.onConnectSuccess(channel);
      }
    }

    /**
     * @see com.linkedin.databus.client.netty.AbstractNettyHttpConnection.ConnectResultListener#onConnectFailure(java.lang.Throwable)
     */
    @Override
    public void onConnectFailure(Throwable cause)
    {
      DbusPrettyLogUtils.logExceptionAtError("Connect cancelled/failed", cause, _log);
      switchToInit();
      _listener.onConnectFailure(cause);
    }
  }


  /** Marks the connection as closed */
  private class NettyChannelCloseListener implements ChannelCloseListener
  {

    @Override
    public void onChannelClose()
    {
      _mutex.lock();
      try
      {
        switchToClosing();
        switchToClosed();
        _connectionClosed.signalAll();
        _log.info("connection closed: " + _server.getAddress());
      }
      finally
      {
        _mutex.unlock();
      }
    }
  }

  /** Notifies about channel close */
  public interface ChannelCloseListener
  {
    /** Notifies about channel close */
    public void onChannelClose();
  }

  /** Notifies for the result of connect attempts */
  public interface ConnectResultListener
  {
    /** Notifies about connect success with the given Netty channel */
    void onConnectSuccess(Channel channel);

    /** Notifies about connect failure with the given cause */
    void onConnectFailure(Throwable cause);
  }

  /** Notifies for the result of sending requests */
  public interface SendRequestResultListener
  {
    /** Notifies about success of sending the specified request */
    void onSendRequestSuccess(HttpRequest req);

    /** Notifies about failure of sending the specified request with the given cause */
    void onSendRequestFailure(HttpRequest req, Throwable cause);
  }

  static class BaseHttpResponseProcessor
        extends AbstractHttpResponseProcessorDecorator <ChunkedBodyReadableByteChannel>
  {
    private final ExtendedReadTimeoutHandler _readTimeOutHandler;
    private final AbstractNettyHttpConnection _parent;
    protected String _serverErrorClass;
    protected String _serverErrorMessage;

    /**
     * Constructor
     * @param parent                the AbstractNettyHttpConnection object that instantiated this
     *                              response processor
     * @param readTimeOutHandler    the ReadTimeoutHandler for the connection handled by this
     *                              response handler.
     */
    public BaseHttpResponseProcessor(AbstractNettyHttpConnection parent,
                                     ExtendedReadTimeoutHandler readTimeOutHandler)
    {
      super(null);
      _readTimeOutHandler = readTimeOutHandler;
      _parent = parent;
    }

    /**
     * @see com.linkedin.databus.client.netty.AbstractHttpResponseProcessorDecorator#finishResponse()
     */
    @Override
    public void finishResponse() throws Exception
    {
      try
      {
        super.finishResponse();
      }
      finally
      {
        stopReadTimeoutTimer();
      }
    }

    protected void stopReadTimeoutTimer()
    {
      if (null != _readTimeOutHandler)
      {
        _readTimeOutHandler.stop();
      }
    }

    /**
     * @see com.linkedin.databus.client.netty.AbstractHttpResponseProcessorDecorator#startResponse(org.jboss.netty.handler.codec.http.HttpResponse)
     */
    @Override
    public void startResponse(HttpResponse response) throws Exception
    {
      //check for errors in the response
      _serverErrorClass = response.getHeader(DatabusHttpHeaders.DATABUS_ERROR_CAUSE_CLASS_HEADER);
      _serverErrorMessage = response.getHeader(DatabusHttpHeaders.DATABUS_ERROR_CAUSE_MESSAGE_HEADER);
      if (null == _serverErrorClass)
      {
        _serverErrorClass = response.getHeader(DatabusHttpHeaders.DATABUS_ERROR_CLASS_HEADER);
        _serverErrorMessage = response.getHeader(DatabusHttpHeaders.DATABUS_ERROR_MESSAGE_HEADER);
      }
      if (null != _serverErrorClass)
      {
        if (null != _parent)
        {
          _parent.getLog().error("server error detected class=" + _serverErrorClass +
                                 " message=" + _serverErrorMessage);
        }
      }

      super.startResponse(response);
      if (null != _parent) _parent.addConnectionTracking(response);
    }

    protected AbstractNettyHttpConnection getParent()
    {
      return _parent;
    }

    /* (non-Javadoc)
     * @see com.linkedin.databus.client.netty.HttpResponseProcessorDecorator#addTrailer(org.jboss.netty.handler.codec.http.HttpChunkTrailer)
     */
    @Override
    public void addTrailer(HttpChunkTrailer trailer) throws Exception
    {
      //check for errors in the middle of the response
      if (null == _serverErrorClass)
      {
        _serverErrorClass = trailer.getHeader(DatabusHttpHeaders.DATABUS_ERROR_CAUSE_CLASS_HEADER);
        _serverErrorMessage = trailer.getHeader(DatabusHttpHeaders.DATABUS_ERROR_CAUSE_MESSAGE_HEADER);
        if (null == _serverErrorClass)
        {
          _serverErrorClass = trailer.getHeader(DatabusHttpHeaders.DATABUS_ERROR_CLASS_HEADER);
          _serverErrorMessage = trailer.getHeader(DatabusHttpHeaders.DATABUS_ERROR_MESSAGE_HEADER);
        }
        if (null != _serverErrorClass)
        {
          if (null != _parent)
          {
            _parent.getLog().error("server error detected class=" + _serverErrorClass +
                                   " message=" + _serverErrorMessage);
          }
        }
      }

      super.addTrailer(trailer);
    }

  }

  protected String getHostHdr()
  {
    return _hostHdr;
  }

  protected String getSvcHdr()
  {
    return _svcHdr;
  }

  @Override
  public int getMaxEventVersion()
  {
    // return default
    return DbusEventFactory.DBUS_EVENT_V1;
  }
}
TOP

Related Classes of com.linkedin.databus.client.netty.AbstractNettyHttpConnection$ChannelCloseListener

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.