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

org.xsocket.connection.http.AbstractHttpConnection 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;

import java.io.IOException;
import java.net.InetAddress;
import java.nio.BufferUnderflowException;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;


import org.xsocket.Execution;
import org.xsocket.MaxReadSizeExceededException;
import org.xsocket.connection.IConnectHandler;
import org.xsocket.connection.IConnectionTimeoutHandler;
import org.xsocket.connection.IDataHandler;
import org.xsocket.connection.IDisconnectHandler;
import org.xsocket.connection.IHandler;
import org.xsocket.connection.IIdleTimeoutHandler;
import org.xsocket.connection.INonBlockingConnection;
import org.xsocket.connection.NonBlockingConnectionPool;
import org.xsocket.connection.http.client.HttpClientConnection;
import org.xsocket.connection.http.server.HttpServerConnection;
import org.xsocket.connection.spi.DefaultIoProvider;




/**
 * 
 * Implemenation base for the {@link HttpClientConnection} and {@link HttpServerConnection}
 * 
 * @author [email protected]
 */
public abstract class AbstractHttpConnection implements IHttpConnection {

	private static final Logger LOG = Logger.getLogger(AbstractHttpConnection.class.getName());
		
	private static final int MAX_HEADER_LENGTH = Integer.MAX_VALUE;

	protected static final String DEFAULT_ENCODING = AbstractMessageHeader.DEFAULT_ENCODING;
	
	private static Executor defaultWorkerPool = null;

	
	private final AtomicBoolean isReceiving = new AtomicBoolean(true);
	
		
	// underlying connection
	private INonBlockingConnection tcpConnection = null;
	
	
	// protocol handler
	private final ProtocolHandler protocolHandler = new ProtocolHandler();
	
		
	// persistent flag
	private boolean isPersistent = true;
	
	// attached parser
	private IDataHandler bodyParser = null;
	
	// attachment
	private Object attachment = null;
	
	
	// call back processing
	private final LinkedList taskQueue = new LinkedList();
	private final MultithreadedTaskProcessor multithreadedTaskProcessor = new MultithreadedTaskProcessor();

	


	/**
	 * constructor 
	 * 
	 * @param nativeConnection   the underlying tcp connection
	 * @throws IOException if an exception occurs
	 */
	protected AbstractHttpConnection(INonBlockingConnection nativeConnection) throws IOException {
		this.tcpConnection = nativeConnection;
		
		nativeConnection.setAutoflush(false);
		nativeConnection.setFlushmode(FlushMode.ASYNC);
		nativeConnection.setAttachment(this);

		protocolHandler.onConnect(nativeConnection);
		nativeConnection.setHandler(protocolHandler);
	}


	/**
	 * retrieve a http connection based on a given native conection (http connection will be attached 
	 * to the native connection within the http connection constructor)
	 * 
	 * @param nativeConnection  the native connection
	 * @return the http connection
	 */
	static AbstractHttpConnection getHttpConnection(INonBlockingConnection nativeConnection) {
		return (AbstractHttpConnection) nativeConnection.getAttachment();
	}
	
	
	
	/**
	 * returns the underlying native tcp connection
	 * 
	 * @return the tcp connetion
	 */
	protected final INonBlockingConnection getUnderlyingConnection() {
		return tcpConnection;
	}

	

	
/*
	final void suspendReceiving() {
		isReceiving.set(false);
	}

	final void resumeReceiving() {
		isReceiving.set(true);
		
		try {
			protocolHandler.onData(getUnderlyingConnection());
		} catch (IOException ioe) {
			if (LOG.isLoggable(Level.FINE)) {
				LOG.fine("error occured by resuming receiving (onData call) " + ioe.toString());
			}
		}
	}*/

	
	/**
	 * returns the default worker pool
	 * 
	 * @return the default worker pool
	 */
	synchronized static Executor getDefaultWorkerpool() {
		if (defaultWorkerPool == null) {
			defaultWorkerPool = Executors.newCachedThreadPool(new DefaultThreadFactory());
		}
		
		return defaultWorkerPool;
	}

	

	/**
	 * process a task non threaded
	 * 
	 * @param task the task to process
	 */
	protected final void processNonThreaded(Runnable task) {
	
		synchronized (taskQueue) {
			
			// no running worker -> process non threaded
			if (taskQueue.isEmpty()) {
				task.run();
			
			// worker is currently running -> add task to queue (the task will be handled by the running worker)
			} else {
				taskQueue.addLast(task);
			}				
		}
	}

	
	/**
	 * process a task multi threaded
	 * 
	 * @param task the task to process
	 */
	protected final void processMultiThreaded(Runnable task) {
		synchronized (taskQueue) {
			
			// no running worker
			if (taskQueue.isEmpty()) {
				taskQueue.addLast(task);

				getUnderlyingConnection().getWorkerpool().execute(multithreadedTaskProcessor);				
			
			// worker is currently running -> add task to queue (the task will be handled by the running worker)
			} else {
				taskQueue.addLast(task);
			}				
		}
	}


	/**
	 * {@inheritDoc}
	 */
	public final boolean isSecure() {
		return tcpConnection.isSecure();
	}
	
	
	/**
	 * returns if the connection is persistent 
	 * 
	 * @return true, if the connection is persistent
	 */
	public final boolean isPersistent() {
		return isPersistent;
	}
	
	
	/**
	 * sets the persistent flag
	 * 
	 * @param isPersistent the persistent flag 
	 */
	protected final void setPersistent(boolean isPersistent) {
		this.isPersistent = isPersistent;
	}
	
	
	
	/**
	 * {@inheritDoc}
	 */
	public final void setOption(String name, Object value) throws IOException {
		tcpConnection.setOption(name, value);
	}

	
	/**
	 * {@inheritDoc}
	 */
	public final Object getOption(String name) throws IOException {
		return tcpConnection.getOption(name);
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@SuppressWarnings("unchecked")
	public final Map getOptions() {
		return tcpConnection.getOptions();
	}
	
	/**
	 * {@inheritDoc}
	 */
	public final void setConnectionTimeoutSec(int timeoutSec) {
		tcpConnection.setConnectionTimeoutSec(timeoutSec);
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final int getConnectionTimeoutSec() {
		return tcpConnection.getConnectionTimeoutSec();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final void setIdleTimeoutSec(int timeoutInSec) {
		tcpConnection.setIdleTimeoutSec(timeoutInSec);
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final int getIdleTimeoutSec() {
		return tcpConnection.getIdleTimeoutSec();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final boolean isOpen() {
		return tcpConnection.isOpen();
	}
	
	

	/**
	 * {@inheritDoc}
	 */
	public final String getId() {
		return tcpConnection.getId();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public int getRemainingSecToConnectionTimeout() {
		return tcpConnection.getRemainingSecToConnectionTimeout();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public int getRemainingSecToIdleTimeout() {
		return tcpConnection.getRemainingSecToIdleTimeout();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final void close() throws IOException {

		Runnable closeTask = new Runnable() {
			
			public void run() {
				try {
					tcpConnection.close();
				} catch (IOException ioe) {
					if (LOG.isLoggable(Level.FINE)) {
						LOG.fine("error occured by closing htttp connection " + getId() + " " + ioe.toString());
					}
				}				
			}		
		};

		processNonThreaded(closeTask);
	}
	

	protected final void closeSilence() {
		try {
			close();
		} catch (IOException ioe) {
			if (LOG.isLoggable(Level.FINE)) {
				LOG.fine("error occured by closing connection " + getId() + " " + ioe.toString());
			}
		}
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final void setAttachment(Object obj) {
		this.attachment = obj;
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final Object getAttachment() {
		return attachment;
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final InetAddress getLocalAddress() {
		return tcpConnection.getLocalAddress();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final int getLocalPort() {
		return tcpConnection.getLocalPort();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final InetAddress getRemoteAddress() {
		return tcpConnection.getRemoteAddress();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final int getRemotePort() {
		return tcpConnection.getRemotePort();
	}
	
	

	/**
	 * destroy the connection
	 */
	protected final void destroy() {
		
		
		Runnable destroyTask = new Runnable() {
			
			public void run() {
				// destroy (underlying tcp) connection by using connection pool. The underlying connection could be a pooled one)
				// The connection pool detects automatically if the connection is pooled or not. The connection will be 
				// closed (logically) anyway
				try {
					NonBlockingConnectionPool.destroy(getUnderlyingConnection());
				} catch (IOException ioe) {
					if (LOG.isLoggable(Level.FINE)) {
						LOG.fine("error occured by destroying htttp connection " + getId() + " " + ioe.toString());
					}
				}				
			}		
		};

		processNonThreaded(destroyTask);
	}
	
	
	
	/**
	 * write the message header 
	 * 
	 * @param header  the message header to write
	 * @return the body data sink 
	 * @throws IOException if an exception occurs
	 */
	protected final BodyDataSink writeMessageHeader(AbstractMessageHeader header) throws IOException {
		BodyDataSink dataSink = null;
				
		// chunked body
		if (header.containsHeader("Transfer-Encoding") && header.getHeader("Transfer-Encoding").equalsIgnoreCase("chunked")) {
			dataSink = new ChunkedBodyDataSink(this, header, header.getCharacterEncoding());
			
			 
		// plain body or non body
		} else {
			dataSink = new PlainBodyDataSink(this, header, header.getCharacterEncoding());			
		}
		
		return dataSink;
	}


	
	/**
	 * call back, which will be called if a new message has been received
	 * 
	 * @param message the new message
	 * @throws IOException  if an exception occurs
	 */
	protected abstract void onMessageHeaderReceived(IMessage message) throws IOException;

	

	/**
	 * disconnect call back 
	 * 
	 * @throws IOException  if an exception occurs
	 */
	protected abstract void onDisconnect() throws IOException;
	
	
	/**
	 * idle time out call back 
	 * 
	 * @throws IOException  if an exception occurs
	 */
	private void onIdleTimeout() throws IOException {
		this.close();
	}
	
	
	/**
	 * connection timeout call back 
	 * 
	 * @throws IOException  if an exception occurs
	 */
	protected void onConnectionTimeout() throws IOException {
		this.close();
	}
	

	/**
	 * read the message header 
	 *  
	 * @param tcpConnection    the underlying tcp connection   
	 * @param maxHeaderLength  the max read size
	 * @return the message 
	 * @throws IOException If some other I/O error occurs
 	 * @throws BufferUnderflowException if not enough data is available 
	 */
	protected abstract IMessage readMessageHeader(INonBlockingConnection tcpConnection, int maxHeaderLength) throws BufferUnderflowException, IOException;
	
	
	
	/**
	 * parse the encoding part of the given content type
	 * 
	 * @param contentType  the content type
	 * @return the encoding
	 */
	protected static final String parseEncoding(String contentType) {
		return AbstractMessageHeader.parseEncoding(contentType);
	}
	
	
	/**
	 * set the close flag for a body data sink
	 * 
	 * @param bodyDataSink  the body data sink
	 * @param isClose       the close flag
	 */
	protected static final void setCloseHttpConnectionAfterWritten(BodyDataSink bodyDataSink, boolean isClose) {
		bodyDataSink.setCloseHttpConnectionAfterWritten(isClose);
	}

	
	/**
	 * add a complete received listener to the given body data source
	 * 
	 * @param bodyDataSource   the body data source
	 * @param listener         the complete received listener
	 */
	protected static final void addListener(NonBlockingBodyDataSource bodyDataSource, ICompleteListener listener) {
		bodyDataSource.addListener(listener);
	}
	
		
	
	/**
	 * remove the current body parser
	 */
	void removeBodyParser() {
		bodyParser = null;
	}

	
	private IHandler getBodyParser() {
		return bodyParser;
	}


	
	private void setBodyParser(IDataHandler parser) {
		this.bodyParser = parser;
	}
	

	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public String toString() {
		
		IDataHandler bodyParser = (IDataHandler) tcpConnection.getAttachment();
		if (bodyParser != null) {
			return tcpConnection.toString() + " (" + bodyParser + ")";
		} else {
			return tcpConnection.toString();
		}
	}
	
	

	
	private void performPendingTasks() {
		assert (DefaultIoProvider.isDispatcherThread() == false);
		
		boolean taskToProcess = true;
		while(taskToProcess) {
			
			// get task from queue
			Runnable task = null;
			synchronized (taskQueue) {
				task = taskQueue.getFirst();
				assert (task != null) : "a task should always be available";
			}
			
			// perform it 
			task.run();
			
			
			// remove task
			synchronized (taskQueue) {
				// remaining tasks to process
				if (taskQueue.size() > 1) {
					if (LOG.isLoggable(Level.FINE)) {
						LOG.fine("more task to process. process next task");
					}
					taskQueue.remove(task);
					taskToProcess = true;
					
				} else {
					taskQueue.remove(task);
					taskToProcess = false;
				}
			}
		}
	}

	


	

	private final class MultithreadedTaskProcessor implements Runnable {
		
		public void run() {
			performPendingTasks();
		}
	}
	
	
	

	
	
	
	@Execution(Execution.Mode.NONTHREADED)
	private final class ProtocolHandler implements IDataHandler, IConnectHandler, IDisconnectHandler, IIdleTimeoutHandler, IConnectionTimeoutHandler {

		
		public boolean onData(INonBlockingConnection connection) throws IOException, BufferUnderflowException, MaxReadSizeExceededException {
			if (isReceiving.get()) {
				IDataHandler bodyParser = (IDataHandler) getHttpConnection(connection).getBodyParser();
				
				// body handling?
				if (bodyParser != null) {
					return bodyParser.onData(connection);
				}
				
				
				// ..., no read header
				AbstractMessage message = (AbstractMessage) readMessageHeader(connection, MAX_HEADER_LENGTH);

				
				if (LOG.isLoggable(Level.FINE)) {
					LOG.fine("[" + connection.getId() + "] header received " + message.getMessageHeader().toString());
				}
	
				// try to read available body data
				try {				
					// chunked body
					if (AbstractMessageHeader.isChunked(message.getMessageHeader())) {
						if (LOG.isLoggable(Level.FINE)) {
							LOG.fine("[" + connection.getId() + "] message has chunked body. set parser");
						}

						ChunkedBodyDataSource body = new ChunkedBodyDataSource(AbstractHttpConnection.this, message.getMessageHeader(), message.getCharacterEncoding());
						message.setBodyDataSource(body);
						setBodyParser(body);
						
						body.onData(connection);

						
					// plain body
					} else if (message.getContentLength() > 0) {
						if (LOG.isLoggable(Level.FINE)) {
							LOG.fine("[" + connection.getId() + "] message has plain body. set parser");
						}
		
						PlainBodyDataSource body = new PlainBodyDataSource(AbstractHttpConnection.this, message.getMessageHeader(), message.getCharacterEncoding());
						message.setBodyDataSource(body);
						setBodyParser(body);
						
						body.onData(connection);
					} 
					
					
				// notify that message header has been received
				} finally {
					onMessageHeaderReceived(message);
				}
			}
			
			return true;
		}
		
		public boolean onConnect(INonBlockingConnection connection) throws IOException, BufferUnderflowException, MaxReadSizeExceededException {
			return true;
		}
		
		public boolean onDisconnect(INonBlockingConnection connection) throws IOException {
			AbstractHttpConnection.this.onDisconnect();
			return true;
		}
		
		public boolean onConnectionTimeout(INonBlockingConnection connection) throws IOException {
			AbstractHttpConnection.this.onConnectionTimeout();
			return true;
		}
		
		public boolean onIdleTimeout(INonBlockingConnection connection) throws IOException {
			AbstractHttpConnection.this.onIdleTimeout();
			return true;
		}
	}
	
	
	
	
	private static class DefaultThreadFactory implements ThreadFactory {
		private static final AtomicInteger poolNumber = new AtomicInteger(1);
		private final ThreadGroup group;
		private final AtomicInteger threadNumber = new AtomicInteger(1);
		private final String namePrefix;

		DefaultThreadFactory() {
			SecurityManager s = System.getSecurityManager();
			group = (s != null)? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
			namePrefix = "xHttpNbcPool-" + poolNumber.getAndIncrement() + "-thread-";
        }

		public Thread newThread(Runnable r) {
			Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
            if (!t.isDaemon()) {
                t.setDaemon(true);
            }
            if (t.getPriority() != Thread.NORM_PRIORITY) {
                t.setPriority(Thread.NORM_PRIORITY);
            }
            return t;
        }
    }
	
	


	/**
	 * Listener, which will be call if an artifact is complete received
	 * 
	 * @author grro
	 */
	protected interface ICompleteListener  {
		
		/**
		 * call back if the artifact is complete received
		 */
		public void onComplete();
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy