Many of the applications I've developed handle data collection or similar processes where the machine is only accessed remotely, and logging information has proven to be extremely useful for debugging issues and tracking statistics. Despite the benefits of logging, it is an offline process. Sure, you can view a log file as it changes, but that requires specific remote access to the server that is running the code.
While working from home, I needed to make some changes to my small SOHO router to allow an application access. In the process, I realized how valuable an embedded Web server could be for remote applications. Inspired by the Web server in my SOHO router, I decided to write a small Web server that I could embed into my applications for displaying status information and administering the application.
Build vs. buy
As with any development project, I started by evaluating what existed that I could buy (or download). I did some research about small Web servers and found that there are several available. However, I wanted something with a small footprint that could easily integrate into existing applications. My search didn’t reveal any code that fit my needs. There are several different small Web server products available that are written in Java, but most are designed to be small Web servers for handling Web pages from disk. The ability to serve pages isn’t the focus for my applications, so I decided to build my own.
Design requirements
First, I wanted it to be capable of handling multiple requests simultaneously. It was a small implementation, but even a small Web server in a remote application may need to deal with a few users. Java makes dealing with multiple threads simple and powerful, so this seemed like a reasonable requirement. Also, I wanted my Web server to expose an easy method for adding new request handlers (e.g., adding new pages), although there wouldn’t be any physical pages on the disk.
I decided that the Web server should expose a request-and-response structure similar to that provided by the Java Servlet specification. I even decided to implement a subset of the methods exposed by the Servlet request-and-response objects to make it familiar for developers who need to add functionality to the Web server. What I ended up with is a simple requirements specification for my embedded Web server. It has to be under 20 KB, allow multiple requests to be handled simultaneously, and expose a simple API for writing additional request handlers.
Components
Figure A shows a diagram of the components that make up my embedded Web server.
As you can see from Figure A, the embedded server uses only a few classes. The main class creates the socket and establishes a number of threads using the WebServerProcessor object. The WebServerProcessor object actually handles each client request. The Web server uses a property file to configure the number of client processors to instantiate. That same property file is used to set up the request handlers that respond to a URL path, such as /logs.
To respond to a client, you have to set up a handler to look for the URL the client is requesting. I made this design simple so only a direct match works for the URL. For example, a handler might be told to respond to /logs and if the client types, his or her request will be passed to the handler for /logs.
Processing requests
The WebServerProcessor maintains a queue of incoming connection requests and creates a WebServerRequest and WebServerResponse object for each connection. Next, it examines the handler list and if it finds a matching handler for the client’s requested URL, it passes control to the handler like this:
WebServerRequest request = new WebServerRequest(connection);
WebServerResponse response = new WebServerResponse(connection);
if ( handlers.containsKey(request.getRequestURI()) ) {
WebRequestHandler handlerObj =
(WebRequestHandler)handlers.get(request.getRequestURI());
response.sendOK();
if ( "post".equalsIgnoreCase(request.getMethod()) ) {
handlerObj.doPost(request, response);
}
else {
handlerObj.doGet(request, response);
} }
else {
response.sendError(HttpConstants.HTTP_NOT_FOUND, "Requested resource not available:");
log.info(HttpConstants.HTTP_NOT_FOUND + " " + request.getRequestURI());
}
response.close();
Each handler implements the WebRequestHandler interface containing the methods doGet and doPost. I designed these to mimic the Java Servlet API, so if you are a JSP/Servlet developer, these methods will be familiar.
The server is designed for you to write a number of these handlers for your particular application. I developed a few handlers to show you how it works. I created a summary handler to display a default page and two log handlers. The first handler lists the files in a directory, and the second takes a query string parameter for the file and streams it back to the browser. The log handlers were my way of getting to the application logs through the Web server. This code snippet shows the handler that lists files in a directory:
package com.versatilesolutions.embeddedweb;
import java.io.File;
import java.io.IOException;
import java.util.Date;
/**
* Outputs a list of the available files for viewing.
*
* @author <a href="mailto:kbrown@versatilesolutions.com">Kevin Brown</a>
* @author $Author: kbrown $
* @version $Revision: 1.2 $
* Last changed $Date: 2002/07/29 14:04:42 $
*/
public class LogListHandler implements WebRequestHandler {
public void doGet(WebServerRequest pRequest, WebServerResponse pResponse)
throws IOException {
pResponse.writeln(HttpConstants.TEMPLATE_OPEN);
pResponse.writeln(HttpConstants.TEMPLATE_HEADER);
File dir = new File("logs");
String[] list = dir.list();
for (int i = 0; list != null && i < list.length; i++) {
File f = new File(dir, list[i]);
if (f.isDirectory()) {
; // Do nothing with directories
} else {
pResponse.write("<a href=\"viewFile?file=");
pResponse.write(list[i]);
pResponse.write("\">");
pResponse.write(list[i]);
pResponse.writeln("</a><br/>");
} }
pResponse.writeln(HttpConstants.TEMPLATE_FOOTER);
pResponse.writeln(HttpConstants.TEMPLATE_CLOSE);
}
public void doPost(WebServerRequest pRequest, WebServerResponse pResponse)
throws IOException {
this.doGet(pRequest, pResponse);
} }
Run the sample
Included in the download is a small startup batch file that will run the embedded Web server so that you can see how it works. Unzip the files to a directory on your machine, and then use Ant to create the test build. If you don’t have Ant, you can download it from the Apache Jakarta project.
Once you've extracted the files, execute ant clean testbuild to build the server. A build directory will be created with a test directory. From the build/test directory type bin\startwebserver.bat and the server will start in the current window. I didn’t supply a script to stop it, so you can just kill the window when finished. Once the Web server is running, you can open a browser and type to see the default page. Then type to see the log file handlers in action. (I didn’t supply a test script for Linux, but the startup is simple and you can easily implement it in Linux.)
How it can work for you
This small Web server has come in handy for my applications. I have used it to allow access to log files without having to train users or distribute software. The embedded programmers I work with didn’t know Java and needed only to see that the equipment they were working on was talking to my software. The embedded Web server did the trick, and the amount of work was minimal.
Of course, there is room for improvement. For example, adding IP filtering capability would be a nice extra. Adding parse capability to the request object for the query string would help with allowing posts of information back to the Web server. Consider combining this Web server with the metrics collection and your metric data can be viewed as XML through the browser. And I’m sure you can think of other good ways to use this small Web server in your applications.