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
- Configurable directory indexes.
- Support for a variety of content types.
- Custom error documents.
- Request and error logging.
- Configurable request handlers against url patterns.
- Support for Velocity Templates (version 1.6.2).
- Support for FreeMarker (version 2.3.16).
- Cookie and Session support.
- The session manager can be re-implemented.
- Can deal with multiple requests concurrently with a configurable thread pool.
Download
| Mango 1.0 | mango-1.0.zip | Download |
| Mango 1.0 Source | mango-1.0-src.zip | Download |
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.
- An instance of the request handler is created.
- The request handler is initialized by calling its init method.
Handling requests.
- A request handler instance is seletected from its configured url pattern, or if none match the default request handler instance is selected.
- The request handler handleRequest method is called with the request/response pair.
Shutting down the server.
- The request handler destroy method is called.
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:
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer.
- Distributions in binary form compiled from source must make that source available for redistribution without sublicense, retaining all copyright notices, including the above copyright notice, this list of conditions and the following disclaimer.
- Distributions must provide a link to http://www.bedican.co.uk
- Neither the name bedican, bedican solutions nor the name of the author, may be used to endorse or promote any website or products without specific prior written permission. The name bedican is a trademark of bedican solutions.
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.
