Package org.xlightweb.client

Source Code of org.xlightweb.client.HttpClient

/*
*  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.net.ConnectException;
import java.net.SocketTimeoutException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.net.ssl.SSLContext;

import org.xlightweb.BodyDataSink;
import org.xlightweb.FutureResponseHandler;
import org.xlightweb.HttpRequest;
import org.xlightweb.IBodyCompleteListener;
import org.xlightweb.IFutureResponse;
import org.xlightweb.IHttpConnection;
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.RequestHandlerChain;
import org.xlightweb.client.HttpClientConnection.ClientExchange;
import org.xlightweb.client.HttpClientConnection.DoNothingResponseHandler;
import org.xlightweb.client.HttpClientConnection.IBodySinkPair;
import org.xsocket.DataConverter;
import org.xsocket.ILifeCycle;
import org.xsocket.connection.IConnectionPool;
import org.xsocket.connection.INonBlockingConnection;
import org.xsocket.connection.NonBlockingConnection;
import org.xsocket.connection.NonBlockingConnectionPool;




/**
* Higher level client-side abstraction of the client side endpoint. Internally, the HttpClient uses a pool
* of {@link HttpClientConnection} to perform the requests.  Example:
*
* <pre>
*   HttpClient httpClient = new HttpClient();
*  
*   // set some properties
*   httpClient.setFollowsRedirect(true); 
*   httpClient.setAutoHandleCookies(false);
*   // ...
*  
*   // perform a synchronous call
*   IHttpResponse response = httpClient.call(new GetRequest("http://www.gmx.com/index.html"));
*   System.out.println(response.getStatus());
*  
*   BlockingBodyDataSource bodyChannel = response.getBlockingBody();
*   System.out.println(bodyChannel.readString());
*  
*  
*   // perform an asynchronous request
*   MyResponseHandler respHdl = new MyResponseHandler();
*   httpClient.send(new HttpRequestHeader("GET", "http://www.gmx.com/index.html"), respHdl);
*  
*   //..
*  
*   httpClient.close();
* </pre> 
*
* @author grro@xlightweb.org
*/
public class HttpClient implements IHttpClientEndpoint, IConnectionPool, Closeable {
 
  private static final Logger LOG = Logger.getLogger(HttpClient.class.getName());
 
  public static final int DEFAULT_POOLED_IDLE_TIMEOUT_MILLIS = 10 * 1000;
  public static final int DEFAULT_POOLED_LIFE_TIMEOUT_MILLIS = 3 * 60 * 1000;
 
  public static final int DEFAULT_MAX_REDIRECTS = 5;
  public static final boolean DEFAULT_FOLLOWS_REDIRECT = false;
  public static final boolean DEFAULT_TREAT_302_REDIRECT_AS_303 = false;
  public static final Long DEFAULT_RESPONSE_TIMEOUT_SEC = Long.MAX_VALUE;
 
  private int maxRedirects = DEFAULT_MAX_REDIRECTS;
  private boolean isFollowsRedirect = DEFAULT_FOLLOWS_REDIRECT;
  private boolean isTreat302RedirectAs303 = DEFAULT_TREAT_302_REDIRECT_AS_303;
 
  public static final boolean DEFAULT_AUTOHANDLING_COOKIES = true;
  private boolean isAutohandlingCookies = DEFAULT_AUTOHANDLING_COOKIES;

 
  // proxy support
  private boolean isProxyActivated = false;
 
 
  // ssl support
  private boolean isSSLSupported = false;
  private final SSLContext sslCtx;

 
  // timeouts
  private long responseTimeoutMillis = IHttpConnection.DEFAULT_RESPONSE_TIMEOUT_MILLIS;
  private long bodyDataReceiveTimeoutMillis = IHttpConnection.DEFAULT_DATA_RESPONSE_TIMEOUT_MILLIS;
 
 
  // pool
  private boolean isPooled = true;
  private final NonBlockingConnectionPool pool;
 
 
  // the internal chain. Internally, the HttpClient uses always a request handler chain. This chain
  // will be used to add handlers such as a CookieHandler
  private final RequestHandlerChain httpClientRequestHandlerChain = new RequestHandlerChain();
 
 
  // auto supported handlers -> will be (de)activated by flags
  private final AutoRedirectHandler redirectHandler = new AutoRedirectHandler(this);
  private final CookieHandler cookiesHandler = new CookieHandler();
  private final ProxyHandler proxyHandler = new ProxyHandler();
 
 
  // the assigned session manager
  private SessionManager sessionManager = null;
 

  // statistics
  private long lastTimeRequestSentMillis = System.currentTimeMillis();

 
  // transaction monitor
  private TransactionLog transactionLog = new TransactionLog(0);
  private TransactionMonitor transactionMonitor = null;
 

 
 
  /**
   * constructor
   */
  public HttpClient() {
    this(null, new IHttpRequestHandler[0]);
  }
 
 
 
  /**
   * constructor
   *
   * @param interceptors  interceptor
   */
  public HttpClient(IHttpRequestHandler... interceptors) {
    this(null, interceptors);
  }
 
 
 
  /**
   * constructor
   *
   * @param sslCtx   the ssl context to use
   */
  public HttpClient(SSLContext sslCtx) {
    this(sslCtx, new IHttpRequestHandler[0]);
  }

 
  /**
   * constructor
   *
   * @param sslCtx        the ssl context to use
   * @param interceptors  the interceptors
   */
  public HttpClient(SSLContext sslCtx, IHttpRequestHandler... interceptors) {
      this.sslCtx = sslCtx;

      if (sslCtx != null) {
      pool = new NonBlockingConnectionPool(sslCtx);
      isSSLSupported = true;
    } else {
      pool = new NonBlockingConnectionPool();
      isSSLSupported = false;
   
   
   
    proxyHandler.setSSLContext(sslCtx);
   
    pool.setPooledMaxIdleTimeMillis(DEFAULT_POOLED_IDLE_TIMEOUT_MILLIS);
    pool.setPooledMaxLifeTimeMillis(DEFAULT_POOLED_LIFE_TIMEOUT_MILLIS);
   
   
    sessionManager = new SessionManager();
   
    resetChain();
   
    for (IHttpRequestHandler interceptor : interceptors) {
      addInterceptor(interceptor);
    }
  }
 
 
  /**
   * adds an interceptor. Example:
   *
   * <pre>
   *  HttpClient httpClient = new HttpClient();
   * 
   *  LoadBalancerRequestInterceptor lbInterceptor = new LoadBalancerRequestInterceptor();
   *  lbInterceptor.addVirtualServer("http://customerService", "srv1:8030", "srv2:8030");
   *  httpClient.addInterceptor(lbInterceptor);
   * 
   *  // ...
   *  GetRequest request = new GetRequest("http://customerService/price?id=2336&amount=5656");
   *  IHttpResponse response = httpClient.call(request);
   *  //...
   *
   * </pre>
  
   * @param interceptor  the interceptor to add
   */
  public void addInterceptor(IHttpRequestHandler interceptor) {

    if (interceptor instanceof ILifeCycle) {
      ((ILifeCycle) interceptor).onInit();
    }
   
    httpClientRequestHandlerChain.addLast(interceptor);
    resetChain();
  }
 

 
  /**
   * sets if redirects should be followed
   * 
   * @param isFollowsRedirect true, if redirects should be followed
   */
  public void setFollowsRedirect(boolean isFollowsRedirect) {
   
    if (this.isFollowsRedirect == isFollowsRedirect) {
      return;
    }
   
    this.isFollowsRedirect = isFollowsRedirect;
    resetChain();
  }

 
  /**
   * returns true, if redirects should be followed
   * @return true, if redirects should be followed
   */
  public boolean getFollowsRedirect() {
    return isFollowsRedirect;
  }
 
 
  /**
   * sets if cookies should be auto handled
   *
   * @param isAutohandlingCookies true, if cookies should be auto handled
   */
  public void setAutoHandleCookies(boolean isAutohandlingCookies) {
    if (this.isAutohandlingCookies == isAutohandlingCookies) {
      return;
    }
   
    this.isAutohandlingCookies = isAutohandlingCookies;
    resetChain();
  }
 
 
 
  /**
   * sets the proxy host to use. Example:
   *
   * <pre>
   * HttpClient httpClient = new HttpClient();
   *
   * // sets the proxy adress
   * httpClient.setProxyHost(host);
   * httpClient.setProxyPort(port);
   *
   * // set auth params (only necessary if proxy authentication is required)
   * httpClient.setProxyUser(user);
   * httpClient.setProxyPassword(pwd);
   *
   * // calling through the proxy  
   * IHttpResponse resp = httpClient.call(new GetRequest("http://www.gmx.com/");
   * // ...
   * </pre>
   *
   * @param proxyHost the proxy host or <null>
   */
  public void setProxyHost(String proxyHost) {
    proxyHandler.setProxyHost(proxyHost);
   
    if ((proxyHost != null) && (proxyHost.length() > 1)) {
      isProxyActivated = true;
   
    resetChain();
  }
 

  /**
   * sets the proxy port. Default is 80. For an example see {@link HttpClient#setProxyHost(String)}
   *
   * @param proxyPort the proxy port
   */
  public void setProxyPort(int proxyPort) {
    proxyHandler.setProxyPort(proxyPort);
  }

 
  /**
   * sets the secured proxy host. Example:
   *
   * <pre>
   * // SSL context has to be set to support SSL
   * HttpClient httpClient = new HttpClient(SSLContext.getDefault());
   *
   * // sets the secured proxy adress
   * httpClient.setProxySecuredHost(host);
   * httpClient.setProxySecuredPort(port);
   *
   * // calling through the proxy  
   * IHttpResponse resp = httpClient.call(new GetRequest("https://www.gmx.com/");
   * // ...
   * </pre>
   *
   * 
   *
   * @param proxyHost the secured proxy host or <null>
   */
  public void setProxySecuredHost(String proxyHost) {
   
    proxyHandler.setSecuredProxyHost(proxyHost);
   
    if ((proxyHost != null) && (proxyHost.length() > 1)) {
      isProxyActivated = true;
    }
    resetChain();
  }

 

  /**
   * sets the secured proxy port. Default is 443. For an example see {@link HttpClient#setProxySecuredHost(String)}
   *
   * @param proxyPort the proxy port
   */
  public void setProxySecuredPort(int proxyPort) {
    proxyHandler.setSecuredProxyPort(proxyPort);   
  }

 
  /**
   * sets the user name for proxy authentification 
   * @param proxyUser  the user name
   */
  public void setProxyUser(String proxyUser) {
    proxyHandler.setProxyUser(proxyUser);
  }
 

  /**
   * sets the user password for proxy authentification 
   *  
   * @param proxyPassword the user password
   */
  public void setProxyPassword(String proxyPassword) {
    proxyHandler.setProxyPassword(proxyPassword);
  }

 
 
  private void resetChain() {
   
    httpClientRequestHandlerChain.remove(cookiesHandler);
    httpClientRequestHandlerChain.remove(redirectHandler);
    httpClientRequestHandlerChain.remove(proxyHandler);
   
   
    if (isFollowsRedirect == true) {
      httpClientRequestHandlerChain.addFirst(redirectHandler);
    }

    if (isAutohandlingCookies == true) {
        httpClientRequestHandlerChain.addFirst(cookiesHandler);
      }
         

    if (isProxyActivated) {
      httpClientRequestHandlerChain.addLast(proxyHandler);
    }
  }
 
 
  /**
   * returns if cookies should be auto handled
   * @return true, if cookies should be auto handled
   */
  public boolean isAutohandleCookies() {
    return isAutohandlingCookies;
  }

 
  /**
   * returns the session manager
   *
   * @return the session manager
   */
  SessionManager getSessionManager() {
    return sessionManager;
  }
 
 
  /**
   * set the max redirects
   *
   * @param maxRedirects  the max redirects
   */
  public void setMaxRedirects(int maxRedirects) {
    this.maxRedirects = maxRedirects;
  }
 
 
  /**
   * get the max redirects
   *
   * @return the max redirects
   */
  public int getMaxRedirects() {
    return maxRedirects;
  }
 
 
  /**
   * sets if a 302 response should be treat as a 303 response
   * 
   * @param isTreat303RedirectAs302 true, if a 303 response should be treat a a 303 response
   */
  public void setTreat302RedirectAs303(boolean isTreat303RedirectAs302) {
    this.isTreat302RedirectAs303 = isTreat303RedirectAs302;
  }
 
 
 
  /**
   * gets if a 302 response should be treat as a 303 response
   *
   * @return true, if a 302 response should be treat as a 303 response
   */
  public boolean isTreat302RedirectAs303() {
    return isTreat302RedirectAs303;
  }
 
 
  /**
   * get the max size of the transaction log
   *
   * @return the max size of the transaction log
   */
  int getTransactionLogMaxSize() {
    return transactionLog.getMaxSize();
  }
 
 
 
  /**
   * returns the number of pending transactions
   *
   * @return the number of pending transactions
   */
  Integer getTransactionsPending() {
    if (transactionMonitor != null) {
      return transactionMonitor.getPendingTransactions();
    } else {
      return null;
    }
  }
 
 
 
  /**
   * sets the max size of the transaction log
   *
   * @param maxSize the max size of the transaction log
   */
  void setTransactionLogMaxSize(int maxSize) {
    transactionLog.setMaxSize(maxSize);
   
    if (maxSize == 0) {
      transactionMonitor = null;
    } else {
      transactionMonitor = new TransactionMonitor(transactionLog);
    }
  }

 
  /**
   * set the worker pool which will be assigned to the connections for call back handling
   *
   * @param workerpool the worker pool
   */
  public void setWorkerpool(Executor workerpool) {
    pool.setWorkerpool(workerpool);
  }

 
  /**
   * returns the assigned worker pool
   *
   * @return  the assigned worker pool
   */
  Executor getWorkerpool() {
    return pool.getWorkerpool();
  }
 
 
  /**
   * returns is pooling is used
   * 
   * @return true, if pooling is used
   */
  public boolean isPooled() {
    return isPooled;
  }
 
 
  /**
   * sets if pooling is used
   *
   * @param isPooled true, if pooling is used
   */
  public void setPooled(boolean isPooled) {
    this.isPooled = isPooled;
  }
 
 
  /**
   * {@inheritDoc}
   */
  public void setResponseTimeoutMillis(long responseTimeoutMillis) {
    if (responseTimeoutMillis < 0) {
      LOG.warning("try to set response time out with " + responseTimeoutMillis + ". This will be ignored");
      return;
    }
    this.responseTimeoutMillis = responseTimeoutMillis;
  }
 
 
  /**
   * {@inheritDoc}
   */
  public long getResponseTimeoutMillis() {
    return responseTimeoutMillis;
  }

  /**
   * {@inheritDoc}
   */
  public final void setBodyDataReceiveTimeoutMillis(long bodyDataReceiveTimeoutMillis) {
    this.bodyDataReceiveTimeoutMillis = bodyDataReceiveTimeoutMillis;
  }

 

  /**
   * {@inheritDoc}
   */
  public void close() throws IOException {
    pool.close();

    httpClientRequestHandlerChain.onDestroy();
    httpClientRequestHandlerChain.clear();   

    sessionManager.close();
    sessionManager = null;
  }
 
 
  /**
   * {@inheritDoc}
   */
  public boolean isOpen() {
    return pool.isOpen();
  }
 
 
  /**
   * returns a unique id
   *
   * @return the id
   */
  public String getId() {
    return Integer.toString(this.hashCode());
  }
 

  /**
   * {@inheritDoc}
   */
  public void addListener(ILifeCycle listener) {
    pool.addListener(listener);
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  public boolean removeListener(ILifeCycle listener) {
    return pool.removeListener(listener);
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  public void setPooledMaxIdleTimeMillis(int idleTimeoutMillis) {
    pool.setPooledMaxIdleTimeMillis(idleTimeoutMillis);
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  public int getPooledMaxIdleTimeMillis() {
    return pool.getPooledMaxIdleTimeMillis();
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  public void setPooledMaxLifeTimeMillis(int lifeTimeoutMillis) {
    pool.setPooledMaxLifeTimeMillis(lifeTimeoutMillis);
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  public int getPooledMaxLifeTimeMillis() {
    return pool.getPooledMaxLifeTimeMillis();
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  public long getCreationMaxWaitMillis() {
    return pool.getCreationMaxWaitMillis();
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  public void setCreationMaxWaitMillis(long maxWaitMillis) {
    pool.setCreationMaxWaitMillis(maxWaitMillis);
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  public void setMaxIdle(int maxIdle) {
    pool.setMaxIdle(maxIdle);
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  public int getMaxIdle() {
    return pool.getMaxIdle();
  }

 
 
  /**
   * {@inheritDoc}
   */
  public void setMaxActive(int maxActive) {
    pool.setMaxActive(maxActive);
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  public int getMaxActive() {
    return pool.getMaxActive();
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  public int getNumActive() {
    return pool.getNumActive();
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  public int getNumIdle() {
    return pool.getNumIdle();
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  int getNumPendingGet() {
    return pool.getNumPendingGet();
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  public int getNumCreated() {
    return pool.getNumCreated();
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  public int getNumDestroyed() {
    return pool.getNumDestroyed();
  }
 
 
  /**
   * get the number of creation errors
   *
   * @return the number of creation errors
   */
    int getNumCreationError() {
      return pool.getNumCreationError();
    }
 
   
    /**
   * {@inheritDoc}
   */
    public int getNumTimeoutPooledMaxIdleTime() {
      return pool.getNumTimeoutPooledMaxIdleTime();
    }

    /**
   * {@inheritDoc}
   */
    public int getNumTimeoutPooledMaxLifeTime() {
      return pool.getNumTimeoutPooledMaxLifeTime();
    }
   
   
 
    /**
   * {@inheritDoc}
   */
  public List<String> getActiveConnectionInfos() {
    return pool.getActiveConnectionInfos();
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  public List<String> getIdleConnectionInfos() {
    return pool.getIdleConnectionInfos();
  }


 
  /**
   * returns the transaction log
   * @return the transaction log
   */
  List<String> getTransactionInfos() {
    List<String> result = new ArrayList<String>();
    for (Transaction transaction : transactionLog.getTransactions()) {
      result.add(transaction.toString());
    }
    return result;
  }
 

   
  /**
   * {@inheritDoc}
   */
  public IHttpResponse call(IHttpRequest request) throws IOException, SocketTimeoutException {
    try {
      IFutureResponse futureResponse = send(request);
      return futureResponse.getResponse();
    } catch (InterruptedException ie) {
      throw new RuntimeException(ie);
    }
  }

 
  /**
   * {@inheritDoc}
   */ 
  public IFutureResponse send(IHttpRequest request) throws IOException, ConnectException {
        FutureResponseHandler responseHandler = new FutureResponseHandler();
        send(request, responseHandler);
       
        return responseHandler;
  }
 
 
  /**
   * {@inheritDoc}
   */
  public void send(IHttpRequest request, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
    lastTimeRequestSentMillis = System.currentTimeMillis();
   
    if (responseHandler == null) {
      responseHandler = new DoNothingResponseHandler();
    }
   
    // create exchange
    ClientExchange exchange = new ClientExchange(this, getWorkerpool());
   
    // init and process it
    exchange.init(request, responseHandler);
    httpClientRequestHandlerChain.onRequest(exchange);
   
   
    // log trace if activated
    if (transactionMonitor != null) {
      transactionMonitor.register(request.getRequestHeader());
    }
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  public BodyDataSink send(IHttpRequestHeader requestHeader, int contentLength, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
    requestHeader.setContentLength(contentLength);
    return sendInternal(requestHeader, responseHandler);
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  public BodyDataSink send(IHttpRequestHeader requestHeader, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
    requestHeader.setTransferEncoding("chunked");
    return sendInternal(requestHeader, responseHandler);
  }
 
 
  private BodyDataSink sendInternal(IHttpRequestHeader requestHeader, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
    lastTimeRequestSentMillis = System.currentTimeMillis();
   
    if (responseHandler == null) {
      responseHandler = new DoNothingResponseHandler();
    }
   
    // create exchange
    ClientExchange exchange = new ClientExchange(this, getWorkerpool());
   
    // create data body sink / data body source pair
    IBodySinkPair pair = HttpClientConnection.newBodySinkPair(null, exchange.getExecutor(), requestHeader.getCharacterEncoding());
    IHttpRequest request = new HttpRequest(requestHeader, pair.getBodyDataSource());

    // init and process it
    exchange.init(request, responseHandler);
    httpClientRequestHandlerChain.onRequest(exchange);
     
   
    // log trace if activated
    if (transactionMonitor != null) {
      transactionMonitor.register(requestHeader);
    }

    return pair.getBodyDataSink();
  }
 
 

 
  /**
   * gets the time when the last request has been sent
   * 
   * @return the time when the last request has been sent
   */
  long getLastTimeRequestSentMillis() {
    return lastTimeRequestSentMillis;
  }
 
 
  /**
   * gets a connection
   *
   * @param isSSL   true, if ssl
   * @param host    the host
   * @param port    the port
   * @param scheme  the scheme
   * @param filter  the filter
   * @return the new connection
   * @throws IOException  if an exception occurs
   * @throws ConnectException  if an connection exception occurs
   */
  HttpClientConnection getConnection(boolean isSSL, String host, int port, String scheme, IHttpRequestHandler filter) throws IOException, ConnectException {
   
    if (port == -1) {
      if (scheme.equalsIgnoreCase("HTTP")) {
        port = 80;
      } else if (scheme.equalsIgnoreCase("HTTPS")) {
        port = 443;
      } else {
        throw new IOException("wrong address host=" + host + " port=" + port + " scheme=" + scheme);
      }
    }
   
   
   
    if ((isSSL == true) && !isSSLSupported) {
      throw new IOException("ssl connection are not supported (use pool sslContext parameter constructor)");
    }
   
    INonBlockingConnection tcpConnection = null;
   
    if (isPooled) {
      try {
        tcpConnection = pool.getNonBlockingConnection(host, port, isSSL);
       
      } catch (IOException ioe) {
        throw new ConnectException("could not connect to " + host + ":" + port);
      }

       
    } else {
      try {
        if (sslCtx != null) {
          tcpConnection = new NonBlockingConnection(host, port, sslCtx, true);
          ((NonBlockingConnection) tcpConnection).setWorkerpool(pool.getWorkerpool());
         
        } else {
          tcpConnection = new NonBlockingConnection(host, port);
          ((NonBlockingConnection) tcpConnection).setWorkerpool(pool.getWorkerpool());
        }
      } catch (IOException ioe) {
        throw new ConnectException("could not connect to " + host + ":" + port + " reason: " + ioe.toString());
      }
    }
   
    HttpClientConnection httpConnection = new HttpClientConnection(tcpConnection);
    httpConnection.setResponseTimeoutMillis(responseTimeoutMillis);
    httpConnection.setBodyDataReceiveTimeoutMillis(bodyDataReceiveTimeoutMillis);
    httpConnection.setAutocloseAfterResponse(true);
   
    if (transactionMonitor != null) {
      httpConnection.setTransactionMonitor(transactionMonitor);
    }
   
    return httpConnection;
  }
 
 
 
 
  /**
   * {@inheritDoc}
   */
  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder(super.toString());
   
    sb.append("\r\nactive connections:");
    for (String connectionInfo : getActiveConnectionInfos()) {
      sb.append("\r\n " + connectionInfo);
    }
   
    sb.append("\r\nidle connections:");
    for (String connectionInfo : getIdleConnectionInfos()) {
      sb.append("\r\n " + connectionInfo);
    }

   
    sb.append("\r\ntransaction log:");
    for (String transactionInfo : getTransactionInfos()) {
      sb.append("\r\n " + transactionInfo);
    }
   
    return sb.toString();
  }
 
 
 
  static final class TransactionMonitor {
   
    private final Map<IHttpRequestHeader, Transaction> pendingTransactions = new HashMap<IHttpRequestHeader, Transaction>();
    private final AtomicInteger pending = new AtomicInteger(0);
   
    private final TransactionLog transactionLog;

    public TransactionMonitor(TransactionLog transactionLog) {
      this.transactionLog = transactionLog;
    }
   
   
    public void register(IHttpRequestHeader requestHeader) {
      Transaction transaction = new Transaction(this);
      transaction.register(requestHeader);
       
      pendingTransactions.put(requestHeader, transaction);
      transactionLog.add(transaction);
    }
   
    public void register(HttpClientConnection con, IHttpRequestHeader requestHeader, IHttpResponse response) {
      Transaction transaction = pendingTransactions.remove(requestHeader);
      if (transaction != null) {
        transaction.register(con, response);
      }
    }
   
    void incPending() {
      pending.incrementAndGet();
    }
   
    void decPending() {
      pending.decrementAndGet();
    }
   
    int getPendingTransactions() {
      return pending.get();
    }
  }
 
 
 
  @SuppressWarnings("unchecked")
  private static final class TransactionLog {
   
    private LinkedList<Transaction> transactions = new LinkedList<Transaction>();
    private int maxSize = 0;
   
    TransactionLog(int maxSize) {
      this.maxSize = maxSize;
    }
   
    void setMaxSize(int maxSize) {
      this.maxSize = maxSize;
      removeOddEntries();
    }
   
    int getMaxSize() {
      return maxSize;
    }
   
    void add(Transaction transaction) {
      transactions.add(transaction);
      removeOddEntries();
    }
   
    private void removeOddEntries() {
      while (transactions.size() > maxSize) {
        try {
          transactions.removeFirst();
        } catch (Exception e) {
            // eat and log exception
          if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("error occured by removing list entry " + e.toString());
          }
        }
      }
    }
   
   
    public List<Transaction> getTransactions() {
      return (List<Transaction>) transactions.clone();
    }
  }
 
 

  private static final class Transaction {
   
    private final TransactionMonitor monitor;
   
    private String requestHeaderInfo = "";
    private String requestBodyInfo = "";
    private String responseHeaderInfo = "";
    private String responseBodyInfo = "";
    private String conInfo = "";
   
    private boolean isHeaderReceived = false;
    private boolean isBodyReceived = false;
   
    private Long requestHeaderSend = null;
    private Long responseBodyReceived = null;
   

    private SimpleDateFormat df = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
   
   
    public Transaction(TransactionMonitor monitor) {
      this.monitor = monitor;
    }
   
   
    public void register(IHttpRequestHeader requestHeader) {
      monitor.incPending();
     
      requestHeaderSend = System.currentTimeMillis();
     
      if (requestHeader.getQueryString() != null) {
        requestHeaderInfo = "[" + df.format(new Date()) + "] " + requestHeader.getServerName() + ":" + requestHeader.getServerPort() +
               " " + requestHeader.getMethod() + " " + requestHeader.getRequestURI() + requestHeader.getQueryString();
      } else {
        requestHeaderInfo = "[" + df.format(new Date()) + "] " + requestHeader.getServerName() + ":" + requestHeader.getServerPort() +
               " " + requestHeader.getMethod() + " " + requestHeader.getRequestURI();
      }
    }
   
    public void register(HttpClientConnection con, final IHttpResponse response) {
      isHeaderReceived = true;

      IHttpResponseHeader responseHeader = response.getResponseHeader();
      responseHeaderInfo = responseHeader.getStatus() + " " + responseHeader.getReason();
      if (responseHeader.containsHeader("connection")) {
        responseHeaderInfo = responseHeaderInfo + " (connection: " + responseHeader.getHeader("connection") + ")";
      }
     
     
      if (response.hasBody()) {
        try {
          responseBodyInfo = "(" + HttpClientConnection.getBodytype(response.getNonBlockingBody()) + ")";
         
          IBodyCompleteListener cl = new IBodyCompleteListener() {
           
            public void onComplete() throws IOException {
              register();
            }
          };
          response.getNonBlockingBody().addCompleteListener(cl);
         
        } catch (IOException ioe) {
          if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("error occured by registering complete listener " + ioe.toString());
          }
        }
       
      } else {
        responseBodyInfo = "(NO BODY)";
        register();
      }
     
      conInfo = "id=" + con.getId();
    }
   
   
    private void register() {
      monitor.decPending();
      isBodyReceived = true;
      responseBodyReceived = System.currentTimeMillis();
    }
   
   
    @Override
    public String toString() {
      String elapsed = "";
      if (responseBodyReceived  != null) {
        elapsed = "elapsed=" + DataConverter.toFormatedDuration(responseBodyReceived - requestHeaderSend);
      } else {
        elapsed = "elapsed=" + DataConverter.toFormatedDuration(System.currentTimeMillis() - requestHeaderSend);
      }
           
      if (isBodyReceived) {
        return requestHeaderInfo + " " + requestBodyInfo + "-> " + responseHeaderInfo + " " + responseBodyInfo + " [" + elapsed + " " + conInfo + "]";
       
      } else if (isHeaderReceived) {
        return requestHeaderInfo + " " + requestBodyInfo + "-> " + responseHeaderInfo + " " + responseBodyInfo + " [READING BODY " + elapsed + " " + conInfo + "]";
       
      } else {
        return requestHeaderInfo + " " + requestBodyInfo + "-> " + responseHeaderInfo + " " + responseBodyInfo + " [WAITING FOR HEADER " + elapsed + " " + conInfo + "]";
      }
    }
  }
}
TOP

Related Classes of org.xlightweb.client.HttpClient

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.