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

org.xsocket.connection.http.AbstractHttpConnection Maven / Gradle / Ivy

/*
 *  Copyright (c) xsocket.org, 2006 - 2008. All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
 * The latest copy of this software may be found on http://www.xsocket.org/
 */
package org.xsocket.connection.http;

import java.io.IOException;
import java.net.InetAddress;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
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.SerializedTaskQueue;
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.AbstractHttpMessage.BodyType;
import org.xsocket.connection.http.client.HttpClientConnection;
import org.xsocket.connection.http.server.HttpServerConnection;





/**
 * 
 * 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());
		
	protected static final int MAX_HEADER_LENGTH = Integer.MAX_VALUE;

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

	
	// open flag
	private boolean isOpen = true;
	
	
	// timer support 
	private static final Timer TIMER = new Timer("xHttpTimer", true);

		
	// underlying connection
	private INonBlockingConnection tcpConnection = null;
	
	
	// protocol handler
	private final ProtocolHandler protocolHandler = new ProtocolHandler();
	
		
	// flags
	private boolean isPersistent = false;
	
	
	
	// attached parser
	private IDataHandler bodyParser = null;
	
		
	// output body close listener support
	private final AtomicReference bodyDataSink = new AtomicReference();
	
	
	// attachment
	private Object attachment = null;
	
	
	
	// call back processing
	private final SerializedTaskQueue taskQueue = new SerializedTaskQueue();
	
	private long lastTimeDataWritten = System.currentTimeMillis();
	

	/**
	 * 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();
	}
	
	
	
	protected static boolean hasChunkedBody(AbstractHttpMessage message) throws IOException {
		return message.getBodyType() == BodyType.CHUNKED;
	}

	protected static boolean hasBoundBody(AbstractHttpMessage message) throws IOException {
		return message.getBodyType() == BodyType.BOUND;
	}
	
	protected static boolean hasConnectionTerminatedBody(AbstractHttpMessage message) throws IOException {
		return message.getBodyType() == BodyType.CONNECTION_TERMINATED;
	}
	
	
	/**
	 * returns the default worker pool
	 * 
	 * @return the default worker pool
	 */
	synchronized static Executor getDefaultWorkerpool() {
		if (defaultWorkerPool == null) {
			defaultWorkerPool = Executors.newCachedThreadPool(new DefaultThreadFactory());
		}
		
		return defaultWorkerPool;
	}

	
	protected static void schedule(TimerTask task, long delay, long period) {
		TIMER.schedule(task, delay, period);
	}
	
	protected final Executor getWorkerpool() {
		return tcpConnection.getWorkerpool();
	}

	protected void suspendRead() throws IOException {
		tcpConnection.suspendRead();
	}

	protected void resumeRead() throws IOException {
		tcpConnection.resumeRead();
	}

	
	protected final long getLastTimeWritten() {
		return lastTimeDataWritten;
	}
	
	
	/**
	 * {@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 setConnectionTimeoutMillis(long timeoutMillis) {
		tcpConnection.setConnectionTimeoutMillis(timeoutMillis);
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final long getConnectionTimeoutMillis() {
		return tcpConnection.getConnectionTimeoutMillis();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final void setIdleTimeoutMillis(long timeoutMillis) {
		tcpConnection.setIdleTimeoutMillis(timeoutMillis);
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final long getIdleTimeoutMillis() {
		return tcpConnection.getIdleTimeoutMillis();
	}
	
	
	protected final String dumpReadChannel() {
		try {
			return tcpConnection.readStringByLength(tcpConnection.available());
		} catch (IOException ioe) {
			return "error occured by dumping read channel";
		}
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final boolean isOpen() {
		if (!isOpen) {
			return false;
		}
		
		return tcpConnection.isOpen();
	}
	
	

	/**
	 * {@inheritDoc}
	 */
	public final String getId() {
		return tcpConnection.getId();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public long getRemainingMillisToConnectionTimeout() {
		return tcpConnection.getRemainingMillisToConnectionTimeout();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public long getRemainingMillisToIdleTimeout() {
		return tcpConnection.getRemainingMillisToIdleTimeout();
	}
	
	protected final void processMultiThreaded(Runnable task) {
		taskQueue.performMultiThreaded(task, tcpConnection.getWorkerpool());
	}
	
	protected final void processNonThreaded(Runnable task) {
		taskQueue.performNonThreaded(task);
	}
	
	
	/**
	 * ad hoc activation of a secured mode (SSL). By performing of this
	 * method all remaining data to send will be flushed.
	 * After this all data will be sent and received in the secured mode
	 *
	 * @throws IOException If some other I/O error occurs
	 */
	public final void activateSecuredMode() throws IOException {
		tcpConnection.activateSecuredMode();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public void close() throws IOException {

		Runnable closeTask = new Runnable() {
			
			public void run() {
				doClose();	
			}		
		};

		processNonThreaded(closeTask);
	}
	
	
	private void doClose() {
		try {
			if (isResusable()) {
				tcpConnection.close();
				
			} else {
				
				if (LOG.isLoggable(Level.FINE)) {
					if (getBodyParser() != null) {
						LOG.fine("destroying connection instead of closing it (BodyParser is set)");
					}
				}
				
				// 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
				NonBlockingConnectionPool.destroy(tcpConnection);
			}
		} catch (IOException ioe) {
			if (LOG.isLoggable(Level.FINE)) {
				LOG.fine("error occured by closing htttp connection " + getId() + " " + ioe.toString());
			}
		}			
	}
	
	
	protected boolean isResusable() {
		return (getBodyParser() == null) && isPersistent; 
	}
	

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

	/**
	 * {@inheritDoc}
	 */
	final void setWriteTransferRate(int bytesPerSecond) throws ClosedChannelException, IOException {
		tcpConnection.setWriteTransferRate(bytesPerSecond);
	}
	
	/**
	 * {@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 void destroy() {
		
		isPersistent = false;
		
		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(tcpConnection);
				} catch (IOException ioe) {
					if (LOG.isLoggable(Level.FINE)) {
						LOG.fine("error occured by destroying htttp connection " + getId() + " " + ioe.toString());
					}
				}				
			}		
		};

		processNonThreaded(destroyTask);
	}
	

	
	final void setFlushmode(FlushMode flushmode) {
		tcpConnection.setFlushmode(flushmode);
	}
	
	final protected void flush() throws IOException {
		tcpConnection.flush();
	}
	
	final int write(String txt) throws IOException {
		lastTimeDataWritten = System.currentTimeMillis();
		return tcpConnection.write(txt);
	}
	
	final long write(ByteBuffer[] buffer) throws IOException {
		lastTimeDataWritten = System.currentTimeMillis();
		return tcpConnection.write(buffer);
	}
	
	protected final int writeHeader(AbstractMessageHeader header) throws IOException {
		lastTimeDataWritten = System.currentTimeMillis();
		
		int written = header.writeTo(tcpConnection);
		
		return written;
	}
	
	
	protected final void sendMessageBody(BodyDataSink bodyDataSink, NonBlockingBodyDataSource bodyDataSource) throws IOException {
		
		try {
			bodyDataSink.setFlushmode(FlushMode.ASYNC);
			bodyDataSink.setAutoflush(false);
	
			// is body complete?
			if (bodyDataSource.isComplete()) {
				int available = bodyDataSource.available();
				if (available > 0) {
					bodyDataSink.write(bodyDataSource.readBytesByLength(available));
				}
				bodyDataSink.close();	
	
			// .. no
			} else {
				BodyDataForwardHandler forwardHandler = new BodyDataForwardHandler(bodyDataSink);
				bodyDataSource.setDataHandler(forwardHandler);
				bodyDataSink.flush();
			}
			
		} catch (ClosedChannelException cce) {
			throw new IOException("http connection " + getId() + " is already closed " + cce.toString());
		}
	}


	
	

	protected final BodyDataSink newChunkedBody(AbstractMessageHeader header) throws IOException {
		return new ChunkedBodyDataSink(this, header, header.getCharacterEncoding());
	}

	protected final BodyDataSink newBoundBody(AbstractMessageHeader header) throws IOException {
		return new BoundBodyDataSink(this, header, header.getCharacterEncoding());		
	}
	
	protected final boolean isComplete(NonBlockingBodyDataSource dataSource) throws IOException {
		return dataSource.isComplete();
	}

	
	protected void onBodyDataReceived() {
		
	}
	
	
	void setBodyDataSink(BodyDataSink bodyChannel) {
		bodyDataSink.set(bodyChannel);
	}
	
	
	boolean removeBodyDataSink(BodyDataSink bodyChannel) {
		BodyDataSink bds = bodyDataSink.get(); 
		if ((bds != null) && (bds == bodyChannel)) {
			bodyDataSink.set(null);
			return true;
		}
		
		return false;
	}
	

	
	protected HttpResponseHeader newEmptyResponseHeader() { 
		return new HttpResponseHeader();
	}
	
	
	protected abstract void onMessage(INonBlockingConnection connection) throws BufferUnderflowException, IOException;
	
	
	
	protected final void addBoundBodyParser(AbstractHttpMessage message, INonBlockingConnection connection) throws IOException {
		BoundBodyDataSource body = new BoundBodyDataSource(AbstractHttpConnection.this, message.getMessageHeader(), message.getCharacterEncoding());
		message.setBodyDataSource(body);
		setBodyParser(body);
				
		body.onData(connection);
	}
	
	protected final void addChunkedBodyParser(AbstractHttpMessage message, INonBlockingConnection connection) throws IOException {
		ChunkedBodyDataSource body = new ChunkedBodyDataSource(AbstractHttpConnection.this, message.getMessageHeader(), message.getCharacterEncoding());
		message.setBodyDataSource(body);
		setBodyParser(body);
		
		body.onData(connection);
	}
	
	
	protected final void addConnectionTerminatedParser(AbstractHttpMessage message, INonBlockingConnection connection) throws IOException {
		setPersistent(false);
		
		ConnectionTerminatedBodyDataSource body = new ConnectionTerminatedBodyDataSource(AbstractHttpConnection.this, message.getMessageHeader(), message.getCharacterEncoding());
		message.setBodyDataSource(body);
		setBodyParser(body);
		
		body.onData(connection);
	}
	

	
	
	protected final void addSimpleMessageBodyParser(AbstractHttpMessage message, INonBlockingConnection connection) throws IOException {
		
		try {
			setPersistent(false);
				
			ConnectionTerminatedBodyDataSource body = new ConnectionTerminatedBodyDataSource(AbstractHttpConnection.this, message.getMessageHeader(), message.getCharacterEncoding());
			message.setBodyDataSource(body);
			setBodyParser(body);
				
			body.onData(connection);	
		} catch (BufferUnderflowException ignore) { }
	}
	

	/**
	 * disconnect call back 
	 * 
	 * @throws IOException  if an exception occurs
	 */
	protected void onDisconnect() throws IOException {

		BodyDataSink bds = bodyDataSink.get();
		if (bds != null) {
			bds.onUnderlyingHttpConnectionClosed();
			bodyDataSink.set(null);
		}		
	}
	
	
	/**
	 * idle time out call back 
	 * 
	 * @throws IOException  if an exception occurs
	 */
	private void onIdleTimeout() throws IOException {
		if (LOG.isLoggable(Level.FINE)) {
			LOG.fine("[" + getId()  + "] idle timeout reached (" + 
					 DataConverter.toFormatedDuration(tcpConnection.getIdleTimeoutMillis()) + 
					 "). terminate connection");
		}

		this.close();
	}
	
	
	/**
	 * connection timeout call back 
	 * 
	 * @throws IOException  if an exception occurs
	 */
	protected void onConnectionTimeout() throws IOException {
		this.close();
	}
	
	
	protected void onProtocolException(Throwable throwable) {
		if (LOG.isLoggable(Level.FINE)) {
			LOG.fine("[" + getId() + "] protocol error. closing connection " + throwable.toString());
		}
		
		
		destroy();
	}


	
	/**
	 * 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);
	}
	
	
	/**
	 * @deprecated
	 */
	protected static final void setCloseHttpConnectionAfterWritten(BodyDataSink bodyDataSink, boolean isClose) {
		bodyDataSink.setCloseHttpConnectionAfterWritten(isClose);
	}
		
	
	/**
	 * remove the current body parser
	 */
	void removeBodyParser() {
		bodyParser = null;
	}

	
	private IHandler getBodyParser() {
		return bodyParser;
	}


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

	protected static String getReason(int statusCode) {
		switch (statusCode) {
		case 200:
			return "OK";

		case 401:
			return "Unauthorized";

		case 404:
			return "Not found";
			
		case 500:
			return "Internal Server Error";
			
		case 501:
			return "Not Implemented";

		case 502:
			return "Bad Gateway";

		case 503:
			return "Service Unavailable";
			
		case 504:
			return "Gateway Timeout";
		
		case 505:
			return "HTTP Version Not Supported";

			
		default:
			return " ";
		}
	}
	


	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public String toString() {		
		return tcpConnection.toString();
	}
	
	

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

		
		public boolean onData(INonBlockingConnection connection) throws BufferUnderflowException {

			try {
				if (connection.available() <= 0) {
					return true;
					
				}
			
				AbstractHttpConnection httpCon = getHttpConnection(connection);
				if (httpCon != null) {
						
					IDataHandler bodyParser = (IDataHandler) httpCon.getBodyParser();
						
					// body handling?
					if (bodyParser != null) {
						return bodyParser.onData(connection);
						
					} 
						
					AbstractHttpConnection.this.onMessage(connection);
						
				} else {
					if (LOG.isLoggable(Level.FINE)) {
						LOG.fine("no http connection assigned. closing tcp connection");
					}

					connection.close();
				}
				
			} catch (ClosedChannelException ignore) {
				
				
			} catch (BufferUnderflowException bue) {
				throw bue;				
				
			} catch (Exception ex) {
				onProtocolException(ex);
			}
			
			return true;
		}
		
		public boolean onConnect(INonBlockingConnection connection) throws IOException, BufferUnderflowException, MaxReadSizeExceededException {
			return true;
		}
		
		public boolean onDisconnect(INonBlockingConnection connection) throws IOException {
			isOpen = false;
			
			IDisconnectHandler bodyParser = (IDisconnectHandler) getHttpConnection(connection).getBodyParser();

			// body handling?
			if (bodyParser != null) {
				bodyParser.onDisconnect(connection);
			}
			
			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 final class BodyDataForwardHandler implements IBodyDataHandler {

		private BodyDataSink bodyDataSink = null;
		private int forwarded = 0;
		
		
		public BodyDataForwardHandler(BodyDataSink bodyDataSink) {
			this.bodyDataSink = bodyDataSink;
		}
		
		
		@Execution(Execution.NONTHREADED)
		public boolean onData(NonBlockingBodyDataSource bodyDataSource) {

			
			try {
				int available = bodyDataSource.available();
				
			
				
				// data available
				if (available > 0) {
					long written = bodyDataSink.write(bodyDataSource.readBytesByLength(available));
					bodyDataSink.flush();
					
					forwarded += written;
					
					if (LOG.isLoggable(Level.FINE)) {
						LOG.fine("[" + getId() + "] " + bodyDataSource.getClass().getSimpleName() + " " + written + " bytes forwarded to data sink " + bodyDataSink.getClass().getSimpleName() + " (total forwarded=" + forwarded + ")");
					}
					
					
				// end of stream reached
				} else if (available == -1) {
					if (LOG.isLoggable(Level.FINE)) {
						LOG.fine("[" + getId() + "] data source " + bodyDataSource.getClass().getSimpleName() + " is closed. Closing data sink " + bodyDataSink.getClass().getSimpleName() + " (total forwarded=" + forwarded + ")");
					}
					bodyDataSink.close();
				}
			
				return true;

			} catch (Exception ioe) {
				if (LOG.isLoggable(Level.FINE)) {
					LOG.fine("error occured by writing data sink " + bodyDataSource + " " + ioe.toString() + " destroying connection " + getId());
				}
				destroy();
				bodyDataSource.destroy();
				bodyDataSink.destroy();
				return false;
			}
		}
	};
	
	
	
	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;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy