Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.xlightweb.AbstractHttpConnection Maven / Gradle / Ivy
Go to download
xLightweb is a lightweight, high performance,
scalable web network library
/*
* Copyright (c) xlightweb.org, 2008 - 2010. 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;
import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TimerTask;
import java.util.concurrent.Executor;
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.xlightweb.HttpUtils.HttpConnectionHandlerInfo;
import org.xlightweb.HttpUtils.RequestHandlerInfo;
import org.xlightweb.HttpUtils.ResponseHandlerInfo;
import org.xlightweb.client.HttpClientConnection;
import org.xlightweb.server.HttpServerConnection;
import org.xsocket.DataConverter;
import org.xsocket.Execution;
import org.xsocket.IDestroyable;
import org.xsocket.MaxReadSizeExceededException;
import org.xsocket.connection.ConnectionUtils;
import org.xsocket.connection.IConnectHandler;
import org.xsocket.connection.IConnection;
import org.xsocket.connection.IConnectionTimeoutHandler;
import org.xsocket.connection.IDataHandler;
import org.xsocket.connection.IDisconnectHandler;
import org.xsocket.connection.IHandler;
import org.xsocket.connection.IHandlerChangeListener;
import org.xsocket.connection.IIdleTimeoutHandler;
import org.xsocket.connection.INonBlockingConnection;
import org.xsocket.connection.IWriteCompletionHandler;
import org.xsocket.connection.NonBlockingConnectionPool;
/**
*
* Implementation base for the {@link HttpClientConnection} and {@link HttpServerConnection} class
*
* This is a xSocket internal class and subject to change
*
* @author [email protected]
*/
public abstract class AbstractHttpConnection implements IHttpConnection, IDestroyable {
private static final Logger LOG = Logger.getLogger(AbstractHttpConnection.class.getName());
// max write buffer size
private static final String DEFAULT_MAX_WRITE_BUFFER_SIZE = "65536";
private static final int MAX_WRITEBUFFER_SIZE = Integer.parseInt(System.getProperty("org.xlightweb.connection.max_write_buffer_size", DEFAULT_MAX_WRITE_BUFFER_SIZE));
// default encoding
private String defaultEncoding = IHttpMessage.DEFAULT_ENCODING;
// dummy response handler
private static final DoNothingResponseHandler DO_NOTHING_RESPONSE_HANDLER = new DoNothingResponseHandler();
// underlying tcp connection
private String closeReason = null;
private final INonBlockingConnection tcpConnection;
// http connection properties
private final AtomicBoolean isPersistentRef = new AtomicBoolean(true);
// life cycle management
private final AtomicBoolean isClosing = new AtomicBoolean(false);
private final AtomicReference networkBodyDataSinkRef = new AtomicReference();
// attachment management
private AtomicReference attachmentRef = new AtomicReference(null);
// protocol handling
private final DataHandler dataHandler = new DataHandler();
private final AbstractHttpProtocolHandler protocolHandler;
// connection life cycle handler support
private final Set connectionHandlers = Collections.synchronizedSet(new HashSet());
private final AtomicBoolean isDisconnectedRef = new AtomicBoolean(false);
// call back processing
private final IMultimodeExecutor multimodeExcutor;
// timeout support
private long bodyDataReceiveTimeoutMillis = Long.MAX_VALUE;
private long lastTimeDataWritten = System.currentTimeMillis();
private long lastTimeHeaderReceivedMillis = System.currentTimeMillis();
private long lastTimeMessageTailReceivedMillis = System.currentTimeMillis();
private long lastTimeDataReceivedMillis = System.currentTimeMillis();
// auto uncompress
boolean isAutoUncompress = false;
// open transactions
private final AtomicInteger openTransactionsRef = new AtomicInteger(0);
// statistics
private final AtomicInteger countReceivedMessagesRef = new AtomicInteger(0);
private final AtomicInteger countSentMessagesRef = new AtomicInteger(0);
private int countSendBytes = 0;
private int countReceivedBytes = 0;
/**
* constructor
*
* @param nativeConnection the underlying tcp connection
* @param isClientSideConnection true, if http connection is client-side connection
*
* @throws IOException if an exception occurs
*/
protected AbstractHttpConnection(INonBlockingConnection tcpConnection, boolean isClientSideConnection) throws IOException {
this.tcpConnection = tcpConnection;
multimodeExcutor = HttpUtils.newMultimodeExecutor(getWorkerpool());
if (isClientSideConnection) {
protocolHandler = new HttpProtocolHandlerClientSide();
} else {
protocolHandler = new HttpProtocolHandlerServerSide();
}
tcpConnection.setFlushmode(FlushMode.ASYNC);
tcpConnection.setAutoflush(false);
tcpConnection.setAttachment(this);
tcpConnection.setOption(IConnection.TCP_NODELAY, true);
}
/**
* initialize this connection
* @throws IOException if an exception occurs
*/
protected final void init() throws IOException {
tcpConnection.setHandler(dataHandler);
onConnect();
}
final boolean isClosing() {
return isClosing.get();
}
/**
* increments the the number of retrieved messages
* @return the number of the received messages after incrementing it
*/
protected final int incCountMessageReceived() {
return countReceivedMessagesRef.incrementAndGet();
}
/**
* returns the number of the received messages
* @return the number of the received messages
*/
protected int getCountMessagesReceived() {
return countReceivedMessagesRef.get();
}
/**
* returns the number of the received bytes
* @return the number of the received bytes
*/
protected int getCountReceivedBytes() {
return countReceivedBytes;
}
/**
* increments the the number of sent messages
* @return the number of the sent messages after incrementing it
*/
protected final int incCountMessageSent() {
return countSentMessagesRef.incrementAndGet();
}
/**
* returns the number of the sent messages
* @return the number of the sent messages
*/
protected int getCountMessagesSent() {
return countSentMessagesRef.get();
}
protected final int getNumOpenTransactions() {
return openTransactionsRef.get();
}
/**
* sets the time when header or body data has been received
* @param time the time when header or body data has been received
*/
protected final void setLastTimeDataReceivedMillis(long time) {
lastTimeDataReceivedMillis = time;
}
/**
* returns the time when header or body data has been received
* @return the time when header or body data has been received
*/
protected long getLastTimeDataReceivedMillis() {
return lastTimeDataReceivedMillis;
}
/**
* sets the time when body< data has been received
* @param time the time when body< data has been received
*/
protected final void setLastTimeMessageTailReceivedMillis(long time) {
lastTimeMessageTailReceivedMillis = time;
}
/**
* gets the time when body data has been received
* @return the time when body data has been received
*/
protected final long getLastTimeMessageTailReceivedMillis() {
return lastTimeMessageTailReceivedMillis;
}
/**
* sets the time when the (complete) header has been received
* @param time the time when the (complete) header has been received
*/
protected final void setLastTimeHeaderReceivedMillis(long time) {
lastTimeHeaderReceivedMillis = time;
}
/**
* gets the time when the (complete) header has been received
* @return the time when the (complete) header has been received
*/
protected final long getLastTimeHeaderReceivedMillis() {
return lastTimeHeaderReceivedMillis;
}
/**
* gets the last time data has been written
* @return the last time data has been written
*/
protected final long getLastTimeWritten() {
return lastTimeDataWritten;
}
protected final IMultimodeExecutor getExecutor() {
return multimodeExcutor;
}
/**
* returns the underlying http connection
* @return the underlying http connection
*/
public INonBlockingConnection getUnderlyingTcpConnection() {
return tcpConnection;
}
/**
* {@inheritDoc}
*/
public final long getBodyDataReceiveTimeoutMillis() {
return bodyDataReceiveTimeoutMillis;
}
final String getCloseReason() {
return closeReason;
}
/**
* {@inheritDoc}
*/
public final void addConnectionHandler(IHttpConnectionHandler connectionHandler) {
if (connectionHandler == null) {
throw new NullPointerException("conection handler has to be set");
}
connectionHandlers.add(connectionHandler);
if (isDisconnectedRef.get()) {
callOnDisconnect(connectionHandler, HttpUtils.getHttpConnectionHandlerInfo(connectionHandler));
}
}
/**
* {@inheritDoc}
*/
public final void removeConnectionHandler(IHttpConnectionHandler connectionHandler) {
connectionHandlers.remove(connectionHandler);
}
/**
* {@inheritDoc}
*/
public final void setBodyDataReceiveTimeoutMillis(long bodyDataReceiveTimeoutMillis) {
this.bodyDataReceiveTimeoutMillis = bodyDataReceiveTimeoutMillis;
}
/**
* {@inheritDoc}
*/
public final void setAttachment(Object attachment) {
attachmentRef.set(attachment);
}
/**
* {@inheritDoc}
*/
public final Object getAttachment() {
return attachmentRef.get();
}
/**
* {@inheritDoc}
*/
public final long getConnectionTimeoutMillis() {
return tcpConnection.getConnectionTimeoutMillis();
}
/**
* {@inheritDoc}
*/
public final void setConnectionTimeoutMillis(long timeoutMillis) {
tcpConnection.setConnectionTimeoutMillis(timeoutMillis);
}
/**
* {@inheritDoc}
*/
public final long getRemainingMillisToConnectionTimeout() {
return tcpConnection.getRemainingMillisToConnectionTimeout();
}
/**
* {@inheritDoc}
*/
public final long getIdleTimeoutMillis() {
return tcpConnection.getIdleTimeoutMillis();
}
/**
* {@inheritDoc}
*/
public final void setIdleTimeoutMillis(long timeoutInMillis) {
tcpConnection.setIdleTimeoutMillis(timeoutInMillis);
}
/**
* {@inheritDoc}
*/
public final long getRemainingMillisToIdleTimeout() {
return tcpConnection.getRemainingMillisToIdleTimeout();
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public final Map getOptions() {
return tcpConnection.getOptions();
}
/**
* {@inheritDoc}
*/
public final Object getOption(String name) throws IOException {
return tcpConnection.getOption(name);
}
/**
* {@inheritDoc}
*/
public final void setOption(String name, Object value) throws IOException {
tcpConnection.setOption(name, value);
}
/**
* {@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();
}
/**
* see {@link INonBlockingConnection#setWriteTransferRate(int)}
*/
public final void setWriteTransferRate(int bytesPerSecond) throws ClosedChannelException, IOException {
tcpConnection.setWriteTransferRate(bytesPerSecond);
}
/**
* see {@link INonBlockingConnection#getFlushmode(FlushMode)}
*/
final FlushMode getFlushmode() {
return tcpConnection.getFlushmode();
}
/**
* see {@link INonBlockingConnection#getWorkerpool()}
*/
public final Executor getWorkerpool() {
return tcpConnection.getWorkerpool();
}
/**
* sets the worker pool
* @param workerpool the worker pool
*/
public void setWorkerpool(Executor workerpool) {
tcpConnection.setWorkerpool(workerpool);
}
/**
* see {@link INonBlockingConnection#activateSecuredMode()}
*/
public void activateSecuredMode() throws IOException {
tcpConnection.activateSecuredMode();
}
/**
* see {@link INonBlockingConnection#isSecure()}
*/
public final boolean isSecure() {
return tcpConnection.isSecure();
}
/**
* checks if the connection is persistent
*
* @return true, if the connection is persistent
*/
public final boolean isPersistent() {
return isPersistentRef.get();
}
/**
* set if the connection is persistent
*
* @param persistent true, if the connection is persistent
*/
final protected void setPersistent(boolean persistent) {
isPersistentRef.set(persistent);
}
public final void setAutoUncompress(boolean isAutoUncompress) {
this.isAutoUncompress = isAutoUncompress;
}
public final boolean isAutoUncompress() {
return isAutoUncompress;
}
/**
* set the body default encoding. According to RFC 2616 the
* initial value is ISO-8859-1
*
* @param encoding the defaultEncoding
*/
protected void setBodyDefaultEncoding(String defaultEncoding) {
this.defaultEncoding = defaultEncoding;
}
/**
* return the body default encoding
* @return the body default encoding
*/
String getBodyDefaultEncoding() {
return defaultEncoding;
}
/**
* {@inheritDoc}
*/
public final String getId() {
try {
return tcpConnection.getId();
} catch (Throwable t) {
return "";
}
}
/**
* {@inheritDoc}
*/
public final boolean isOpen() {
return (!isDisconnectedRef.get() && tcpConnection.isOpen());
}
protected void onMessageCompleteReceived(IHttpMessageHeader header) {
openTransactionsRef.decrementAndGet();
}
/**
* {@inheritDoc}
*/
public void close() throws IOException {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] closing");
}
try {
AbstractNetworkBodyDataSink messageWriter = networkBodyDataSinkRef.get();
if (messageWriter != null) {
messageWriter.close();
}
releaseResources();
if (isReuseable()) {
tcpConnection.close();
// 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
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] http connection is not reusable (isPersistent=" + isPersistentRef.get() + "). destroying it");
}
NonBlockingConnectionPool.destroy(tcpConnection);
}
} catch (Exception e) {
// eat and log exception
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("error occured by closing htttp connection " + getId() + " " + DataConverter.toString(e));
}
}
}
/**
* {@inheritDoc}
*/
public final void closeQuitly() {
try {
close();
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by closing connection " + getId() + " " + ioe.toString());
}
try {
NonBlockingConnectionPool.destroy(tcpConnection);
} catch (IOException e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by closing connection " + getId() + " " + e.toString());
}
}
}
}
private void releaseResources() {
AbstractNetworkBodyDataSink messageWriter = networkBodyDataSinkRef.get();
if (messageWriter != null) {
networkBodyDataSinkRef.set(null);
messageWriter.onDisconnect();
}
}
protected boolean isReuseable() {
return isPersistentRef.get() && !isReceivingSuspended();
}
/**
* destroy the connection
*/
public final void destroy() {
destroy(0);
}
/**
* destroy the connection
*/
protected final void destroy(int delayMillis) {
destroy("user initiated", delayMillis);
}
/**
* destroy the connection
*
* @param reason the reason
*/
protected final void destroy(String reason) {
destroy(reason, 0);
}
/**
* destroy the connection
*
* @param reason the reason
* @param delayMillis the close delay
*/
protected final void destroy(String reason, int delayMillis) {
if (closeReason != null) {
closeReason = "destroyed - " + reason;
}
setPersistent(false);
isClosing.set(true);
if (delayMillis > 0) {
schedule(new DelayedCloser(), delayMillis);
} else {
performDestroy();
}
}
private final class DelayedCloser extends TimerTask {
public void run() {
performDestroy();
}
}
private void performDestroy() {
releaseResources();
// 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("[" + getId() + "] error occured by destroying htttp connection " + getId() + " " + ioe.toString());
}
}
}
/**
* {@inheritDoc}
*/
final public void suspendReceiving() throws IOException {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] suspend receving");
}
tcpConnection.suspendReceiving();
}
/**
* {@inheritDoc}
*/
final public boolean isReceivingSuspended() {
return tcpConnection.isReceivingSuspended();
}
/**
* {@inheritDoc}
*/
final public void resumeReceiving() throws IOException {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] resume receving");
}
tcpConnection.resumeReceiving();
}
final protected void setNetworkBodyDataSinkIgnoreWriteError() {
AbstractNetworkBodyDataSink ds = networkBodyDataSinkRef.get();
if (ds != null) {
ds.setIgnoreWriteError();
}
}
final void setNetworkBodyDataSink(AbstractNetworkBodyDataSink networkBodyDataSink) {
networkBodyDataSinkRef.set(networkBodyDataSink);
}
final boolean removeNetworkBodyDataSink(AbstractNetworkBodyDataSink networkBodyDataSink) {
AbstractNetworkBodyDataSink oldNetworkBodyDataSink = networkBodyDataSinkRef.get();
if ((oldNetworkBodyDataSink != null) && (oldNetworkBodyDataSink == networkBodyDataSink)) {
networkBodyDataSinkRef.set(networkBodyDataSink);
return true;
}
return false;
}
final void flush() throws IOException {
tcpConnection.flush();
}
final int write(String txt) throws IOException {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] TCP write: " + txt);
}
lastTimeDataWritten = System.currentTimeMillis();
int size = (int) tcpConnection.write(txt);
lastTimeDataWritten = System.currentTimeMillis();
countSendBytes += size;
return size;
}
final int write(byte[] bytes, IWriteCompletionHandler completionHandler) throws IOException {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] TCP write: " + new String(bytes));
}
lastTimeDataWritten = System.currentTimeMillis();
int size = 0;
if (completionHandler != null) {
ByteBuffer[] buffer = new ByteBuffer[] { ByteBuffer.wrap(bytes) };
size = bytes.length;
tcpConnection.write(buffer, completionHandler);
} else {
size = (int) tcpConnection.write(bytes);
}
lastTimeDataWritten = System.currentTimeMillis();
countSendBytes += size;
return size;
}
final long write(ByteBuffer[] buffer, IWriteCompletionHandler completionHandler) throws IOException {
if (LOG.isLoggable(Level.FINE)) {
ByteBuffer[] bufs = new ByteBuffer[buffer.length];
for (int i = 0; i < buffer.length; i++) {
bufs[i] = buffer[i].duplicate();
}
LOG.fine("[" + getId() + "] TCP write: " + DataConverter.toString(bufs));
}
int size = 0;
if (completionHandler != null) {
for (ByteBuffer byteBuffer : buffer) {
size += byteBuffer.remaining();
}
tcpConnection.write(buffer, completionHandler);
} else {
size = (int) tcpConnection.write(buffer);
}
lastTimeDataWritten = System.currentTimeMillis();
countSendBytes += size;
return size;
}
final long write(ByteBuffer[] buffer) throws IOException {
if (LOG.isLoggable(Level.FINE)) {
ByteBuffer[] bufs = new ByteBuffer[buffer.length];
for (int i = 0; i < buffer.length; i++) {
bufs[i] = buffer[i].duplicate();
}
LOG.fine("[" + getId() + "] TCP write: " + DataConverter.toString(bufs));
}
int size = (int) tcpConnection.write(buffer);
lastTimeDataWritten = System.currentTimeMillis();
countSendBytes += size;
return size;
}
protected abstract IMessageHeaderHandler getMessageHeaderHandler();
protected void onProtocolException(Exception ex) {
destroy(ex.toString());
}
protected void onMessageWritten() {
}
protected void onConnect() throws IOException {
for (final IHttpConnectionHandler connectionHandler : connectionHandlers) {
HttpConnectionHandlerInfo connectionHandlerInfo = HttpUtils.getHttpConnectionHandlerInfo(connectionHandler);
if (connectionHandlerInfo.isConnectHandler()) {
Runnable task = new Runnable() {
public void run() {
try {
((IHttpConnectHandler) connectionHandler).onConnect(AbstractHttpConnection.this);
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by performing onConnect on " + connectionHandler + " reason: " + ioe.toString());
}
destroy();
}
}
};
if (connectionHandlerInfo.isConnectHandlerMultithreaded()) {
multimodeExcutor.processMultithreaded(task);
} else {
multimodeExcutor.processNonthreaded(task);
}
}
}
}
protected void onDisconnect() {
try {
if (!isDisconnectedRef.getAndSet(true)) {
for (final IHttpConnectionHandler connectionHandler : connectionHandlers) {
HttpConnectionHandlerInfo connectionHandlerInfo = HttpUtils.getHttpConnectionHandlerInfo(connectionHandler);
if (connectionHandlerInfo.isDisconnectHandler()) {
callOnDisconnect(connectionHandler, connectionHandlerInfo);
}
}
connectionHandlers.clear();
releaseResources();
}
} catch (Throwable t) {
// log and eat exception
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by closing http connection " + DataConverter.toString(t));
}
}
}
private void callOnDisconnect(final IHttpConnectionHandler connectionHandler, HttpConnectionHandlerInfo connectionHandlerInfo) {
Runnable task = new Runnable() {
public void run() {
try {
((IHttpDisconnectHandler) connectionHandler).onDisconnect(AbstractHttpConnection.this);
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by performing ondisconnect on " + connectionHandler + " reason: " + ioe.toString());
}
destroy();
}
}
};
if (connectionHandlerInfo.isDisconnectHandlerMultithreaded()) {
multimodeExcutor.processMultithreaded(task);
} else {
multimodeExcutor.processNonthreaded(task);
}
}
protected void onConnectionTimeout() {
closeQuitly();
}
protected void onIdleTimeout() {
closeQuitly();
}
protected final BodyDataSink writeMessage(int autocompressThresholdBytes, IHttpMessageHeader header) throws IOException {
lastTimeDataWritten = System.currentTimeMillis();
openTransactionsRef.incrementAndGet();
BodyDataSink ds = internalWriteMessage(header);
ds.setAutocompressThreshold(autocompressThresholdBytes);
return ds;
}
private BodyDataSink internalWriteMessage(IHttpMessageHeader header) throws IOException {
BodyDataSink dataSink = null;
// web socket handeshake?
if ((header.getHeader("Upgrade") != null) && (header.getHeader("Upgrade").equalsIgnoreCase("WebSocket"))) {
// request?
if ((header.getHeader("Sec-WebSocket-Key1") != null)) {
dataSink = internalWriteMessage(header, 8);
// ... no, response
} else {
dataSink = internalWriteMessage(header, 16);
}
// transfer encoding header is set with chunked -> chunked body
} else if (isChunkedTransferEncoding(header)) {
dataSink = new FullMessageChunkedBodyDataSink(this, header);
// contains a non-zero Content-Length header -> bound body
} else if (header.getContentLength() != -1) {
dataSink = internalWriteMessage(header, header.getContentLength());
// is connection header set with close? -> SimpleMessage
} else if ((header.getHeader("Connection") != null) && (header.getHeader("Connection").equalsIgnoreCase("close"))) {
setPersistent(false);
dataSink = new SimpleMessageBodyDataSink(this, header);
// is content-type set?
} else if (header.getContentType() != null) {
setPersistent(false);
dataSink = new SimpleMessageBodyDataSink(this, header);
// return PseudoBody
} else {
dataSink = new BodylessBodyDataSink(this, header);
}
return dataSink;
}
protected final BodyDataSink writeMessage(IHttpMessageHeader header, int length) throws IOException {
lastTimeDataWritten = System.currentTimeMillis();
openTransactionsRef.incrementAndGet();
return internalWriteMessage(header, length);
}
private BodyDataSink internalWriteMessage(IHttpMessageHeader header, int length) throws IOException {
// contains a non-zero Content-Length header -> bound body
if (length > 0) {
return new FullMessageBodyDataSink(this, header, length);
} else {
header.removeHeader("Content-Type");
// return PseudoBody
return new BodylessBodyDataSink(this, header);
}
}
protected final BodyDataSink writeMessage(int autocompressThreshold, IHttpMessage message) throws IOException {
lastTimeDataWritten = System.currentTimeMillis();
openTransactionsRef.incrementAndGet();
return internalWriteMessage(autocompressThreshold, message);
}
protected final BodyDataSink writeMessageSilence(IHttpMessage message) throws IOException {
lastTimeDataWritten = System.currentTimeMillis();
return internalWriteMessage(Integer.MAX_VALUE, message);
}
private BodyDataSink internalWriteMessage(int autocompressThreshold, IHttpMessage message) throws IOException {
if (message.hasBody()) {
return writeMessageWithBody(autocompressThreshold, message);
} else {
return writeMessageWithoutBody(message);
}
}
private BodyDataSink writeMessageWithoutBody(IHttpMessage message) throws IOException {
write(message.getMessageHeader().toString() + "\r\n");
flush();
return null;
}
private BodyDataSink writeMessageWithBody(int autocompressThreshold, IHttpMessage message) throws IOException {
BodyDataSink bodyDataSink = internalWriteMessage(message.getMessageHeader());
bodyDataSink.setAutocompressThreshold(autocompressThreshold);
NonBlockingBodyDataSource bodyDataSource = message.getNonBlockingBody();
// is body complete?
if (bodyDataSource.isComplete()) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] message body to sent is complete. writing all data to body data sink");
}
bodyDataSink.setFlushmode(FlushMode.ASYNC);
bodyDataSink.setAutoflush(false); // close will flush the data sink
int available = bodyDataSource.available();
if (available > 0) {
bodyDataSink.write(bodyDataSource.readByteBufferByLength(available));
}
bodyDataSink.close();
// .. no
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] message body to sent is not complete. creating forwarder");
}
bodyDataSink.setAutoflush(true);
bodyDataSink.setFlushmode(FlushMode.ASYNC);
bodyDataSource.forwardTo(bodyDataSink);
}
return bodyDataSink;
}
static boolean isChunkedTransferEncoding(IHttpMessageHeader header) {
String transferEncoding = header.getTransferEncoding();
if ((transferEncoding != null) && (transferEncoding.equalsIgnoreCase("chunked"))) {
return true;
}
return false;
}
protected static boolean isNetworkendpoint(NonBlockingBodyDataSource dataSource) {
return dataSource.isNetworkendpoint();
}
protected static int getDataReceived(NonBlockingBodyDataSource dataSource) {
return dataSource.getDataReceived();
}
protected static void setDataHandlerSilence(NonBlockingBodyDataSource dataSource, IBodyDataHandler dataHandler) {
dataSource.setDataHandlerSilence(dataHandler);
}
protected static IBodyDataHandler getDataHandlerSilence(NonBlockingBodyDataSource dataSource) {
return dataSource.getDataHandlerSilence();
}
protected static int availableSilence(NonBlockingBodyDataSource dataSource) throws IOException {
return dataSource.availableSilence();
}
protected static ByteBuffer[] readByteBufferByLengthSilence(NonBlockingBodyDataSource dataSource, int length) throws IOException {
return dataSource.readByteBufferByLengthSilence(length);
}
/**
* returns true if the requests will accept a chunked body
* @param request the request
* @return true if the requests will accept a chunked body
*/
protected static boolean isAcceptingChunkedResponseBody(IHttpRequest request) {
String protocolVersion = request.getProtocolVersion();
if (protocolVersion.equals("1.1")) {
return true;
} else {
int idx = protocolVersion.indexOf(".");
int minor = Integer.parseInt(protocolVersion.substring(idx + 1, protocolVersion.length()));
if (minor > 0) {
return true;
}
int major = Integer.parseInt(protocolVersion.substring(0, idx));
if (major > 1) {
return true;
}
}
return false;
}
/**
* schedule a time task
*
* @param task the timer task
* @param delay the delay
* @param period the period
*/
protected static void schedule(TimerTask task, long delay, long period) {
HttpUtils.schedule(task, delay, period);
}
/**
* schedule a time task
*
* @param task the timer task
* @param delay the delay
*/
protected static void schedule(TimerTask task, long delay) {
HttpUtils.schedule(task, delay);
}
protected static void setAutocompressThreshold(BodyDataSink dataSink, int autocompressThreshold) {
dataSink.setAutocompressThreshold(autocompressThreshold);
}
protected static IMultimodeExecutor newMultimodeExecutor(Executor workerpool) {
return HttpUtils.newMultimodeExecutor(workerpool);
}
protected static String parseEncoding(String contentType) {
return HttpUtils.parseEncoding(contentType);
}
/*
protected static BodyDataSink newInMemoryBodyDataSink(String id, IHttpMessageHeader header, IMultimodeExecutor executor) throws IOException {
return new InMemoryBodyDataSink(id, header, executor);
}*/
protected static BodyDataSink newInMemoryBodyDataSink(String id, IHttpMessageHeader header) throws IOException {
return new InMemoryBodyDataSink(id, header);
}
protected static NonBlockingBodyDataSource getDataSourceOfInMemoryBodyDataSink(BodyDataSink dataSink) {
return ((InMemoryBodyDataSink) dataSink).getDataSource();
}
protected static boolean isComplete(NonBlockingBodyDataSource body) {
return body.isComplete();
}
protected static boolean isForwardable(NonBlockingBodyDataSource dataSource) {
return dataSource.isForwardable();
}
protected static void forwardBody(NonBlockingBodyDataSource dataSource, BodyDataSink bodyDataSink) throws IOException {
((IForwardable) dataSource).forwardTo(bodyDataSink);
}
protected static void forward(NonBlockingBodyDataSource dataSource, BodyDataSink dataSink) throws IOException {
dataSource.forwardTo(dataSink);
}
protected static void addConnectionAttribute(IHttpResponseHeader header, IHttpConnection con) {
HttpUtils.addConnectionAttribute(header, con);
}
protected static int getSizeDataReceived(NonBlockingBodyDataSource body) {
return body.getSizeDataReceived();
}
protected static int getSizeWritten(BodyDataSink body) {
return body.getSizeWritten();
}
protected static boolean isEmpty(ByteBuffer[] data) {
return HttpUtils.isEmpty(data);
}
protected static ClosedChannelException newDetailedClosedChannelException(String reason) {
return new DetailedClosedChannelException(reason);
}
protected static final IHttpResponseHandler newDoNothingResponseHandler() {
return DO_NOTHING_RESPONSE_HANDLER;
}
protected static boolean isContentTypeSupportsCharset(String contentType) {
return HttpUtils.isContentTypeSupportsCharset(contentType);
}
protected static boolean isSupports100Contine(IHttpResponseHandler handler) {
return HttpUtils.getResponseHandlerInfo(handler).isContinueHandler();
}
protected static IHeader getReceviedHeader(ProtocolException pe) {
return pe.getReceivedHeader();
}
/**
* wraps the request by a request object which will read parameters from the body
* @param request the request to wrap
* @return request object which will read parameters from the body
* @throws IOException if an exception occurs
*/
protected static IHttpRequest newFormEncodedRequestWrapper(IHttpRequest request) throws IOException {
return HttpUtils.newFormEncodedRequestWrapper(request);
}
/**
* register the task as which will be executed if the body data sink will be closed
* @param bodyDataSink the body data sink
* @param task the task to execute
*/
protected final void setBodyCloseListener(BodyDataSink bodyDataSink, Runnable task) {
bodyDataSink.addCloseListener(new BodyCloseListener(task));
}
private static final class BodyCloseListener implements IBodyCloseListener {
private Runnable task = null;
public BodyCloseListener(Runnable task) {
this.task = task;
}
public void onClose() throws IOException {
task.run();
}
}
/**
* return the max write buffer size
*
* @return the max write buffer size
*/
protected static int getMaxWriteBufferSize() {
return MAX_WRITEBUFFER_SIZE;
}
/**
* generates a system error html
*
* @param errorCode the error code
* @param msg the error message
* @param connectionId the connection id
* @return the html page
*/
protected static String generateErrorMessageHtml(int errorCode, String msg, String connectionId) {
if (msg == null) {
msg = HttpUtils.getReason(errorCode);
}
msg = msg.replace("\r", " ");
String txt = "\r\n" +
" \r\n" +
" \r\n" +
" \r\n" +
" \r\n" +
" Error " + errorCode + " \r\n" +
" " +
" \r\n\r\n" +
" \r\n" +
" ERROR " + errorCode + " \r\n" +
" " + msg + "
\r\n" +
" " + new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z").format(new Date()) + " xLightweb (" + HttpUtils.getImplementationVersion() + ")
\r\n" +
" \r\n" +
"\r\n";
return txt;
}
/**
* creates a empty body data sink
* @param the header
* @return the body data sink
* @throws IOException in an exception occurs
*/
protected final BodyDataSink newEmtpyBodyDataSink(IHttpMessageHeader header) throws IOException {
return new EmptyBodyDataSink(header);
}
private AbstractHttpProtocolHandler getProtocolHandler() {
return protocolHandler;
}
private static AbstractHttpConnection getHttpConnection(INonBlockingConnection tcpConnection) {
return (AbstractHttpConnection) tcpConnection.getAttachment();
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getId() + " " + tcpConnection.getLocalAddress() + ":" + tcpConnection.getLocalPort() + " -> " + tcpConnection.getRemoteAddress() + ":" + tcpConnection.getRemotePort());
if (!tcpConnection.isOpen()) {
sb.append(" (closed)");
}
if (tcpConnection.isReceivingSuspended()) {
sb.append(" (suspended)");
}
return sb.toString();
}
String toDetailedString() {
StringBuilder sb = new StringBuilder();
sb.append(getId());
try {
sb.append(tcpConnection.getLocalAddress() + ":" + tcpConnection.getLocalPort() + " -> " + tcpConnection.getRemoteAddress() + ":" + tcpConnection.getRemotePort());
} catch (Throwable ignore) { }
if (!tcpConnection.isOpen()) {
sb.append(" (closed)");
}
if (protocolHandler != null) {
sb.append(" " + protocolHandler.toString());
} else {
sb.append(" protocolHandler=null");
}
sb.append(" isPersistent=" + isPersistentRef.get());
return sb.toString();
}
/**
* @author grro
*/
protected interface IMessageHeaderHandler {
/**
* call back if a message header is received
*
* @param message the message
* @throws IOException if an exception occurs
*/
public IMessageHandler onMessageHeaderReceived(IHttpMessage message) throws IOException;
/**
* call back if an exception is occurred
*
* @param ioe the exception
* @param rawData the unprocessed network data
*/
public void onHeaderException(IOException ioe, ByteBuffer[] rawData);
/**
* returns the associated header
* @return the associated header
*/
public IHttpMessageHeader getAssociatedHeader();
}
/**
* @author grro
*/
protected interface IMessageHandler {
/**
* call back if an exception is occurred
*
* @param ioe the exception
* @param rawData the unprocessed network data
*/
public void onBodyException(IOException ioe, ByteBuffer[] rawData);
/**
* call back
* @throws IOException io exception
*/
public void onMessageReceived() throws IOException;
/**
* call back
* @throws IOException io exception
*/
public void onHeaderProcessed() throws IOException;
}
/**
* multimode executor definition
* @author grro
*/
protected interface IMultimodeExecutor {
/**
* process the task multi threaded
* @param task the task to process
*/
void processMultithreaded(Runnable task);
/**
* process the task non threaded
* @param task the task to process
*/
void processNonthreaded(Runnable task);
Executor getWorkerpool();
}
protected abstract static class AbstractExchange implements IHttpExchange {
private final AtomicBoolean isResponseCommitted = new AtomicBoolean(false);
private boolean is100ContinueSent = false;
private final AbstractExchange parentExchange;
private final IMultimodeExecutor executor;
private IHttpConnection httpCon = null;
protected AbstractExchange(IHttpExchange parentExchange) {
if ((parentExchange != null) && (parentExchange instanceof AbstractExchange)) {
this.parentExchange = (AbstractExchange) parentExchange;
executor = ((AbstractExchange) parentExchange).getExecutor();
} else {
this.parentExchange = null;
executor = HttpUtils.newMultimodeExecutor();
}
if (parentExchange != null) {
httpCon = parentExchange.getConnection();
}
}
protected AbstractExchange(AbstractExchange parentExchange, Executor workerpool) {
this.parentExchange = parentExchange;
executor = HttpUtils.newMultimodeExecutor();
}
protected AbstractExchange(AbstractExchange parentExchange, AbstractHttpConnection httpCon) {
this.parentExchange = parentExchange;
setHttpConnection(httpCon);
this.executor = httpCon.getExecutor();
}
protected final void setHttpConnection(IHttpConnection httpCon) {
this.httpCon = httpCon;
}
IMultimodeExecutor getExecutor() {
return executor;
}
protected final void setHttpConnection(AbstractHttpConnection httpCon) {
this.httpCon = httpCon;
}
protected final boolean isResponseCommitted() {
return isResponseCommitted.get();
}
protected final void setResponseCommited(boolean isCommitted) {
isResponseCommitted.set(isCommitted);
}
protected final void ensureResponseHasNotBeenCommitted() throws IOException {
if (isResponseCommitted() == true) {
throw new IOException("response has already been committed");
}
}
protected String getId() {
if (httpCon != null) {
return httpCon.getId();
} else {
return Integer.toString(hashCode());
}
}
/**
* {@inheritDoc}
*/
public final IHttpConnection getConnection() {
if (httpCon != null) {
return httpCon;
} else {
return new NullConnection(executor.getWorkerpool());
}
}
/**
* {@inheritDoc}
*/
public final void destroy() {
if (httpCon != null) {
httpCon.destroy();
}
}
protected final void destroy(String reason) {
if (httpCon != null) {
httpCon.destroy();
}
}
public void sendRedirect(String location) throws IllegalStateException {
if (!HttpUtils.isAbsoluteURL(location)) {
String url = HttpUtils.getRequestURLWithoutQueryParams(getRequest().getRequestHeader());
if (location.startsWith("/")) {
String base = url.substring(0, url.length() - getRequest().getRequestURI().length());
location = base + getRequest().getContextPath() + location;
} else {
location = url + "/" + location;
}
}
String txt = "\r\n" +
"\r\n" +
"302 Found \r\n" +
"\r\n" +
"Found \r\n" +
"The document has moved here .
\r\n" +
"";
try {
HttpResponse response = new HttpResponse(302, "text/html", txt);
response.setHeader("Location", location);
send(response);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
/**
* send an error response
*
* @param errorCode the error code
*/
public final void sendError(int errorCode) {
sendError(errorCode, HttpUtils.getReason(errorCode));
}
/**
* send an error response
*
* @param errorCode the error code
* @param msg the error message
*/
public void sendError(int errorCode, String msg) {
try {
send(new HttpResponse(errorCode, "text/html", generateErrorMessageHtml(errorCode, msg, getId())));
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] could not send error message " + errorCode + " reason " + ioe.toString());
}
destroy(ioe.toString());
}
}
public final boolean sendContinueIfRequested() throws IOException {
if (HttpUtils.isContainExpect100ContinueHeader(getRequest().getRequestHeader()) && (!is100ContinueSent())) {
is100ContinueSent = true;
doSendContinue();
if (parentExchange != null) {
parentExchange.set100ContinueSent(is100ContinueSent);
}
return true;
}
return false;
}
void set100ContinueSent(boolean is100ContinueSent) {
this.is100ContinueSent = is100ContinueSent;
if (parentExchange != null) {
parentExchange.set100ContinueSent(is100ContinueSent);
}
}
protected void doSendContinue() throws IOException {
send(new HttpResponse(100));
}
protected final boolean is100ContinueSent() {
if (parentExchange != null) {
return (parentExchange.is100ContinueSent() || is100ContinueSent);
} else {
return is100ContinueSent;
}
}
protected final void callResponseHandler(IHttpResponseHandler responseHandler, IHttpResponse response) throws IOException {
ResponseHandlerInfo handlerInfo = HttpUtils.getResponseHandlerInfo(responseHandler);
if (response.hasBody() && handlerInfo.isResponseHandlerInvokeOnMessageReceived()) {
NonBlockingBodyDataSource ds = response.getNonBlockingBody();
BodyListener bodyListener = new BodyListener(ds, responseHandler, handlerInfo, response);
ds.addCompleteListener(bodyListener);
ds.addDestroyListener(bodyListener);
} else {
callResponseHandler(responseHandler, handlerInfo, response);
}
}
private final class BodyListener implements IBodyCompleteListener, IBodyDestroyListener {
private NonBlockingBodyDataSource ds;
private IHttpResponseHandler responseHandler;
private ResponseHandlerInfo handlerInfo;
private IHttpResponse response;
public BodyListener(NonBlockingBodyDataSource ds, IHttpResponseHandler responseHandler, ResponseHandlerInfo handlerInfo, IHttpResponse response) {
this.ds = ds;
this.responseHandler = responseHandler;
this.handlerInfo = handlerInfo;
this.response = response;
}
public void onComplete() throws IOException {
callResponseHandler(responseHandler, handlerInfo, response);
}
public void onDestroyed() throws IOException {
IOException ioe = ds.getException();
if (ioe == null) {
ioe = new IOException("data source hhas been destroyed");
}
responseHandler.onException(ioe);
}
};
protected final void callResponseHandler(final IHttpResponseHandler responseHandler, final ResponseHandlerInfo handlerInfo, final IHttpResponse response) {
if (handlerInfo.isUnsynchronized()) {
call(responseHandler, response);
} else {
Runnable task = new Runnable() {
public void run() {
call(responseHandler, response);
}
};
if (handlerInfo.isResponseHandlerMultithreaded()) {
executor.processMultithreaded(task);
} else {
executor.processNonthreaded(task);
}
}
}
private void call(IHttpResponseHandler responseHandler, IHttpResponse response) {
try {
responseHandler.onResponse(response);
} catch (IOException ioe) {
if (httpCon != null) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + httpCon.getId() + "] Error occured by calling response handler " + responseHandler + " " + ioe.toString());
}
destroy(ioe.toString());
}
}
}
protected final void callResponseHandler(IHttpResponseHandler responseHandler, IOException ioe) {
callResponseHandler(responseHandler, HttpUtils.getResponseHandlerInfo(responseHandler), ioe);
}
protected final void callResponseHandler(final IHttpResponseHandler responseHandler, final ResponseHandlerInfo handlerInfo, final IOException ioe) {
if (handlerInfo.isUnsynchronized()) {
call(responseHandler, ioe);
} else {
Runnable task = new Runnable() {
public void run() {
call(responseHandler, ioe);
}
};
if (handlerInfo.isResponseHandlerMultithreaded()) {
executor.processMultithreaded(task);
} else {
executor.processNonthreaded(task);
}
}
}
private void call(IHttpResponseHandler responseHandler, IOException ioe) {
try {
if ((ioe instanceof SocketTimeoutException) && (HttpUtils.getResponseHandlerInfo(responseHandler).isSocketTimeoutHandler())) {
((IHttpSocketTimeoutHandler) responseHandler).onException((SocketTimeoutException) ioe);
}else {
responseHandler.onException(ioe);
}
} catch (IOException e) {
if (httpCon != null) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + httpCon.getId() + "] Error occured by calling response handler " + responseHandler + " " + e.toString());
}
destroy(e.toString());
}
}
}
}
@Execution(Execution.NONTHREADED)
final class DataHandler implements IDataHandler, IConnectHandler, IDisconnectHandler, IIdleTimeoutHandler, IConnectionTimeoutHandler, IHandlerChangeListener {
// network data
private ByteBuffer[] rawBuffers = null;
public boolean onData(final INonBlockingConnection connection) throws BufferUnderflowException {
lastTimeDataReceivedMillis = System.currentTimeMillis();
if (connection.isOpen() && !isClosing()) {
try {
// get the protocol handler
AbstractHttpProtocolHandler protocolHandler = (AbstractHttpProtocolHandler) getHttpConnection(connection).getProtocolHandler();
// copying available network data into raw data buffer
int available = connection.available();
if (available > 0) {
ByteBuffer[] data = connection.readByteBufferByLength(available);
protocolHandler.incReveived(available);
if (rawBuffers == null) {
rawBuffers = data;
} else {
rawBuffers = HttpUtils.merge(rawBuffers, data);
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] TCP read (total received: " + protocolHandler.getReceived() + ", currentRawBuffer=" + HttpUtils.computeRemaining(rawBuffers) + ") ");
LOG.fine("[" + getId() + "] calling protocol handler " + protocolHandler.getClass().getName() + "#" + protocolHandler.hashCode());
}
// handle data based on the buffer
rawBuffers = protocolHandler.onData(AbstractHttpConnection.this, rawBuffers);
}
return true;
} catch (ClosedChannelException cce) {
setPersistent(false);
protocolHandler.onDisconnect(AbstractHttpConnection.this, rawBuffers);
} catch (IOException ex) {
setPersistent(false);
protocolHandler.onException(AbstractHttpConnection.this, ex, rawBuffers);
onProtocolException(ex);
}
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId()+ "] Connection is closed. ignoring incoming data");
}
}
return true;
}
public void onHanderReplaced(IHandler oldHandler, IHandler newHandler) {
if (oldHandler == this) {
try {
if (rawBuffers != null) {
getUnderlyingTcpConnection().unread(HttpUtils.compact(rawBuffers));
}
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] could not unread " + ioe.toString());
}
}
}
}
public boolean onConnect(INonBlockingConnection connection) throws IOException, BufferUnderflowException, MaxReadSizeExceededException {
return true;
}
public boolean onDisconnect(final INonBlockingConnection connection) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] disconnected");
}
AbstractHttpProtocolHandler protocolHandler = (AbstractHttpProtocolHandler) getHttpConnection(connection).getProtocolHandler();
protocolHandler.onDisconnect(AbstractHttpConnection.this, rawBuffers);
AbstractHttpConnection.this.onDisconnect();
return true;
}
public boolean onConnectionTimeout(final INonBlockingConnection connection) {
if (!isClosing()) {
AbstractHttpConnection.this.onConnectionTimeout();
}
return true;
}
public boolean onIdleTimeout(final INonBlockingConnection connection) {
if (!isClosing()) {
AbstractHttpConnection.this.onIdleTimeout();
}
return true;
}
ByteBuffer[] drainBuffer() {
ByteBuffer[] result = rawBuffers;
rawBuffers = null;
return result;
}
}
protected final static class RequestHandlerAdapter {
private final IHttpRequestHandler requestHandler;
private final IHttpRequestTimeoutHandler requestTimeoutHandler;
private final boolean isInvokeOnMessageReceived;
private final boolean isContinueHandler;
public RequestHandlerAdapter(IHttpRequestHandler delegate) {
RequestHandlerInfo handlerInfo = HttpUtils.getRequestHandlerInfo(delegate);
isInvokeOnMessageReceived = handlerInfo.isRequestHandlerInvokeOnMessageReceived();
isContinueHandler = handlerInfo.isContinueHandler();
// wrapping request timeout handler
if (handlerInfo.isRequestTimeoutHandler()) {
if (handlerInfo.isUnsynchronized()) {
requestTimeoutHandler = (IHttpRequestTimeoutHandler) delegate;
} else if (handlerInfo.isRequestTimeoutHandlerMultithreaded()) {
requestTimeoutHandler = new MultithreadedRequestTimeoutHandlerAdapter((IHttpRequestTimeoutHandler) delegate);
} else {
requestTimeoutHandler = new NonthreadedRequestTimeoutHandlerAdapter((IHttpRequestTimeoutHandler) delegate);
}
} else {
requestTimeoutHandler = new EmptyRequestTimeoutHandlerAdapter();
}
// wrap session-scope request handler
if (handlerInfo.isRequestHandlerSynchronizedOnSession()) {
delegate = new SessionSynchronizedRequestHandlerAdapter(delegate);
}
// wrap threaded request handler
if (handlerInfo.isRequestHandlerSynchronizedOnSession()) {
requestHandler = new MultithreadedRequestHandlerAdapter(delegate);
} else if (handlerInfo.isUnsynchronized()) {
requestHandler = delegate;
} else if (handlerInfo.isRequestHandlerMultithreaded()) {
requestHandler = new MultithreadedRequestHandlerAdapter(delegate);
} else {
requestHandler = new NonthreadedRequestHandlerAdapter(delegate);
}
}
@Override
public String toString() {
return requestHandler.toString();
}
public final IHttpRequestHandler getDelegate() {
return requestHandler;
}
public final boolean isInvokeOnMessageReceived() {
return isInvokeOnMessageReceived;
}
private final boolean isContinueHandler() {
return isContinueHandler;
}
public final void onRequest(IHttpExchange exchange) {
performRequestHandler(requestHandler, exchange);
}
private void performRequestHandler(IHttpRequestHandler handler, final IHttpExchange exchange) {
try {
if (!isContinueHandler() && HttpUtils.isContainExpect100ContinueHeader(exchange.getRequest().getRequestHeader())) {
if (exchange.getRequest().hasBody()) {
IBodyAccessListener al = new IBodyAccessListener() {
public boolean onBodyAccess() {
try {
exchange.sendContinueIfRequested();
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("error occured by trying to dsend 100-continue " + ioe.toString());
}
}
return true;
}
};
exchange.getRequest().getNonBlockingBody().setBodyAccessListener(al);
}
}
handler.onRequest(exchange);
} catch (BadMessageException bme) {
if (HttpUtils.isShowDetailedError()) {
exchange.sendError(bme.getStatus(), DataConverter.toString(bme));
} else {
exchange.sendError(bme.getStatus());
}
} catch (IOException e) {
if (exchange.getConnection() instanceof AbstractHttpConnection) {
((AbstractHttpConnection) exchange.getConnection()).setPersistent(false);
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + exchange.getConnection().getId() + "] error occured by calling on request " + requestHandler + " " + DataConverter.toString(e));
}
if (exchange instanceof AbstractExchange) {
if (!((AbstractExchange) exchange).isResponseCommitted()) {
exchange.sendError(e);
return;
}
}
exchange.destroy();
} catch (Exception e) {
if (exchange.getConnection() instanceof AbstractHttpConnection) {
((AbstractHttpConnection) exchange.getConnection()).setPersistent(false);
}
LOG.warning("[" + exchange.getConnection().getId() + "] error occured by calling on request " + requestHandler + " " + DataConverter.toString(e));
if (exchange instanceof AbstractExchange) {
if (!((AbstractExchange) exchange).isResponseCommitted()) {
exchange.sendError(e);
return;
}
}
exchange.destroy();
}
}
public final void onRequestTimeout(IHttpConnection connection) {
performRequestTimeoutHandler(requestTimeoutHandler, connection);
}
private void performRequestTimeoutHandler(IHttpRequestTimeoutHandler handler, IHttpConnection connection) {
try {
boolean isHandled = handler.onRequestTimeout(connection);
if (!isHandled) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + connection.getId() + "] request timeout reached for http server connection. terminate connection (timeout handler returned false)");
}
closeConnection(connection);
}
} catch (Exception e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + connection.getId() + "] error occured by calling on request " + handler + " " + e.toString());
}
if (connection instanceof AbstractHttpConnection) {
((AbstractHttpConnection) connection).destroy(e.toString());
}
}
}
private void closeConnection(IHttpConnection connection) {
try {
connection.close();
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + connection.getId() + "] error occured by closing connection");
}
}
}
private static final class EmptyRequestTimeoutHandlerAdapter implements IHttpRequestTimeoutHandler {
public boolean onRequestTimeout(final IHttpConnection connection) throws IOException {
return false;
}
@Override
public String toString() {
return this.getClass().getSimpleName() + "#" + hashCode();
}
}
private final class MultithreadedRequestTimeoutHandlerAdapter implements IHttpRequestTimeoutHandler {
private final IHttpRequestTimeoutHandler delegate;
public MultithreadedRequestTimeoutHandlerAdapter(IHttpRequestTimeoutHandler delegate) {
this.delegate = delegate;
}
public boolean onRequestTimeout(final IHttpConnection connection) throws IOException {
Runnable task = new Runnable() {
public void run() {
performRequestTimeoutHandler(delegate, (AbstractHttpConnection) connection);
}
};
if (connection instanceof AbstractHttpConnection) {
((AbstractHttpConnection) connection).getExecutor().processMultithreaded(task);
} else {
HttpUtils.newMultimodeExecutor().processMultithreaded(task);
}
return true;
}
@Override
public String toString() {
return super.getClass().getName() + "*" + delegate.getClass().getName() + "@" + Integer.toHexString(hashCode());
}
}
private final class NonthreadedRequestTimeoutHandlerAdapter implements IHttpRequestTimeoutHandler {
private final IHttpRequestTimeoutHandler delegate;
public NonthreadedRequestTimeoutHandlerAdapter(IHttpRequestTimeoutHandler delegate) {
this.delegate = delegate;
}
public boolean onRequestTimeout(final IHttpConnection connection) throws IOException {
Runnable task = new Runnable() {
public void run() {
performRequestTimeoutHandler(delegate, (AbstractHttpConnection) connection);
}
};
if (connection instanceof AbstractHttpConnection) {
((AbstractHttpConnection) connection).getExecutor().processNonthreaded(task);
} else {
HttpUtils.newMultimodeExecutor().processNonthreaded(task);
}
return true;
}
@Override
public String toString() {
return super.getClass().getName() + "*" + delegate.getClass().getName() + "@" + Integer.toHexString(hashCode());
}
}
@Supports100Continue
private final class NonthreadedRequestHandlerAdapter implements IHttpRequestHandler{
private final IHttpRequestHandler delegate;
public NonthreadedRequestHandlerAdapter(IHttpRequestHandler delegate) {
this.delegate = delegate;
}
public void onRequest(final IHttpExchange exchange) throws IOException, BadMessageException {
Runnable task = new Runnable() {
public void run() {
performRequestHandler(delegate, exchange);
}
};
if (exchange instanceof AbstractExchange) {
((AbstractExchange) exchange).getExecutor().processNonthreaded(task);
} else {
HttpUtils.newMultimodeExecutor().processNonthreaded(task);
}
}
@Override
public String toString() {
return super.getClass().getName() + "*" + delegate.getClass().getName() + "@" + Integer.toHexString(hashCode());
}
}
@Supports100Continue
private final class MultithreadedRequestHandlerAdapter implements IHttpRequestHandler {
private final IHttpRequestHandler delegate;
public MultithreadedRequestHandlerAdapter(IHttpRequestHandler delegate) {
this.delegate = delegate;
}
public void onRequest(final IHttpExchange exchange) throws IOException, BadMessageException {
Runnable task = new Runnable() {
public void run() {
performRequestHandler(delegate, exchange);
}
};
if (exchange instanceof AbstractExchange) {
((AbstractExchange) exchange).getExecutor().processMultithreaded(task);
} else {
HttpUtils.newMultimodeExecutor().processMultithreaded(task);
}
}
@Override
public String toString() {
return super.getClass().getName() + "*" + delegate.getClass().getName() + "@" + Integer.toHexString(hashCode());
}
}
@Supports100Continue
private static final class SessionSynchronizedRequestHandlerAdapter implements IHttpRequestHandler {
private IHttpRequestHandler delegate = null;
public SessionSynchronizedRequestHandlerAdapter(IHttpRequestHandler delegate) {
this.delegate = delegate;
}
public void onRequest(IHttpExchange exchange) throws IOException {
IHttpSession session = exchange.getSession(false);
if (session == null) {
delegate.onRequest(exchange);
} else {
synchronized (session) {
delegate.onRequest(exchange);
}
}
}
@Override
public String toString() {
return super.getClass().getName() + "*" + delegate.getClass().getName() + "@" + Integer.toHexString(hashCode());
}
}
}
private static interface IResponseHandlerAdapter {
void onResponse(IHttpResponse response, AbstractHttpConnection httpConnection);
}
protected static final class ResponseHandlerAdapter implements IResponseHandlerAdapter {
private final IResponseHandlerAdapter responseHandlerAdapter;
private final IHttpResponseHandler exceptionHandler;
private final ResponseHandlerInfo handlerInfo;
// continue support
private final boolean isContinueResponseExpected;
private final AtomicBoolean is100ContinueHandled = new AtomicBoolean(false);
public ResponseHandlerAdapter(IHttpResponseHandler delegate, IHttpRequestHeader requestHeader) {
handlerInfo = HttpUtils.getResponseHandlerInfo(delegate);
isContinueResponseExpected = HttpUtils.isContainExpect100ContinueHeader(requestHeader);
exceptionHandler = delegate;
if (handlerInfo.isUnsynchronized()) {
responseHandlerAdapter = new UnsynchronizedResponseHandlerAdapter(delegate);
} else if (handlerInfo.isResponseHandlerMultithreaded()) {
responseHandlerAdapter = new MultithreadedResponseHandlerAdapter(delegate);
} else {
responseHandlerAdapter = new NonthreadedResponseHandlerAdapter(delegate);
}
}
public boolean isInvokeOnMessageReceived() {
return handlerInfo.isResponseHandlerInvokeOnMessageReceived();
}
public boolean isContinueHandler() {
return handlerInfo.isContinueHandler();
}
public void onResponse(IHttpResponse response, AbstractHttpConnection httpConnection) {
if (response.getStatus() == 100) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("got 100-continue response");
}
// 100-Continue expected?
if (isContinueResponseExpected && handlerInfo.isContinueHandler()) {
if (is100ContinueHandled.getAndSet(true)) {
return;
}
// no
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("ignore 100-continue (isContinueResponseExpected=" + isContinueResponseExpected +
", isContinueHandler=" + handlerInfo.isContinueHandler() + ")");
}
return;
}
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("call response handler " + responseHandlerAdapter);
}
responseHandlerAdapter.onResponse(response, httpConnection);
}
private void performResponseHandler(IHttpResponseHandler handler, IHttpResponse response, AbstractHttpConnection httpConnection) {
HttpUtils.addConnectionAttribute(response.getResponseHeader(), httpConnection);
try {
handler.onResponse(response);
} catch (Exception e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("error occured by calling on response " + responseHandlerAdapter + " " + e.toString());
}
httpConnection.destroy(e.toString());
}
}
public void onException(final IOException ioe, final AbstractHttpConnection httpConnection) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("notifing response handler");
}
if (handlerInfo.isUnsynchronized()) {
performResponseHandler(ioe, httpConnection);
return;
}
boolean isMultithreaded = false;
if ((ioe instanceof SocketTimeoutException)) {
if (handlerInfo.isSocketTimeoutHandlerMultithreaded()) {
isMultithreaded = true;
}
} else if (handlerInfo.isResponseExeptionHandlerMultithreaded()) {
isMultithreaded = true;
}
Runnable task = new Runnable() {
public void run() {
performResponseHandler(ioe, httpConnection);
}
};
if (isMultithreaded) {
httpConnection.getExecutor().processMultithreaded(task);
} else {
httpConnection.getExecutor().processNonthreaded(task);
}
}
private void performResponseHandler(IOException ioe, AbstractHttpConnection httpConnection) {
try {
if ((ioe instanceof SocketTimeoutException) && (handlerInfo.isSocketTimeoutHandler())) {
((IHttpSocketTimeoutHandler) exceptionHandler).onException((SocketTimeoutException) ioe);
} else {
exceptionHandler.onException(ioe);
}
} catch (Exception e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("error occured by calling on exception " + responseHandlerAdapter + " " + e.toString());
}
httpConnection.destroy(e.toString());
}
}
private final class UnsynchronizedResponseHandlerAdapter implements IResponseHandlerAdapter {
private final IHttpResponseHandler delegate;
public UnsynchronizedResponseHandlerAdapter(IHttpResponseHandler delegate) {
this.delegate = delegate;
}
public void onResponse(final IHttpResponse response, final AbstractHttpConnection httpConnection) {
performResponseHandler(delegate, response, httpConnection);
}
@Override
public String toString() {
return this.getClass().getName() + "*" + delegate;
}
}
private final class NonthreadedResponseHandlerAdapter implements IResponseHandlerAdapter {
private final IHttpResponseHandler delegate;
public NonthreadedResponseHandlerAdapter(IHttpResponseHandler delegate) {
this.delegate = delegate;
}
public void onResponse(final IHttpResponse response, final AbstractHttpConnection httpConnection) {
Runnable task = new Runnable() {
public void run() {
performResponseHandler(delegate, response, httpConnection);
}
};
httpConnection.getExecutor().processNonthreaded(task);
}
@Override
public String toString() {
return this.getClass().getName() + "*" + delegate;
}
}
private final class MultithreadedResponseHandlerAdapter implements IResponseHandlerAdapter {
private final IHttpResponseHandler delegate;
public MultithreadedResponseHandlerAdapter(IHttpResponseHandler delegate) {
this.delegate = delegate;
}
public void onResponse(final IHttpResponse response, final AbstractHttpConnection httpConnection) {
Runnable task = new Runnable() {
public void run() {
performResponseHandler(delegate, response, httpConnection);
}
};
httpConnection.getExecutor().processMultithreaded(task);
}
@Override
public String toString() {
return this.getClass().getName() + "*" + delegate;
}
}
}
private static final class DoNothingResponseHandler implements IHttpResponseHandler, IUnsynchronized {
public void onResponse(IHttpResponse response) throws IOException {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("ignoring response " + response.getResponseHeader());
}
}
public void onException(IOException ioe) throws IOException {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("ignoring exception " + ioe.toString());
}
}
@Override
public String toString() {
return this.getClass().getName();
}
}
private static final class NullConnection implements IHttpConnection {
private Object attachment;
private final Executor workerpool;
public NullConnection(Executor workerpool) {
this.workerpool = workerpool;
}
public void activateSecuredMode() throws IOException {
}
public void addConnectionHandler(IHttpConnectionHandler connectionHandler) {
}
public long getBodyDataReceiveTimeoutMillis() {
return 0;
}
public INonBlockingConnection getUnderlyingTcpConnection() {
return null;
}
public Executor getWorkerpool() {
return workerpool;
}
public boolean isPersistent() {
return false;
}
public boolean isReceivingSuspended() {
return false;
}
public boolean isSecure() {
return false;
}
public void removeConnectionHandler(IHttpConnectionHandler connectionHandler) {
}
public void resumeReceiving() throws IOException {
}
public void setBodyDataReceiveTimeoutMillis(long bodyDataReceiveTimeoutMillis) {
}
public void setWriteTransferRate(int bytesPerSecond) throws ClosedChannelException, IOException {
}
public void suspendReceiving() throws IOException {
}
public long getConnectionTimeoutMillis() {
return 0;
}
public String getId() {
return "";
}
public long getIdleTimeoutMillis() {
return 0;
}
public InetAddress getLocalAddress() {
return null;
}
public int getLocalPort() {
return 0;
}
public Object getOption(String name) throws IOException {
return null;
}
@SuppressWarnings("unchecked")
public Map getOptions() {
return null;
}
public long getRemainingMillisToConnectionTimeout() {
return 0;
}
public long getRemainingMillisToIdleTimeout() {
return 0;
}
public InetAddress getRemoteAddress() {
return null;
}
public int getRemotePort() {
return 0;
}
public boolean isOpen() {
return false;
}
public boolean isServerSide() {
return false;
}
public void setAttachment(Object obj) {
this.attachment = obj;
}
public Object getAttachment() {
return attachment;
}
public void setConnectionTimeoutMillis(long timeoutMillis) {
}
public void setIdleTimeoutMillis(long timeoutInMillis) {
}
public void setOption(String name, Object value) throws IOException {
}
public void close() throws IOException {
}
public void closeQuitly() {
}
public void destroy() {
}
}
private static final class EmptyBodyDataSink extends BodyDataSinkImplBase {
public EmptyBodyDataSink(IHttpMessageHeader header) throws IOException {
super(header, HttpUtils.newMultimodeExecutor());
}
@Override
protected boolean isNetworkendpoint() {
return false;
}
@Override
void onClose() throws IOException {
}
@Override
void onDestroy(String reason) {
}
@Override
int getPendingWriteDataSize() {
return 0;
}
@Override
int onWriteData(ByteBuffer[] dataToWrite, IWriteCompletionHandler completionHandler) throws IOException {
int written = 0;
for (ByteBuffer buffer : dataToWrite) {
written += buffer.remaining();
}
return written;
}
}
}