Download from
http://www.nyangau.org/se/se.zip
.
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-<version>.jar:hello.jar HelloServer
The Servlet Engine compiles up to form a .jar
file, containing
classes in the jakarta.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(ServletConfig sc) throws ServletException; public ServletConfig getServletConfig() public abstract void service(ServletRequest req, ServletResponse resp) throws ServletException, IOException; } public interface HttpServletRequest extends ServletRequest { public static final String BASIC_AUTH = "BASIC"; public static final String FORM_AUTH = "FORM"; public static final String CLIENT_CERT_AUTH = "CLIENT_CERT"; public static final String DIGEST_AUTH = "DIGEST"; public static final String PRE_AUTH = "PRE"; // Non-standard public abstract String getAuthType(); 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. Basic authentication can be 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.
Lets assume we have a simple servlet called HelloServlet, which we wish to host.
// // HelloServlet.java // import java.io.*; import jakarta.servlet.*; import jakarta.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>"); } }
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 | 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.
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=mykeystore-password
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.
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.
This file can also contain MIME mappings in the form
mime.extension=mimetype
, eg:
mime.png=image/png
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.
Users can be authenticated against a small repository of users. The Servlet Engine uses HTTP Basic Authentication to do this. eg:
realm=Famous fictional figures website userids=tom,dick password.tom=toms-password password.dick=dicks-password
You can also configure a web server (eg: Apache HTTPD) to authenticate users before proxying the request to the Servlet Engine. It would need to pass the authenticated userid in a request header, and the Servlet Engine will trust this rather than try to authenticate the user itself. However, it will only trust the web server if the web server provides a secret value in another request header, eg:
preauth.secret.header.name=PreAuthSecret preauth.secret.header.value=web-servers-password preauth.userid.header.name=PreAuthUserid
If you are using Apache HTTPD to do the pre-authentication,
your httpd.conf
might look a bit like this :-
<Location "/"> AuthType Basic AuthName "Restricted Resource" AuthBasicProvider file AuthUserFile "/etc/httpd/conf/httpd.users" # chcon -t httpd_sys_content_t /etc/httpd/conf/httpd.users Require valid-user RequestHeader set PreAuthSecret "web-servers-password" # RequestHeader set PreAuthUserid "%{REMOTE_USER}e" RewriteEngine On RewriteCond %{IS_SUBREQ} =false RewriteCond %{LA-U:REMOTE_USER} (.+) RewriteRule . - [E=RU:%1] RequestHeader set PreAuthUserid "%{RU}e" env=RU </Location> ProxyPass "/" "http://host:port/" ProxyPassReverse "/" "http://host:port/" # /usr/sbin/setsebool httpd_can_network_connect 1
Observe the contrived way in which we get access to the authenticated
userid, in order to be able to pass it on in a request header.
The commented-out line doesn't work because Apache HTTPD does URI mapping
before it has determined the REMOTE_USER
.
%{LA-U:REMOTE_USER}
returns the REMOTE_USER from the evaluation of
a second internal look-ahead request, which is discarded.
The look-ahead feature is something understood by mod_rewrite.so
,
not environment variable references in general.
The %{IS_SUBREQ} =false
test doesn't appear in Internet examples,
but appears necessary to prevent AH00125: Request exceeded the limit
of 10 subrequest nesting levels
in the error log.
If the pre-authentication mechanism is used, then note that
HttpServletRequest.getAuthType()
returns the non-standard
value HttpServletRequest.PRE_AUTH
.
With either of these approaches, configure the Servlet Engine to only
listen over SSL to avoid passwords being transmitted unencrypted
(ie: don't use http:
like in the example above).
Users can be assigned roles, eg:
roles=appuser roles.tom=appuser,write roles.dick=appuser,investigate,write
By default, if you don't assign roles to a user then they have no roles. However, you can grant a default set of roles, eg:
roles.default=readonly
Normally all content is visible to all users. You can declare that certain URI patterns are not accessible by default. Roles can be granted access to certain URI patterns. eg:
secureURIs=/* secureURIs.appuser=/*
The roles
property needs to include all role names that
grant access to a secureURI
.
In other words all roles R
where property
secureURIs.R
exists.
Other roles granted to a user needn't be listed.
HelloServlet.props
is a Java properties file containing
initialisation time parameters for the servlet. ie: the values returned from
the getInitParameter
method of the
jakarta.servlet.ServletConfig
class.
Java 11 or later is required.
Assuming $n
is the path to find
nyangau-se-<version>.jar
:-
When compiling with javac
, be sure to
-extdirs nyangau-se-<version>.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-<version>.jar:hello.jar HelloServer
Servlet Engine exposes the following MBeans :-
ObjectName | Attribute | Meaning | |||
---|---|---|---|---|---|
nyangau.se:type=ConnectionServer,scheme=http
|
You might only see one of the above, depending on the ports you've
enabled.
Revision history
Version | Date | Comments |
---|---|---|
1.0 | First public release. | |
1.1 | Slight tidy up. | |
1.2 | 2010-06-16 | Now possible to construct
ServletContext and
ServletConfig from
Properties .
Add JMX MBeans (had to refactor inner classes). |
1.3 | 2010-07-18 | getRequestURI should still include + and %hh non-URLDecoded |
1.4 | 2014-10-01 | Use generic types |
1.5 | 2014-12-12 | Correctly URL decode request parameters |
1.6 | 2014-12-12 | Support for basic authentication |
1.7 | 2014-12-14 | POST application/x-www-form-urlencoded
support (ASCII only)
|
1.8 | 2014-12-28 | Ability to secure some URIs and not others. Support for Date response headers. |
1.9 | Silently tolerate empty connections with no request. | |
2.0 | 2016-03-25 | server= property, to reveal less to attackers. |
2.1 | 2017-05-28 | Pre-authenticated feature. |
2.2 | 2021-12-31 | Mavenized. Use Java 8. |
2.3 | 2023-08-05 | Use Java 11. |
3.0 | 2024-02-17 | Move to jakarta. packages |
future... | If I write servlets which need APIs not implemented so far, I may well add them. |
Feel free to copy, its public domain. Caveat Emptor.