org.xsocket.connection.http.RequestHandlerChain Maven / Gradle / Ivy
The newest version!
// $Id: HandlerChain.java 1312 2007-06-09 12:39:47Z grro $
/*
* Copyright (c) xsocket.org, 2006 - 2007. 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.ConnectException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xsocket.Execution;
import org.xsocket.ILifeCycle;
import org.xsocket.connection.ConnectionUtils;
import org.xsocket.connection.http.HttpUtils.HttpHandlerInfo;
/**
* Implements a handler chain. Each handler of the chain will be called (in the registering order),
* until a handler sends the response. In this case the chain handling will be terminated.
*
* Nested chains is not supported yet
*
*
* @author [email protected]
*/
@Execution(Execution.NONTHREADED)
public final class RequestHandlerChain implements IHttpHandler, IHttpRequestHandler, IHttpRequestTimeoutHandler, IHttpConnectHandler, IHttpDisconnectHandler, ILifeCycle {
private static final Logger LOG = Logger.getLogger(RequestHandlerChain.class.getName());
private final List handlers = new ArrayList();
private final List lifeCycleChain = new ArrayList();
private boolean isOnConnectPathMultithreaded = false;
private final List connectHandlerChain = new ArrayList();
private final ArrayList requestHandlerChain = new ArrayList();
private boolean isOnRequestTimeoutPathMultithreaded = false;
private final List requestTimeoutHandlerChain = new ArrayList();
private boolean isOnDisconnectPathMultithreaded = false;
private final List disconnectHandlerChain = new ArrayList();
/**
* constructor
*
*/
public RequestHandlerChain() {
}
/**
* constructor
*
* @param handlers the initial handlers
*/
public RequestHandlerChain(List handlers) {
for (IHttpHandler hdl : handlers) {
addLast(hdl);
}
}
/**
* add a handler to the end of the chain
*
* @param handler the handler to add
*/
public void addLast(IHttpHandler handler) {
if (handler instanceof RequestHandlerChain) {
throw new RuntimeException("a nested chains are not supported");
}
handlers.add(handler);
computePath();
}
public List getHandlers() {
return Collections.unmodifiableList(handlers);
}
List getHandlerInfo() {
List result = new ArrayList();
for (int i = 0; i < handlers.size(); i++) {
result.add("[" + i + "] " + handlers.get(i).getClass().getSimpleName() + "#" + handlers.get(i).hashCode());
}
return result;
}
private void computePath() {
lifeCycleChain.clear();
connectHandlerChain.clear();
isOnConnectPathMultithreaded = false;
requestHandlerChain.clear();
requestTimeoutHandlerChain.clear();
isOnRequestTimeoutPathMultithreaded = false;
disconnectHandlerChain.clear();
isOnDisconnectPathMultithreaded = false;
for (IHttpHandler handler : handlers) {
HttpHandlerInfo handlerInfo = HttpUtils.getHttpHandlerInfo(handler);
if (handlerInfo.isLifeCycle()) {
lifeCycleChain.add((ILifeCycle) handler);
}
if (handlerInfo.isConnectHandler()) {
connectHandlerChain.add((IHttpConnectHandler) handler);
isOnConnectPathMultithreaded = isOnConnectPathMultithreaded || handlerInfo.isConnectHandlerMultithreaded();
}
if (handlerInfo.isRequestHandler()) {
IHttpRequestHandler hdl = (IHttpRequestHandler) handler;
if (handlerInfo.isRequestHandlerMultithreaded()) {
hdl = new MultithreadedMessageHandler(hdl);
}
if (handlerInfo.isRequestHandlerInvokeOnMessageReceived()) {
hdl = new OnMessageHandler(hdl);
}
requestHandlerChain.add(hdl);
}
if (handlerInfo.isRequestTimeoutHandler()) {
requestTimeoutHandlerChain.add((IHttpRequestTimeoutHandler) handler);
isOnRequestTimeoutPathMultithreaded = isOnRequestTimeoutPathMultithreaded || handlerInfo.isRequestTimeoutHandlerMultithreaded();
}
if (handlerInfo.isDisconnectHandler()) {
disconnectHandlerChain.add((IHttpDisconnectHandler) handler);
isOnDisconnectPathMultithreaded = isOnDisconnectPathMultithreaded || handlerInfo.isDisconnectHandlerMultithreaded();
}
}
}
public void onInit() {
for (ILifeCycle lifeCycle : lifeCycleChain) {
lifeCycle.onInit();
}
}
public void onDestroy() throws IOException {
for (ILifeCycle lifeCycle : lifeCycleChain) {
lifeCycle.onDestroy();
}
}
private void performMultihtreaded(IHttpConnection httpConnection, Runnable task) {
((AbstractHttpConnection) httpConnection).processMultiThreaded(task);
}
private void performNontreaded(IHttpConnection httpConnection, Runnable task) {
((AbstractHttpConnection) httpConnection).processNonThreaded(task);
}
public boolean onConnect(IHttpConnection httpConnection) throws IOException {
if (connectHandlerChain.isEmpty()) {
if (LOG.isLoggable(Level.FINER)) {
LOG.finer("no connect handler set. ignore callback");
}
return false;
}
if (isOnConnectPathMultithreaded) {
performMultihtreaded(httpConnection, new OnConnectCaller(httpConnection));
} else {
performNontreaded(httpConnection, new OnConnectCaller(httpConnection));
}
return true;
}
private boolean callOnConnectCallback(IHttpConnection connection) throws IOException {
for (IHttpConnectHandler connectHandler : connectHandlerChain) {
boolean result = connectHandler.onConnect(connection);
if (result == true) {
return true;
}
}
return false;
}
@InvokeOn(InvokeOn.HEADER_RECEIVED)
public void onRequest(IHttpExchange exchange) throws IOException {
if (requestHandlerChain.isEmpty()) {
if (LOG.isLoggable(Level.FINER)) {
LOG.finer("no request handler set. ignore callback");
}
return;
}
callOnRequestCallback(exchange);
}
@SuppressWarnings("unchecked")
private void callOnRequestCallback(final IHttpExchange exchange) throws IOException {
IHttpResponseHandler respHandler = new IHttpResponseHandler() {
@InvokeOn(InvokeOn.HEADER_RECEIVED)
@Execution(Execution.NONTHREADED)
public void onResponse(IHttpResponse response) throws IOException {
exchange.send(response);
}
@Execution(Execution.NONTHREADED)
public void onException(IOException ioe) {
exchange.sendError(500);
}
};
ChainExchange chainExchange = new ChainExchange(exchange, exchange.getRequest(), (List) requestHandlerChain.clone(), respHandler);
chainExchange.handle();
}
public boolean onRequestTimeout(IHttpConnection httpConnection)throws IOException {
if (requestTimeoutHandlerChain.isEmpty()) {
if (LOG.isLoggable(Level.FINER)) {
LOG.finer("no request timeout handler set. ignore callback");
}
return false;
}
if (isOnRequestTimeoutPathMultithreaded) {
performMultihtreaded(httpConnection, new OnRequestTimeoutCaller(httpConnection));
} else {
performNontreaded(httpConnection, new OnRequestTimeoutCaller(httpConnection));
}
return true;
}
private boolean callOnRequestTimeoutCallback(IHttpConnection connection) throws IOException {
for (IHttpRequestTimeoutHandler requestTimeoutHandler : requestTimeoutHandlerChain) {
boolean result = requestTimeoutHandler.onRequestTimeout(connection);
if (result == true) {
return true;
}
}
return false;
}
public boolean onDisconnect(IHttpConnection httpConnection) throws IOException {
if (disconnectHandlerChain.isEmpty()) {
if (LOG.isLoggable(Level.FINER)) {
LOG.finer("no disconnect handler set. ignore callback");
}
return false;
}
if (isOnDisconnectPathMultithreaded) {
performMultihtreaded(httpConnection, new OnDisconnectCaller(httpConnection));
} else {
performNontreaded(httpConnection, new OnDisconnectCaller(httpConnection));
}
return true;
}
private boolean callOnDisconnectCallback(IHttpConnection connection) throws IOException {
for (IHttpDisconnectHandler disconnectHandler : disconnectHandlerChain) {
boolean result = disconnectHandler.onDisconnect(connection);
if (result == true) {
return true;
}
}
return false;
}
private final class OnRequestTimeoutCaller implements Runnable {
private IHttpConnection httpConnection = null;
public OnRequestTimeoutCaller(IHttpConnection httpConnection) {
this.httpConnection = httpConnection;
}
public void run() {
try {
callOnRequestTimeoutCallback(httpConnection);
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Error occured by calling onRequestTimeout callback " + ioe.toString());
}
}
}
}
private final class OnConnectCaller implements Runnable {
private IHttpConnection httpConnection = null;
public OnConnectCaller(IHttpConnection httpConnection) {
this.httpConnection = httpConnection;
}
public void run() {
try {
callOnConnectCallback(httpConnection);
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Error occured by calling onConnect callback " + ioe.toString());
}
}
}
}
private final class OnDisconnectCaller implements Runnable {
private IHttpConnection httpConnection = null;
public OnDisconnectCaller(IHttpConnection httpConnection) {
this.httpConnection = httpConnection;
}
public void run() {
try {
callOnDisconnectCallback(httpConnection);
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Error occured by calling onDisconnect callback " + ioe.toString());
}
}
}
}
private static final class ChainExchange implements IHttpExchange {
private boolean isResponseCommitted = false;
private IHttpExchange exchange = null;
private List requestHandlerChain = null;
private String targetURL = null;
private IHttpRequest request = null;
private IHttpResponseHandler responseHandler = null;
private HttpHandlerInfo responseHandlerInfo = null;
public ChainExchange(IHttpExchange exchange, IHttpRequest request, List requestHandlerChain, IHttpResponseHandler responseHandler) {
this.exchange = exchange;
this.request = request;
this.requestHandlerChain = requestHandlerChain;
this.responseHandler = responseHandler;
targetURL = request.getTargetURL().toString();
if (targetURL.indexOf("?") != -1) {
targetURL = targetURL.substring(0, targetURL.indexOf("?"));
}
if (responseHandler != null) {
responseHandlerInfo = HttpUtils.getHttpHandlerInfo(responseHandler);
}
}
void handle() throws IOException {
// is chain ends is reached -> calling external
if (requestHandlerChain.isEmpty()) {
IHttpResponseHandler respHdl = new IHttpResponseHandler() {
@Execution(Execution.NONTHREADED)
public void onResponse(IHttpResponse response) throws IOException {
send(response);
}
public void onException(IOException ioe) {
sendError(500);
}
};
exchange.forward(request, respHdl);
// get next handler of chain
} else {
final IHttpRequestHandler requestHandler = requestHandlerChain.remove(0);
if (getRequest().hasBody()) {
final boolean isContentTypeFormUrlencoded = HttpUtils.isContentTypeFormUrlencoded(getRequest());
if (!getRequest().getNonBlockingBody().isComplete() && ((HttpUtils.getHttpHandlerInfo(requestHandler).isRequestHandlerInvokeOnMessageReceived() || isContentTypeFormUrlencoded))) {
IBodyCompleteListener cl = new IBodyCompleteListener() {
@Execution(Execution.NONTHREADED)
public void onComplete() throws IOException {
if (isContentTypeFormUrlencoded) {
ChainExchange.this.request = HttpUtils.newFormEncodedRequestWrapper(request);
}
requestHandler.onRequest(ChainExchange.this);
}
};
getRequest().getNonBlockingBody().addCompleteListener(cl);
return;
}
}
requestHandler.onRequest(this);
}
}
public IHttpConnection getConnection() {
return exchange.getConnection();
}
public IHttpRequest getRequest() {
return request;
}
public void destroy() {
exchange.destroy();
}
public BodyDataSink send(IHttpResponseHeader header) throws IOException, IllegalStateException {
if (responseHandler == null) {
return new BodyDataSink((AbstractHttpConnection) exchange.getConnection(), header, null);
}
if (header.getContentLength() != -1) {
header.removeHeader("Content-Length");
}
if ((header.getTransferEncoding() == null)) {
header.setHeader("Transfer-Encoding", "chunked");
}
final NonBlockingBodyDataSource bodyDataSource = new NonBlockingBodyDataSource(header.getCharacterEncoding());
IBodyWriter bodyWriter = new IBodyWriter() {
public void flush(ByteBuffer[] bodyData) throws IOException {
bodyDataSource.append(bodyData);
}
public void close() throws IOException {
bodyDataSource.setComplete(true);
}
public void destroy() {
bodyDataSource.destroy();
}
};
BodyDataSink bodyDataSink = new BodyDataSink((AbstractHttpConnection) exchange.getConnection(), header, bodyWriter);
send(new HttpResponse(header, bodyDataSource));
return bodyDataSink;
}
public BodyDataSink send(IHttpResponseHeader header, int contentLength) throws IOException, IllegalStateException {
if (responseHandler == null) {
return new BodyDataSink((AbstractHttpConnection) exchange.getConnection(), header, null);
}
if ((header.getTransferEncoding() != null) && (header.getTransferEncoding().equalsIgnoreCase("chunked"))) {
header.removeHeader("Transfer-Encoding");
}
if (header.getContentLength() == -1) {
header.setContentLength(contentLength);
}
final NonBlockingBodyDataSource bodyDataSource = new NonBlockingBodyDataSource(header.getCharacterEncoding());
IBodyWriter bodyWriter = new IBodyWriter() {
public void flush(ByteBuffer[] bodyData) throws IOException {
bodyDataSource.append(bodyData);
}
public void close() throws IOException {
bodyDataSource.setComplete(true);
}
public void destroy() {
bodyDataSource.destroy();
}
};
BodyDataSink bodyDataSink = new BodyDataSink((AbstractHttpConnection) exchange.getConnection(), header, bodyWriter);
send(new HttpResponse(header, bodyDataSource));
return bodyDataSink;
}
public void send(final IHttpResponse response) throws IOException, IllegalStateException {
if (isResponseCommitted) {
throw new IllegalStateException("response is already committed");
}
isResponseCommitted = true;
if (responseHandler == null) {
LOG.warning("response will not been send, because no response handler is assigned");
return;
}
if (responseHandlerInfo.isResponseHandlerInvokeOnMessageReceived()) {
IBodyCompleteListener copmleteListener = new IBodyCompleteListener() {
@Execution(Execution.NONTHREADED)
public void onComplete() throws IOException {
performOnResponse(response);
}
};
response.getNonBlockingBody().addCompleteListener(copmleteListener);
} else {
performOnResponse(response);
}
}
private void performOnResponse(final IHttpResponse response) throws IOException {
if (responseHandlerInfo.isResponseHandlerMultithreaded()) {
Runnable task = new Runnable() {
public void run() {
try {
responseHandler.onResponse(response);
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("error occured by performing on response on " + responseHandler);
}
}
}
};
exchange.getConnection().getWorkerpool().execute(task);
} else {
responseHandler.onResponse(response);
}
}
public void sendError(int errorCode) throws IllegalStateException {
if (isResponseCommitted) {
throw new IllegalStateException("response is already committed");
}
isResponseCommitted = true;
}
public void sendError(int errorCode, String msg) throws IllegalStateException {
if (isResponseCommitted) {
throw new IllegalStateException("response is already committed");
}
isResponseCommitted = true;
}
public BodyDataSink forward(IHttpRequestHeader requestHeader, IHttpResponseHandler responseHandler) throws IOException, ConnectException, IllegalStateException {
if (requestHeader.getContentLength() != -1) {
requestHeader.removeHeader("Content-Length");
}
if (requestHeader.getMethod().equalsIgnoreCase("GET") || requestHeader.getMethod().equalsIgnoreCase("HEAD")) {
throw new IOException(requestHeader.getMethod() + " is a bodyless request");
}
if ((requestHeader.getTransferEncoding() == null)) {
requestHeader.setHeader("Transfer-Encoding", "chunked");
}
// internal forward
if (isInternalForward()) {
final NonBlockingBodyDataSource bodyDataSource = new NonBlockingBodyDataSource(requestHeader.getCharacterEncoding());
HttpRequest request = new HttpRequest(requestHeader, bodyDataSource);
IBodyWriter bodyWriter = new IBodyWriter() {
public void flush(ByteBuffer[] bodyData) throws IOException {
bodyDataSource.append(bodyData);
}
public void close() throws IOException {
bodyDataSource.setComplete(true);
}
public void destroy() {
bodyDataSource.destroy();
}
};
BodyDataSink bodyDataSink = new BodyDataSink((AbstractHttpConnection) exchange.getConnection(), requestHeader, bodyWriter);
ChainExchange chainExchange = new ChainExchange(exchange, request, requestHandlerChain, responseHandler);
chainExchange.handle();
return bodyDataSink;
// external forward
} else {
return exchange.forward(requestHeader, responseHandler);
}
}
public BodyDataSink forward(IHttpRequestHeader requestHeader, int contentLength, IHttpResponseHandler responseHandler) throws IOException, ConnectException, IllegalStateException {
if ((requestHeader.getTransferEncoding() != null) && (requestHeader.getTransferEncoding().equalsIgnoreCase("chunked"))) {
requestHeader.removeHeader("Transfer-Encoding");
}
if (requestHeader.getMethod().equalsIgnoreCase("GET") || requestHeader.getMethod().equalsIgnoreCase("HEAD")) {
throw new IOException(requestHeader.getMethod() + " is a bodyless request");
}
if (requestHeader.getContentLength() == -1) {
requestHeader.setContentLength(contentLength);
}
// internal forward
if (isInternalForward()) {
final NonBlockingBodyDataSource bodyDataSource = new NonBlockingBodyDataSource(requestHeader.getCharacterEncoding());
HttpRequest request = new HttpRequest(requestHeader, bodyDataSource);
IBodyWriter bodyWriter = new IBodyWriter() {
public void flush(ByteBuffer[] bodyData) throws IOException {
bodyDataSource.append(bodyData);
}
public void close() throws IOException {
bodyDataSource.setComplete(true);
}
public void destroy() {
bodyDataSource.destroy();
}
};
BodyDataSink bodyDataSink = new BodyDataSink((AbstractHttpConnection) exchange.getConnection(), requestHeader, bodyWriter);
ChainExchange chainExchange = new ChainExchange(exchange, request, requestHandlerChain, responseHandler);
chainExchange.handle();
return bodyDataSink;
// external forward
} else {
return exchange.forward(requestHeader, responseHandler);
}
}
public void forward(IHttpRequest request, IHttpResponseHandler responseHandler) throws IOException, ConnectException, IllegalStateException {
if (isInternalForward()) {
ChainExchange chainExchange = new ChainExchange(exchange, request, requestHandlerChain, responseHandler);
chainExchange.handle();
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("(external) forwarding to " + request.getTargetURL());
}
IHttpResponseHandler respHdl = new IHttpResponseHandler() {
@Execution(Execution.NONTHREADED)
public void onResponse(IHttpResponse response) throws IOException {
send(response);
}
public void onException(IOException ioe) {
sendError(500);
}
};
exchange.forward(request, respHdl);
}
}
boolean isResponseCommitted() {
return isResponseCommitted;
}
private boolean isInternalForward() {
String url = request.getTargetURL().toString();
if (url.indexOf("?") != -1) {
url = url.substring(0, url.indexOf("?"));
}
if (url.equals(targetURL)) {
return true;
} else {
return false;
}
}
}
private static final class OnMessageHandler implements IHttpRequestHandler {
private IHttpRequestHandler delegee = null;
public OnMessageHandler(IHttpRequestHandler delegee) {
this.delegee = delegee;
}
public void onRequest(final IHttpExchange exchange) throws IOException {
if (exchange.getRequest().hasBody()) {
NonBlockingBodyDataSource bodyDataSource = exchange.getRequest().getNonBlockingBody();
if (!bodyDataSource.isComplete()) {
IBodyCompleteListener cl = new IBodyCompleteListener() {
public void onComplete() throws IOException {
delegee.onRequest(exchange);
}
};
bodyDataSource.addCompleteListener(cl);
return;
}
}
delegee.onRequest(exchange);
}
}
private static final class MultithreadedMessageHandler implements IHttpRequestHandler {
private IHttpRequestHandler delegee = null;
public MultithreadedMessageHandler(IHttpRequestHandler delegee) {
this.delegee = delegee;
}
public void onRequest(final IHttpExchange exchange) throws IOException {
Runnable task = new Runnable() {
public void run() {
try {
delegee.onRequest(exchange);
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("error occured by performing onRequest call back " + ioe.toString());
}
}
}
};
((AbstractHttpConnection) exchange.getConnection()).processMultiThreaded(task);
}
}
}