Servlet Engine

Download from http://www.nyangau.org/se/se.zip.

What is it

This is an implementation of a very small subset of the Servlet standards. It does not claim to comply with any particular level of specification, but rather it implements those calls used (so far) in my trivially simple servlets.

It is sufficient to host small web sites including simple servlets.

There is a class of servlet which don't require full blown application servers (such as WebSphere) to host them. They might not require EJBs, might need to consume very little memory or CPU, and perhaps might only utililise just a few of the Servlet APIs. A large number of vendor products now provide simple web interfaces to their products, and often the functionality is basic, perhaps being restricted to read-only status viewing (eg: the Tivoli monitor agent, Best/1 performance capacity/trend graphing review, etc.).

This Servlet Engine is designed to be extremely lightweight. In fact, at this time, the .jar file is <35KB. To host a Servlet, you need the Servlet itself, a "Server" which you write (which basically instantiates an instance of the Servlet Engine, and registers the Servlet into it), and the .jar file.

It is also designed to allow the construction 'tightly packaged' servers. When looking for properties files (or content via ServletContext.getResourceAsStream) or static web content, it uses the classloader first, and then the filesystem. This allows the bundling of Servlets, "Server", properties files and static web content into a single .jar file.

eg: You could have hello.jar containing :-

HelloServlet.class
HelloServlet.props
HelloServer.class
HelloServer.props

and you'd run it via :-

java -cp nyangau-se.jar:hello.jar HelloServer

Functionality provided

The Servlet Engine compiles up to form a .jar file, containing classes in the javax.servlet. namespace and below. Thus servlets compiled up to run in WebSphere, JRun, WebLogic, Tomcat etc., should require zero modification to run in this environment (providing they only use the subset of APIs supported by this Servlet Engine).

public class ServletException extends Exception
  {
  public ServletException();
  public ServletException(String s);
  }  

public abstract class ServletOutputStream extends OutputStream
  {
  public abstract void print(String s)
    throws IOException;
  public abstract void println()
    throws IOException;
  public abstract void println(String s)
    throws IOException;
  }

public interface ServletContext
  {
  public abstract String getInitParameter(String n);
  public abstract Enumeration<String> getInitParameterNames();
  public abstract String getMimeType(String fn);
  public abstract ServletContext getContext(String uripath);
  public abstract InputStream getResourceAsStream(String path);
  }

public interface ServletConfig
  {
  public abstract ServletContext getServletContext();
  public abstract String getInitParameter(String n);
  public abstract Enumeration<String> getInitParameterNames();
  }

public interface ServletRequest
  {
  public abstract String getScheme();
  public abstract int getServerPort();
  public abstract String getProtocol();
  public abstract String getParameter(String p);
  public abstract Enumeration<String> getParameterNames();
  public abstract String[] getParameterValues(String p);
  }

public interface ServletResponse
  {
  public abstract boolean isCommitted();
  public abstract void setContentType(String ct);
  public abstract void setContentLength(int length);
  public abstract ServletOutputStream getOutputStream()
    throws IOException;
  public abstract PrintWriter getWriter()
    throws IOException;
  }

public interface Servlet
  {
  public abstract void init(ServletConfig sc)
    throws ServletException;
  public abstract ServletConfig getServletConfig();
  public abstract void service(ServletRequest req, ServletResponse resp)
    throws ServletException, IOException;
  }

public abstract class GenericServlet implements Servlet
  {
  public void init()
    throws ServletException;
  public void init(javax.servlet.ServletConfig sc)
    throws ServletException;
  public ServletConfig getServletConfig()
  public abstract void service(ServletRequest req, ServletResponse resp)
    throws ServletException, IOException;
  }

public interface HttpServletRequest extends ServletRequest
  {
  public abstract String getMethod();
  public abstract String getRequestURI();
  public abstract String getQueryString();
  public abstract int getIntHeader(String h)
    throws NumberFormatException;
  public abstract Enumeration<String> getHeaders(String h);
  public abstract Enumeration<String> getHeaderNames();
  public abstract String getRemoteUser();
  public abstract boolean isUserInRole(String role);
  }

public interface HttpServletResponse extends ServletResponse
  {
  static final int SC_OK                    = 200;
  static final int SC_MULTIPLE_CHOICES      = 300;
  static final int SC_MOVED_PERMANENTLY     = 301;
  static final int SC_MOVED_TEMPORARILY     = 302;
  static final int SC_BAD_REQUEST           = 400;
  static final int SC_UNAUTHORIZED          = 401;
  static final int SC_FORBIDDEN             = 403;
  static final int SC_NOT_FOUND             = 404;
  static final int SC_METHOD_NOT_ALLOWED    = 405;
  static final int SC_GONE                  = 410;
  static final int SC_INTERNAL_SERVER_ERROR = 500;
  static final int SC_NOT_IMPLEMENTED       = 501;
  static final int SC_SERVICE_UNAVAILABLE   = 503;
  public abstract boolean containsHeader(String h);
  public abstract void addHeader(String h, String v);
  public abstract void setHeader(String h, String v);
  public abstract void addIntHeader(String h, int v);
  public abstract void setIntHeader(String h, int v);
  public abstract void addDateHeader(String h, long v);
  public abstract void setDateHeader(String h, long v);
  public abstract void setStatus(int sc);
  public abstract void sendError(int sc)
    throws IOException;
  public abstract void sendError(int sc, String message)
    throws IOException;
  public void sendRedirect(String url)
    throws IOException;
  public String encodeURL(String url);
  public String encodeRedirectURL(String url);
  }

public abstract class HttpServlet extends GenericServlet
  {
  public void service(ServletRequest req, ServletResponse resp)
    throws ServletException, IOException;
  protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException;
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException;
  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException;
  protected void doHead(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException;
  }

Access to the web application can be over HTTP, or over HTTPS, or both.

Access to the web application can be unauthenticated, or authenticated against a small set of userids configured in the properties file. If authenticated access is enabled, then the whole web application is protected. Basic authentication is used, so best to combine this with the use of HTTPS, so that the base64 encoded userid:password cannot be snooped in-flight.

Userids can be associated with roles.

GET and POST methods are supported. According to the standards, the GET method is only supposed to work for ASCII characters in the parameter names and values. The POST method is supposed to work for any ISO10464 character, although this only works for ASCII characters too. The POST file-upload mechanism isn't implemented.

How to use

A simple Servlet

Lets assume we have a simple servlet called HelloServlet, which we wish to host.

//
// HelloServlet.java
//

import java.io.*;
import java.servlet.*;
import java.servlet.http.*;

public class HelloServlet extends HttpServlet
  {
  public void doGet(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException
    {
    resp.setContentType("text/html");
    ServletOutputStream os = resp.getOutputStream();
    os.println("<H1>Hello</H1>");
    }
  }

A Servlet Engine to host it

We need to construct a Servlet Engine to run it in :-

//
// HelloServer.java - Hello Server
//

import java.net.*;
import java.io.*;
import java.util.*;
import nyangau.se.*;

public class HelloServer
  {
  public static void main(String args[])
    {
    try
      {
      ServletContext sctx = new ServletContext("HelloServer.props", "docroot");
      ServletEngine  seng = new ServletEngine(sctx);
      ServletConfig  scfg = new ServletConfig("HelloServlet.props");
      seng.registerServlet("/", new HelloServlet(), scfg);
      seng.serve();
      }
    catch ( javax.servlet.ServletException e )
      {
      System.err.println("HelloServer: "+e.toString());
      }
    catch ( IOException e )
      {
      System.err.println("HelloServer: "+e.toString());
      }
    }
  }

You can see where a new instance of HelloServlet is registered into the URI space (at / in the example).

The ServletEngine.serve method is where the server begins listening for inbound web requests and responds to them.

Just for completeness, we display any exceptions which occur during initialisation. Exceptions which occur during the serving of individual pages will be logged to stderr.

ServletContext

HelloServer.props is a Java properties file containing servlet-context wide properties. As there is only one servlet context (ie: one web application) in this servlet engine, these properties are effectively global to the entire server.

The server properties file looks like this :-

# Uncomment to conceal technology used
# server=

# HTTP listen port
port=8080

# HTTPS listen port
port_ssl=8443
keystore=mykeystore.jks
storepass=mykeystorepassword

# Security
roles=appuser
userids=tom,dick
password.tom=sawyer
password.dick=tracy
roles.tom=appuser,write
roles.dick=appuser,investigate,write
realm=Famous fictional figures website
secureURIs=/*
secureURIs.appuser=/*

If there is no port, then it doesn't listen for HTTP requests.

If there is no port_ssl, then it doesn't listen for HTTPS requests. If keystore is omitted, then the default is se.jks. If storepass is omitted, then the default is password. Obviously, when using SSL, you need to have a keystore, with the indicated filename and password.

If neither port or port_ssl parameters are given, the ServletEngine.serve() method returns immediately, which normally results in the process terminating.

Basic authentication is used, so its best to combine this feature with SSL.

roles is the list of roles. userids is the list of userids, and each of these has a password. a list of roles assigned to them. secureURIs is a list of URI patterns, and any content below these requires an authenticated user, and each authenticated user has a list of roles assigned to it. Each role can grant access to a list of URI patterns.

This file can also contain MIME mappings in the form mime.extension=mimetype, eg:

mime.png=image/png

Normally the Server: HTTP response header returned is nyangau.se V.V where V.V is the version. By setting the server property, you can lie about what web server technology is being used or by setting to nothing, prevent the header from being returned at all. Web security folk like this as it reveals less to a potential hacker.

The Servlet Engine will use these when serving static pages. If the file extension in question is not listed here, the Servlet Engine will fall back on its internal hard coded list of around 70 common MIME types.

Note also that where the ServletContext is set, a document root is chosen. This is for any static web content to be included.

ServletConfig

HelloServlet.props is a Java properties file containing initialisation time parameters for the servlet. ie: the values returned from the getInitParameter method of the javax.servlet.ServletConfig class.

Compiling and running

Java 5 or later is required.

Assuming $n is the path to find nyangau-se.jar :-

When compiling with javac, be sure to -extdirs nyangau-se.jar.

javac -extdirs $n HelloServlet.java
javac -extdirs $n HelloServer.java
jar -cf hello.jar *.class *.props

When running your servlet engine with java, be sure to include nyangau-se.jar on the classpath.

java -cp $n/nyangau-se.jar:hello.jar HelloServer

Monitoring

Servlet Engine exposes the following MBeans :-

ObjectNameAttributeMeaning
nyangau.se:type=ConnectionServer,scheme=http RequestsHow many requests over HTTP
nyangau.se:type=ConnectionServer,scheme=https RequestsHow many requests over HTTPS

You might only see one of the above, depending on the ports you've enabled.

Revision history

VersionDateComments
1.0  First public release.
1.1  Slight tidy up.
1.22010-06-16Now possible to construct ServletContext and ServletConfig from Properties.
Add JMX MBeans (had to refactor inner classes).
1.32010-07-18getRequestURI should still include + and %hh non-URLDecoded
1.42014-10-01Use generic types
1.52014-12-12Correctly URL decode request parameters
1.62014-12-12Support for basic authentication
1.72014-12-14POST application/x-www-form-urlencoded support (ASCII only)
1.82014-12-28Ability to secure some URIs and not others. Support for Date response headers.
1.9  Silently tolerate empty connections with no request.
2.02016-03-25server= property, to reveal less to attackers.
future...If I write servlets which need APIs not implemented so far, I may well add them.

Copying

Feel free to copy, its public domain. Caveat Emptor.


The documentation is written and maintained by the Servlet Engine author, Andy Key
andy.z.key@googlemail.com