org.xsocket.connection.http.client.HttpClientConnection Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) xsocket.org, 2006 - 2008. 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.xsocket.org/
*/
package org.xsocket.connection.http.client;
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.nio.BufferUnderflowException;
import java.nio.channels.ClosedChannelException;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xsocket.DataConverter;
import org.xsocket.Execution;
import org.xsocket.MaxReadSizeExceededException;
import org.xsocket.connection.INonBlockingConnection;
import org.xsocket.connection.NonBlockingConnection;
import org.xsocket.connection.http.AbstractHttpConnection;
import org.xsocket.connection.http.BodyDataSink;
import org.xsocket.connection.http.HttpResponse;
import org.xsocket.connection.http.HttpResponseHeader;
import org.xsocket.connection.http.HttpUtils;
import org.xsocket.connection.http.IBodyCompleteListener;
import org.xsocket.connection.http.IHttpConnectHandler;
import org.xsocket.connection.http.IHttpConnection;
import org.xsocket.connection.http.IHttpDisconnectHandler;
import org.xsocket.connection.http.IHttpHandler;
import org.xsocket.connection.http.IHttpRequest;
import org.xsocket.connection.http.IHttpRequestHandler;
import org.xsocket.connection.http.IHttpRequestHeader;
import org.xsocket.connection.http.IHttpResponse;
import org.xsocket.connection.http.IHttpResponseHandler;
import org.xsocket.connection.http.IHttpResponseTimeoutHandler;
/**
* 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 [email protected]
*/
public final class HttpClientConnection extends AbstractHttpConnection implements IHttpConnection, IHttpClientEndpoint {
private static final Logger LOG = Logger.getLogger(HttpClientConnection.class.getName());
private static final long MIN_WATCHDOG_PERIOD_MILLIS = 30 * 1000;
private static String versionInfo = null;
// timer
private static final Timer TIMER = new Timer("xHttpClientTimer", true);
public static final Integer DEFAULT_RECEIVE_TIMEOUT_SEC = Integer.MAX_VALUE;
private int receiveTimeoutSec = DEFAULT_RECEIVE_TIMEOUT_SEC;
private final ArrayList responseHandlers = new ArrayList();
private IHttpHandler handler = null;
private ConnectHandlerAdapter connectHandlerAdapter = null;
private DisconnectHandlerAdapter disconnectHandlerAdapter = null;
// close after response flag
private boolean isCloseAfterResponse = false;
// close handling (non persistent connection)
private final ConnectionAutoCloseListener connectionAutoCloseListner = new ConnectionAutoCloseListener();
// auto handle
boolean isAutohandle100ContinueResponse = DEFAULT_AUTOHANDLE_100CONTINUE_RESPONSE;
// response timeout support
private WatchDogTask watchDogTask = null;
// statistics
private int countReceivedMessages = 0;
private int countSendMessages = 0;
/**
* 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)));
}
/**
* constructor
*
* @param host the remote host
* @param port the remote port
* @param httpHandler the handler (supported: {@link IHttpConnectHandler}, {@link IHttpDisconnectHandler}, {@link IHttpConnectionTimeoutHandler})
* @throws IOException if an exception occurs
* @throws ConnectException if an error occurred while attempting to connect to a remote address and port.
*/
public HttpClientConnection(String host, int port, IHttpHandler httpHandler) throws IOException, ConnectException {
this(newNonBlockingConnection(new InetSocketAddress(host, port)), httpHandler);
}
/**
* 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));
}
/**
* constructor
*
* @param connection the underlying tcp connection
* @throws IOException if an exception occurs
*/
public HttpClientConnection(INonBlockingConnection connection) throws IOException {
this(connection, 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
* @param handler the handler (supported: {@link IHttpConnectHandler}, {@link IHttpDisconnectHandler}, {@link IHttpConnectionTimeoutHandler})
* @throws IOException if an exception occurs
*/
public HttpClientConnection(INonBlockingConnection connection, IHttpHandler handler) throws IOException {
super(connection);
this.handler = handler;
if (HttpUtils.isConnectHandler(handler)) {
connectHandlerAdapter = new ConnectHandlerAdapter(HttpUtils.isConnectHandlerMultithreaded(handler));
}
if (HttpUtils.isDisconnectHandler(handler)) {
disconnectHandlerAdapter = new DisconnectHandlerAdapter(HttpUtils.isDisconnectHandlerMultithreaded(handler));
}
init();
onConnect();
}
void setCloseAfterResponse(boolean isCloseAfterResponse) {
this.isCloseAfterResponse = isCloseAfterResponse;
}
/**
* {@inheritDoc}
*/
public final void setAutohandle100ContinueResponse(boolean isAutohandle100ContinueResponse) {
this.isAutohandle100ContinueResponse = isAutohandle100ContinueResponse;
}
/**
* {@inheritDoc}
*/
public boolean isAutohandle100ContinueResponse() {
return isAutohandle100ContinueResponse;
}
/**
* {@inheritDoc}
*/
public void setResponseTimeoutSec(int receiveTimeoutSec) {
if (this.receiveTimeoutSec != receiveTimeoutSec) {
this.receiveTimeoutSec = receiveTimeoutSec;
long watchdogPeriod = receiveTimeoutSec * 100;
if (watchdogPeriod > MIN_WATCHDOG_PERIOD_MILLIS) {
watchdogPeriod = MIN_WATCHDOG_PERIOD_MILLIS;
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() +"] response timeout set to " + receiveTimeoutSec + " sec. Updating wachdog tas to check period " + watchdogPeriod + " millis");
}
updateWatchDog(watchdogPeriod);
}
}
private synchronized void updateWatchDog(long watchDogPeriod) {
terminateWatchDogTask();
watchDogTask = new WatchDogTask(this);
TIMER.schedule(watchDogTask, watchDogPeriod, watchDogPeriod);
}
private synchronized void terminateWatchDogTask() {
if (watchDogTask != null) {
watchDogTask.cancel();
}
}
/**
* {@inheritDoc}
*/
public int getResponseTimeoutSec() {
return receiveTimeoutSec;
}
protected void onMessage(INonBlockingConnection connection) throws BufferUnderflowException, IOException {
assert (Thread.currentThread().getName().startsWith("xDispatcher"));
HttpResponse response = null;
// read and handle the header
if (isFullResponse(connection)) {
// reading the header
HttpResponseHeader responseHeader = HttpResponseHeader.readFrom(connection, MAX_HEADER_LENGTH);
response = new HttpResponse(responseHeader);
handleResponseHeader(responseHeader);
// add body parser
addBodyParserIfRequired(response, connection);
// simple (header less) response
} else {
// create empty header
HttpResponseHeader responseHeader = newEmptyResponseHeader();
response = new HttpResponse(responseHeader);
handleResponseHeader(responseHeader);
// add body parser
addConnectionTerminatedBodyParser(response, connection);
}
// update statistics
countReceivedMessages++;
if (LOG.isLoggable(Level.FINE)) {
if (response.getNonBlockingBody() == null) {
LOG.fine("[" + connection.getId() + "] bodyless response received from " + connection.getRemoteAddress() +
":" + connection.getRemotePort() +
" " + response.getResponseHeader().toString());
} else {
String body = "";
String contentType = response.getContentType();
if (contentType != null) {
if (contentType.startsWith("application/x-www-form-urlencode")) {
body = response.getNonBlockingBody().toString() + "\n";
}
}
LOG.fine("[" + connection.getId() + "] response received from " + connection.getRemoteAddress() +
":" + connection.getRemotePort() +
": " + response.getResponseHeader().toString() + body);
}
}
// handle 100 continue header
if (isAutohandle100ContinueResponse() && (response.getStatus() == 100)) {
return;
}
// get response handler adapter
ResponseHandlerAdapter adapter = null;
synchronized (responseHandlers) {
if (responseHandlers.isEmpty()) {
// could occur in a timeout case
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("response handler is null (may be a receive timeout has been occured");
}
return;
} else {
adapter = responseHandlers.remove(0);
}
}
// ... and handle the response
if (adapter != null) {
final ResponseHandlerAdapter rsa = adapter;
final HttpResponse res = response;
IBodyCompleteListener completeListener = new IBodyCompleteListener() {
@Execution(Execution.NONTHREADED)
public final void onComplete() {
rsa.onMessageCompleteReceived();
// call handlers call back method if is HEADER_MESSAGE triggered
if (rsa.isResponseHandlerInvokeOnMessageReceived()) {
callOnResponseCallback(rsa, res);
}
}
};
// body containing message?
if (response.hasBody()) {
if (getResponseTimeoutSec() != Integer.MAX_VALUE) {
// sets the body receive timeout
response.getNonBlockingBody().setReceiveTimeoutSec(adapter.getRemainingResponseTimeSec(System.currentTimeMillis()));
}
response.getNonBlockingBody().addCompleteListener(connectionAutoCloseListner);
response.getNonBlockingBody().addCompleteListener(completeListener);
// ... no
} else {
completeListener.onComplete();
connectionAutoCloseListner.onComplete();
}
// call handlers call back method if is HEADER_RECEIVED triggered
if (!adapter.isResponseHandlerInvokeOnMessageReceived()) {
callOnResponseCallback(adapter, response);
}
}
}
private void addBodyParserIfRequired(HttpResponse response, INonBlockingConnection connection) throws IOException {
try {
// TODO If a particular HTTP response is not allowed to have a body return (e.g. HEAD request)
// body less response by definition (304, 204 or 1xx response)
int status = response.getStatus();
if ((status == 304) || (status == 204) || (status < 199)) {
return;
}
// transfer encoding header is set with chunked -> chunked body
String transferEncoding = response.getTransferEncoding();
if ((transferEncoding != null) && (transferEncoding.equalsIgnoreCase("chunked"))) {
addChunkedBodyParser(response);
return;
}
// contains a non-zero Content-Length header -> bound body
if ((response.getContentLength() != -1)) {
if (response.getContentLength() > 0) {
addBoundBodyParser(response);
}
return;
}
// no bound body, no chunked body, content type set -> connection terminated body
if (response.getContentType() != null) {
addConnectionTerminatedBodyParser(response, connection);
return;
}
} catch (BufferUnderflowException ignore) { }
}
private void handleResponseHeader(HttpResponseHeader responseHeader) throws IOException {
// if HTTP 1.1 -> connection is persistent by default
if ((responseHeader.getProtocol() != null) && responseHeader.getProtocol().equals("HTTP/1.1")) {
setPersistent(true);
}
// handle connection header
handleConnectionHeaders(responseHeader);
}
private void handleConnectionHeaders(HttpResponseHeader responseHeader) throws IOException {
String keepAliveHeader = responseHeader.getHeader("Keep-Alive");
if (keepAliveHeader != null) {
String[] tokens = keepAliveHeader.split(",");
for (String token : tokens) {
handleKeepAlive(token);
}
}
//check if persistent connection
String connectionHeader = responseHeader.getHeader("Connection");
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) {
try {
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");
}
setResponseTimeoutSec(timeoutSec);
} 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");
}
setResponseTimeoutSec(timeoutSec);
}
} catch (Exception e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("error occured by handling keep alive option " + option + " " + e.toString());
}
}
}
private void callOnResponseCallback(final ResponseHandlerAdapter adapter, final HttpResponse response) {
Runnable task = new Runnable() {
public void run() {
adapter.performOnResponse(response);
}
};
if (adapter.isResponseHandler()) {
if (adapter.isResponseHandlerMultithreaded()) {
processMultiThreaded(task);
} else {
processNonThreaded(task);
}
} else {
LOG.warning("[" + getId() + "] message received, but response handler is not set");
}
}
private boolean isFullResponse(INonBlockingConnection connection) throws IOException {
connection.markReadPosition();
try {
String statusLine = connection.readStringByDelimiter("\n", 999);
if ((statusLine.length() > 0) && (statusLine.charAt(0) == '\r')) {
statusLine = statusLine.substring(1, statusLine.length());
}
int pos1 = statusLine.indexOf(" ");
int pos2 = statusLine.indexOf(" ", pos1 + 1);
if (pos2 == -1) {
pos2 = statusLine.length();
}
try {
String protocol = statusLine.substring(0, pos1);
if (!protocol.toUpperCase().startsWith("HTTP")) {
return false;
}
// try to read status code to check if response is a full response
Integer.parseInt(statusLine.substring(pos1 + 1, pos2).trim());
return true;
} catch (Exception e) {
return false;
}
} catch (BufferUnderflowException bue) {
if (isOpen()) {
throw bue;
} else {
return false;
}
} catch (MaxReadSizeExceededException mre) {
return false;
} finally {
connection.resetToReadMark();
}
}
/**
* {@inheritDoc}
*/
public IHttpResponse call(IHttpRequest request) throws IOException, SocketTimeoutException {
// create response handler
BlockingResponseHandler responseHandler = new BlockingResponseHandler();
// send request
send(request, responseHandler);
return responseHandler.getResponse();
}
/**
* {@inheritDoc}
*/
public void send(IHttpRequest request, IHttpResponseHandler responseHandler) throws IOException {
send(request, new ResponseHandlerAdapter(responseHandler, this, receiveTimeoutSec));
}
private void performPreSendActions(IHttpRequestHeader requestHeader) throws IOException {
countSendMessages++;
}
void send(IHttpRequest request, ResponseHandlerAdapter responseHandlerAdapter) throws IOException {
if (isOpen()) {
try {
// body less request?
if (request.getNonBlockingBody() == null) {
sendBodyless(request.getRequestHeader(), responseHandlerAdapter);
// 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)");
}
BodyDataSink bodyDataSink = null;
// send with plain body
if (request.getRequestHeader().getContentLength() >= 0) {
bodyDataSink = send(request.getRequestHeader(), request.getContentLength(), responseHandlerAdapter);
// send with chunked body
} else {
bodyDataSink = send(request.getRequestHeader(), responseHandlerAdapter);
}
if (LOG.isLoggable(Level.FINE)) {
String remoteAddr = "";
InetAddress rAddr = getRemoteAddress();
if (rAddr != null) {
remoteAddr = rAddr.toString();
}
LOG.fine("[" + getId() + "] sending (" + bodyDataSink.getClass().getSimpleName() +
" body, target=" + remoteAddr + "): " + request.getRequestHeader());
}
sendMessageBody(bodyDataSink, request.getNonBlockingBody());
}
} 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();
throw new IOException(msg);
}
} else {
throw new ClosedChannelException();
}
}
/**
* {@inheritDoc}
*/
public BodyDataSink send(IHttpRequestHeader requestHeader, IHttpResponseHandler responseHandler) throws IOException {
return send(requestHeader, new ResponseHandlerAdapter(responseHandler, this, receiveTimeoutSec));
}
private BodyDataSink send(IHttpRequestHeader requestHeader, ResponseHandlerAdapter responseHandlerAdapter) throws IOException {
if (requestHeader.getContentLength() != -1) {
return sendPlain(requestHeader, requestHeader.getContentLength(), responseHandlerAdapter);
}
return sendChunked(requestHeader, responseHandlerAdapter);
}
/**
* {@inheritDoc}
*/
public BodyDataSink send(IHttpRequestHeader requestHeader, int contentLength, IHttpResponseHandler responseHandler) throws IOException {
if (requestHeader.getMethod().equalsIgnoreCase("GET") || requestHeader.getMethod().equalsIgnoreCase("HEAD")) {
throw new IOException(requestHeader.getMethod() + " is a bodyless request");
}
return send(requestHeader, contentLength, new ResponseHandlerAdapter(responseHandler, this, receiveTimeoutSec));
}
private BodyDataSink send(IHttpRequestHeader requestHeader, int contentLength, ResponseHandlerAdapter responseHandlerAdapter) throws IOException {
if ((requestHeader.getTransferEncoding() != null) && (requestHeader.getTransferEncoding().equalsIgnoreCase("chunked"))) {
LOG.warning("message header contains Transfer-Encoding: chunked. removing it because message should be sent with predefined length (Content-Length)");
requestHeader.removeHeader("Transfer-Encoding");
}
if (requestHeader.getMethod().equalsIgnoreCase("GET") || requestHeader.getMethod().equalsIgnoreCase("HEAD")) {
throw new IOException(requestHeader.getMethod() + " is a bodyless request");
}
return sendPlain(requestHeader, contentLength, responseHandlerAdapter);
}
private void sendBodyless(IHttpRequestHeader requestHeader, ResponseHandlerAdapter responseHandlerAdapter) throws IOException {
performPreSendActions(requestHeader);
synchronized (responseHandlers) {
responseHandlers.add(responseHandlerAdapter);
}
enhanceHeader(requestHeader);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] sending (bodyless, target=" + getRemoteAddress().toString() + "): " + requestHeader);
}
FlushMode savedFlushmode = getFlushmode();
setFlushmode(FlushMode.ASYNC);
writeHeader(requestHeader);
flush();
setFlushmode(savedFlushmode);
}
BodyDataSink sendPlain(IHttpRequestHeader requestHeader, int contentLength, ResponseHandlerAdapter responseHandlerAdapter) throws IOException {
performPreSendActions(requestHeader);
synchronized (responseHandlers) {
responseHandlers.add(responseHandlerAdapter);
}
return writePlainRequest(requestHeader, contentLength);
}
BodyDataSink sendChunked(IHttpRequestHeader requestHeader, ResponseHandlerAdapter responseHandlerAdapter) throws IOException {
performPreSendActions(requestHeader);
synchronized (responseHandlers) {
responseHandlers.add(responseHandlerAdapter);
}
return writeChunkedRequest(requestHeader);
}
private BodyDataSink writePlainRequest(IHttpRequestHeader header, int contentLength) throws IOException {
if (contentLength > 0) {
header.setContentLength(contentLength);
}
enhanceHeader(header);
return newBoundBodyDataSink(header);
}
private BodyDataSink writeChunkedRequest(IHttpRequestHeader header) throws IOException {
String transferEncoding = header.getTransferEncoding();
if (transferEncoding == null) {
header.setTransferEncoding("chunked");
}
enhanceHeader(header);
return newChunkedBodyDataSink(header);
}
private void enhanceHeader(IHttpRequestHeader header) throws IOException {
String host = header.getHeader("HOST");
if (host == null) {
header.setHeader("Host", getRemoteHostInfo());
}
if (header.getScheme() == null) {
if (isSecure()) {
header.setScheme("https");
} else {
header.setScheme("http");
}
}
String userAgent = header.getHeader("USER-AGENT");
if (userAgent == null) {
header.setHeader("User-Agent", getVersionInfo());
}
}
private static String getVersionInfo() {
if (versionInfo == null) {
versionInfo = "xSocket-http/" + HttpUtils.getVersionInfo();
}
return versionInfo;
}
private String getRemoteHostInfo() throws IOException {
InetAddress remoteAddress = getRemoteAddress();
if (remoteAddress == null) {
return "";
}
int remotePort = getRemotePort();
return remoteAddress.getHostName() + ":" + remotePort;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
String s = "HttpClientConnection (requests=" + countSendMessages + "; responses=" + countReceivedMessages + ") " + super.toString();
return s;
}
private void onConnect() throws IOException {
if (connectHandlerAdapter != null) {
connectHandlerAdapter.callOnConnect();
}
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
protected void onDisconnect() throws IOException {
terminateWatchDogTask();
ArrayList responseHandlersCopy = null;
synchronized (responseHandlers) {
responseHandlersCopy = (ArrayList) responseHandlers.clone();
}
for (ResponseHandlerAdapter responseHandlerAdapter : responseHandlersCopy) {
callExceptionCallback(responseHandlerAdapter, new IOException("connection has been disconnected. " + this.toString()));
}
if (disconnectHandlerAdapter != null) {
disconnectHandlerAdapter.callOnDisconnect();
}
super.onDisconnect();
if (LOG.isLoggable(Level.FINE)) {
LOG.finer("[" + getId() + "] http client connection closed");
}
}
@SuppressWarnings("unchecked")
private void checkTimeouts() {
try {
List responseHandlersCopy = null;
synchronized (responseHandlers) {
responseHandlersCopy = (List) responseHandlers.clone();
}
long currentTimeMillis = System.currentTimeMillis();
for (ResponseHandlerAdapter responseHandlerAdapter : responseHandlersCopy) {
if (responseHandlerAdapter.isResponseTimeoutReached(currentTimeMillis)) {
onResponseTimeout(responseHandlerAdapter);
}
}
} catch (Exception e) {
LOG.warning("exception occured by checking timouts. Reason: " + e.toString());
}
}
private void onResponseTimeout(ResponseHandlerAdapter responseHandlerAdapter) {
setPersistent(false);
// and notify the response timeout handler
callExceptionCallback(responseHandlerAdapter, new SocketTimeoutException("timeout " + responseHandlerAdapter.getReceiveTimeoutSec() + " sec reached"));
// destroy the connection
destroy();
}
private void callExceptionCallback(final ResponseHandlerAdapter responseHandlerAdapter, final IOException ioe) {
synchronized (responseHandlers) {
if (responseHandlers.contains(responseHandlerAdapter)) {
responseHandlers.remove(responseHandlerAdapter);
} else {
return;
}
}
if ((ioe instanceof SocketTimeoutException) && responseHandlerAdapter.isResponseTimeoutHandler()) {
Runnable task = new Runnable() {
public void run() {
responseHandlerAdapter.performOnException((SocketTimeoutException) ioe);
}
};
if (responseHandlerAdapter.isResponseTimeoutHandlerMultithreaded()) {
processMultiThreaded(task);
} else {
processNonThreaded(task);
}
return;
}
Runnable task = new Runnable() {
public void run() {
responseHandlerAdapter.performOnException(ioe);
}
};
if (responseHandlerAdapter.isResponseExceptionHandlerMultithreaded()) {
processMultiThreaded(task);
} else {
processNonThreaded(task);
}
}
static class ResponseHandlerAdapter {
private IHttpResponseHandler responseHandler = null;
private boolean isResponseHandler = true;
private boolean isResponseHandlerMultithreaded = true;
private boolean isResponseExceptionHandlerMultithreaded = true;
private boolean isResponseHandlerInvokeOnMessageReceived = false;
private boolean isResponseTimeoutHandler = false;
private boolean isResponseTimeoutHandlerMultithreaded = true;
private HttpClientConnection httpConnection = null;
private long creationTimeMillis = System.currentTimeMillis();
private int receiveTimeoutSec = 0;
ResponseHandlerAdapter(IHttpResponseHandler handler, HttpClientConnection httpConnection, int receiveTimeoutSec) throws IOException {
this(handler, HttpUtils.isResponseHandler(handler), HttpUtils.isResponseExceptionHandlerMultithreaded(handler), HttpUtils.isResponseHandlerMultithreaded(handler), HttpUtils.isResponseTimeoutHandler(handler), HttpUtils.isRequestTimeoutHandlerMultithreaded(handler), HttpUtils.isResponseHandlerInvokeOnMessageReceived(handler), httpConnection, receiveTimeoutSec);
}
ResponseHandlerAdapter(IHttpResponseHandler responseHandler, boolean isResponseHandler, boolean isResponseExceptionHandlerMultithreaded, boolean isResponseHandlerMultithreaded, boolean isResponseTimeoutHandler, boolean isResponseTimeoutHandlerMultithreaded, boolean isResponseHandlerInvokeOnMessageReceived, HttpClientConnection httpConnection, int receiveTimeoutSec) throws IOException {
this.responseHandler = responseHandler;
this.httpConnection = httpConnection;
this.receiveTimeoutSec = receiveTimeoutSec;
this.isResponseHandler = isResponseHandler;
this.isResponseExceptionHandlerMultithreaded = isResponseExceptionHandlerMultithreaded;
this.isResponseHandlerMultithreaded = isResponseHandlerMultithreaded;
this.isResponseTimeoutHandler = isResponseTimeoutHandler;
this.isResponseTimeoutHandlerMultithreaded = isResponseTimeoutHandlerMultithreaded;
this.isResponseHandlerInvokeOnMessageReceived = isResponseHandlerInvokeOnMessageReceived;
}
boolean isResponseTimeoutHandlerMultithreaded() {
return isResponseTimeoutHandlerMultithreaded;
}
boolean isResponseTimeoutHandler() {
return isResponseTimeoutHandler;
}
boolean isResponseHandlerInvokeOnMessageReceived() {
return isResponseHandlerInvokeOnMessageReceived;
}
boolean isResponseHandler() {
return isResponseHandler;
}
boolean isResponseHandlerMultithreaded() {
return isResponseHandlerMultithreaded;
}
boolean isResponseExceptionHandlerMultithreaded() {
return isResponseExceptionHandlerMultithreaded;
}
final HttpClientConnection getHttpConnection() {
return httpConnection;
}
final boolean isResponseTimeoutReached(long currentTime) {
if (getRemainingResponseTimeSec(currentTime) <= 0) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + httpConnection.getId() + "] response timeout " +
DataConverter.toFormatedDuration((long) receiveTimeoutSec * 1000) + " reached)");
}
return true;
} else {
return false;
}
}
private int getRemainingResponseTimeSec(long currentTime) {
return receiveTimeoutSec - getElapsedResponseTimeSec(currentTime);
}
private int getElapsedResponseTimeSec(long currentTimeMillis) {
long elapsed = currentTimeMillis - creationTimeMillis;
if (elapsed < 1000) {
return 0;
} else {
return (int) elapsed / 1000;
}
}
int getReceiveTimeoutSec() {
return receiveTimeoutSec;
}
public void performOnResponse(HttpResponse response) {
try {
responseHandler.onResponse(response);
} catch (Exception e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + httpConnection.getId() + "] error occured by calling on response " + responseHandler + " " + response + " " + e.toString());
LOG.fine("destroying connection " + httpConnection.getId());
}
httpConnection.destroy();
}
}
public void performOnException(IOException ioe) {
responseHandler.onException(ioe);
}
public void performOnException(SocketTimeoutException stoe) {
((IHttpResponseTimeoutHandler) responseHandler).onException(stoe);
}
public void onMessageCompleteReceived() { }
}
BodyDataSink newInterception(HttpClient httpClient, HttpClientConnection con, IHttpRequestHeader requestHeader, IHttpRequestHandler interceptor, boolean isInterceptorChain, IHttpResponseHandler responseHandler) throws IOException {
return super.newClientInterception(httpClient, con, requestHeader, interceptor, isInterceptorChain, responseHandler);
}
void newExchange(HttpClient httpClient, HttpClientConnection con, IHttpRequest request, IHttpRequestHandler requestHandler, boolean isRequestHandlerChain, IHttpResponseHandler responseHandler) throws IOException {
super.newClientExhange(httpClient, con, request, requestHandler, isRequestHandlerChain, responseHandler);
}
private final class ConnectionAutoCloseListener implements IBodyCompleteListener {
@Execution(Execution.NONTHREADED)
public void onComplete() throws IOException {
// is not persistent?
if (!isPersistent()) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] destroying connection because it is not persistent");
}
// destroy it and return
destroy();
return;
// .. it is persistent
} else {
// close after response flag?
if (isCloseAfterResponse) {
close();
}
}
}
}
final static class BlockingResponseHandler implements IHttpResponseHandler {
private final Object readLock = new Object();
private final AtomicReference responseRef = new AtomicReference();
private final AtomicReference ioExceptionRef = new AtomicReference();
@Execution(Execution.NONTHREADED)
public void onResponse(IHttpResponse response) throws IOException {
responseRef.set(response);
synchronized (readLock) {
readLock.notifyAll();
}
}
@Execution(Execution.NONTHREADED)
public void onException(IOException ioe) {
ioExceptionRef.set(ioe);
synchronized (readLock) {
readLock.notifyAll();
}
}
IHttpResponse getResponse() throws IOException {
do {
synchronized (readLock) {
if (ioExceptionRef.get() != null) {
throw ioExceptionRef.get();
}
if (responseRef.get() == null) {
try {
readLock.wait();
} catch (InterruptedException ignore) { }
} else {
return responseRef.get();
}
}
} while (true);
}
}
private final class ConnectHandlerAdapter implements Runnable {
private boolean isMultithreaded = true;
public ConnectHandlerAdapter(boolean isMultithreaded) {
this.isMultithreaded = isMultithreaded;
}
private void callOnConnect() {
if (isMultithreaded) {
processMultiThreaded(this);
} else {
processNonThreaded(this);
}
}
public void run() {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("calling onConnect on " + handler);
}
try {
((IHttpConnectHandler) handler).onConnect(HttpClientConnection.this);
} catch (Exception e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by calling on connect " + handler + " " + e.toString());
}
}
}
}
private final class DisconnectHandlerAdapter implements Runnable {
private boolean isMultithreaded = true;
public DisconnectHandlerAdapter(boolean isMultithreaded) {
this.isMultithreaded = isMultithreaded;
}
private void callOnDisconnect() {
if (isMultithreaded) {
processMultiThreaded(this);
} else {
processNonThreaded(this);
}
}
public void run() {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("calling onDisconnect on " + handler);
}
try {
((IHttpDisconnectHandler) handler).onDisconnect(HttpClientConnection.this);
} catch (Exception e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by calling on connect " + handler + " " + e.toString());
}
}
}
}
private static final class WatchDogTask extends TimerTask {
private WeakReference httpClientConnectionRef = null;
public WatchDogTask(HttpClientConnection httpClientConnection) {
httpClientConnectionRef = new WeakReference(httpClientConnection);
}
@Override
public void run() {
HttpClientConnection httpClientConnection = httpClientConnectionRef.get();
if (httpClientConnection == null) {
this.cancel();
} else {
httpClientConnection.checkTimeouts();
}
}
}
}