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

org.xlightweb.client.HttpClientConnection Maven / Gradle / Ivy

Go to download

xLightweb is a lightweight, high performance, scalable web network library

There is a newer version: 2.13.2
Show newest version
/*
 *  Copyright (c) xlightweb.org, 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.xlightweb.org/
 */
package org.xlightweb.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.net.URL;
import java.nio.channels.ClosedChannelException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.TimerTask;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;


import org.xlightweb.AbstractHttpConnection;
import org.xlightweb.BodyDataSink;
import org.xlightweb.FutureResponseHandler;
import org.xlightweb.HttpRequest;
import org.xlightweb.HttpResponse;
import org.xlightweb.ResponseHandlerInfo;
import org.xlightweb.IHttpSession;
import org.xlightweb.HttpUtils;
import org.xlightweb.IBodyCompleteListener;
import org.xlightweb.IHttpConnection;
import org.xlightweb.IHttpConnectionHandler;
import org.xlightweb.IHttpExchange;
import org.xlightweb.IHttpMessage;
import org.xlightweb.IHttpRequest;
import org.xlightweb.IHttpRequestHandler;
import org.xlightweb.IHttpRequestHeader;
import org.xlightweb.IHttpResponse;
import org.xlightweb.IHttpResponseHandler;
import org.xlightweb.IHttpResponseHeader;
import org.xlightweb.IHttpSocketTimeoutHandler;
import org.xlightweb.RequestHandlerChain;
import org.xsocket.DataConverter;
import org.xsocket.Execution;
import org.xsocket.SerializedTaskQueue;
import org.xsocket.connection.INonBlockingConnection;
import org.xsocket.connection.NonBlockingConnection;





/**
 * 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 IHttpClientEndpoint {

	
	private static final Logger LOG = Logger.getLogger(HttpClientConnection.class.getName());
	
	private static String implementationVersion;

	
	// life cycle
	private boolean isAutocloseAfterResponse = false;
	private final AtomicBoolean isDisconnected = new AtomicBoolean(false);
	
	
	// response timeout support
	private static final Long DEFAULT_RESPONSE_TIMEOUT_SEC = Long.MAX_VALUE;
	private static final long MIN_WATCHDOG_PERIOD_MILLIS = 30 * 1000;
	private long responseTimeoutMillis = DEFAULT_RESPONSE_TIMEOUT_SEC;
	private WatchDogTask watchDogTask;
	
	
	// handler management
	private final List bodyReceivingResponseHandlers = Collections.synchronizedList(new ArrayList());
	private final ArrayList handlersWaitingForResponseHeader = new ArrayList();

	
	// transaction monitor support
	private ITransactionMonitor transactionMonitor;
	

	
	/**
	 * 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)), null);
	}

	
	/**
	 * 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), 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
	 * @throws IOException    if an exception occurs
	 */
	public HttpClientConnection(INonBlockingConnection connection) throws IOException {
		this(connection, null);
		init();
	}
	

	
	
	/**
	 * constructor 
	 * 
	 * @param host               the remote host
	 * @param port               the remote port
	 * @param connectionHandler  the connection handler
	 * @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, IHttpConnectionHandler connectionHandler) throws IOException, ConnectException {
		this(newNonBlockingConnection(new InetSocketAddress(host, port)), connectionHandler);
	}

	
	
	/**
	 * constructor 
	 * 
	 * @param connection    the underlying tcp connection
	 * @param handler       the handler
	 * @throws IOException    if an exception occurs
	 */
	private HttpClientConnection(INonBlockingConnection connection, IHttpConnectionHandler connectionHandler) throws IOException {
		super(connection, true);

		if (connectionHandler != null) {
			addConnectionHandler(connectionHandler);
		}
		init();
	}
	
	

	/**
	 * sets a transaction monitor
	 * 
	 * @param transactionMonitor the transaction monitor
	 */
	void setTransactionMonitor(ITransactionMonitor transactionMonitor) {
		this.transactionMonitor = transactionMonitor;
	}

	
	
	/**
     * {@inheritDoc}
     */
	@Override
	protected void onIdleTimeout() throws IOException {

	    // notify waiting handler
        for (MessageHandler messageHandler : getHandlersWaitingForResponseCopy()) {
            messageHandler.onException(new SocketTimeoutException("idle timeout " + DataConverter.toFormatedDuration(getIdleTimeoutMillis()) + " reached"));
        }
        handlersWaitingForResponseHeader.clear();

	    super.onIdleTimeout();
	}
	
	
	
	
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void onConnectionTimeout() throws IOException {

		// notify waiting handler
		for (MessageHandler messageHandler : getHandlersWaitingForResponseCopy()) {
			messageHandler.onException(new SocketTimeoutException("connection timeout " + DataConverter.toFormatedDuration(getConnectionTimeoutMillis()) + " reached"));
		}
		handlersWaitingForResponseHeader.clear();

		super.onConnectionTimeout();
	}
	
	@SuppressWarnings("unchecked")
	private List getHandlersWaitingForResponseCopy() {
		synchronized (handlersWaitingForResponseHeader) {
			return (List) handlersWaitingForResponseHeader.clone();
		}
	}
	
		
	/**
	 * {@inheritDoc}
	 */	
	@Override
	protected void onDisconnect() {
		
		if (!isDisconnected.get()) {
			isDisconnected.set(true);
			
			// notify pending handlers 
			while (!bodyReceivingResponseHandlers.isEmpty()) {
				bodyReceivingResponseHandlers.remove(0).onException(new ClosedChannelException());
			}
			
			
			// notify waiting handler 
			for (MessageHandler messageHandler : getHandlersWaitingForResponseCopy()) {
				messageHandler.onException(new ClosedChannelException());
			}
			handlersWaitingForResponseHeader.clear();
			
			super.onDisconnect();
		}
	}

	
	
	/**
	 * generates a error page
	 * 
	 * 
	 * @param errorCode  the error code
	 * @param msg        the message
	 * @param id         the connection id
	 * @return the error page
	 */
	protected static String generateErrorMessageHtml(int errorCode, String msg, String id) {
		return AbstractHttpConnection.generateErrorMessageHtml(errorCode, msg, id);
	}

	
	/**
	 * schedules a timer task 
	 * 
	 * @param task     the timer task 
	 * @param delay    the delay 
	 * @param period   the period 
	 */
	protected static void schedule(TimerTask task, long delay, long period) {
		AbstractHttpConnection.schedule(task, delay, period);
	}


	/**
	 * {@inheritDoc}
	 */
	public IHttpResponse call(IHttpRequest request) throws IOException, ConnectException, SocketTimeoutException {
		
		// create response handler 
		FutureResponseHandler responseHandler = new FutureResponseHandler();
		
		
		// send request 
		send(request, wrapResponsehandler(responseHandler));
		return responseHandler.getResponse();
	}

	
	/**
	 * set if the connection should be closed after sending a response 
	 * 
	 * @param isAutocloseAfterResponse true, if the connection should be closed after sending a response
	 */
	void setAutocloseAfterResponse(boolean isAutocloseAfterResponse) {
		this.isAutocloseAfterResponse = isAutocloseAfterResponse;
	}
	

	/**
     * {@inheritDoc}
     */
	public boolean isServerSide() {
	    return false;
	}
	

	/**
	 * {@inheritDoc}
	 */
	public void setResponseTimeoutMillis(long responseTimeoutMillis) {
		
		
		if (this.responseTimeoutMillis != responseTimeoutMillis) {
			this.responseTimeoutMillis = responseTimeoutMillis;

			if (responseTimeoutMillis == Long.MAX_VALUE) {
				terminateWatchDogTask();
				
			} else {
				
				long watchdogPeriod = 100;
				if (responseTimeoutMillis > 1000) {
					watchdogPeriod = responseTimeoutMillis / 10;
				}
				
				if (watchdogPeriod > MIN_WATCHDOG_PERIOD_MILLIS) {
					watchdogPeriod = MIN_WATCHDOG_PERIOD_MILLIS;
				}
			
				if (LOG.isLoggable(Level.FINE)) {
					LOG.fine("[" + getId() +"] response timeout to " + DataConverter.toFormatedDuration(responseTimeoutMillis) + ". Updating wachdog tas to check period " + watchdogPeriod + " millis");
				}
				
				updateWatchDog(watchdogPeriod);
			}
		}
	}
	
	
	private synchronized void updateWatchDog(long watchDogPeriod) {
		terminateWatchDogTask();

        watchDogTask = new WatchDogTask(this); 
        schedule(watchDogTask, watchDogPeriod, watchDogPeriod);
	}

	
	
	private synchronized void terminateWatchDogTask() {
		if (watchDogTask != null) {
            watchDogTask.cancel();
        }
	}

	
	/**
	 * {@inheritDoc}
	 */
	public long getResponseTimeoutMillis() {
		return responseTimeoutMillis;
	}
	



	private void checkTimeouts() {
		try {
			
			long currentMillis = System.currentTimeMillis();
			
			for (Object hdl : handlersWaitingForResponseHeader.toArray()) {
				boolean isTimeoutReached = ((MessageHandler) hdl).isResponseTimeoutReached(currentMillis);
				if (isTimeoutReached) {
					
					if (handlersWaitingForResponseHeader.remove(hdl)) {
						onResponseTimeout((MessageHandler) hdl);
					}
					
					destroy();
				}
			}
			
		} catch (Exception e) {
            // eat and log exception
			LOG.warning("exception occured by checking timouts. Reason: " + e.toString());
		}
	}


	private void onResponseTimeout(MessageHandler internalResponseHandler) {
		
		SocketTimeoutException ste = new SocketTimeoutException("response timeout " + DataConverter.toFormatedDuration(internalResponseHandler.getTimeout()) + " reached");
		
		if (LOG.isLoggable(Level.FINE)) {
			LOG.fine(ste.getMessage());
		}
		
		performResponseTimeoutHandler(internalResponseHandler.getAppHandler(), ste);
	}
	
	
	
	private void performResponseTimeoutHandler(final IHttpResponseHandler handler, final SocketTimeoutException ste) {

		final ResponseHandlerInfo responseHandlerInfo = AbstractHttpConnection.getResponseHandlerInfo(handler);
		
		
		Runnable responseTimeoutHandlerCaller = new Runnable()  {
			
			public void run() {

				try {
					if ( responseHandlerInfo.isSocketTimeoutHandler()) {
						IHttpSocketTimeoutHandler hdl = (IHttpSocketTimeoutHandler) handler;
						hdl.onException(ste);
						
					} else {
						handler.onException(ste);
					}
						
				} catch (Exception e) {
					if (LOG.isLoggable(Level.FINE)) {
						LOG.fine("[" +getId() + "] error occured by calling on request " + handler + " " + e.toString());
					}
					throw new RuntimeException(e);
					
				} finally {
					destroy();
				}
			}		
		};
			
		// ... and perform the handler 
		if (responseHandlerInfo.isSocketTimeoutHandlerMultithreaded()) {
			getExecutor().processMultithreaded(responseTimeoutHandlerCaller);

		} else {
			getExecutor().processNonthreaded(responseTimeoutHandlerCaller);
		}			
	}
	

	
	/**
	 * {@inheritDoc}
	 */
	public BodyDataSink send(IHttpRequestHeader requestHeader, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
		
		if (isOpen()) {
			
			if (responseHandler == null) {
				responseHandler = new DoNothingResponseHandler();
			}

			
			if (requestHeader.getContentLength() != -1) {
				requestHeader.removeHeader("Content-Length");
			}
							 
			if ((requestHeader.getTransferEncoding() == null)) {
				requestHeader.setHeader("Transfer-Encoding", "chunked");
			}

			enhanceHeader(requestHeader);

			
			try{
				return sendInternal(requestHeader, responseHandler);
				
			} catch (IOException ioe) {
				String msg = "can not send request \r\n " + requestHeader.toString() +
				 "\r\n\r\nhttpConnection:\r\n" + this.toString() +
	            "\r\n\r\nreason:\r\n" + ioe.toString();

				destroy();
				throw new IOException(msg);
			}
			
		} else {
			throw new ClosedChannelException();
		}		

	}
	
	private BodyDataSink sendInternal(IHttpRequestHeader requestHeader, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
		
		incCountMessageSent();
		
		synchronized (handlersWaitingForResponseHeader) {
			handlersWaitingForResponseHeader.add(new MessageHandler(responseHandler, requestHeader, responseTimeoutMillis));
		}

		
		BodyDataSink bodyDataSink = writeMessage(requestHeader);
		
		return bodyDataSink;		
	}

	

	/**
	 * {@inheritDoc}
	 */
	public BodyDataSink send(IHttpRequestHeader requestHeader, int contentLength, IHttpResponseHandler responseHandler) throws IOException, ConnectException {

		if (isOpen()) {

			if (responseHandler == null) {
				responseHandler = new DoNothingResponseHandler();
			}

			
			if (requestHeader.getContentLength() != -1) {
				requestHeader.removeHeader("Content-Length");
			}
							 
			if ((requestHeader.getTransferEncoding() == null)) {
				requestHeader.setHeader("Transfer-Encoding", "chunked");
			}


			
			enhanceHeader(requestHeader);


			try{
				 if ((requestHeader.getTransferEncoding() != null) && (requestHeader.getTransferEncoding().equalsIgnoreCase("chunked"))) {
					 requestHeader.removeHeader("Transfer-Encoding");
				 }
						
				 if (requestHeader.getContentLength() == -1) {
					 requestHeader.setContentLength(contentLength);
				 }
						
				return sendInternal(requestHeader, contentLength, responseHandler);
				
			} catch (IOException ioe) {
				String msg = "can not send request \r\n " + requestHeader.toString() +
				 "\r\n\r\nhttpConnection:\r\n" + this.toString() +
	            "\r\n\r\nreason:\r\n" + ioe.toString();
	
				destroy();
				throw new IOException(msg);
			}
			
		} else {
			throw new ClosedChannelException();
		}		

	}
	
	
	private BodyDataSink sendInternal(IHttpRequestHeader requestHeader, int contentLength, IHttpResponseHandler responseHandler) throws IOException, ConnectException {

		incCountMessageSent();

		synchronized (handlersWaitingForResponseHeader) {
			handlersWaitingForResponseHeader.add(new MessageHandler(responseHandler, requestHeader, responseTimeoutMillis));
		}
		
		BodyDataSink bodyDataSink = writeMessage(requestHeader, contentLength);
		
		return bodyDataSink;
	}

	
	
	/**
	 * {@inheritDoc}
	 */
	public void send(IHttpRequest request, IHttpResponseHandler responseHandler) throws IOException, ConnectException {

		if (isOpen()) {

			if (responseHandler == null) {
				responseHandler = new DoNothingResponseHandler();
			}
			
			IHttpRequestHeader requestHeader = request.getRequestHeader();
			enhanceHeader(requestHeader);			

			try {
				sendInternal(request, responseHandler);									
				
			} 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();
				
				destroy();
				throw new IOException(msg);
			}
				
		} else {
			throw new ClosedChannelException();
		}
	}
	
	
	private void sendInternal(IHttpRequest request, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
		
		incCountMessageSent();

		synchronized (handlersWaitingForResponseHeader) {
			handlersWaitingForResponseHeader.add(new MessageHandler(responseHandler, request.getRequestHeader(), responseTimeoutMillis));
		}

		
		// body less request?
		if (request.getNonBlockingBody() == null) {

			if (LOG.isLoggable(Level.FINE)) {
				LOG.fine("[" + getId() + "] sending (bodyless): " + request.getRequestHeader());
			}

			BodyDataSink bodyDataSink = writeMessage(request.getRequestHeader(), 0);
			bodyDataSink.setFlushmode(FlushMode.ASYNC);
			bodyDataSink.close();
			
			
			
		// 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)"); 
			}

			writeMessage(request);
		}			
	}

	


	private IHttpResponseHandler wrapResponsehandler(IHttpResponseHandler responseHandler) {
		
		if (AbstractHttpConnection.getResponseHandlerInfo(responseHandler).isResponseHandlerInvokeOnMessageReceived()) {
			return new InvokeOnMessageWrapper(responseHandler, this);
		} else {
			return responseHandler;
		}
	}
	
	
	
	

	private void enhanceHeader(IHttpRequestHeader header) throws IOException {
		String host = header.getHost();
		if (host == null) {
			header.setHost(getRemoteHostInfo());
		}
				
		String userAgent = header.getUserAgent();
		if (userAgent == null) {
			header.setUserAgent(getImplementationVersion());
		}		
	}
	

	 
	private static String getImplementationVersion() {
		if (implementationVersion == null) {
			implementationVersion = "xLightweb/" + HttpUtils.getImplementationVersion();
		}
		
		return implementationVersion;
	}

	
	private String getRemoteHostInfo() throws IOException {
		InetAddress remoteAddress = getRemoteAddress();
		
		if (remoteAddress == null) {
			return "";
		}
		
		int remotePort = getRemotePort();
		
		return remoteAddress.getHostName() + ":" + remotePort;
	}
	

	

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected IMessageHandler getMessageHandler() {
		synchronized (handlersWaitingForResponseHeader) {
			if (handlersWaitingForResponseHeader.isEmpty()) {
				return null;
			} else {
				return handlersWaitingForResponseHeader.remove(0);
			}
		}
	}

	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(getId() + " " + getUnderlyingTcpConnection().getLocalAddress() + ":" + getUnderlyingTcpConnection().getLocalPort() + " -> " + getUnderlyingTcpConnection().getRemoteAddress() + ":" + getUnderlyingTcpConnection().getRemotePort());
		if (!getUnderlyingTcpConnection().isOpen()) {
			sb.append("  (closed)");
		}
		
		return sb.toString();
	}
	
	
	
	/**
	 * message handler 
	 * 
	 * @author grro
	 */
	final class MessageHandler implements IMessageHandler, Runnable {

		private final AtomicBoolean isCommitted = new AtomicBoolean(false);
		
		private IHttpResponseHandler delegee = null;
		private ResponseHandlerInfo delegeeInfo = null;
		private IHttpRequestHeader requestHeader = null;
		
		private IHttpResponse response = null;
		
		private long timeout = Long.MAX_VALUE;
		private long timeoutDate = Long.MAX_VALUE;
		
		

		/**
		 * constructor 
		 * 
		 * @param delegee         the response handler 
		 * @param requestHeader   the request header
		 * @param responseTimeout hte response timeout
		 */
		public MessageHandler(IHttpResponseHandler delegee, IHttpRequestHeader requestHeader, long responseTimeout) {
			this.delegee = delegee;
			this.delegeeInfo = AbstractHttpConnection.getResponseHandlerInfo(delegee);
			this.requestHeader = requestHeader;
			
			this.timeout = responseTimeout;
			if ((responseTimeout != Long.MAX_VALUE) && (responseTimeout < Long.MAX_VALUE * 0.9)) {
				timeoutDate = timeout + System.currentTimeMillis();
			}
		}
		
		
		/**
		 * returns the response handler 
		 * 
		 * @return the response handler
		 */
		IHttpResponseHandler getAppHandler() {
			return delegee;
		}


		/**
		 * returns if the response timeout is reached
		 *  
		 * @param currentMillis the current time
		 * @return true, if the respnose timeout is reached
		 */
		boolean isResponseTimeoutReached(long currentMillis) {
			if (isCommitted.get()) {
				return false;
			}
			
			return (currentMillis > timeoutDate);
		}
		
		
		/**
		 * returns the timeout
		 * 
		 * @return the timeout
		 */
		long getTimeout() {
			return timeout;
		}
		
		
		
		/**
		 * {@inheritDoc}
		 */
		public boolean isBodylessMessageExpected() {
			if (requestHeader.getMethod().equals(IHttpMessage.HEAD_METHOD) || 
				requestHeader.getMethod().equals(IHttpMessage.OPTIONS_METHOD) || 
				requestHeader.getMethod().equals(IHttpMessage.CONNECT_METHOD)) {
				return true;
			}
			
			return false;
		}
		
		
		
		
		/**
		 * {@inheritDoc}
		 */
		public void onMessage(IHttpMessage message) throws IOException {
			this.response = (IHttpResponse) message;
			
			assert (Thread.currentThread().getName().startsWith("xDispatcher"));
			
			incCountMessageReceived();
			
			if (transactionMonitor != null) {
				transactionMonitor.addTransaction(HttpClientConnection.this, requestHeader, response.getResponseHeader());
			}
			
			if (LOG.isLoggable(Level.FINE)) {
				
				if (response.getNonBlockingBody() == null) {
					LOG.fine("[" + getId() + "] bodyless response received from " + getRemoteAddress() +
							 ":" + getRemotePort() + 
							 " (" + getCountMessagesReceived() + ". request) " + response.getMessageHeader().toString());
				
				} else {
					String body = "";
					
					String contentType = response.getContentType();
					if ((contentType != null) && (contentType.startsWith("application/x-www-form-urlencode"))) {
						body = response.getNonBlockingBody().toString() + "\n";
					} 
					
					LOG.fine("[" + getId() + "]response received from " + getRemoteAddress() + 
							 ":" + getRemotePort() + 
							 " (" + getCountMessagesReceived() + ". request) " + response.getMessageHeader().toString() + body);
				}
			}
					
			
			// handle life cycle headers
			handleLifeCycleHeaders(response);
			
			
			
			// swallow 100 response
			if (response.getStatus() == 100) {
				synchronized (handlersWaitingForResponseHeader) {
					ArrayList hdls = new ArrayList(); 
					handlersWaitingForResponseHeader.removeAll(hdls);
					handlersWaitingForResponseHeader.add(this);
					handlersWaitingForResponseHeader.addAll(hdls);
				}
				
				setPersistent(true);
				return;
			}
			
			// if response code 5xx -> set connection not reusable 
			if ((response.getStatus() >= 500) && (response.getStatus() < 600)) {
				if (LOG.isLoggable(Level.FINE)) {
					LOG.fine("got return code 5xx. Set connection " + getId() + " to non persistent");
				}
				setPersistent(false);
			}
			
			if (isPersistent() == false) {
				if (response.hasBody()) {
					setDestroyConnectionAfterReceived(response.getNonBlockingBody(), true);
				} else {
					destroy();  // destroy required, because underlying tcp connection could be a pooled one
				}
				
			} else {
				if (isAutocloseAfterResponse) {
					if (response.hasBody()) {
						setCloseConnectionAfterReceived(response.getNonBlockingBody(), true);
					} else {
						closeSilence();
					}
				}
			}
			
			if (response.hasBody() && delegeeInfo.isResponseHandlerInvokeOnMessageReceived()) {
				bodyReceivingResponseHandlers.add(this);
				
				IBodyCompleteListener cl = new IBodyCompleteListener() {
					
					@Execution(Execution.NONTHREADED)
					public void onComplete() throws IOException {
						
						bodyReceivingResponseHandlers.remove(this);
						isCommitted.set(true);
						
						if (delegeeInfo.isResponseHandlerMultithreaded()) {
							getExecutor().processMultithreaded(MessageHandler.this);
						} else {
							getExecutor().processNonthreaded(MessageHandler.this);
						}
					}
				};
				
				response.getNonBlockingBody().addCompleteListener(cl);
				
			} else {
				isCommitted.set(true);
				
				if (delegeeInfo.isResponseHandlerMultithreaded()) {
					getExecutor().processMultithreaded(this);
				} else {
					getExecutor().processNonthreaded(this);
				}
			}
		}
		
		
		/**
		 * {@inheritDoc}
		 */
		public void run() {
			try {
				delegee.onResponse(response);
			} catch (IOException ioe) {
				if (LOG.isLoggable(Level.FINE)) {
					LOG.fine("Error occured by calling onResponse of " + delegee + " " + ioe.toString());
				}
			}
		}


		
		private void handleLifeCycleHeaders(IHttpResponse response) throws IOException {

			// if HTTP 1.1 -> connection is persistent by default
			if ((response.getProtocol() != null) && response.getProtocol().equals("HTTP/1.1")) {
				setPersistent(true);
				
			} else {
				
				// response of connect is persistent, implicitly
				if (requestHeader.getMethod().equals(IHttpMessage.CONNECT_METHOD)) {
					setPersistent(true);
				}
			}				

		
			// handle connection header
			handleConnectionHeaders(response.getResponseHeader());			
		}

		
		
		private void handleConnectionHeaders(IHttpResponseHeader responseHeader) throws IOException {	
			
			String keepAliveHeader = responseHeader.getKeepAlive();
			if (keepAliveHeader != null) {
				String[] tokens = keepAliveHeader.split(",");
				for (String token : tokens) {
					handleKeepAlive(token);
				}
			} 
			
			
			//check if persistent connection
			String connectionHeader = responseHeader.getConnection();
			
			
			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) {
		    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");
                }
                setResponseTimeoutMillis(timeoutSec * 1000L);

                
            } 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");
                }
                setResponseTimeoutMillis(timeoutSec * 1000L);
            }			
		}

		
		/**
		 * {@inheritDoc}
		 */
		public void onException(final IOException ioe) {
			
			if (isCommitted.get()) {
				return;
			}
			
			isCommitted.set(true);
			
			if ((ioe instanceof SocketTimeoutException) && (delegeeInfo.isSocketTimeoutHandler())) {
				
				Runnable task = new OnSocketTimeoutExceptionCaller((SocketTimeoutException) ioe, (IHttpSocketTimeoutHandler) delegee);
				
				if (delegeeInfo.isSocketTimeoutHandlerMultithreaded()) {
					getExecutor().processMultithreaded(task);
				} else {
					getExecutor().processNonthreaded(task);
				}
				
				
			} else {
				
				Runnable task = new OnIOExceptionCaller(ioe, delegee);
				
				if (delegeeInfo.isResponseExeptionHandlerMultithreaded()) {
					getExecutor().processMultithreaded(task);
				} else {
					getExecutor().processNonthreaded(task);
				}
				
			}
		}
	}
	

	private static final class OnSocketTimeoutExceptionCaller implements Runnable {
		
		private SocketTimeoutException ste = null;
		private IHttpSocketTimeoutHandler hdl = null;
		
		public OnSocketTimeoutExceptionCaller(SocketTimeoutException ste, IHttpSocketTimeoutHandler hdl) {
			this.ste = ste;
			this.hdl = hdl;
		}
		
		public void run() {
			hdl.onException(ste);
		}		
	}
	
	
	private static final class OnIOExceptionCaller implements Runnable {

		private IOException ioe = null;
		private IHttpResponseHandler hdl = null;
		

		public OnIOExceptionCaller(IOException ioe, IHttpResponseHandler hdl) {
			this.ioe = ioe;
			this.hdl = hdl;
		}
		
		public void run() {
			try {
				hdl.onException(ioe);
			} catch (IOException ioe) {
				if (LOG.isLoggable(Level.FINE)) {
					LOG.fine("Error occured by performing onException " + hdl + ". reason: " + ioe.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();
			 }
		 }		
	 }


	 /**
	  * wrapper 
	  *  
	  * @author [email protected]
	  */
	 static final class InvokeOnMessageWrapper implements IHttpResponseHandler {

	 	private static final Logger LOG = Logger.getLogger(InvokeOnMessageWrapper.class.getName());
	 	
	 	private IHttpResponseHandler delegee = null;
	 	private ResponseHandlerInfo delegeeInfo = null;
	 	
	 	private HttpClientConnection connection = null;
	 	
	 	
	 	public InvokeOnMessageWrapper(IHttpResponseHandler delegee, HttpClientConnection connection) {
	 		this.delegee = delegee;
	 		this.delegeeInfo = AbstractHttpConnection.getResponseHandlerInfo(delegee);
	 		this.connection = connection;
	 	}
	 	
	 	@Execution(Execution.NONTHREADED)
	 	public void onResponse(final IHttpResponse response) throws IOException {
	 		
	 		
	 		final Runnable task = new Runnable() {
	 			public void run() {
	 				try {
	 					delegee.onResponse(response);
	 				} catch (IOException ioe) {
	 					if (LOG.isLoggable(Level.FINE)) {
	 						LOG.fine("Error occured by calling onResponse of " + delegee + " " + ioe.toString());
	 					}
	 				}
	 			}
	 		};
	 		
	 		if (response.hasBody()) {
	 			IBodyCompleteListener cl = new IBodyCompleteListener() {
	 				public void onComplete() throws IOException {
	 					if (delegeeInfo.isResponseHandlerMultithreaded()) {
	 						connection.getExecutor().processMultithreaded(task);
	 					} else {
	 						connection.getExecutor().processNonthreaded(task);
	 					}
	 				}
	 			};
	 			
	 			response.getNonBlockingBody().addCompleteListener(cl);
	 			
	 		} else {
	 			if (delegeeInfo.isResponseHandlerMultithreaded()) {
	 				connection.getExecutor().processMultithreaded(task);
	 			} else {
	 				connection.getExecutor().processNonthreaded(task);
	 			}
	 		}
	 	}
	 	
	 	@Execution(Execution.NONTHREADED)
	 	public void onException(final IOException ioe) {
	 		
	 		Runnable task = new Runnable() {
	 			public void run() {
	 				try {
	 					delegee.onException(ioe);
					} catch (IOException ioe) {
						if (LOG.isLoggable(Level.FINE)) {
							LOG.fine("Error occured by performing onException " + delegee + ". reason: " + ioe.toString());
						}
					}
	 			}
	 				
	 		};
	 		
	 		if (delegeeInfo.isResponseExeptionHandlerMultithreaded()) {
	 			connection.getExecutor().processMultithreaded(task);
	 		} else {
	 			connection.getExecutor().processNonthreaded(task);
	 		}
	 	}
	 }
	 
	 
	 static BodyDataSink handle(HttpClient httpClient, IHttpRequestHeader requestHeader, IHttpResponseHandler responseHandler, RequestHandlerChain requestHandlerChain, Executor workerpool) throws IOException {
		 
		 ClientExchange exchange = new ClientExchange(httpClient, workerpool);
		 
		 DataSourceSinkPair pair = newBodyDataSourceSinkPair(null, exchange.getExecutor(), requestHeader.getCharacterEncoding());
		 IHttpRequest request = new HttpRequest(requestHeader, pair.getBodyDataSource());
			
		 exchange.init(request, responseHandler);
		 
		 requestHandlerChain.onRequest(exchange);
			
		 return pair.getBodyDataSink();
	 }
	 

	 static final class ClientExchange implements IHttpExchange {
			
		 private boolean isResponseCommitted = false; 
		
		 private HttpClientConnection con = null;
		 private IHttpRequest request = null;
		 private String targetURL = null;
		 private IHttpResponseHandler responseHandler = null;
		 private ResponseHandlerInfo responseHandlerInfo = null;
		 
		 private HttpClient httpClient = null;
		 private IMultimodeExecutor executor = null;

		 
		 public ClientExchange(HttpClient httpClient, Executor workerpool) {
			 this.httpClient = httpClient;
			 executor = new MultimodeExecutor(workerpool);
		 }
		 
		 void init(IHttpRequest request, IHttpResponseHandler responseHandler) {
			 this.request = request;
			 this.responseHandler = responseHandler;
			 this.responseHandlerInfo = AbstractHttpConnection.getResponseHandlerInfo(responseHandler);
			 
			 targetURL = request.getRequestUrl().toString();
			 if (targetURL.indexOf("?") != -1) {
				 targetURL = targetURL.substring(0, targetURL.indexOf("?"));
			 }
		 }
		 
		 IMultimodeExecutor getExecutor() {
			 return executor;
		 }
		 
		 
		 /**
		  * {@inheritDoc}
		  */
		 public IHttpRequest getRequest() {
			return request;
		 }
		
		 
		 /**
		  * {@inheritDoc}
		  */
		 public IHttpSession getSession(boolean create) {
			 return httpClient.getSessionManager().getSession(httpClient, request.getRemoteHost(), request.getRequestURI(), create);
		 }

		 public String encodeURL(String url) {
			return url;
		}
		 
		 
		 /**
		  * {@inheritDoc}
		  */
		 public void forward(IHttpRequest request) throws IOException, ConnectException {
			 forward(request, new ForwardingResponseHandler(this));
		 }
		 
		 
		 /**
		  * {@inheritDoc}
		  */
		 public void forward(IHttpRequest request, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
			 
			 if (responseHandler == null) {
				 responseHandler = new DoNothingResponseHandler();
			 }
			 
			 URL targetURL = request.getRequestUrl();
			 con = httpClient.getConnection(request.isSecure(), targetURL.getHost(), targetURL.getPort(), targetURL.getProtocol(), (IHttpRequestHandler) null);
			 con.send(request, responseHandler);
		 }
		 
		 
		 
		 /**
		  * {@inheritDoc}
		  */
		 public BodyDataSink forward(IHttpRequestHeader requestHeader) throws IOException, ConnectException, IllegalStateException {
			return forward(requestHeader, new ForwardingResponseHandler(this));
		}
		 
		 
		 
		 /**
		  * {@inheritDoc}
		  */
		 public BodyDataSink forward(IHttpRequestHeader requestHeader, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
				
			 if (responseHandler == null) {
				 responseHandler = new DoNothingResponseHandler();
			 }
			 
			 if (requestHeader.getContentLength() != -1) {
				 requestHeader.removeHeader("Content-Length");
			 }
				
			 if (requestHeader.getMethod().equalsIgnoreCase("GET") || requestHeader.getMethod().equalsIgnoreCase("HEAD")) {
				 throw new IOException(requestHeader.getMethod() + " is a bodyless request");
			 }

			 if ((requestHeader.getTransferEncoding() == null)) {
				 requestHeader.setHeader("Transfer-Encoding", "chunked");
			 }
				
			 
			 URL targetURL = requestHeader.getRequestUrl();
			 con = httpClient.getConnection(requestHeader.isSecure(), targetURL.getHost(), targetURL.getPort(), targetURL.getProtocol(), null);
			 return con.send(requestHeader, responseHandler);
		 }
		 
		 
		 public BodyDataSink forward(IHttpRequestHeader requestHeader, int contentLength) throws IOException, ConnectException, IllegalStateException {
			return forward(requestHeader, contentLength, new ForwardingResponseHandler(this));
		}
		 
		 
		 /**
		  * {@inheritDoc}
		  */
		 public BodyDataSink forward(IHttpRequestHeader requestHeader, int contentLength, IHttpResponseHandler responseHandler) throws IOException, ConnectException {

			 if (responseHandler == null) {
				 responseHandler = new DoNothingResponseHandler();
			 }
			 
			 if ((requestHeader.getTransferEncoding() != null) && (requestHeader.getTransferEncoding().equalsIgnoreCase("chunked"))) {
				 requestHeader.removeHeader("Transfer-Encoding");
			 }
				
			 if (requestHeader.getMethod().equalsIgnoreCase("GET") || requestHeader.getMethod().equalsIgnoreCase("HEAD")) {
				 throw new IOException(requestHeader.getMethod() + " is a bodyless request");
			 }

			 if (requestHeader.getContentLength() == -1) {
				 requestHeader.setContentLength(contentLength);
			 }
				
			 URL targetURL = requestHeader.getRequestUrl();
			 con = httpClient.getConnection(requestHeader.isSecure(), targetURL.getHost(), targetURL.getPort(), targetURL.getProtocol(), null);
			 return con.send(requestHeader, contentLength, responseHandler);
		 }
		 
			
		 
			
		 public BodyDataSink send(IHttpResponseHeader header) throws IOException, IllegalStateException {
				
			 if (header.getContentLength() != -1) {
				 header.removeHeader("Content-Length");
			 }
						 
			 if ((header.getTransferEncoding() == null)) {
				 header.setHeader("Transfer-Encoding", "chunked");
			 }
			
			 DataSourceSinkPair pair = newBodyDataSourceSinkPair((AbstractHttpConnection) getConnection(), executor, header.getCharacterEncoding());
				
			 send(new HttpResponse(header, pair.getBodyDataSource()));
			 return pair.getBodyDataSink();
		 }


		 public BodyDataSink send(IHttpResponseHeader header, int contentLength) throws IOException, IllegalStateException {
					
			 if ((header.getTransferEncoding() != null) && (header.getTransferEncoding().equalsIgnoreCase("chunked"))) {
				 header.removeHeader("Transfer-Encoding");
			 }
					
			 if (header.getContentLength() == -1) {
				 header.setContentLength(contentLength);
			 }
					
			
			 DataSourceSinkPair pair = newBodyDataSourceSinkPair((AbstractHttpConnection) getConnection(), executor, header.getCharacterEncoding());
				
			 send(new HttpResponse(header, pair.getBodyDataSource()));
			 return pair.getBodyDataSink();
		 }
		
			
			
		 
		
		 /**
		  * {@inheritDoc}
		  */
		 public void send(final IHttpResponse response) throws IOException, IllegalStateException {
			if (isResponseCommitted) {
				throw new IllegalStateException("response is already committed"); 
			}			
			isResponseCommitted = true;

			
			if (responseHandler == null) {
				LOG.warning("response will not been send, because no response handler is assigned");
				return;
			}
			
			
			if (responseHandlerInfo.isResponseHandlerInvokeOnMessageReceived()) {
				
				IBodyCompleteListener completeListener = new IBodyCompleteListener() {
					
					@Execution(Execution.NONTHREADED)
					public void onComplete() throws IOException {
						performOnResponse(response);
					}
				};
				response.getNonBlockingBody().addCompleteListener(completeListener);
				
			} else {
				performOnResponse(response);
			}
		}
		
		
		private void performOnResponse(final IHttpResponse response) throws IOException {
			
			Runnable task = new Runnable() {
				
				public void run() {
					try {
						responseHandler.onResponse(response);
					} catch (IOException ioe) {
						if (LOG.isLoggable(Level.FINE)) {
							LOG.fine("error occured by performing on response on " + responseHandler);
						}
					}
				}
			};
			
			
			if (responseHandlerInfo.isResponseHandlerMultithreaded()) {
				executor.processMultithreaded(task);
			} else {
				executor.processNonthreaded(task);
			}
		}
		
		
		

		
		public IHttpConnection getConnection() {
			return con;
		}
		
		public void destroy() {
			if (con != null) {
				con.destroy();
			}
		}
		
		
		public void sendError(int errorCode) throws IllegalStateException {
			sendError(errorCode, HttpUtils.getReason(errorCode));
		}
		
		
		
		public void sendError(int errorCode, String msg) throws IllegalStateException {
			try {
				String id = "";
				IHttpConnection connection = getConnection(); 
				if (connection != null) {
					id = connection.getId();
				}
				send(new HttpResponse(errorCode, "text/html", HttpClientConnection.generateErrorMessageHtml(errorCode, msg, id)));
			} catch (IOException ioe) {
				if (LOG.isLoggable(Level.FINE)) {
					LOG.fine("could not send error message " + errorCode + " reason " + ioe.toString());
				}
				destroy();
			}
		}
		
		
		public void sendError(final Exception e) {
			if (isResponseCommitted) {
				throw new IllegalStateException("response is already committed"); 
			}			
			isResponseCommitted = true;

			
			if (responseHandlerInfo.isSocketTimeoutHandler() && (e instanceof SocketTimeoutException)) {

				Runnable task = new Runnable() {
					public void run() {
						((IHttpSocketTimeoutHandler) responseHandler).onException((SocketTimeoutException) e);
					}
				};

				if (responseHandlerInfo.isSocketTimeoutHandlerMultithreaded()) {
					executor.processMultithreaded(task);
				} else {
					executor.processNonthreaded(task);
				}
				
				
			} else if (e instanceof IOException) {
				
				Runnable task = new Runnable() {
					public void run() {
						try {
							responseHandler.onException((IOException) e);
						} catch (IOException ioe) {
							if (LOG.isLoggable(Level.FINE)) {
								LOG.fine("Error occured by performing onException " + responseHandler + ". reason: " + ioe.toString());
							}
						}
					}
				};

				
				if (responseHandlerInfo.isResponseExeptionHandlerMultithreaded()) {
					executor.processMultithreaded(task);
				} else {
					executor.processNonthreaded(task);
				}
				
			} else {
				if (LOG.isLoggable(Level.FINE)) {
					LOG.fine("error occured. sendig 500. Error: " + DataConverter.toString(e));
				}
				sendError(500);
			}
		}
	 }
	 
	 
	private static final class MultimodeExecutor implements IMultimodeExecutor  {
		
		private final SerializedTaskQueue taskQueue = new SerializedTaskQueue();

		private Executor workerpool = null;
		
		public MultimodeExecutor(Executor workerpool) {
			this.workerpool = workerpool;
		}

		public void processMultithreaded(Runnable task) {
			taskQueue.performMultiThreaded(task, workerpool);
		}

		public void processNonthreaded(Runnable task) {
			taskQueue.performNonThreaded(task);
		}
	}
	
	
	@Execution(Execution.NONTHREADED)
	private static final class ForwardingResponseHandler implements IHttpResponseHandler {

		private IHttpExchange exchange = null;
		
		public ForwardingResponseHandler(IHttpExchange exchange) {
			this.exchange = exchange;
		}

		
		public void onResponse(IHttpResponse response) throws IOException {
			exchange.send(response);
		}

		public void onException(IOException ioe) throws IOException {
			exchange.sendError(ioe);
		}
	}
	

	@Execution(Execution.NONTHREADED)
	static final class DoNothingResponseHandler implements IHttpResponseHandler {

		public void onResponse(IHttpResponse response) throws IOException { }

		public void onException(IOException ioe) throws IOException { }
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy