Taking you nowhere you think you should be ...

Mango Web Server

Mango is an extendable open source HTTP web server written in Java.

The project originated from wanting to port an old hosting control panel I wrote in PHP to Java, however this quickly turned in to a seperate web server (I am not quite sure how) and thought it would be a good exercise to develop the software as its own project.

Features

Download

Mango 1.0mango-1.0.zipDownload
Mango 1.0 Sourcemango-1.0-src.zipDownload

Javadocs

Please see the online Javadocs for the server API and source code.

Running the server

The server can be started or stopped using the start/stop scripts within the bin directory, or manually with the following.

# start
java -jar lib/mango.jar

# stop
java -jar lib/mango.jar stop

Configuration

Server configuration is maintained within the file ./etc/conf.xml

All values within the config node are optional. More configuration options will be described throughout this document.

<server>
	<config>
		<!-- The port where the server should listen for requests. -->
		<port>80</port>

		<!-- The size of the worker thread pool for concurrent requests. -->
		<workers>10</workers>

		<!-- The document root. If not absolute, relative to the current working directory. -->
		<docroot>www</docroot>

		<!-- The port where the server should listen for local control signal requests. -->
		<controller-port>8088</controller-port>

		<!-- The name of the default request handler, configured within the handlers section. -->
		<default-handler>MyDefaultHandler</default-handler>

		<!-- The name of the error request handler, configured within the handlers section. -->
		<error-handler>MyErrorHandler</error-handler>

		<!-- The class name of the session manager. -->
		<session-manager>uk.co.bedican.mango.DefaultSessionManager</session-manager>

		<!-- The request log filename. If not absolute, relative to the logs directory under the current working directory. -->
		<request-log>request_log</request-log>

		<!-- The error log filename. If not absolute, relative to the logs directory under the current working directory. -->
		<error-log>error_log</error-log>

		<!-- Used by ErrorRequestHandler, whether to display a stack trace or a 500 status error page. -->
		<print-stack-trace>false</print-stack-trace>

		<!-- If set true, a server signature is sent within the Server HTTP header. -->
		<display-server-signature>true</display-server-signature>
	</config>

	...

</server>

Directory indexes

Directory indexes should be listed in search order and are configured as shown below.

<server>
	<directory-index>
		<index>index.html</index>
		<index>index.htm</index>
		<index>index.vm</index>
		<index>index.ftl</index>
	</directory-index>
</server>

Content types

The available content types and corresponding file extensions are configured as shown below.

<server>
	<mime-types>
		<mime-type>
			<type>text/plain</type>
			<extension>txt</extension>
		</mime-type>
		<mime-type>
			<type>text/html</type>
			<extensions>
				<extension>html</extension>
				<extension>htm</extension>
				<extension>vm</extension>
				<extension>ftl</extension>
			</extensions>
		</mime-type>
	</mime-types>
</server>

Request handlers

As the name suggests, a request handler is the object handling the request to produce a response. By default, requests are handled by the catch-all class DefaultRequestHandler.

The DefaultRequestHandler provides the functionality to process the requested file or the appropriate directory index.

Support for Velocity (version 1.6.2) and FreeMarker (version 2.3.16) template engines is provided with VelocityRequestHandler, mapped to .vm template files, and FreeMarkerRequestHandler, mapped to .ftl template files, both subclasses of DefaultRequestHandler. The standard objects supplied to the templates are request, response, session (or null if not present) and context.

Configuration

Each defined handler with unique name represents an instance of the specified class. Different request handlers can be configured against specific url patterns and configurations. An url pattern is specified as a requested path where * may match any character excluding a forward slash and ** may match any character including a forward slash.

The default and error request handlers can be configured as shown above with the configuration options "/server/config/default-handler" and "/server/config/error-handler" respectively. The name specified should reference the name given to one of the defined request handlers.

Configuration values can be obtained within a RequestHandler with the method getInitParameter(String name) which is defined within the base class AbstractRequestHandler.

A sample request handler configuration is shown below.

<server>
	<handlers>
		<handler>
			<name>HelloWorld</name>
			<class>uk.co.bedican.mango.ext.HelloWorldRequestHandler</class>
			<url-patterns>
				<url-pattern>/hello</url-pattern>
				<url-pattern>/helloworld</url-pattern>
			</url-patterns>
			<params>
				<param name="foo">bar</param>
			</params>
		</handler>
		<handler>
			<name>Velocity</name>
			<class>uk.co.bedican.mango.VelocityRequestHandler</class>
			<url-pattern>/**.vm</url-pattern>
		</handler>
		<handler>
			<name>FreeMarker</name>
			<class>uk.co.bedican.mango.FreeMarkerRequestHandler</class>
			<url-pattern>/**.ftl</url-pattern>
		</handler>
		<handler>
			<name>MyDefaultHandler</name>
			<class>uk.co.bedican.mango.DefaultRequestHandler</class>
		</handler>
		<handler>
			<name>MyErrorHandler</name>
			<class>uk.co.bedican.mango.ErrorRequestHandler</class>
		</handler>
	</handlers>
</server>

The request handler lifecycle

Starting the server.

Handling requests.

Shutting down the server.

Writing a request handler

Request handlers are created by implementing the RequestHandler interface. The abstract class AbstractRequestHandler is provided to make this process easier.

The following class provides a skeleton RequestHandler.

package my.package;

import uk.co.bedican.mango.*;
import java.io.*;

public class HelloWorldRequestHandler extends AbstractRequestHandler
{
	public HelloWorldRequestHandler()
	{
	}

	/**
	 * The doInit method is called at the start of the RequestHandler lifecyle after initialization.
	 * An empty implementation exists within the superclass.
	 */
	public void doInit() throws RequestHandlerException
	{
	}

	/**
	 * The destroy method is called at the end of the RequestHandler lifecyle.
	 * An empty implementation exists within the superclass.
	 */
	public void destroy()
	{
	}

	/**
	 * Handles the request / response pair.
	 */
	protected void doRequest(Request request, Response response) throws RequestHandlerException, SessionManagerException, IOException
	{
		response.getWriter().println("Hello World !");
	}
}

Alternatively, the class DefaultRequestHandler can be extended to maintain the file selection and directory index search behaviour.

package my.package;

import uk.co.bedican.mango.*;
import java.io.*;

public class HelloWorldRequestHandler extends DefaultRequestHandler
{
	public HelloWorldRequestHandler()
	{
	}

	/**
	 * If a directory was requested, a file will be resolved at this point to the appropriate directory index.
	 */
	protected void writeFile(Request request, Response response) throws IOException
	{
		File file = request.getFile();
		// ...
	}
}

Request forward/include

Within a request handler, it may be necessary to include the response of another request handler as part of the current output, or to forward control to another handler completely. This is achieved with a request dispatcher and calling either the include or forward methods. When calling the forward method, the current response output buffer is cleared before and closed after. The request dispatcher encapsulates the correct request handler for the supplied path.

protected void doRequest(Request request, Response response) throws RequestHandlerException, SessionManagerException, IOException
{
	// ...
	RequestDispatcher dispatcher = this.getContext().getRequestDispatcher("/file/to/forward.vm");
	dispatcher.forward(request, response);
	return; // Although the response is now closed, we should return.
}

Error documents

Errors are handled by a special RequestHandler, by default the class ErrorRequestHandler. When the method sendError or sendThrowable is called on the Response object, control is forwarded to the error request handler.

The default error request handler can be configured as shown above with the configuration option "/server/config/error-handler".

Alternatively, specific error documents can be defined against error status codes, and the relevant RequestHandler for that document will be used instead.

Custom error documents can be configured as shown below.

<server>
	<error-documents>
		<error-document>
			<status>404</status>
			<location>/errors/404.vm</location>
		</error-document>
	</error-documents>
</server>

Session managers

A session manager provides the interface to a session storage mechanism.

By default, the session manager is an instance of DefaultSessionManager which provides file based session storage, stored within the system temporary directory or if it exists a ./tmp directory.

A HashSessionManager class is also provided and can be configured to be used for a memory based session manager.

Sessions are obtained using the getSession method of the request object. An optional boolean create parameter can be provided, if the session does not exist and the create parameter is true a new session will be returned, otherwise null.

Configuration

An alternative session manager can be configured as shown above with the configuration option "/server/config/session-manager"

Writing a session manager

Session managers are created by implementing the SessionManager interface. The abstract class AbstractSessionManager which deals with session events and session id generation, is provided to make this process easier.

The following class provides a skeleton SessionManager, the method newId is implemented within AbstractSessionManager but is shown here for illustration.

package my.package;

import uk.co.bedican.mango.*;

public class MySessionManager extends AbstractSessionManager
{
	public MySessionManager()
	{
	}

	/**
	 * Return a new unique id. This method is optional and implemented within the superclass.
	 */
	protected String newId()
	{
		return super.newId();
	}

	/**
	 * Retrieve a session from the store from a given session id.
	 * If the session does not exist and the create parameter is true, a new session should be returned, otherwise null.
	 */
	protected Session load(String sessionId, boolean create) throws SessionManagerException
	{
		return null;
	}

	/**
	 * Save a given session to the store.
	 */
	protected void save(Session session) throws SessionManagerException
	{
	}

	/**
	 * Remove a session from the store.
	 */ 
	protected void delete(Session session) throws SessionManagerException
	{
	}

	/**
	 * Called on server shutdown.
	 */
	public void shutdown()
	{
	}
}

Magic class loading

Jar files located within the ./ext/lib directory and the directory ./ext/classes are added to the class path when the server is started. This means the classpath does not need to be configured providing this convention is followed.

License

© Copyright 2010 Bedican Solutions

Redistribution and use of this software in source and binary forms, with or without modification, are permitted providing the following conditions are met:

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.