All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xsocket.connection.http.client.HttpClientConnection Maven / Gradle / Ivy

There is a newer version: 2.0-beta-1
Show 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.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