org.xsocket.connection.http.client.HttpClientConnection Maven / Gradle / Ivy
/*
* 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.AtomicBoolean;
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.HttpUtils;
import org.xsocket.connection.http.IBodyCompleteListener;
import org.xsocket.connection.http.IHttpConnectHandler;
import org.xsocket.connection.http.IHttpConnection;
import org.xsocket.connection.http.IHttpConnectionTimeoutHandler;
import org.xsocket.connection.http.IHttpDisconnectHandler;
import org.xsocket.connection.http.IHttpHandler;
import org.xsocket.connection.http.HttpRequest;
import org.xsocket.connection.http.HttpRequestHeader;
import org.xsocket.connection.http.HttpResponse;
import org.xsocket.connection.http.HttpResponseHeader;
import org.xsocket.connection.http.client.ClientUtils.HttpHandlerInfo;
import org.xsocket.connection.http.client.ClientUtils.ResponseHandlerInfo;
import org.xsocket.connection.spi.DefaultIoProvider;
/**
* 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_MILLIS = Integer.MAX_VALUE;
private int receiveTimeoutMillis = DEFAULT_RECEIVE_TIMEOUT_MILLIS;
private final ArrayList responseHandlers = new ArrayList();
private IHttpHandler handler = null;
private ConnectHandlerAdapter connectHandlerAdapter = null;
private DisconnectHandlerAdapter disconnectHandlerAdapter = null;
private ConnectionTimeoutHandlerAdapter connectionTimeoutHandlerAdapter = 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 httpHandler the handler (supported: {@link IHttpConnectHandler}, {@link IHttpDisconnectHandler}, {@link IHttpConnectionTimeoutHandler})
* @throws IOException if an exception occurs
*/
public HttpClientConnection(INonBlockingConnection connection, IHttpHandler httpHandler) throws IOException {
super(connection);
HttpHandlerInfo httpHandlerInfo = ClientUtils.getHttpHandlerInfo(httpHandler);
this.handler = httpHandler;
if (httpHandlerInfo.isConnectHandler()) {
connectHandlerAdapter = new ConnectHandlerAdapter(httpHandlerInfo.isConnectHandlerMultithreaded());
}
if (httpHandlerInfo.isDisconnectHandler()) {
disconnectHandlerAdapter = new DisconnectHandlerAdapter(httpHandlerInfo.isDisconnectHandlerMultithreaded());
}
if (httpHandlerInfo.isConnectionTimeoutHandler()) {
connectionTimeoutHandlerAdapter = new ConnectionTimeoutHandlerAdapter(httpHandlerInfo.isConnectionTimeoutHandlerMultithreaded());
}
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 setResponseTimeoutMillis(int receiveTimeoutMillis) {
if (this.receiveTimeoutMillis != receiveTimeoutMillis) {
this.receiveTimeoutMillis = receiveTimeoutMillis;
long watchdogPeriod = 0;
if (receiveTimeoutMillis > 100) {
watchdogPeriod = receiveTimeoutMillis / 10;
} else {
watchdogPeriod = receiveTimeoutMillis;
}
if (watchdogPeriod > MIN_WATCHDOG_PERIOD_MILLIS) {
watchdogPeriod = MIN_WATCHDOG_PERIOD_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 getResponseTimeoutMillis() {
return receiveTimeoutMillis;
}
protected void onMessage(INonBlockingConnection connection) throws BufferUnderflowException, IOException {
assert (DefaultIoProvider.isDispatcherThread());
HttpResponse response = null;
// read and handle the header
if (hasHeader(connection)) {
// reading the header
HttpResponseHeader responseHeader = HttpResponseHeader.readFrom(connection, MAX_HEADER_LENGTH);
response = new HttpResponse(responseHeader);
handleResponseHeader(responseHeader);
// response with body
if ((response.getStatus() != 304) && (response.getStatus() != 204)) {
addFullMessageBodyParser(response, connection);
}
// simple (header less) response
} else {
// create emtpy header
HttpResponseHeader responseHeader = newEmptyResponseHeader();
response = new HttpResponse(responseHeader);
handleResponseHeader(responseHeader);
// add body parser
addSimpleMessageBodyParser(response, connection);
}
// update statistics
countReceivedMessages++;
if (LOG.isLoggable(Level.FINE)) {
if (response.getNonBlockingBody() == null) {
LOG.fine("[" + connection.getId() + "] response received (bodyless): " + response.getResponseHeader().toString());
} else {
if (response.getNonBlockingBody().isComplete()) {
LOG.fine("[" + connection.getId() + "] response received (" + response.getNonBlockingBody().getClass().getSimpleName() +
" isComplete=true; size= " + response.getNonBlockingBody().available() + " bytes): " + response.getResponseHeader().toString());
} else {
LOG.fine("[" + connection.getId() + "] response received (" + response.getNonBlockingBody().getClass().getSimpleName() +
" isComplete=false): " + response.getResponseHeader().toString());
}
}
}
// 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) {
adapter.setResponse(response);
final ResponseHandlerAdapter rsa = adapter;
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.getResponseHandlerInfo().isInvokationOnMessageReceived()) {
callOnResponse(rsa);
}
}
};
// body containing message?
if (response.hasBody()) {
if (getResponseTimeoutMillis() != Integer.MAX_VALUE) {
// sets the body receive timeout
response.getNonBlockingBody().setReceiveTimeoutMillis(adapter.getRemainingTime(System.currentTimeMillis()));
}
response.getNonBlockingBody().addCompleteListener(connectionAutoCloseListner);
response.getNonBlockingBody().addCompleteListener(completeListener);
// ... no
} else {
connectionAutoCloseListner.onComplete();
}
// call handlers call back method if is HEADER_RECEIVED triggered
if (!adapter.getResponseHandlerInfo().isInvokationOnMessageReceived()) {
callOnResponse(adapter);
}
}
}
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.FINE)) {
LOG.fine("[" + 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()));
setResponseTimeoutMillis(timeoutSec * 1000);
} else {
Integer seconds = Integer.parseInt(option);
setResponseTimeoutMillis(seconds * 1000);
}
} catch (Exception e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("error occured by handling keep alive option " + option + " " + e.toString());
}
}
}
private void callOnResponse(ResponseHandlerAdapter adapter) {
if (adapter.getResponseHandlerInfo().isResponseHandler()) {
if (adapter.getResponseHandlerInfo().isOnResponseMultithreaded()) {
processMultiThreaded(adapter);
} else {
processNonThreaded(adapter);
}
} else {
LOG.warning("[" + getId() + "] message received, but response handler is not set");
}
}
private boolean hasHeader(INonBlockingConnection connection) throws IOException {
connection.markReadPosition();
try {
String statusLine = connection.readStringByDelimiter("\r\n", 999);
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));
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 HttpResponse call(HttpRequest request) throws IOException, SocketTimeoutException {
// create response handler
BlockingResponseHandler responseHandler = new BlockingResponseHandler(this, receiveTimeoutMillis);
// send request
send(request, responseHandler);
return responseHandler.getResponse();
}
/**
* {@inheritDoc}
*/
public void send(HttpRequest request, IHttpResponseHandler responseHandler) throws IOException {
if (isWriteTransactionRunning()) {
throw new IOException("concurrency error. A http request transaction is running");
}
send(request, new ResponseHandlerAdapter(responseHandler, this, receiveTimeoutMillis));
}
private void performPreSendActions(HttpRequestHeader requestHeader) throws IOException {
countSendMessages++;
if (isWriteTransactionRunning()) {
throw new IOException("concurrency error. A http request transaction is running");
}
}
void send(HttpRequest request, ResponseHandlerAdapter responseHandlerAdapter) throws IOException {
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)) {
LOG.fine("[" + getId() + "] sending (" + bodyDataSink.getClass().getSimpleName() +
" body): " + 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);
}
}
/**
* {@inheritDoc}
*/
public BodyDataSink send(HttpRequestHeader requestHeader, IHttpResponseHandler responseHandler) throws IOException {
return send(requestHeader, new ResponseHandlerAdapter(responseHandler, this, receiveTimeoutMillis));
}
private BodyDataSink send(HttpRequestHeader requestHeader, ResponseHandlerAdapter responseHandlerAdapter) throws IOException {
if (isWriteTransactionRunning()) {
throw new IOException("concurrency error. A http request transaction is running");
}
return sendChunked(requestHeader, responseHandlerAdapter);
}
/**
* {@inheritDoc}
*/
public BodyDataSink send(HttpRequestHeader requestHeader, int contentLength, IHttpResponseHandler responseHandler) throws IOException {
return send(requestHeader, contentLength, new ResponseHandlerAdapter(responseHandler, this, receiveTimeoutMillis));
}
private BodyDataSink send(HttpRequestHeader requestHeader, int contentLength, ResponseHandlerAdapter responseHandlerAdapter) throws IOException {
if (isWriteTransactionRunning()) {
throw new IOException("concurrency error. A http request transaction is running");
}
return sendPlain(requestHeader, contentLength, responseHandlerAdapter);
}
private void sendBodyless(HttpRequestHeader requestHeader, ResponseHandlerAdapter responseHandlerAdapter) throws IOException {
performPreSendActions(requestHeader);
synchronized (responseHandlers) {
responseHandlers.add(responseHandlerAdapter);
}
enhanceHeader(requestHeader);
assert (getUnderlyingConnection().getFlushmode() == FlushMode.ASYNC);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] sending (bodyless): " + requestHeader);
}
requestHeader.writeTo(getUnderlyingConnection());
getUnderlyingConnection().flush();
}
BodyDataSink sendPlain(HttpRequestHeader requestHeader, int contentLength, ResponseHandlerAdapter responseHandlerAdapter) throws IOException {
performPreSendActions(requestHeader);
synchronized (responseHandlers) {
responseHandlers.add(responseHandlerAdapter);
}
return writePlainRequest(requestHeader, contentLength);
}
BodyDataSink sendChunked(HttpRequestHeader requestHeader, ResponseHandlerAdapter responseHandlerAdapter) throws IOException {
performPreSendActions(requestHeader);
synchronized (responseHandlers) {
responseHandlers.add(responseHandlerAdapter);
}
return writeChunkedRequest(requestHeader);
}
private BodyDataSink writePlainRequest(HttpRequestHeader header, int contentLength) throws IOException {
if (contentLength > 0) {
header.setContentLength(contentLength);
}
enhanceHeader(header);
return newBoundBody(header);
}
private BodyDataSink writeChunkedRequest(HttpRequestHeader header) throws IOException {
String transferEncoding = header.getTransferEncoding();
if (transferEncoding == null) {
header.setTransferEncoding("chunked");
}
enhanceHeader(header);
return newChunkedBody(header);
}
private void enhanceHeader(HttpRequestHeader header) throws IOException {
String host = header.getHeader("HOST");
if (host == null) {
header.setHeader("HOST", getRemoteHostInfo());
}
if (header.getScheme() == null) {
if (getUnderlyingConnection().isSecure()) {
header.setScheme("HTTPS");
} else {
header.setScheme("HTTP");
}
}
String userAgent = header.getHeader("USER-AGENT");
if (userAgent == null) {
header.setHeader("USER-AGENT", getVersionInfo());
}
if ((header.getHeader("CONNECTION") == null) && (header.getHeader("KEEP-ALIVE") == null)) {
header.setHeader("CONNECTION", "Keep-Alive");
header.setHeader("KEEP-ALIVE", "10");
}
}
private static String getVersionInfo() {
if (versionInfo == null) {
versionInfo = "xSocket-http/" + HttpUtils.getVersionInfo();
}
return versionInfo;
}
private String getRemoteHostInfo() throws IOException {
INonBlockingConnection con = getUnderlyingConnection();
InetAddress remoteAddress = con.getRemoteAddress();
int remotePort = con.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) {
if (responseHandlerAdapter.getResponseHandlerInfo().isResponseTimeoutHandler()) {
callResponseTimeout(responseHandlerAdapter);
}
}
if (disconnectHandlerAdapter != null) {
disconnectHandlerAdapter.callOnDisconnect();
}
super.onDisconnect();
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] http client connection closed");
}
}
/**
* {@inheritDoc}
*/
@Override
protected void onConnectionTimeout() throws IOException {
if (connectionTimeoutHandlerAdapter != null) {
connectionTimeoutHandlerAdapter.callOnConnectionTimeout();
} else {
super.onConnectionTimeout();
}
}
@SuppressWarnings("unchecked")
private void checkTimeouts() {
List responseHandlersCopy = null;
synchronized (responseHandlers) {
responseHandlersCopy = (List) responseHandlers.clone();
}
long currentTimeMillis = System.currentTimeMillis();
for (ResponseHandlerAdapter responseHandlerAdapter : responseHandlersCopy) {
if (responseHandlerAdapter.getRemainingTime(currentTimeMillis) < 0) {
// and notify the response timeout handler
callResponseTimeout(responseHandlerAdapter);
// destroy the connection
destroy();
}
}
}
private void callResponseTimeout(final ResponseHandlerAdapter responseHandlerAdapter) {
synchronized (responseHandlers) {
if (responseHandlers.contains(responseHandlerAdapter)) {
responseHandlers.remove(responseHandlerAdapter);
} else {
return;
}
}
if (responseHandlerAdapter.getResponseHandlerInfo().isResponseTimeoutHandler()) {
Runnable task = new Runnable() {
public void run() {
try {
IHttpResponseTimeoutHandler responseTimeoutHandler = responseHandlerAdapter.getResponseTmeoutHandler();
responseTimeoutHandler.onResponseTimeout();
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("error occured by calling onResponseTimeout " + ioe.toString());
closeSilence();
}
}
}
};
if (responseHandlerAdapter.getResponseHandlerInfo().isOnResponseTimeoutMultithreaded()) {
processMultiThreaded(task);
} else {
processNonThreaded(task);
}
} else {
closeSilence();
}
}
static class ResponseHandlerAdapter implements Runnable {
private IHttpResponseHandler responseHandler = null;
private IHttpResponseTimeoutHandler responseTimeoutHandler = null;
private HttpResponse response = null;
private ResponseHandlerInfo responseHandlerInfo = null;
private HttpClientConnection httpConnection = null;
private long creationTime = System.currentTimeMillis();
private int receiveTimeoutMillis = 0;
private ResponseHandlerAdapter(ResponseHandlerInfo responseHandlerInfo, HttpClientConnection httpConnection, int receiveTimeoutMillis) {
this.responseHandlerInfo = responseHandlerInfo;
this.httpConnection = httpConnection;
this.receiveTimeoutMillis = receiveTimeoutMillis;
}
ResponseHandlerAdapter(IHttpResponseHandler responseHandler, HttpClientConnection httpConnection, int receiveTimeoutMillis) throws IOException {
this(ClientUtils.getResponseHandlerInfo(responseHandler), httpConnection, receiveTimeoutMillis);
this.responseHandler = responseHandler;
if (responseHandlerInfo.isResponseTimeoutHandler()) {
responseTimeoutHandler = (IHttpResponseTimeoutHandler) responseHandler;
}
}
private final ResponseHandlerInfo getResponseHandlerInfo() {
return responseHandlerInfo;
}
private final IHttpResponseTimeoutHandler getResponseTmeoutHandler() {
return responseTimeoutHandler;
}
final HttpClientConnection getHttpConnection() {
return httpConnection;
}
private int getRemainingTime(long currentTime) {
return (int) (receiveTimeoutMillis - (currentTime - creationTime));
}
int getReceiveTimeoutMillis() {
return receiveTimeoutMillis;
}
void setResponse(HttpResponse response) {
this.response = response;
}
public final void run() {
performOnResponse(response);
}
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());
}
}
}
public void onMessageCompleteReceived() { }
}
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();
}
}
}
}
static class BlockingResponseHandler extends ResponseHandlerAdapter implements IHttpResponseTimeoutHandler {
private final Object readLock = new Object();
private final AtomicReference responseRef = new AtomicReference();
private final AtomicBoolean timeoutOccured = new AtomicBoolean(false);
public BlockingResponseHandler(HttpClientConnection httpConnection, int receiveTimeoutMillis) {
this(ClientUtils.RESPONSE_HANDLER_INFO_NONTHREADED_HEADER_RECEIVED, httpConnection, receiveTimeoutMillis);
}
public BlockingResponseHandler(ResponseHandlerInfo responseHandlerInfo, HttpClientConnection httpConnection, int receiveTimeoutMillis) {
super(responseHandlerInfo, httpConnection, receiveTimeoutMillis);
super.responseTimeoutHandler = this;
}
public void performOnResponse(HttpResponse response) {
responseRef.set(response);
synchronized (readLock) {
readLock.notifyAll();
}
}
public void onResponseTimeout() throws IOException {
synchronized (readLock) {
timeoutOccured.set(true);
readLock.notifyAll();
}
}
HttpResponse getResponse() throws SocketTimeoutException, ClosedChannelException {
do {
synchronized (readLock) {
if (responseRef.get() == null) {
try {
readLock.wait();
} catch (InterruptedException ignore) { }
} else {
return responseRef.get();
}
}
} while (!timeoutOccured.get());
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("receive timeout " + DataConverter.toFormatedDuration(getReceiveTimeoutMillis()) + " reached. destroying connection and throwing a timeout exception");
}
getHttpConnection().destroy();
throw new SocketTimeoutException("timeout reached");
}
}
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 final class ConnectionTimeoutHandlerAdapter implements Runnable {
private boolean isMultithreaded = true;
public ConnectionTimeoutHandlerAdapter(boolean isMultithreaded) {
this.isMultithreaded = isMultithreaded;
}
private void callOnConnectionTimeout() {
if (isMultithreaded) {
processMultiThreaded(this);
} else {
processNonThreaded(this);
}
}
public void run() {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("calling onConnectionTimeout on " + handler);
}
try {
((IHttpConnectionTimeoutHandler) handler).onConnectionTimeout(HttpClientConnection.this);
} catch (Exception e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by calling on connection timeout " + 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();
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy