/*
* 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 + "]";
}
}
}
}