Developing consistent, reliable Web applications that interface with such different devices as home networks, home automation systems, and realtime telemetry devices can be vastly simplified by treating devices as URLs. The URL programming interface (UPI) effectively provides a set of URLs for a device that is available to any user capable of performing the HTTP protocol. HTTP has become so universal that computer science students are often given the assignment of creating an HTTP stack.
Using the Java language, Sun Microsystems Laboratories developed a technology with which users can deploy very small HTTP stacks, with a core of less than 100 KB. These small servers can be embedded in devices or used for small tactical application servers that are similar to traditional Web servers. Such minimal servers can be used to provide an integrated presentation and service layer for a device. Because these servers can act as Web servers that answer URL requests, universal access to devices from any Internet node can be achieved.
This article demonstrates how HTTP stacks can be used to deploy interfaces to devices, creating an extranet that contains those devices. The technology discussed in this article was developed at Sun Labs, which has built several prototypes for smart cards, realtime weather stations, and home automation systems that provide authenticated access to any device on the Internet capable of supporting a Java runtime environment directly or via a proxy.
One of the benefits of this architecture is that its different components can easily be used with one another. For example, you can use a smart card to authenticate access to your home by having it interact with your home network and/or environmental systems. What if every device in your home had a URL with some amount of functionality behind it? Or, better yet, what if you could script applications using HTML that allowed you to do things like change the energy management or shut down lights, locally or remotely, using the same interface?
Supporting devices with Web applications will add new utility to the Internet, if the technology is implemented securely. Without security, however, remote home automation will never succeed. Java technology is perfect for this type of application because of its security model and small size.
Let's first examine the Sun Labs Brazil server. Features of Brazil include:
- A simple extension API (called a handler) that uses a delegation-based object model.
- Dynamic loading of application functionality and configurations. The handlers that provide application functionality are resolved and loaded at runtime.
- Mechanisms for composing application modules, encouraging code reuse with well-known design patterns. Information specific to an entire application is in one place, and made available to all of the handlers, simplifying server modification and configuration.
To use the server as part of your application, you will need to write one or more handlers. A handler is similar to a servlet in Java Web server terminology, but is lighter in weight.
A handler is written by creating a Java class that implements the handler interface. This consists of only two methods,
Handler.init(Server, String) and
Handler.respond(Request). The first is called once, when the server is initialized, and the other is called upon each HTTP request.
For many applications, the entire functionality of the system can be encapsulated in one or more handlers. In these cases, the program can be started by the supplied
Main program by stating the name of your handler class and network port on the command line:
Java sunlabs.brazil.server.Main -port 8080 -handler my_handler_class_name
If your handler can be configured at runtime with different settings or operating parameters, then these settings or parameters can be appended to the command line in the form of name/value pairs:
Java sunlabs.brazil.server.Main -handler my_handler_class_name parameter_1 value_1 parameter_2 value_2 ...
Main program will put these name/value pairs into the
Server.props field of the server object, which is passed as a parameter to the handler's
Alternately, these command-line parameters can be placed in a properties format file and referenced via the
-config flag of the
Main program, as in:
Java sunlabs.brazil.server.Main -config my_config_file (contents of my_config_file) port=8080 handler=my_handler_class_name parameter_1=value_1 parameter_2=value_2
For most applications, you'll want to use multiple handlers, either written specifically for the application or in combination with some of the handlers provided in the
sunlabs.brazil.handler package. The
Server class utilizes both the
Handler.init(Server, String) method and the
ChainHandler to allow multiple handlers to work together, define their own configuration parameters, and avoid the confusion of matching various configuration parameters to the right handlers.
ChainHandler is the default mechanism used to run multiple handlers. It looks for a single configuration parameter, called
handlers, that contains a list of tokens, each of which is the name of another handler. For example, the following entry in a config file:
handlers = a b c
specifies that three handlers should run sequentially: first
b, followed by
c. The use of Java properties provides for indirect naming. The actual handlers referred to by the above tokens would be:
a.class = "some Java class implementing the handler interface" b.class = same as above but code for handler b c.class = same as above but code for handler c
Call chains of arbitrary depth can be configured this way. The config file handlers property can even refer to other instances of the
ChainHandler, enabling the creation of an arbitrary tree of handlers.
ChainHandler starts the other handlers by first instantiating them with
newInstance() and then by invoking their
init method, which is passed the handler's name. The handler uses its name to get its configuration parameters from
Server.props as described below.
Suppose an application uses two handlers,
A uses configuration parameters
B uses configuration parameters
other. When handler
A and handler
B examine the properties, they use their respective assigned names to differentiate configuration parameters that might have the same name. The server that uses these two handlers might have a configuration file that looks like this:
handler=sunlabs.brazil.server.ChainHandler handlers=first second first.class=A first.option=value first.setting=another value second.class=B second.option=another value second.other=none
The first line is interpreted by the server, which is instructed to use the
ChainHandler looks at the
handlers line for the list of tokens that represent each handler it will run (in this case,
ChainHandler then creates an instance of classes
B, specified by
second.class entries. The handler
init method is called with
first. as its prefix. Handler
B is called with
second. as its prefix.
A looks for its configuration parameters in
Server.props, it looks for
setting). Handlers are not required to use their prefix when examining the parameters, and may therefore share properties, such as the default document root property
X10 handler overview
First, we'll construct a handler for an X10 power-line interface that will enable home automation through the Web.
This sample X10 handler is not intended to be complete, although it has been tested and verified to work. Its primary purpose is to touch on many of the issues that commonly arise when providing URL interfaces to devices, including:
- URL processing
- Device control
- Concurrency management
- Interaction with applets
- Interfacing with HTML
The sample X10 handler is implemented with two classes: an abstract class,
GenericX10Handler.java, which provides a URL interface to a generic X10 interface device, and
TwoHandler.java, which maps the generic X10 interface device onto a specific device, such as the TwoWay power-line interface module. The handler provides URLs with the ability to send commands on to the power line, and to wait for asynchronous notification of power-line events.
While sending power-line commands involves straightforward mapping of URLs to X10 commands, the asynchronous notification requires special considerations. HTTP is client driven: the client needs to initiate all requests. However, the asynchronous notification is intrinsically server driven, or server push. We implement this server push with the help of the (paradoxically named) pull applet. The applet sends a notification URL to the server, which sits and waits for a command to appear on the power line. As soon as one is available, the server wakes up and satisfies the request. The applet immediately reissues the request, causing it to wait for the next event.
You can see annotated source code for the generic X10 handler here: http://cv360428-a.norwlk1.ct.home.com/jw/x10/GenericX10Handler.html
Generic X10 proxy handler
We start the handler by defining a class that implements the handler interface. When we implement the asynchronous notification of power-line events, we also implement the runnable interface so we can create an instance as a background thread. The class keeps instances of the
prefix in case the concrete subclass needs to refer to them.
server.props for all entries that begin with
prefix is the standard way of retrieving the configuration properties for this handler.
By convention, the names of any parameters used by the handler are defined as
static public final String. In this case,
device is used to name the physical device connected to the power-line interface, and
prefix defines the leading part of the URL for this handler.
The private variable
MAX_Q is used internally to limit the number of asynchronous power-line messages that will be queued.
Although there is usually only one instance of a handler created by a server, there can be many requests, each in its own thread, running the
respond method. For resources such as files, this presents no problem. However, in the case of X10, there is only one physical interface device, so we must limit the number of simultaneous requests to two -- one sending output to the power line, and the other waiting for input. These objects,
notifyMutex, are used as mutexes to ensure that only one request of each type is accessed at a time.
For X10, there are only a few possible commands allowed. We arbitrarily pick semimnemonic strings to represent each X10 command, and store them in the
cmdList. These strings will be encoded as part of the URL in the query parameter.
We can choose several different ways of encoding the commands into the URL. Using query parameters allows access to X10 commands directly from HTML forms.
The TwoWay X10 controller provides for asynchronous notification any time a command is seen on the power line. We start a background thread that listens for these commands and stuffs them on the queue.
By calling the
run method, we can use
TwoWayHandler as a normal handler (by implementing the handler interface), and as a background thread (by implementing the runnable interface).
For thread safety, we need to synchronize on the queue. If a client has requested an asynchronous event, and the queue is empty, it waits. The
notifyAll wakes it up when an item is placed on the queue.
init method is called by the server once, when it starts, just after the object is created (with
newInstance, by the server). The server object, passed as a parameter, encapsulates all of the information that holds for all requests. The
prefix string is used to identify the server properties that are specific to this handler. A reference to these parameters is stored in the object, just in case a concrete subclass needs additional server information. For example, a subclass might need to look for additional server properties.
String deviceName = server.props.getProperty(propsPrefix + DEVICE, "/dev/cua/a");
is idiomatic to
init methods, and represents the standard way of fishing handler-specific property information out of the server object.
Because this method is called once, just after the object is created, it is a convenient place to start the background thread. The
respond method is the primary callback routine, and it is called once for every HTTP request that comes into the server.
As with the server object above, the request object also contains a reference to the server properties. The primary difference is that other upstream handlers can modify the information in the request properties, but the server properties are fixed.
It is considered good practice to examine the configuration parameters for every request, instead of only once in the
init method, if the parameters are expected to change on a per-request basis. This lets another handler upstream change the parameter, which in turn allows this handler's code to be reused in a different context.
Normally the request method is called for every handler on each request. Something like this code fragment: