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.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