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

org.xsocket.connection.http.server.HttpServerConnection Maven / Gradle / Ivy

There is a newer version: 2.0-beta-1
Show newest version
/*
 *  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