org.xsocket.connection.http.server.HttpServerConnection 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.server;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.nio.BufferUnderflowException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xsocket.DataConverter;
import org.xsocket.Execution;
import org.xsocket.ILifeCycle;
import org.xsocket.connection.INonBlockingConnection;
import org.xsocket.connection.http.AbstractHttpConnection;
import org.xsocket.connection.http.BodyDataSink;
import org.xsocket.connection.http.IBodyHandler;
import org.xsocket.connection.http.IHttpConnectHandler;
import org.xsocket.connection.http.IHttpConnection;
import org.xsocket.connection.http.IHttpConnectionTimeoutHandler;
import org.xsocket.connection.http.IHttpDisconnectHandler;
import org.xsocket.connection.http.IMessage;
import org.xsocket.connection.http.InvokeOn;
import org.xsocket.connection.http.NonBlockingBodyDataSource;
import org.xsocket.connection.http.Request;
import org.xsocket.connection.http.RequestHeader;
import org.xsocket.connection.http.Response;
import org.xsocket.connection.http.ResponseHeader;
import org.xsocket.connection.http.IHttpHandler;
import org.xsocket.connection.http.server.ServerUtils.HttpHandlerInfo;
import org.xsocket.connection.http.server.ServerUtils.ServerHandlerInfo;
import org.xsocket.connection.spi.DefaultIoProvider;
/**
* Represents the server side endpoint implementation of a http connection. Typically a
* HttpServerConnection will be created by the {@link HttpProtocolAdapter}. A HttpServerConnection
* can also be created manually by passing over a {@link INonBlockingConnection} and the
* server handler.
*
* @author [email protected]
*/
public class HttpServerConnection extends AbstractHttpConnection implements IHttpConnection, IHttpServerEndpoint {
private static final Logger LOG = Logger.getLogger(HttpServerConnection.class.getName());
public static final boolean DEFAULT_REMOVE_REQUEST_CONNECTION_HEADER = false;
private static final boolean DEFAULT_CLOSE_ON_SENDING_ERROR = true;
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z");
private Object handler = null;
private ServerHandlerAdapter serverHandlerAdapter = null;
private ConnectHandlerAdapter connectHandlerAdapter = null;
private DisconnectHandlerAdapter disconnectHandlerAdapter = null;
private ConnectionTimeoutHandlerAdapter connectionTimeoutHandlerAdapter = null;
private boolean isCloseOnSendingError = DEFAULT_CLOSE_ON_SENDING_ERROR;
private boolean isCloseAfterResponse = false;
private final List requestQueue = new ArrayList();
private int requestQueueVersion = 0;
// timeout support
private long lastTimeReceived = System.currentTimeMillis();
private int requestTimeoutMillis = Integer.MAX_VALUE;
/**
* constructor
*
* @param connection the underlying tcp connection
* @param serverHandler the server handler (supported: {@link IRequestHandler}, {@link IHttpConnectHandler}, {@link IHttpDisconnectHandler}, {@link IHttpConnectionTimeoutHandler}, {@link ILifeCycle})
* @throws IOException if an exception occurs
*/
public HttpServerConnection(INonBlockingConnection connection, IHttpHandler serverHandler) throws IOException {
this(connection, ServerUtils.getServerHandlerInfo(serverHandler), ServerUtils.getHttpHandlerInfo(serverHandler), serverHandler, Integer.MAX_VALUE, false);
}
/**
* constructor
*
* @param connection the underlying tcp connection
* @param serverHandlerInfo the server handler info
* @param httpHandlerInfo the http handler info
* @param serverHandler the server handler (supported: {@link IRequestHandler}, {@link IHttpConnectHandler}, {@link IHttpDisconnectHandler}, {@link IHttpConnectionTimeoutHandler}, {@link ILifeCycle})
* @param requestTimeoutMillis the request timout
* @param isCloseOnSendingError true, if the connetion should be closed on error
* @throws IOException if an exception occurs
*/
HttpServerConnection(INonBlockingConnection connection, ServerHandlerInfo serverHandlerInfo, HttpHandlerInfo httpHandlerInfo, IHttpHandler serverHandler, int requestTimeoutMillis, boolean isCloseOnSendingError) throws IOException {
super(connection);
this.requestTimeoutMillis = requestTimeoutMillis;
this.isCloseOnSendingError = isCloseOnSendingError;
this.handler = serverHandler;
if (httpHandlerInfo.isConnectHandler()) {
connectHandlerAdapter = new ConnectHandlerAdapter(httpHandlerInfo.isConnectHandlerMultithreaded());
}
if (httpHandlerInfo.isDisconnectHandler()) {
disconnectHandlerAdapter = new DisconnectHandlerAdapter(httpHandlerInfo.isDisconnectHandlerMultithreaded());
}
if (httpHandlerInfo.isConnectionTimeoutHandler()) {
connectionTimeoutHandlerAdapter = new ConnectionTimeoutHandlerAdapter(httpHandlerInfo.isConnectionTimeoutHandlerMultithreaded());
}
serverHandlerAdapter = new ServerHandlerAdapter(serverHandlerInfo.getInvokationMode(), serverHandlerInfo.getOnRequestExecutionMode());
onConnect();
}
void checkResponseTimeout(long currentTimeMillis) throws SocketTimeoutException {
if (currentTimeMillis > (lastTimeReceived + requestTimeoutMillis)) {
throw new SocketTimeoutException("request timeout " + DataConverter.toFormatedDuration(requestTimeoutMillis) + " received");
}
}
/**
* set is if the connection will closed, if an error message is sent
*
* @param isCloseOnSendingError if the connection will closed, if an error message is sent
*/
public void setCloseOnSendingError(boolean isCloseOnSendingError) {
this.isCloseOnSendingError = isCloseOnSendingError;
}
/**
* returns if the connection will closed, if an error message will be sent
* @return true, if the connection will closed by sending an error message
*/
public boolean isCloseOnSendingError() {
return isCloseOnSendingError;
}
/**
* {@inheritDoc}
*/
@Override
protected IMessage readMessageHeader( INonBlockingConnection tcpConnection, int maxHeaderLength) throws BufferUnderflowException, IOException {
RequestHeader requestHeader = RequestHeader.readFrom(tcpConnection, maxHeaderLength);
return new Request(requestHeader);
}
/**
* {@inheritDoc}
*/
@Override
protected void onMessageHeaderReceived(IMessage message) throws IOException {
lastTimeReceived = System.currentTimeMillis();
Request request = (Request) message;
handleConnectionHeaders(request);
synchronized (requestQueue) {
requestQueue.add(request);
requestQueueVersion++;
}
serverHandlerAdapter.process();
}
/**
* {@inheritDoc}
*/
public final Request readRequest() {
Request request = null;
synchronized (requestQueue) {
if (!requestQueue.isEmpty()) {
request = requestQueue.remove(0);
requestQueueVersion++;
}
}
return request;
}
private void handleConnectionHeaders(Request request) {
String keepAliveHeader = request.getKeepAlive();
if (keepAliveHeader != null) {
String[] tokens = keepAliveHeader.split(",");
for (String token : tokens) {
handleKeepAlive(token);
}
}
String connectionHeader = request.getConnection();
if (connectionHeader != null) {
String[] values = connectionHeader.split(",");
for (String value : values) {
value = value.trim();
if (value.equalsIgnoreCase("close")) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("received connection: closed header. destroying connection");
}
isCloseAfterResponse = true;
}
}
}
}
private void handleKeepAlive(String option) {
try {
if (option.toUpperCase().startsWith("TIMEOUT=")) {
int timeoutSec = Integer.parseInt(option.substring("TIMEOUT=".length(), option.length()));
setIdleTimeoutSec(timeoutSec);
} else {
Integer seconds = Integer.parseInt(option);
setIdleTimeoutSec(seconds);
}
} catch (Exception e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("error occured by handling keep alive option " + option + " " + e.toString());
}
}
}
/**
* {@inheritDoc}
*/
public final BodyDataSink send(ResponseHeader header) throws IOException {
if (header.getContentLength() < 0) {
if (header.getTransferEncoding() == null) {
header.setTransferEncoding("chunked");
}
}
return sendHeader(header);
}
/**
* {@inheritDoc}
*/
public final BodyDataSink send(ResponseHeader header, int contentLength) throws IOException {
header.setContentLength(contentLength);
return sendHeader(header);
}
private BodyDataSink sendHeader(ResponseHeader header) throws IOException {
enhanceHeader(header);
BodyDataSink bodyDataSink = writeMessageHeader(header);
if (isCloseAfterResponse) {
setCloseHttpConnectionAfterWritten(bodyDataSink, true);
}
return bodyDataSink;
}
/**
* {@inheritDoc}
*/
public final void send(Response response) throws IOException {
ResponseHeader responseHeader = response.getResponseHeader();
// body less request?
if (response.getNonBlockingBody() == null) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("sending (bodyless): " + response);
}
sendBodyless(response.getResponseHeader());
// no, request has body
} else {
if (response.getNonBlockingBody().getDataHandler() != null) {
throw new IOException("a body handler is already assigned to the message body. sending such messages is not supported (remove data handler)");
}
LOG.fine("sending (with body): " + response);
BodyDataSink bodyDataSink = null;
if (response.getContentLength() >= 0) {
bodyDataSink = send(responseHeader, response.getContentLength());
} else {
bodyDataSink = send(responseHeader);
}
bodyDataSink.setFlushmode(FlushMode.ASYNC);
bodyDataSink.setAutoflush(false);
if (response.getNonBlockingBody().isComplete()) {
NonBlockingBodyDataSource bodyDataSource = response.getNonBlockingBody();
bodyDataSink.write(bodyDataSource.readAvailableByteBuffer());
bodyDataSink.close();
} else {
final BodyDataSink bds = bodyDataSink;
IBodyHandler bodyForwarder = new IBodyHandler() {
@Execution(Execution.Mode.NONTHREADED)
public boolean onData(NonBlockingBodyDataSource bodyDataSource) throws IOException {
bds.write(bodyDataSource.readAvailableByteBuffer());
if (bodyDataSource.isComplete()) {
bds.close();
}
return true;
}
};
response.getNonBlockingBody().setDataHandler(bodyForwarder);
}
}
}
private void sendBodyless(ResponseHeader responseHeader) throws IOException {
enhanceHeader(responseHeader);
assert (getUnderlyingConnection().getFlushmode() == FlushMode.ASYNC);
responseHeader.writeTo(getUnderlyingConnection());
getUnderlyingConnection().flush();
}
/**
* {@inheritDoc}
*/
public final void sendError(int errorCode) throws IOException {
sendError(errorCode, errorCode + " error occured");
}
/**
* {@inheritDoc}
*/
public final void sendError(int errorCode, String msg) throws IOException {
if (isCloseOnSendingError) {
isCloseAfterResponse = true;
}
String txt = "" +
"" +
"Error " + errorCode + " " +
"" +
"" +
"ERROR " + errorCode + "
" +
"" + msg + "" +
"
" + DATE_FORMAT.format(new Date()) + " xSocket (" + ServerUtils.getVersionInfo() + ")" +
"" +
"";
byte[] data = txt.getBytes(DEFAULT_ENCODING);
ResponseHeader response = new ResponseHeader(errorCode, "text/html; charset=" + DEFAULT_ENCODING);
if (isCloseAfterResponse) {
response.addHeader("connection", "close");
}
BodyDataSink body = send(response, data.length);
body.setFlushmode(FlushMode.ASYNC);
body.write(data);
body.close();
if (isCloseAfterResponse) {
close();
}
}
private void enhanceHeader(ResponseHeader header) {
String contentType = header.getContentType();
if (contentType != null) {
String encoding = parseEncoding(contentType);
if (encoding == null) {
header.setHeader("Content-Type", contentType + "; charset=" + header.getCharacterEncoding());
}
}
String server = header.getServer();
if (server == null) {
header.setServer(ServerUtils.getComponentInfo());
}
}
private void onConnect() throws IOException {
if (connectHandlerAdapter != null) {
connectHandlerAdapter.callOnConnect();
}
}
/**
* {@inheritDoc}
*/
@Override
protected void onDisconnect() throws IOException {
if (disconnectHandlerAdapter != null) {
disconnectHandlerAdapter.callOnDisconnect();
}
}
/**
* {@inheritDoc}
*/
/* @Override
protected void onIdleTimeout() throws IOException {
if (idleTimeoutHandlerAdapter != null) {
idleTimeoutHandlerAdapter.callOnIdleTimeout();
} else {
super.onIdleTimeout();
}
}*/
/**
* {@inheritDoc}
*/
@Override
protected void onConnectionTimeout() throws IOException {
if (connectionTimeoutHandlerAdapter != null) {
connectionTimeoutHandlerAdapter.callOnConnectionTimeout();
} else {
super.onConnectionTimeout();
}
}
final class ConnectHandlerAdapter implements Runnable {
private boolean isMultithreaded = true;
public ConnectHandlerAdapter(boolean isMultithreaded) {
this.isMultithreaded = isMultithreaded;
}
private void callOnConnect() {
if (isMultithreaded) {
processMultiThreaded(this);
} else {
processNonThreaded(this);
}
}
public void run() {
try {
((IHttpConnectHandler) handler).onConnect(HttpServerConnection.this);
} catch (Exception e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by calling on connect " + handler + " " + e.toString());
}
}
}
}
final class DisconnectHandlerAdapter implements Runnable {
private boolean isMultithreaded = true;
public DisconnectHandlerAdapter(boolean isMultithreaded) {
this.isMultithreaded = isMultithreaded;
}
private void callOnDisconnect() {
if (isMultithreaded) {
processMultiThreaded(this);
} else {
processNonThreaded(this);
}
}
public void run() {
try {
((IHttpDisconnectHandler) handler).onDisconnect(HttpServerConnection.this);
} catch (Exception e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by calling on connect " + handler + " " + e.toString());
}
}
}
}
/* final class IdleTimeoutHandlerAdapter implements Runnable {
private boolean isMultithreaded = true;
public IdleTimeoutHandlerAdapter(boolean isMultithreaded) {
this.isMultithreaded = isMultithreaded;
}
private void callOnIdleTimeout() {
if (isMultithreaded) {
processMultiThreaded(this);
} else {
processNonThreaded(this);
}
}
public void run() {
try {
((IHttpTimeoutHandler) handler).onIdleTimeout(HttpServerConnection.this);
} catch (Exception e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by calling on idle timeout " + handler + " " + e.toString());
}
}
}
}*/
final class ConnectionTimeoutHandlerAdapter implements Runnable {
private boolean isMultithreaded = true;
public ConnectionTimeoutHandlerAdapter(boolean isMultithreaded) {
this.isMultithreaded = isMultithreaded;
}
private void callOnConnectionTimeout() {
if (isMultithreaded) {
processMultiThreaded(this);
} else {
processNonThreaded(this);
}
}
public void run() {
try {
((IHttpConnectionTimeoutHandler) handler).onConnectionTimeout(HttpServerConnection.this);
} catch (Exception e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by calling on connection timeout " + handler + " " + e.toString());
}
}
}
}
final class ServerHandlerAdapter implements Runnable, ICompleteListener {
private boolean invokeOnHeader = true;
private boolean executeMultithreaded = true;
private ServerHandlerAdapter(InvokeOn.Mode invokeMode, Execution.Mode executionMode) {
invokeOnHeader = (invokeMode == InvokeOn.Mode.HEADER_RECEIVED);
executeMultithreaded = (executionMode == Execution.Mode.MULTITHREADED);
}
public void process() {
assert (DefaultIoProvider.isDispatcherThread());
// call handlers call back method if is HEADER_RECEIVED triggered
if (invokeOnHeader) {
callOnRequest();
} else {
Request request = null;
synchronized (requestQueue) {
request = requestQueue.get(0);
}
if (request != null) {
if (request.hasBody()) {
addListener(request.getNonBlockingBody(), this);
} else {
onComplete();
}
}
}
}
public final void onComplete() {
assert (DefaultIoProvider.isDispatcherThread());
// call handlers call back method if is HEADER_MESSAGE triggered
if (!invokeOnHeader) {
callOnRequest();
}
}
private void callOnRequest() {
if (executeMultithreaded) {
processMultiThreaded(this);
} else {
processNonThreaded(this);
}
}
public void run() {
boolean isRequestAvailable = false;
int currentRequestQueueVersion = 0;
do {
synchronized (requestQueue) {
isRequestAvailable = !requestQueue.isEmpty();
currentRequestQueueVersion = requestQueueVersion;
}
if (isRequestAvailable) {
try {
((IRequestHandler) handler).onRequest(HttpServerConnection.this);
} catch (Exception e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by calling on request " + handler + " " + e.toString());
}
}
} else {
break;
}
// if handler has not read the request break the loop to void infinite looping
synchronized (requestQueue) {
if (currentRequestQueueVersion == requestQueueVersion) {
break;
}
}
} while (isRequestAvailable);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy