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.channels.ClosedChannelException;
import java.util.LinkedList;
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.AtomicBoolean;
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.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;
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());
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;
private final AtomicBoolean isReceiving = new AtomicBoolean(true);
// 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;
// sent transaction flag
private final AtomicBoolean isWriteTransactionRunning = new AtomicBoolean(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 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();
}
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 underlying native tcp connection
*
* @return the tcp connetion
*/
protected final INonBlockingConnection getUnderlyingConnection() {
return tcpConnection;
}
/**
* 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);
}
/**
* 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 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();
}
/**
* {@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();
}
/**
* {@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}
*/
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() {
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);
}
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 BodyDataSink newUnboundBody(AbstractMessageHeader header) throws IOException {
return new UnboundBodyDataSink(this, header, header.getCharacterEncoding());
}*/
protected void onBodyDataReceived() {
}
void setBodyDataSink(BodyDataSink bodyChannel) {
isWriteTransactionRunning.set(true);
bodyDataSink.set(bodyChannel);
}
boolean removeBodyDataSink(BodyDataSink bodyChannel) {
BodyDataSink bds = bodyDataSink.get();
if ((bds != null) && (bds == bodyChannel)) {
bodyDataSink.set(null);
isWriteTransactionRunning.set(false);
return true;
}
return false;
}
protected final boolean isWriteTransactionRunning() {
return isWriteTransactionRunning.get();
}
protected HttpResponseHeader newEmptyResponseHeader() {
return new HttpResponseHeader();
}
protected abstract void onMessage(INonBlockingConnection connection) throws BufferUnderflowException, IOException;
protected final void addFullMessageBodyParser(AbstractHttpMessage message, INonBlockingConnection connection) throws IOException {
try {
// chunked body
if (AbstractMessageHeader.hasChunkedBody(message.getMessageHeader())) {
ChunkedBodyDataSource body = new ChunkedBodyDataSource(AbstractHttpConnection.this, message.getMessageHeader(), message.getCharacterEncoding());
message.setBodyDataSource(body);
setBodyParser(body);
body.onData(connection);
// length defined plain body
} else if (AbstractMessageHeader.hasBoundBody(message.getMessageHeader())) {
BoundBodyDataSource body = new BoundBodyDataSource(AbstractHttpConnection.this, message.getMessageHeader(), message.getCharacterEncoding());
message.setBodyDataSource(body);
setBodyParser(body);
body.onData(connection);
// connection terminated body
} else if (AbstractMessageHeader.hasConnectionTerminatedBody(message.getMessageHeader())) {
setPersistent(false);
ConnectionTerminatedBodyDataSource body = new ConnectionTerminatedBodyDataSource(AbstractHttpConnection.this, message.getMessageHeader(), message.getCharacterEncoding());
message.setBodyDataSource(body);
setBodyParser(body);
body.onData(connection);
}
} catch (BufferUnderflowException ignore) { }
}
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());
}
throwable.printStackTrace();
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);
}
/**
* 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);
}
/**
* 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 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();
}
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.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;
}
if (isReceiving.get()) {
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 {
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();
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] " + bodyDataSource.getClass().getSimpleName() + "available=" + 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 + " 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() + "] " + bodyDataSource.getClass().getSimpleName() + " 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() + " closing connection " + getId());
}
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