/*
* 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.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
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.CacheHandler;
import org.xlightweb.FutureResponseHandler;
import org.xlightweb.HttpRequest;
import org.xlightweb.HttpUtils;
import org.xlightweb.IFutureResponse;
import org.xlightweb.IHttpConnectHandler;
import org.xlightweb.IHttpConnection;
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.InvokeOn;
import org.xlightweb.RequestHandlerChain;
import org.xlightweb.client.HttpClientConnection.ClientExchange;
import org.xlightweb.client.TransactionMonitor.Transaction;
import org.xlightweb.client.TransactionMonitor.TransactionLog;
import org.xsocket.ILifeCycle;
import org.xsocket.connection.IConnectionPool;
/**
* 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>
*
* The HttpClient is thread-safe
*
* @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_CREATION_MAX_WAIT_TIMEOUT = 60 * 1000;
public static final int DEFAULT_POOLED_LIFE_TIMEOUT_MILLIS = 30 * 1000;
public static final int DEFAULT_POOLED_IDLE_TIMEOUT_MILLIS = 3 * 1000;
public static final int DEFAULT_MAX_REDIRECTS = 5;
public static final boolean DEFAULT_TREAT_302_REDIRECT_AS_303 = false;
public static final Long DEFAULT_RESPONSE_TIMEOUT_SEC = IHttpConnection.DEFAULT_RESPONSE_TIMEOUT_MILLIS;
private final AtomicInteger maxRedirects = new AtomicInteger(DEFAULT_MAX_REDIRECTS);
private final AtomicBoolean isTreat302RedirectAs303 = new AtomicBoolean(DEFAULT_TREAT_302_REDIRECT_AS_303);
private final AtomicInteger connectTimeoutMillis = new AtomicInteger(IHttpConnection.DEFAULT_CONNECT_TIMEOUT_MILLIS);
private String defaultEncoding = IHttpMessage.DEFAULT_ENCODING;
// pool
private boolean isPooled = true;
private final HttpClientConnectionPool pool;
private final AtomicBoolean isCallReturnOnMessage = new AtomicBoolean(false);
// auto supported handlers
public static final boolean DEFAULT_FOLLOWS_REDIRECT = false;
private final AtomicBoolean isFollowsRedirect = new AtomicBoolean(DEFAULT_FOLLOWS_REDIRECT);
private final RedirectHandler redirectHandler;
public static final int DEFAULT_MAX_RETRIES = 4;
private final AtomicInteger maxRetries = new AtomicInteger(DEFAULT_MAX_RETRIES);
private final RetryHandler retryHandler;
public static final boolean DEFAULT_AUTOHANDLING_COOKIES = true;
private final AtomicBoolean isAutohandlingCookies = new AtomicBoolean(DEFAULT_AUTOHANDLING_COOKIES);
private final CookieHandler cookiesHandler;
public static final boolean DEFAULT_PROXY_ACTIVATED = false;
private final AtomicBoolean isProxyActivated = new AtomicBoolean(DEFAULT_PROXY_ACTIVATED);
private final ProxyHandler proxyHandler;
public static final int DEFAULT_CACHE_SIZE = 0;
private final CacheHandler cacheHandler;
private boolean isShowCache = false;
private final RequestHandlerChain chain = new RequestHandlerChain();
// the assigned session manager
private SessionManager sessionManager = null;
// statistics
private long lastTimeRequestSentMillis = System.currentTimeMillis();
// transaction monitor
private TransactionMonitor transactionMonitor = null;
private final TransactionLog transactionLog = new TransactionLog(0);
/**
* 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) {
if (sslCtx != null) {
pool = new HttpClientConnectionPool(sslCtx);
} else {
pool = new HttpClientConnectionPool();
}
setPooledMaxIdleTimeMillis(DEFAULT_POOLED_IDLE_TIMEOUT_MILLIS);
setPooledMaxLifeTimeMillis(DEFAULT_POOLED_LIFE_TIMEOUT_MILLIS);
sessionManager = new SessionManager();
retryHandler = new RetryHandler(HttpClient.this);
cacheHandler = new CacheHandler(DEFAULT_CACHE_SIZE);
cookiesHandler = new CookieHandler();
redirectHandler = new RedirectHandler(this);
proxyHandler = new ProxyHandler(pool);
for (IHttpRequestHandler interceptor : interceptors) {
addInterceptor(interceptor);
}
resetChain();
chain.onInit();
}
/**
* 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();
}
if (!HttpUtils.isConnectHandlerWarningIsSuppressed() && (interceptor instanceof IHttpConnectHandler)) {
LOG.warning("only IHttpRequestHandler is supported. The onConnect(...) method will not be called. (suppress this warning by setting system property org.xlightweb.httpConnectHandler.suppresswarning=true)");
}
chain.addLast(interceptor);
resetChain();
}
private void resetChain() {
synchronized (this) {
chain.remove(retryHandler);
chain.remove(cacheHandler);
chain.remove(cookiesHandler);
chain.remove(redirectHandler);
chain.remove(proxyHandler);
if (getMaxRetries() > 0) {
chain.addFirst(retryHandler);
}
if (cacheHandler.getMaxCacheSizeKB() > 0) {
for (IHttpRequestHandler hdl : chain.getHandlers()) {
if (hdl instanceof CacheHandler) {
LOG.warning("a cache handler is already activated. Adding another one (setting HttpClient's cacheMaxSizeKB larger than zero adds a cache handler automatically)");
}
}
chain.addFirst(cacheHandler);
}
if (isFollowsRedirect.get()) {
chain.addFirst(redirectHandler);
}
if (isAutohandlingCookies.get()) {
chain.addFirst(cookiesHandler);
}
if (isProxyActivated.get()) {
chain.addLast(proxyHandler);
}
}
}
/**
* sets if redirects should be followed
*
* @param isFollowsRedirect true, if redirects should be followed
*/
public void setFollowsRedirect(boolean isFollowsRedirect) {
if (this.isFollowsRedirect.get() == isFollowsRedirect) {
return;
}
this.isFollowsRedirect.set(isFollowsRedirect);
resetChain();
}
/**
* returns true, if redirects should be followed
* @return true, if redirects should be followed
*/
public boolean getFollowsRedirect() {
return isFollowsRedirect.get();
}
/**
* sets if cookies should be auto handled
*
* @param isAutohandlingCookies true, if cookies should be auto handled
*/
public void setAutoHandleCookies(boolean isAutohandlingCookies) {
if (this.isAutohandlingCookies.get() == isAutohandlingCookies) {
return;
}
this.isAutohandlingCookies.set(isAutohandlingCookies);
resetChain();
}
/**
* sets the cache size (in kilo bytes)
*
* @param maxSizeKB the max cache size in bytes or 0 to deactivate caching
*/
public void setCacheMaxSizeKB(int maxSizeKB) {
cacheHandler.setMaxCacheSizeKB(maxSizeKB);
resetChain();
}
/**
* returns the max cache size
*
* @return the max cache size
*/
public int getCacheMaxSizeKB() {
return (cacheHandler.getMaxCacheSizeKB());
}
/**
* returns the cache size
*
* @return the cache size
*/
public float getCacheSizeKB() {
return ((float) cacheHandler.getCurrentCacheSizeBytes() / 1000);
}
/**
* sets if the cache is shared between users
*
* @param isSharedCache true, if the cache is shared between users
*/
public void setCacheShared(boolean isSharedCache) {
cacheHandler.setSharedCache(isSharedCache);
}
/**
* returns true, if the cache is shared between users
*
* @return true, if the cache is shared between users
*/
public boolean isCacheShared() {
return cacheHandler.isSharedCache();
}
/**
* 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.set(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);
}
/**
* @deprecated using {@link HttpClient#setProxyHost(String)}
*/
public void setProxySecuredHost(String proxyHost) {
proxyHandler.setSecuredProxyHost(proxyHost);
if ((proxyHost != null) && (proxyHost.length() > 1)) {
isProxyActivated.set(true);
}
resetChain();
}
/**
* @deprecated use {@link HttpClient#setProxyPort(int)}
*/
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);
}
/**
* returns if cookies should be auto handled
* @return true, if cookies should be auto handled
*/
public boolean isAutohandleCookies() {
return isAutohandlingCookies.get();
}
/**
* 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.set(maxRedirects);
}
/**
* get the max redirects (of GET, DELETE and PUT calls)
*
* @return the max redirects
*/
public int getMaxRedirects() {
return maxRedirects.get();
}
/**
* get the max retries (of GET, DELETE and PUT calls)
*
* @return the max retries
*/
public int getMaxRetries() {
return maxRetries.get();
}
/**
* sets the max retries (of GET, DELETE and PUT calls)
*
* @param maxRetries the max retries
*/
public void setMaxRetries(int maxRetries) {
if (maxRetries > 0) {
this.maxRetries.set(maxRetries);
} else {
this.maxRetries.set(0);
}
resetChain();
}
/**
* 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.set(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();
}
/**
* 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);
}
pool.setTranactionMonitor(transactionMonitor);
}
/**
* 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();
}
/**
* @deprecated
*/
public boolean isPooled() {
return isPooled;
}
/**
* @deprecated
*/
public void setPooled(boolean isPooled) {
if (!isPooled) {
LOG.warning("isPooled is deprecated and will be ignored");
}
this.isPooled = isPooled;
}
/**
* set the response body default encoding. According to RFC 2616 the
* initial value is ISO-8859-1
*
* @param encoding the defaultEncoding
*/
public void setResponseBodyDefaultEncoding(String defaultEncoding) {
this.defaultEncoding = defaultEncoding;
}
/**
* set that the call will return if the complete message is received.
* If false is set, the call will return when the response header is received (default is false)
*
* @param isCallReturnOnMessage true, if the call is returned on Message
*/
public void setCallReturnOnMessage(boolean isCallReturnOnMessage) {
this.isCallReturnOnMessage.set(isCallReturnOnMessage);
}
/**
* return true when the call will return, if the message is received
*
* @return true when the call will return, if the message is received
*/
public boolean getCallReturnOnMessage() {
return isCallReturnOnMessage.get();
}
/**
* {@inheritDoc}
*/
public void setResponseTimeoutMillis(long responseTimeoutMillis) {
pool.setResponseTimeoutMillis(responseTimeoutMillis);
}
/**
* {@inheritDoc}
*/
public long getResponseTimeoutMillis() {
return pool.getResponseTimeoutMillis();
}
/**
* {@inheritDoc}
*/
public final void setBodyDataReceiveTimeoutMillis(long bodyDataReceiveTimeoutMillis) {
pool.setBodyDataReceiveTimeoutMillis(bodyDataReceiveTimeoutMillis);
}
/**
* {@inheritDoc}
*/
public final long getBodyDataReceiveTimeoutMillis() {
return pool.getBodyDataReceiveTimeoutMillis();
}
/**
* {@inheritDoc}
*/
public void close() throws IOException {
pool.close();
chain.onDestroy();
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();
}
/**
* @deprecated
*/
public long getCreationMaxWaitMillis() {
return pool.getCreationMaxWaitMillis();
}
/**
* @deprecated
*/
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 getMaxActivePerServer() {
return pool.getMaxActivePerServer();
}
/**
* {@inheritDoc}
*/
public void setMaxActivePerServer(int maxActivePerServer) {
pool.setMaxActivePerServer(maxActivePerServer);
}
/**
* {@inheritDoc}
*/
public int getMaxActive() {
return pool.getMaxActive();
}
/**
* @deprecated
*/
public void setMaxWaiting(int maxWaiting) {
pool.setMaxWaiting(maxWaiting);
}
/**
* sets the connect timeout
*
* @param connectTimeoutMillis the connect timeout
*/
public void setConnectTimeoutMillis(int connectTimeoutMillis) {
this.connectTimeoutMillis.set(connectTimeoutMillis);
}
/**
* returns the connect timeout
* @return the connect timeout
*/
public int getConnectTimeoutMillis() {
return connectTimeoutMillis.get();
}
/**
* @deprecated
*/
public int getMaxWaiting() {
return pool.getMaxWaiting();
}
/**
* {@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();
}
/**
* returns the number of cache hits
* @return the number of cache hits
*/
public int getNumCacheHit() {
return cacheHandler.getCountCacheHit();
}
/**
* returns the number of cache misses
* @return the number of cache misses
*/
public int getNumCacheMiss() {
return cacheHandler.getCountCacheMiss();
}
/**
* {@inheritDoc}
*/
public List<String> getActiveConnectionInfos() {
return pool.getActiveConnectionInfos();
}
/**
* {@inheritDoc}
*/
public List<String> getIdleConnectionInfos() {
return pool.getIdleConnectionInfos();
}
boolean isCacheInfoDisplay() {
return isShowCache;
}
void setCacheInfoDisplay(boolean isShowCache) {
this.isShowCache = isShowCache;
}
List<String> getCacheInfo() {
if (isShowCache) {
return cacheHandler.getCacheInfo();
} else {
return null;
}
}
double getCacheHitRatio() {
return cacheHandler.getCurrentHitRatio();
}
/**
* 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 {
if (isCallReturnOnMessage.get()) {
try {
request.setAttribute(RetryHandler.RETRY_KEY, false);
FutureMessageResponseHandler responseHandler = new FutureMessageResponseHandler(request);
send(request, responseHandler);
return responseHandler.getResponse();
} catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
} else {
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();
// log trace if activated
if (transactionMonitor != null) {
transactionMonitor.register(request.getRequestHeader());
}
ClientExchange clientExchange = new ClientExchange(defaultEncoding, pool, sessionManager, responseHandler, request, connectTimeoutMillis.get());
chain.onRequest(clientExchange);
}
/**
* {@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 {
if (requestHeader.getContentLength() != -1) {
return send(requestHeader, requestHeader.getContentLength(), responseHandler);
}
if ((requestHeader.getTransferEncoding() == null)) {
requestHeader.setHeader("Transfer-Encoding", "chunked");
}
return sendInternal(requestHeader, responseHandler);
}
private BodyDataSink sendInternal(IHttpRequestHeader requestHeader, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
lastTimeRequestSentMillis = System.currentTimeMillis();
BodyDataSink dataSink = HttpClientConnection.newInMemoryBodyDataSink(requestHeader.getCharacterEncoding(), pool.getWorkerpool());
IHttpRequest request = new HttpRequest(requestHeader, HttpClientConnection.getDataSourceOfInMemoryBodyDataSink(dataSink));
send(request, responseHandler);
return dataSink;
}
/**
* gets the time when the last request has been sent
*
* @return the time when the last request has been sent
*/
long getLastTimeRequestSentMillis() {
return lastTimeRequestSentMillis;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder(super.toString());
sb.append("\r\nnumCreatedConnections " + getNumCreated());
sb.append("\r\nnumCreationError " + getNumCreationError());
sb.append("\r\nnumDestroyedConnections " + getNumDestroyed());
List<String> active = getActiveConnectionInfos();
if (active.isEmpty()) {
sb.append("\r\nnumActiveConnections 0");
} else {
sb.append("\r\n" + active.size() + " active connections:");
for (String connectionInfo : getActiveConnectionInfos()) {
sb.append("\r\n " + connectionInfo);
}
}
List<String> idle = getActiveConnectionInfos();
if (idle.isEmpty()) {
sb.append("\r\nnumIdleConnections 0");
} else {
sb.append("\r\n" + idle.size() + " idle 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();
}
@InvokeOn(InvokeOn.MESSAGE_RECEIVED)
private final class FutureMessageResponseHandler extends FutureResponseHandler {
private final IHttpRequest request;
private final int currentRetryNum;
public FutureMessageResponseHandler(IHttpRequest request) {
this.request = request;
Integer numReq = (Integer) request.getAttribute("org.xlightweb.client.FutureMessageResponseHandler.currentRetryNum");
if (numReq == null) {
numReq = 0;
}
currentRetryNum = numReq + 1;
request.setAttribute("org.xlightweb.client.FutureMessageResponseHandler.currentRetryNum", currentRetryNum);
}
@Override
public void onException(IOException ioe) {
if (isRetryableMethod() && RetryHandler.isRetryable(ioe)) {
if (currentRetryNum < getMaxRetries()) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("try to retrying request (retry num " + currentRetryNum + "). I/O exception caught when processing request " + ioe.toString());
}
retry();
return;
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("max retries " + getMaxRetries() + ". I/O exception caught when processing request " + ioe.toString());
}
}
}
super.onException(ioe);
}
@Override
public void onException(SocketTimeoutException stoe) {
if (isRetryableMethod()) {
if (currentRetryNum < getMaxRetries()) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("try to retrying request (retry num " + currentRetryNum + "). I/O exception caught when processing request " + stoe.toString());
}
retry();
return;
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("max retries " + getMaxRetries() + ". I/O exception caught when processing request " + stoe.toString());
}
}
}
super.onException(stoe);
}
private void retry() {
Runnable task = new Runnable() {
public void run() {
try {
IHttpResponse response = call(request);
onResponse(response);
} catch (IOException ioe) {
FutureMessageResponseHandler.super.onException(ioe);
}
}
};
// run in own thread to prevent dead locks
getWorkerpool().execute(task);
}
private boolean isRetryableMethod() {
return (request.getMethod().equalsIgnoreCase("GET"));
}
}
}