All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy