Package org.xlightweb.client

Source Code of org.xlightweb.client.HttpClientConnection$DoNothingResponseHandler

/*
*  Copyright (c) xlightweb.org, 2008 - 2009. All rights reserved.
*
*  This library is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public
*  License as published by the Free Software Foundation; either
*  version 2.1 of the License, or (at your option) any later version.
*
*  This library is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*  Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public
*  License along with this library; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
* Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
* The latest copy of this software may be found on http://www.xlightweb.org/
*/
package org.xlightweb.client;

import java.io.Closeable;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.nio.channels.ClosedChannelException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.TimerTask;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;


import org.xlightweb.AbstractHttpConnection;
import org.xlightweb.BodyDataSink;
import org.xlightweb.FutureResponseHandler;
import org.xlightweb.HttpResponse;
import org.xlightweb.IFutureResponse;
import org.xlightweb.NonBlockingBodyDataSource;
import org.xlightweb.ResponseHandlerInfo;
import org.xlightweb.IHttpSession;
import org.xlightweb.HttpUtils;
import org.xlightweb.IBodyCompleteListener;
import org.xlightweb.IHttpConnection;
import org.xlightweb.IHttpConnectionHandler;
import org.xlightweb.IHttpExchange;
import org.xlightweb.IHttpMessage;
import org.xlightweb.IHttpRequest;
import org.xlightweb.IHttpRequestHandler;
import org.xlightweb.IHttpRequestHeader;
import org.xlightweb.IHttpResponse;
import org.xlightweb.IHttpResponseHandler;
import org.xlightweb.IHttpResponseHeader;
import org.xlightweb.IHttpSocketTimeoutHandler;
import org.xlightweb.client.HttpClient.TransactionMonitor;

import org.xsocket.DataConverter;
import org.xsocket.Execution;
import org.xsocket.IDestroyable;
import org.xsocket.SerializedTaskQueue;
import org.xsocket.connection.INonBlockingConnection;
import org.xsocket.connection.NonBlockingConnection;





/**
* Represents the client side endpoint implementation of a http connection. The HttpClientConnection supports
* constructors which accepts the remote address or a existing {@link INonBlockingConnection}.  A INonBlockingConnection
* can become a HttpClientConnection at any time.
*
* @author grro@xlightweb.org
*/
public final class HttpClientConnection extends AbstractHttpConnection implements IHttpClientEndpoint {

 
  private static final Logger LOG = Logger.getLogger(HttpClientConnection.class.getName());
 
  private static String implementationVersion;

 
  // life cycle
  private boolean isAutocloseAfterResponse = false;
  private final AtomicBoolean isDisconnected = new AtomicBoolean(false);
 
 
  // response timeout support
  private static final Long DEFAULT_RESPONSE_TIMEOUT_SEC = Long.MAX_VALUE;
  private static final long MIN_WATCHDOG_PERIOD_MILLIS = 30 * 1000;
  private long responseTimeoutMillis = DEFAULT_RESPONSE_TIMEOUT_SEC;
  private WatchDogTask watchDogTask;
 
 
  // handler management
  private final List<MessageHandler> bodyReceivingResponseHandlers = Collections.synchronizedList(new ArrayList<MessageHandler>());
  private final ArrayList<MessageHandler> handlersWaitingForResponseHeader = new ArrayList<MessageHandler>();

 
  // transaction monitor support
  private TransactionMonitor transactionMonitor;
 

 
  /**
   * constructor
   *
   * @param host            the remote host
   * @param port            the remote port
   * @throws ConnectException if an error occurred while attempting to connect to a remote address and port.
   * @throws IOException    if an exception occurs
   */
  public HttpClientConnection(String host, int port) throws IOException, ConnectException {
    this(newNonBlockingConnection(new InetSocketAddress(host, port)), null);
  }

 
  /**
   * constructor
   *
   * @param address the server address         
   * @throws ConnectException if an error occurred while attempting to connect to a remote address and port.
   * @throws IOException    if an exception occurs
   */
  public HttpClientConnection(InetSocketAddress address) throws IOException, ConnectException {
    this(newNonBlockingConnection(address), null);
  }

 

  private static INonBlockingConnection newNonBlockingConnection(InetSocketAddress address) throws ConnectException {
    try {
      return new NonBlockingConnection(address);
    } catch (IOException ioe) {
      throw new ConnectException(ioe.toString());
    }
  }
 
 

  /**
   * constructor
   *
   * @param connection    the underlying tcp connection
   * @throws IOException    if an exception occurs
   */
  public HttpClientConnection(INonBlockingConnection connection) throws IOException {
    this(connection, null);
    init();
  }
 

 
 
  /**
   * constructor
   *
   * @param host               the remote host
   * @param port               the remote port
   * @param connectionHandler  the connection handler
   * @throws ConnectException if an error occurred while attempting to connect to a remote address and port.
   * @throws IOException    if an exception occurs
   */
  public HttpClientConnection(String host, int port, IHttpConnectionHandler connectionHandler) throws IOException, ConnectException {
    this(newNonBlockingConnection(new InetSocketAddress(host, port)), connectionHandler);
  }

 
 
  /**
   * constructor
   *
   * @param connection    the underlying tcp connection
   * @param handler       the handler
   * @throws IOException    if an exception occurs
   */
  private HttpClientConnection(INonBlockingConnection connection, IHttpConnectionHandler connectionHandler) throws IOException {
    super(connection, true);

    if (connectionHandler != null) {
      addConnectionHandler(connectionHandler);
    }
    init();
  }
 
 

  /**
   * sets a transaction monitor
   *
   * @param transactionMonitor the transaction monitor
   */
  void setTransactionMonitor(TransactionMonitor transactionMonitor) {
    this.transactionMonitor = transactionMonitor;
  }

 
 
  /**
     * {@inheritDoc}
     */
  @Override
  protected void onIdleTimeout() throws IOException {

      // notify waiting handler
        for (MessageHandler messageHandler : getHandlersWaitingForResponseCopy()) {
            messageHandler.onException(new SocketTimeoutException("idle timeout " + DataConverter.toFormatedDuration(getIdleTimeoutMillis()) + " reached"));
        }
        handlersWaitingForResponseHeader.clear();

      super.onIdleTimeout();
  }
 
 
 
 
 
  /**
   * {@inheritDoc}
   */
  @Override
  protected void onConnectionTimeout() throws IOException {

    // notify waiting handler
    for (MessageHandler messageHandler : getHandlersWaitingForResponseCopy()) {
      messageHandler.onException(new SocketTimeoutException("connection timeout " + DataConverter.toFormatedDuration(getConnectionTimeoutMillis()) + " reached"));
    }
    handlersWaitingForResponseHeader.clear();

    super.onConnectionTimeout();
  }
 
  @SuppressWarnings("unchecked")
  private List<MessageHandler> getHandlersWaitingForResponseCopy() {
    synchronized (handlersWaitingForResponseHeader) {
      return (List<MessageHandler>) handlersWaitingForResponseHeader.clone();
    }
  }
 
   
  /**
   * {@inheritDoc}
   */ 
  @Override
  protected void onDisconnect() {
   
    if (!isDisconnected.getAndSet(true)) {
     
      // notify pending handlers
      while (!bodyReceivingResponseHandlers.isEmpty()) {
        bodyReceivingResponseHandlers.remove(0).onException(new ClosedChannelException());
      }
     
     
      // notify waiting handler
      for (MessageHandler messageHandler : getHandlersWaitingForResponseCopy()) {
        ExtendedClosedChannelException cce = new ExtendedClosedChannelException("channel " + getId() + " is closed (by peer?) while receiving response data " +
                                                                            "(countMessagesSent=" + getCountMessagesSent() + ", countMessagesReceived=" + getCountMessagesReceived() +
                                                                            ", countSendBytes=" + getCountSendBytes() + ", countReceivedBytes=" + getCountReceivedBytes() + ")");
        System.out.println(getUnderlyingTcpConnection());
        messageHandler.onException(cce);
      }
      handlersWaitingForResponseHeader.clear();
      
      transactionMonitor = null;
     
      if (watchDogTask != null) {
        watchDogTask.close();
      }
      watchDogTask = null;
     
      super.onDisconnect();
    }
  }
 
  private static final class ExtendedClosedChannelException extends ClosedChannelException {
   
    private static final long serialVersionUID = 561074260045048337L;
   
    private final String msg;
   
    public ExtendedClosedChannelException(String msg) {
      this.msg = msg;
    }
   
    @Override
    public String getMessage() {
      return msg;
    }
  }
 
 
 
  /**
   * generates a error page
   *
   *
   * @param errorCode  the error code
   * @param msg        the message
   * @param id         the connection id
   * @return the error page
   */
  protected static String generateErrorMessageHtml(int errorCode, String msg, String id) {
    return AbstractHttpConnection.generateErrorMessageHtml(errorCode, msg, id);
  }

 
  /**
   * returns the body type string
   *
   * @param body  the body
   * @return the body type string
   */
  static String getBodytype(NonBlockingBodyDataSource body) {
    return AbstractHttpConnection.getBodyType(body);
  }
 
 
  /**
   * schedules a timer task
   *
   * @param task     the timer task
   * @param delay    the delay
   * @param period   the period
   */
  protected static void schedule(TimerTask task, long delay, long period) {
    AbstractHttpConnection.schedule(task, delay, period);
  }


  /**
   * {@inheritDoc}
   */
  public IHttpResponse call(IHttpRequest request) throws IOException, ConnectException, SocketTimeoutException {
   
    // create response handler
    FutureResponseHandler responseHandler = new FutureResponseHandler();
   
   
    // send request
    send(request, wrapResponsehandler(responseHandler));
    try {
        return responseHandler.getResponse();
    } catch (InterruptedException ie) {
        throw new IOException(ie.toString());
    }
  }

 
  /**
   * set if the connection should be closed after sending a response
   *
   * @param isAutocloseAfterResponse true, if the connection should be closed after sending a response
   */
  void setAutocloseAfterResponse(boolean isAutocloseAfterResponse) {
    this.isAutocloseAfterResponse = isAutocloseAfterResponse;
  }
 

  /**
     * {@inheritDoc}
     */
  public boolean isServerSide() {
      return false;
  }
 

  /**
   * {@inheritDoc}
   */
  public void setResponseTimeoutMillis(long responseTimeoutMillis) {
   
   
    if (this.responseTimeoutMillis != responseTimeoutMillis) {
      this.responseTimeoutMillis = responseTimeoutMillis;

      if (responseTimeoutMillis == Long.MAX_VALUE) {
        terminateWatchDogTask();
       
      } else {
       
        long watchdogPeriod = 100;
        if (responseTimeoutMillis > 1000) {
          watchdogPeriod = responseTimeoutMillis / 10;
        }
       
        if (watchdogPeriod > MIN_WATCHDOG_PERIOD_MILLIS) {
          watchdogPeriod = MIN_WATCHDOG_PERIOD_MILLIS;
        }
     
        if (LOG.isLoggable(Level.FINE)) {
          LOG.fine("[" + getId() +"] response timeout to " + DataConverter.toFormatedDuration(responseTimeoutMillis) + ". Updating wachdog tas to check period " + watchdogPeriod + " millis");
        }
       
        updateWatchDog(watchdogPeriod);
      }
    }
  }
 
 
  private synchronized void updateWatchDog(long watchDogPeriod) {
    terminateWatchDogTask();

        watchDogTask = new WatchDogTask(this);
        schedule(watchDogTask, watchDogPeriod, watchDogPeriod);
  }

 
 
  private synchronized void terminateWatchDogTask() {
    if (watchDogTask != null) {
            watchDogTask.close();
        }
  }

 
  /**
   * {@inheritDoc}
   */
  public long getResponseTimeoutMillis() {
    return responseTimeoutMillis;
  }
 



  private void checkTimeouts() {
    try {
     
      long currentMillis = System.currentTimeMillis();
     
      for (Object hdl : handlersWaitingForResponseHeader.toArray()) {
        boolean isTimeoutReached = ((MessageHandler) hdl).isResponseTimeoutReached(currentMillis);
        if (isTimeoutReached) {
         
          if (handlersWaitingForResponseHeader.remove(hdl)) {
            onResponseTimeout((MessageHandler) hdl);
          }
         
          destroy();
        }
      }
     
    } catch (Exception e) {
            // eat and log exception
      LOG.warning("exception occured by checking timouts. Reason: " + e.toString());
    }
  }


  private void onResponseTimeout(MessageHandler internalResponseHandler) {
   
    SocketTimeoutException ste = new SocketTimeoutException("response timeout " + DataConverter.toFormatedDuration(internalResponseHandler.getTimeout()) + " reached");
   
    if (LOG.isLoggable(Level.FINE)) {
      LOG.fine(ste.getMessage());
    }
   
    performResponseTimeoutHandler(internalResponseHandler.getAppHandler(), ste);
  }
 
 
 
  private void performResponseTimeoutHandler(final IHttpResponseHandler handler, final SocketTimeoutException ste) {

    final ResponseHandlerInfo responseHandlerInfo = AbstractHttpConnection.getResponseHandlerInfo(handler);
   
   
    Runnable responseTimeoutHandlerCaller = new Runnable()  {
     
      public void run() {

        try {
          if ( responseHandlerInfo.isSocketTimeoutHandler()) {
            IHttpSocketTimeoutHandler hdl = (IHttpSocketTimeoutHandler) handler;
            hdl.onException(ste);
           
          } else {
            handler.onException(ste);
          }
           
        } catch (Exception e) {
          if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("[" +getId() + "] error occured by calling on request " + handler + " " + e.toString());
          }
          throw new RuntimeException(e);
         
        } finally {
          destroy();
        }
      }   
    };
     
    // ... and perform the handler
    if (responseHandlerInfo.isSocketTimeoutHandlerMultithreaded()) {
      getExecutor().processMultithreaded(responseTimeoutHandlerCaller);

    } else {
      getExecutor().processNonthreaded(responseTimeoutHandlerCaller);
    }     
  }
 

 
  /**
   * {@inheritDoc}
   */
  public BodyDataSink send(IHttpRequestHeader requestHeader, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
   
    if (isOpen()) {
     
      if (responseHandler == null) {
        responseHandler = new DoNothingResponseHandler();
      }

     
      if (requestHeader.getContentLength() != -1) {
        requestHeader.removeHeader("Content-Length");
      }
              
      if ((requestHeader.getTransferEncoding() == null)) {
        requestHeader.setHeader("Transfer-Encoding", "chunked");
      }

      enhanceHeader(requestHeader);

     
      try{
        return sendInternal(requestHeader, responseHandler);
       
      } catch (IOException ioe) {
        String msg = "can not send request \r\n " + requestHeader.toString() +
         "\r\n\r\nhttpConnection:\r\n" + this.toString() +
              "\r\n\r\nreason:\r\n" + ioe.toString();

        destroy();
        throw new IOException(msg);
      }
     
    } else {
      throw new ClosedChannelException();
    }   
  }
 
 
 
  private BodyDataSink sendInternal(IHttpRequestHeader requestHeader, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
   
    prepareResponseHandler(responseHandler, this);
   
    synchronized (handlersWaitingForResponseHeader) {
      handlersWaitingForResponseHeader.add(new MessageHandler(responseHandler, requestHeader, responseTimeoutMillis));
    }

   
    BodyDataSink bodyDataSink = writeMessage(requestHeader, false);

    return bodyDataSink;   
  }

 

  /**
   * {@inheritDoc}
   */
  public BodyDataSink send(IHttpRequestHeader requestHeader, int contentLength, IHttpResponseHandler responseHandler) throws IOException, ConnectException {

    if (isOpen()) {

      if (responseHandler == null) {
        responseHandler = new DoNothingResponseHandler();
      }

     
      if (requestHeader.getContentLength() != -1) {
        requestHeader.removeHeader("Content-Length");
      }
              
      if ((requestHeader.getTransferEncoding() == null)) {
        requestHeader.setHeader("Transfer-Encoding", "chunked");
      }


     
      enhanceHeader(requestHeader);


      try{
         if ((requestHeader.getTransferEncoding() != null) && (requestHeader.getTransferEncoding().equalsIgnoreCase("chunked"))) {
           requestHeader.removeHeader("Transfer-Encoding");
         }
           
         if (requestHeader.getContentLength() == -1) {
           requestHeader.setContentLength(contentLength);
         }
           
        return sendInternal(requestHeader, contentLength, responseHandler);
       
      } catch (IOException ioe) {
        String msg = "can not send request \r\n " + requestHeader.toString() +
         "\r\n\r\nhttpConnection:\r\n" + this.toString() +
              "\r\n\r\nreason:\r\n" + ioe.toString();
 
        destroy();
        throw new IOException(msg);
      }
     
    } else {
      throw new ClosedChannelException();
    }   

  }
 
 
  private BodyDataSink sendInternal(IHttpRequestHeader requestHeader, int contentLength, IHttpResponseHandler responseHandler) throws IOException, ConnectException {

    prepareResponseHandler(responseHandler, this);

    synchronized (handlersWaitingForResponseHeader) {
      handlersWaitingForResponseHeader.add(new MessageHandler(responseHandler, requestHeader, responseTimeoutMillis));
    }
   
    BodyDataSink bodyDataSink = writeMessage(requestHeader, false, contentLength);

    return bodyDataSink;
  }

 
 
  /**
   * {@inheritDoc}
   */
  public void send(IHttpRequest request, IHttpResponseHandler responseHandler) throws IOException, ConnectException {

    if (isOpen()) {

      if (responseHandler == null) {
        responseHandler = new DoNothingResponseHandler();
      }
     
      IHttpRequestHeader requestHeader = request.getRequestHeader();
      enhanceHeader(requestHeader);     

      try {
        sendInternal(request, responseHandler);                 
       
      } catch (IOException ioe) {
        String msg = "can not send request \r\n " + request.toString() +
               "\r\n\r\nhttpConnection:\r\n" + this.toString() +
                     "\r\n\r\nreason:\r\n" + ioe.toString();
       
        destroy();
        throw new IOException(msg);
      }
       
    } else {
      throw new ClosedChannelException();
    }
  }
 
 

    /**
     * {@inheritDoc}
     */
    public IFutureResponse send(IHttpRequest request) throws IOException, ConnectException {
        FutureResponseHandler responseHandler = new FutureResponseHandler();
       
        send(request, responseHandler);
       
        return responseHandler;
    }
 
 
  private void sendInternal(IHttpRequest request, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
   
    prepareResponseHandler(responseHandler, this);

    synchronized (handlersWaitingForResponseHeader) {
      handlersWaitingForResponseHeader.add(new MessageHandler(responseHandler, request.getRequestHeader(), responseTimeoutMillis));
    }

   
    // body less request?
    if (request.getNonBlockingBody() == null) {

      if (LOG.isLoggable(Level.FINE)) {
        LOG.fine("[" + getId() + "] sending (bodyless): " + request.getRequestHeader());
      }

      BodyDataSink bodyDataSink = writeMessage(request.getRequestHeader(), false, 0);
      bodyDataSink.setFlushmode(FlushMode.ASYNC);
      bodyDataSink.close();
     
     
     
    // no, request has body
    } else {
     
      if (request.getNonBlockingBody().getDataHandler() != null) {
        throw new IOException("a body handler is already assigned to the message body. sending such messages is not supported (remove data handler)");
      }

      writeMessage(request, false);
    }   
  }
 
 
  private static void prepareResponseHandler(IHttpResponseHandler responseHandler, IDestroyable destroyable) {
     
      if (responseHandler instanceof FutureResponseHandler) {
          setUnderlyingResource((FutureResponseHandler) responseHandler, destroyable);
      }
  }

 


  private IHttpResponseHandler wrapResponsehandler(IHttpResponseHandler responseHandler) {
   
    if (AbstractHttpConnection.getResponseHandlerInfo(responseHandler).isResponseHandlerInvokeOnMessageReceived()) {
      return new InvokeOnMessageWrapper(responseHandler, this);
    } else {
      return responseHandler;
    }
  }
 
 
 
 

  private void enhanceHeader(IHttpRequestHeader header) throws IOException {
    String host = header.getHost();
    if (host == null) {
      header.setHost(getRemoteHostInfo());
    }
       
    String userAgent = header.getUserAgent();
    if (userAgent == null) {
      header.setUserAgent(getImplementationVersion());
    }   
  }
 

  
  private static String getImplementationVersion() {
    if (implementationVersion == null) {
      implementationVersion = "xLightweb/" + HttpUtils.getImplementationVersion();
    }
   
    return implementationVersion;
  }

 
  private String getRemoteHostInfo() throws IOException {
    InetAddress remoteAddress = getRemoteAddress();
   
    if (remoteAddress == null) {
      return "";
    }
   
    int remotePort = getRemotePort();
   
    return remoteAddress.getHostName() + ":" + remotePort;
  }
 

 

  /**
   * {@inheritDoc}
   */
  @Override
  protected IMessageHandler getMessageHandler() {
    synchronized (handlersWaitingForResponseHeader) {
      if (handlersWaitingForResponseHeader.isEmpty()) {
        return null;
      } else {
        return handlersWaitingForResponseHeader.remove(0);
      }
    }
  }

 
  /**
   * {@inheritDoc}
   */
  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append(getId() + " " + getUnderlyingTcpConnection().getLocalAddress() + ":" + getUnderlyingTcpConnection().getLocalPort() + " -> " + getUnderlyingTcpConnection().getRemoteAddress() + ":" + getUnderlyingTcpConnection().getRemotePort());
    if (!getUnderlyingTcpConnection().isOpen()) {
      sb.append("  (closed)");
    }
   
    return sb.toString();
  }
 
 
 
  /**
   * message handler
   *
   * @author grro
   */
  final class MessageHandler implements IMessageHandler, Runnable {

    private final AtomicBoolean isCommitted = new AtomicBoolean(false);
   
    private IHttpResponseHandler delegee = null;
    private ResponseHandlerInfo delegeeInfo = null;
    private IHttpRequestHeader requestHeader = null;
   
    private IHttpResponse response = null;
   
    private long timeout = Long.MAX_VALUE;
    private long timeoutDate = Long.MAX_VALUE;
   
   

    /**
     * constructor
     *
     * @param delegee         the response handler
     * @param requestHeader   the request header
     * @param responseTimeout hte response timeout
     */
    public MessageHandler(IHttpResponseHandler delegee, IHttpRequestHeader requestHeader, long responseTimeout) {
      this.delegee = delegee;
      this.delegeeInfo = AbstractHttpConnection.getResponseHandlerInfo(delegee);
      this.requestHeader = requestHeader;
     
      this.timeout = responseTimeout;
      if ((responseTimeout != Long.MAX_VALUE) && (responseTimeout < Long.MAX_VALUE * 0.9)) {
        timeoutDate = timeout + System.currentTimeMillis();
      }
    }
   
   
    /**
     * returns the response handler
     *
     * @return the response handler
     */
    IHttpResponseHandler getAppHandler() {
      return delegee;
    }


    /**
     * returns if the response timeout is reached
     * 
     * @param currentMillis the current time
     * @return true, if the respnose timeout is reached
     */
    boolean isResponseTimeoutReached(long currentMillis) {
      if (isCommitted.get()) {
        return false;
      }
     
      return (currentMillis > timeoutDate);
    }
   
   
    /**
     * returns the timeout
     *
     * @return the timeout
     */
    long getTimeout() {
      return timeout;
    }
   
   
   
    /**
     * {@inheritDoc}
     */
    public boolean isBodylessMessageExpected() {
      if (requestHeader.getMethod().equals(IHttpMessage.HEAD_METHOD) ||
        requestHeader.getMethod().equals(IHttpMessage.OPTIONS_METHOD) ||
        requestHeader.getMethod().equals(IHttpMessage.CONNECT_METHOD)) {
        return true;
      }
     
      return false;
    }
   
   
   
   
    /**
     * {@inheritDoc}
     */
    public void onMessage(IHttpMessage message) throws IOException {
      this.response = (IHttpResponse) message;
     
      incCountMessageReceived();
     
      if (transactionMonitor != null) {
        transactionMonitor.register(HttpClientConnection.this, requestHeader, response);
      }
     
      if (LOG.isLoggable(Level.FINE)) {
       
        if (response.getNonBlockingBody() == null) {
          LOG.fine("[" + getId() + "] bodyless response received from " + getRemoteAddress() +
               ":" + getRemotePort() +
               " (" + getCountMessagesReceived() + ". request) " + response.getMessageHeader().toString());
       
        } else {
          String body = "";
         
          String contentType = response.getContentType();
          if ((contentType != null) && (contentType.startsWith("application/x-www-form-urlencode"))) {
            body = response.getNonBlockingBody().toString() + "\n";
          }
         
          LOG.fine("[" + getId() + "]response received from " + getRemoteAddress() +
               ":" + getRemotePort() +
               " (" + getCountMessagesReceived() + ". request) " + response.getMessageHeader().toString() + body);
        }
      }
         
     
      // handle life cycle headers
      handleLifeCycleHeaders(response);
     
     
     
      // swallow 100 response
      if (response.getStatus() == 100) {
        synchronized (handlersWaitingForResponseHeader) {
          ArrayList<MessageHandler> hdls = new ArrayList<MessageHandler>();
          handlersWaitingForResponseHeader.removeAll(hdls);
          handlersWaitingForResponseHeader.add(this);
          handlersWaitingForResponseHeader.addAll(hdls);
        }
       
        setPersistent(true);
        return;
      }
     
      // if response code 5xx -> set connection not reusable
      if ((response.getStatus() >= 500) && (response.getStatus() < 600)) {
        if (LOG.isLoggable(Level.FINE)) {
          LOG.fine("got return code 5xx. Set connection " + getId() + " to non persistent");
        }
        setPersistent(false);
      }
     
      if (isPersistent() == false) {
        if (response.hasBody()) {
          setDestroyConnectionAfterReceived(response.getNonBlockingBody(), true);
        } else {
          destroy()// destroy required, because underlying tcp connection could be a pooled one
        }
       
      } else {
        if (isAutocloseAfterResponse) {
          if (response.hasBody()) {
            setCloseConnectionAfterReceived(response.getNonBlockingBody(), true);
          } else {
            closeSilence();
          }
        }
      }
     
      if (response.hasBody() && delegeeInfo.isResponseHandlerInvokeOnMessageReceived()) {
        bodyReceivingResponseHandlers.add(this);
       
        IBodyCompleteListener cl = new IBodyCompleteListener() {
         
          @Execution(Execution.NONTHREADED)
          public void onComplete() throws IOException {
           
            bodyReceivingResponseHandlers.remove(this);
            isCommitted.set(true);
           
            if (delegeeInfo.isResponseHandlerMultithreaded()) {
              getExecutor().processMultithreaded(MessageHandler.this);
            } else {
              getExecutor().processNonthreaded(MessageHandler.this);
            }
          }
        };
       
        response.getNonBlockingBody().addCompleteListener(cl);
       
      } else {
        isCommitted.set(true);
       
        if (delegeeInfo.isResponseHandlerMultithreaded()) {
          getExecutor().processMultithreaded(this);
        } else {
          getExecutor().processNonthreaded(this);
        }
      }
    }
   
   
    /**
     * {@inheritDoc}
     */
    public void run() {
      try {
        delegee.onResponse(response);
      } catch (IOException ioe) {
        if (LOG.isLoggable(Level.FINE)) {
          LOG.fine("Error occured by calling onResponse of " + delegee + " " + ioe.toString());
        }
      }
    }


   
    private void handleLifeCycleHeaders(IHttpResponse response) throws IOException {

      // if HTTP 1.1 -> connection is persistent by default
      if ((response.getProtocol() != null) && response.getProtocol().equals("HTTP/1.1")) {
        setPersistent(true && getPersistent());
       
      } else {
       
        // response of connect is persistent, implicitly
        if (requestHeader.getMethod().equals(IHttpMessage.CONNECT_METHOD)) {
          setPersistent(true && getPersistent());
        }
      }       

   
      // handle connection header
      handleConnectionHeaders(response.getResponseHeader());     
    }

   
   
    private void handleConnectionHeaders(IHttpResponseHeader responseHeader) throws IOException
     
      String keepAliveHeader = responseHeader.getKeepAlive();
      if (keepAliveHeader != null) {
        String[] tokens = keepAliveHeader.split(",");
        for (String token : tokens) {
          handleKeepAlive(token);
        }
      }
     
     
      //check if persistent connection
      String connectionHeader = responseHeader.getConnection();
     
     
      if (connectionHeader != null) {
        String[] values = connectionHeader.split(",");
       
       
        for (String value : values) {
          value = value.trim();
         
          if (value.equalsIgnoreCase("close")) {
            if (LOG.isLoggable(Level.FINER)) {
              LOG.finer("[" + getId() + " http client connection received 'connection: close' header. set isPersistent=false");
            }
            setPersistent(false);
          }
        }
      }
    }

   
   
    private void handleKeepAlive(String option) {
        if (option.toUpperCase().startsWith("TIMEOUT=")) {
                int timeoutSec = Integer.parseInt(option.substring("TIMEOUT=".length(), option.length()));
               
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine("response keep alive defines timout. set timeout " + timeoutSec + " sec");
                }
                setResponseTimeoutMillis(timeoutSec * 1000L);

               
            } else if (option.toUpperCase().startsWith("MAX=")) {
                int maxTransactions = Integer.parseInt(option.substring("MAX=".length(), option.length()));
                if (maxTransactions == 0) {
                    setPersistent(false);
                }
               
            } else {
                Integer timeoutSec = Integer.parseInt(option);
               
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine("response keep alive defines timout. set timeout " + timeoutSec + " sec");
                }
                setResponseTimeoutMillis(timeoutSec * 1000L);
            }     
    }

   
    /**
     * {@inheritDoc}
     */
    public void onException(final IOException ioe) {
     
      if (isCommitted.get()) {
        return;
      }
     
      isCommitted.set(true);
     
      if ((ioe instanceof SocketTimeoutException) && (delegeeInfo.isSocketTimeoutHandler())) {
       
        Runnable task = new OnSocketTimeoutExceptionCaller((SocketTimeoutException) ioe, (IHttpSocketTimeoutHandler) delegee);
       
        if (delegeeInfo.isSocketTimeoutHandlerMultithreaded()) {
          getExecutor().processMultithreaded(task);
        } else {
          getExecutor().processNonthreaded(task);
        }
       
       
      } else {
       
        Runnable task = new OnIOExceptionCaller(ioe, delegee);
       
        if (delegeeInfo.isResponseExeptionHandlerMultithreaded()) {
          getExecutor().processMultithreaded(task);
        } else {
          getExecutor().processNonthreaded(task);
        }
       
      }
    }
  }
 

  private static final class OnSocketTimeoutExceptionCaller implements Runnable {
   
    private SocketTimeoutException ste = null;
    private IHttpSocketTimeoutHandler hdl = null;
   
    public OnSocketTimeoutExceptionCaller(SocketTimeoutException ste, IHttpSocketTimeoutHandler hdl) {
      this.ste = ste;
      this.hdl = hdl;
    }
   
    public void run() {
      hdl.onException(ste);
    }   
  }
 
 
  private static final class OnIOExceptionCaller implements Runnable {

    private IOException ioe = null;
    private IHttpResponseHandler hdl = null;
   

    public OnIOExceptionCaller(IOException ioe, IHttpResponseHandler hdl) {
      this.ioe = ioe;
      this.hdl = hdl;
    }
   
    public void run() {
      try {
        hdl.onException(ioe);
      } catch (IOException ioe) {
        if (LOG.isLoggable(Level.FINE)) {
          LOG.fine("Error occured by performing onException " + hdl + ". reason: " + ioe.toString());
        }
      }
    }
  }

  
  private static final class WatchDogTask extends TimerTask implements Closeable {
     
     private WeakReference<HttpClientConnection> httpClientConnectionRef;
     
     public WatchDogTask(HttpClientConnection httpClientConnection) {
       httpClientConnectionRef = new WeakReference<HttpClientConnection>(httpClientConnection);
     }
   
     
     @Override
     public void run() {
       WeakReference<HttpClientConnection> ref = httpClientConnectionRef;
       if (ref != null) {
         HttpClientConnection httpClientConnection = ref.get();
         
         if (httpClientConnection == null)  {
           this.close();           
         } else {
           httpClientConnection.checkTimeouts();
         }
       }
     } 
    
     public void close() {
       this.cancel();
       httpClientConnectionRef = null;
    }
   }


   /**
    * wrapper
    * 
    * @author grro@xlightweb.org
    */
   static final class InvokeOnMessageWrapper implements IHttpResponseHandler {

     private static final Logger LOG = Logger.getLogger(InvokeOnMessageWrapper.class.getName());
    
     private IHttpResponseHandler delegee = null;
     private ResponseHandlerInfo delegeeInfo = null;
    
     private HttpClientConnection connection = null;
    
    
     public InvokeOnMessageWrapper(IHttpResponseHandler delegee, HttpClientConnection connection) {
       this.delegee = delegee;
       this.delegeeInfo = AbstractHttpConnection.getResponseHandlerInfo(delegee);
       this.connection = connection;
     }
    
     @Execution(Execution.NONTHREADED)
     public void onResponse(final IHttpResponse response) throws IOException {
      
      
       final Runnable task = new Runnable() {
         public void run() {
           try {
             delegee.onResponse(response);
           } catch (IOException ioe) {
             if (LOG.isLoggable(Level.FINE)) {
               LOG.fine("Error occured by calling onResponse of " + delegee + " " + ioe.toString());
             }
           }
         }
       };
      
       if (response.hasBody()) {
         IBodyCompleteListener cl = new IBodyCompleteListener() {
           public void onComplete() throws IOException {
             if (delegeeInfo.isResponseHandlerMultithreaded()) {
               connection.getExecutor().processMultithreaded(task);
             } else {
               connection.getExecutor().processNonthreaded(task);
             }
           }
         };
        
         response.getNonBlockingBody().addCompleteListener(cl);
        
       } else {
         if (delegeeInfo.isResponseHandlerMultithreaded()) {
           connection.getExecutor().processMultithreaded(task);
         } else {
           connection.getExecutor().processNonthreaded(task);
         }
       }
     }
    
     @Execution(Execution.NONTHREADED)
     public void onException(final IOException ioe) {
      
       Runnable task = new Runnable() {
         public void run() {
           try {
             delegee.onException(ioe);
          } catch (IOException ioe) {
            if (LOG.isLoggable(Level.FINE)) {
              LOG.fine("Error occured by performing onException " + delegee + ". reason: " + ioe.toString());
            }
          }
         }
          
       };
      
       if (delegeeInfo.isResponseExeptionHandlerMultithreaded()) {
         connection.getExecutor().processMultithreaded(task);
       } else {
         connection.getExecutor().processNonthreaded(task);
       }
     }
   }
  

  
   static IBodySinkPair newBodySinkPair(AbstractHttpConnection connection, IMultimodeExecutor executor, String encoding) throws IOException {
     final DataSourceSinkPair pair = AbstractHttpConnection.newBodyDataSourceSinkPair(null, executor, encoding);
    
     return new IBodySinkPair() {
      
       public BodyDataSink getBodyDataSink() {
        return pair.getBodyDataSink();
       }
      
       public NonBlockingBodyDataSource getBodyDataSource() {
        return pair.getBodyDataSource();
      }
     };
   }
  
  
   static interface IBodySinkPair {
     BodyDataSink getBodyDataSink();
    
     NonBlockingBodyDataSource getBodyDataSource();
   }
  

   static final class ClientExchange implements IHttpExchange {
     
     private boolean isResponseCommitted = false;
   
     private HttpClientConnection con = null;
     private IHttpRequest request = null;
     private String targetURL = null;
     private IHttpResponseHandler responseHandler = null;
     private ResponseHandlerInfo responseHandlerInfo = null;
    
     private HttpClient httpClient = null;
     private IMultimodeExecutor executor = null;

    
     public ClientExchange(HttpClient httpClient, Executor workerpool) {
       this.httpClient = httpClient;
       executor = new MultimodeExecutor(workerpool);
     }
    
     void init(IHttpRequest request, IHttpResponseHandler responseHandler) {
       this.request = request;
       this.responseHandler = responseHandler;
       this.responseHandlerInfo = AbstractHttpConnection.getResponseHandlerInfo(responseHandler);
      
       prepareResponseHandler(responseHandler, this);
       targetURL = request.getRequestUrl().toString();
       if (targetURL.indexOf("?") != -1) {
         targetURL = targetURL.substring(0, targetURL.indexOf("?"));
       }
     }
    
     IMultimodeExecutor getExecutor() {
       return executor;
     }
    
    
     /**
      * {@inheritDoc}
      */
     public IHttpRequest getRequest() {
      return request;
     }
   
    
     /**
      * {@inheritDoc}
      */
     public IHttpSession getSession(boolean create) {
       return httpClient.getSessionManager().getSession(httpClient, request.getRemoteHost(), request.getRequestURI(), create);
     }

     public String encodeURL(String url) {
      return url;
     }
    
 
    
     /**
      * {@inheritDoc}
      */
     public void forward(IHttpRequest request) throws IOException, ConnectException {
       forward(request, new ForwardingResponseHandler(this));
     }
    
    
     /**
      * {@inheritDoc}
      */
     public void forward(IHttpRequest request, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
      
       if (responseHandler == null) {
         responseHandler = new DoNothingResponseHandler();
       }
      
       URL targetURL = request.getRequestUrl();
       con = httpClient.getConnection(request.isSecure(), targetURL.getHost(), targetURL.getPort(), targetURL.getProtocol(), (IHttpRequestHandler) null);
       con.send(request, responseHandler);
     }
    
    
    
     /**
      * {@inheritDoc}
      */
     public BodyDataSink forward(IHttpRequestHeader requestHeader) throws IOException, ConnectException, IllegalStateException {
      return forward(requestHeader, new ForwardingResponseHandler(this));
    }
    
    
    
     /**
      * {@inheritDoc}
      */
     public BodyDataSink forward(IHttpRequestHeader requestHeader, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
       
       if (responseHandler == null) {
         responseHandler = new DoNothingResponseHandler();
       }
      
       if (requestHeader.getContentLength() != -1) {
         requestHeader.removeHeader("Content-Length");
       }
       
       if (requestHeader.getMethod().equalsIgnoreCase("GET") || requestHeader.getMethod().equalsIgnoreCase("HEAD")) {
         throw new IOException(requestHeader.getMethod() + " is a bodyless request");
       }

       if ((requestHeader.getTransferEncoding() == null)) {
         requestHeader.setHeader("Transfer-Encoding", "chunked");
       }
       
      
       URL targetURL = requestHeader.getRequestUrl();
       con = httpClient.getConnection(requestHeader.isSecure(), targetURL.getHost(), targetURL.getPort(), targetURL.getProtocol(), null);
       return con.send(requestHeader, responseHandler);
     }
    
    
     public BodyDataSink forward(IHttpRequestHeader requestHeader, int contentLength) throws IOException, ConnectException, IllegalStateException {
      return forward(requestHeader, contentLength, new ForwardingResponseHandler(this));
    }
    
    
     /**
      * {@inheritDoc}
      */
     public BodyDataSink forward(IHttpRequestHeader requestHeader, int contentLength, IHttpResponseHandler responseHandler) throws IOException, ConnectException {

       if (responseHandler == null) {
         responseHandler = new DoNothingResponseHandler();
       }
      
       if ((requestHeader.getTransferEncoding() != null) && (requestHeader.getTransferEncoding().equalsIgnoreCase("chunked"))) {
         requestHeader.removeHeader("Transfer-Encoding");
       }
       
       if (requestHeader.getMethod().equalsIgnoreCase("GET") || requestHeader.getMethod().equalsIgnoreCase("HEAD")) {
         throw new IOException(requestHeader.getMethod() + " is a bodyless request");
       }

       if (requestHeader.getContentLength() == -1) {
         requestHeader.setContentLength(contentLength);
       }
       
       URL targetURL = requestHeader.getRequestUrl();
       con = httpClient.getConnection(requestHeader.isSecure(), targetURL.getHost(), targetURL.getPort(), targetURL.getProtocol(), null);
       return con.send(requestHeader, contentLength, responseHandler);
     }
    
     
    
     
     public BodyDataSink send(IHttpResponseHeader header) throws IOException, IllegalStateException {
       
       if (header.getContentLength() != -1) {
         header.removeHeader("Content-Length");
       }
            
       if ((header.getTransferEncoding() == null)) {
         header.setHeader("Transfer-Encoding", "chunked");
       }
     
       DataSourceSinkPair pair = newBodyDataSourceSinkPair((AbstractHttpConnection) getConnection(), executor, header.getCharacterEncoding());
       
       send(new HttpResponse(header, pair.getBodyDataSource()));
       return pair.getBodyDataSink();
     }


     public BodyDataSink send(IHttpResponseHeader header, int contentLength) throws IOException, IllegalStateException {
         
       if ((header.getTransferEncoding() != null) && (header.getTransferEncoding().equalsIgnoreCase("chunked"))) {
         header.removeHeader("Transfer-Encoding");
       }
         
       if (header.getContentLength() == -1) {
         header.setContentLength(contentLength);
       }
         
     
       DataSourceSinkPair pair = newBodyDataSourceSinkPair((AbstractHttpConnection) getConnection(), executor, header.getCharacterEncoding());
       
       send(new HttpResponse(header, pair.getBodyDataSource()));
       return pair.getBodyDataSink();
     }
   
     
     
    
   
     /**
      * {@inheritDoc}
      */
     public void send(final IHttpResponse response) throws IOException, IllegalStateException {
      if (isResponseCommitted) {
        throw new IllegalStateException("response is already committed");
      }     
      isResponseCommitted = true;

     
      if (responseHandler == null) {
        LOG.warning("response will not been send, because no response handler is assigned");
        return;
      }
     
     
      if (responseHandlerInfo.isResponseHandlerInvokeOnMessageReceived()) {
       
        IBodyCompleteListener completeListener = new IBodyCompleteListener() {
         
          @Execution(Execution.NONTHREADED)
          public void onComplete() throws IOException {
            performOnResponse(response);
          }
        };
        response.getNonBlockingBody().addCompleteListener(completeListener);
       
      } else {
        performOnResponse(response);
      }
    }
   
   
    private void performOnResponse(final IHttpResponse response) throws IOException {
     
      Runnable task = new Runnable() {
       
        public void run() {
          try {
            responseHandler.onResponse(response);
          } catch (IOException ioe) {
            if (LOG.isLoggable(Level.FINE)) {
              LOG.fine("error occured by performing on response on " + responseHandler);
            }
          }
        }
      };
     
     
      if (responseHandlerInfo.isResponseHandlerMultithreaded()) {
        executor.processMultithreaded(task);
      } else {
        executor.processNonthreaded(task);
      }
    }
   
   
   

   
    public IHttpConnection getConnection() {
      return con;
    }
   
    public void destroy() {
      if (con != null) {
        con.destroy();
      }
    }
   
   
    public void sendError(int errorCode) throws IllegalStateException {
      sendError(errorCode, HttpUtils.getReason(errorCode));
    }
   
   
   
    public void sendError(int errorCode, String msg) throws IllegalStateException {
      try {
        String id = "";
        IHttpConnection connection = getConnection();
        if (connection != null) {
          id = connection.getId();
        }
        send(new HttpResponse(errorCode, "text/html", HttpClientConnection.generateErrorMessageHtml(errorCode, msg, id)));
      } catch (IOException ioe) {
        if (LOG.isLoggable(Level.FINE)) {
          LOG.fine("could not send error message " + errorCode + " reason " + ioe.toString());
        }
        destroy();
      }
    }
   

   
    public void sendError(final Exception e) throws IllegalStateException {

      if (isResponseCommitted) {
        throw new IllegalStateException("response is already committed");
      }     
      isResponseCommitted = true;

     
      if (responseHandlerInfo.isSocketTimeoutHandler() && (e instanceof SocketTimeoutException)) {

        Runnable task = new Runnable() {
          public void run() {
            ((IHttpSocketTimeoutHandler) responseHandler).onException((SocketTimeoutException) e);
          }
        };

        if (responseHandlerInfo.isSocketTimeoutHandlerMultithreaded()) {
          executor.processMultithreaded(task);
        } else {
          executor.processNonthreaded(task);
        }
       
       
      } else if (e instanceof IOException) {
       
        Runnable task = new Runnable() {
          public void run() {
            try {
              responseHandler.onException((IOException) e);
            } catch (IOException ioe) {
              if (LOG.isLoggable(Level.FINE)) {
                LOG.fine("Error occured by performing onException " + responseHandler + ". reason: " + ioe.toString());
              }
            }
          }
        };

       
        if (responseHandlerInfo.isResponseExeptionHandlerMultithreaded()) {
          executor.processMultithreaded(task);
        } else {
          executor.processNonthreaded(task);
        }
       
      } else {
        if (LOG.isLoggable(Level.FINE)) {
          LOG.fine("error occured. sendig 500. Error: " + DataConverter.toString(e));
        }
       
        if (HttpUtils.isShowDetailedError()) {
          sendError(500, DataConverter.toString(e));
        } else {
          sendError(500);
        }
      }
    }
   }
  
  
  private static final class MultimodeExecutor implements IMultimodeExecutor  {
   
    private final SerializedTaskQueue taskQueue = new SerializedTaskQueue();

    private Executor workerpool = null;
   
    public MultimodeExecutor(Executor workerpool) {
      this.workerpool = workerpool;
    }

    public void processMultithreaded(Runnable task) {
      taskQueue.performMultiThreaded(task, workerpool);
    }

    public void processNonthreaded(Runnable task) {
      taskQueue.performNonThreaded(task, workerpool);
    }
  }
 
 
  @Execution(Execution.NONTHREADED)
  private static final class ForwardingResponseHandler implements IHttpResponseHandler {

    private IHttpExchange exchange = null;
   
    public ForwardingResponseHandler(IHttpExchange exchange) {
      this.exchange = exchange;
    }

   
    public void onResponse(IHttpResponse response) throws IOException {
      exchange.send(response);
    }

    public void onException(IOException ioe) throws IOException {
      exchange.sendError(ioe);
    }
  }
 

  @Execution(Execution.NONTHREADED)
  static final class DoNothingResponseHandler implements IHttpResponseHandler {

    public void onResponse(IHttpResponse response) throws IOException { }

    public void onException(IOException ioe) throws IOException { }
  }
}
TOP

Related Classes of org.xlightweb.client.HttpClientConnection$DoNothingResponseHandler

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.