Project 3
CMSC 433
Programming Language Technologies and Paradigms
Fall 2003
Due Wed., Nov. 5, 2003
Introduction
Increasing numbers of applications---include cell phones, routers, and
industrial control applications---are choosing to use HTTP, HTML, and other
WWW protocols to perform operations like configuration, control, query, and
more. In this assignment, we will build a server that uses the web
protocols to communicate. Rather than hardcoding the server to serve files, as
happens for most webservers, we will abstract away from what is being
served, and focus on the server infrastructure itself. This way, the same
code can be used to write web-based servers for files, router configuration,
industrial machine control, and so on.
This project has several goals:
-
To familiarize you with some object-oriented design patterns.
In particular, you will be implementing the Template pattern, the Command
pattern, the Observer
pattern, the Factory pattern, and some others in this assignment. In addition, you will
make use of the Typesafe Enum pattern, the Iterator pattern, and the
Decorator pattern (by virtue of using Java I/O). As you work through this
project pay attention to how the patterns are used. Note that we overdid things
a bit to give you some practice with different patterns.
-
To gain familiarity with HTTP and the WWW client/server
approach. This knowledge is relevant to the way many applications are
written today.
All provided Java files for this assignment can be found at (TBD). You can also get this assignment in PDF.
1 Basic Webserver Model
The general
structure of the webserver will be just as with any server: there will be an
infinite loop that listens with a ServerSocket on a given port,
accepts TCP connections, and then processes requests on those connections.
Each time you get a connection, you will do the following:
-
Parse the HTTP request. Connections are made to web servers
using the HyperText Transfer Protocol, or HTTP. HTTP requests will be
of the form: "GET target VERSION", for example "GET /images/wamlogo.gif
HTTP/1.0". This is the request automatically sent by a browser when
you give it the address http://ServerAddr:portNum/target. To parse this
request, you will use a class we have provided,
HttpRequest,
and call its static parseRequest method with the connection's InputStream as argument. The result
will be that it returns an HttpRequest object, that
stores the key aspects of an HTTP request: the requested file, the HTTP
version number, and operation; for this project, only the requested file
will be of interest. If something goes wrong, parseRequest will throw an
IOException. BasicServer will catch the exception, create an html error message
and send it to the client on the client's OutputStream. If the client has
disconnected the current processing task will simply be dropped.
-
Create an HttpReply object and an HttpRequestEvent object. The HttpReply
is a
helper class for creating a properly formatted http reply to return to the
client. The HttpRequestEvent object will contain the original HttpRequest, the HttpReply
and the address of the client. You will need to study the
documentation to understand how to use these helper classes.
- Invoke the
attached HttpReqestProcessor. The webserver will contain a request processor
for processing requests. You will invoke the processor passing in an HttpRequestEvent
object. All output generated by this process will be written into the Event
object, including any error messages if something goes wrong. The Strings
provided to these methods should be HTML (for a primer on HTML, see
http://www.htmlprimer.com/).
- Send the HTTP response. Once the HttpRequestProcessor has completed, the BasicServer will send the output to client by
invoking the send method of the HttpReply created by the Event. For
convenience, we will provide you with a basic class StandardHttpRequestEvent
which will organize a lot of this work.
Note: You are responsible for studying and understanding how the support
classes we give you work. Read the documentation thoroughly. Pay attention to
the consequences of writing responses to the client. The kind of response you
create and the time at which you create it affect how other handlers must
behave.A key part of the implementation is the calling of HttpRequestProcessors (we
will refer them as handlers from now on) to process the
request. We will use the Observer pattern to forward requests to handlers. That is, a
number of handlers, which are the observers, attach to the
server, which is the subject, indicating that they are interested in
processing web events. When web events come in, the server will forward them to interested handler
so they can process them and create a response. In our case, the update is a call to the
handler's process method, described below. The responses of
each of the invoked handlers are combined and a single HTTP response is
sent to the client.
This project has several kinds of Handlers. They are HttpRequestProcessors,
HttpCommandProcessors, and HttpRequestDispatchers. In a nutshell, HttpRequestProcessors
only
handle simple Http requests. HttpCommandProcessors handle http requests and
simple commands. HttpRequestDispatchers handle simple http requests and perform
simple commands, but also
forward HttpRequestEvents to other HttpRequestProcessors, and support commands
for manipulating attached HttpRequestProcessors..
Each handler will implement the HttpRequestProcessor interface, which is
described more completely in the class documentation:public interface
HttpRequestProcessor{
public void
process(HttpRequestEvent event);
public String getStatus();
public String getName();
public String getType();
}
Subclasses must implement the following methods:
- process performs application-specific processing on standard http requests.
These requests are contained in the HttpRequestEvent
parameter. If no errors occur, process may write output by using
HttpRequestEvent's attachStream methods. Note that once attachStream is
called, no further reply can be generated. In general a processor should
catch its own Exceptions (such as FileNotFoundException) and write an
informative response to the client using the writeError() method to indicate
that the request was not successfully processed. Care should be used
when using writeError as a response as it blocks all further processes from
writing to the event.
- getStatus - gets the object's current status
- getName - gets the object's public name.
- getType - gets a descriptive name for the Processor class
1.1.1 Handling Errors
Errors should generally be handled directly by the processor using the
HttpRequestEvent's writeError method.
Besides serving web pages handlers also perform computations and dynamically update the behavior of the
server. To make this process more extensible we will implement some handler
services in a subclasses of the HttpCommand interface. These objects will be registered with
handlers and later invoked by them in response to specific http requests. This
is an example of the Command pattern.
public interface HttpCommand{
public void execute(String[] args, HttpRequestEvent e);
}
The HttpCommand execute methods assumes that args[0] is the command name. All other arguments are to be treated as
command parameters. HttpCommand implementations are responsible for parsing/casting command
parameters.
HttpCommandProcessors execute HttpCommands to perform services requested
by clients. Client requests services by sending http requests with encoded commands.
Commands are encoded in a browser as follows:
http://serverName:portNum/?recipient_name/command,arg1,arg2,...
Assume that the http request is a command whenever the first two characters
of the request target are "/?".
Recipient_name represents one or more HttpCommandProcessors to whom the
command is sent. HttpCommandProcessors whose "name" exactly matches the recipient_name will then invoke the indicated
command. Otherwise they will ignore the command. The special recipient_name
"global" matches all HttpCommandProcessors. For instance, the
address request:
http://serverName:portNum/?global/name
Will cause all handlers supporting the 'name' command to write their names to
the response.
The HttpCommandProcessor is an class that implements HttpRequestProcessor and adds some new methods to handle commands.
Command processors contain HttpCommand instances that implement specific
commands. This class' process method uses the Template pattern. An outline of the new methods for this class is given below:
public class HttpCommandProcessor implements HttpRequestProcessor{
public HttpCommand registerCommand(String name, HttpCommand h);
public void removeCommand(String name);
public HttpCommand getCommand(String name);
public Map getCommands() //returns all commands in an unmodifiable form.
public void preProcess(HttpRequestEvent event);
public void postProcess(HttpRequestEvent event);
public final void process(HttpRequestEvent event) {
preProcess(event);
String args[] = parseArgs(event); // looks for /?name/a,b,c and returns "a","b","c"
if(args!=null){
Command c = getCommand(args[0]);
if(c!=null) {
try{ c.execute(args,
event); }
catch(Exception e){ writeError(...);
}
}
}
postProcess(event);
}
}
-
registerCommand attaches a command to the CommandProcessor
-
detachCommand detaches one command by name
-
getCommand retrieves one command object by name
- preProcess encapsulates any processing done before processing a command
- postProcess encapsulates any processiong done after processing a command
The process
method is a Template method. It calls preProcess, parses the incoming command (parseArgs
is provided for you), finds the requested command executes it, and calls
postProcess. If the http request is not an encoded command or if the
recipient_name does not match the name of the current object, then parseArgs
returns null.
As an example of how HttpCommands work, consider a client who wants to know
the status of a particular handler named "handler27." That client would issue
the following request.
http://serverAddr/?handler27/status
This command will get passed to every handler. If there is a
HttpCommandProcessor with the name "handler27" (let's call it hcp), it will look
for a registered HttpCommand object with the name "status". Assume that hcp has
been configured to support the status command, it will find an instance of the HttpCommandStatus
class (class is defined later, let's call the instance s). Next, hcp will call
s.execute, which will get cp's HttpRequestEvent, get the status of cp, and write
that status to the HttpRequestEvent.
Note: if your HttpCommandProcessor must also handle traditional http
requests, that code must be placed in the preProcess (before command execution)
or postProcess (after execution) methods (which is usually given in the class
specification).
1.4 The HttpRequestDispatcher class
Sometimes we want to implement complex server behaviors by composing the
behaviors of several components. This allows us both to build up higher level
services from lower level ones, and to change behaviors at
runtime. To do this we use the HttpRequestDispatcher class. This class forwards
incoming HttpRequests to any registered HttpRequestProcessors. You will need to
implement methods to attach and remove HttpRequestProcessors.
HttpRequestDispatchers can perform local processing before or after forwarding the
HttpRequestEvent (depends on the application), but the process method should forward the HttpRequestEvent in
the order in which HttpRequestProcessors are attached.
An outline of this new methods for this class is as follows:
public class HttpRequestDispatcher extends HttpCommandProcessor{
public void attach(HttpRequestProcessor h);
public void attach(int position, HttpRequestProcessor h);
public HttpRequestProcessor removeAt(int pos);
public HttpRequestProcessor remove(String pos);
}
Note: HttpRequestDispatcher should handle commands and traditional http
requests before forwarding the HttpRequestEvent to any attached
HttpRequestProcessors. Therefore, place the forwarding code in the postProcess
method.
2. Implementing the BasicServer class
The BasicServer class must be implemented as follows:public class BasicServer {
HttpRequestProcessor proc;
public BasicServer(int port, HttpRequestProcessor proc);
public void go ();
}
The constructor takes as an argument the TCP port that the server will listen on for connections
and an HttpRequestProcessor which will process the incoming requests. The go method is used to actually start the server; it will contain the infinite server loop that accepts connections on the given port, parses the HTTP request, passes the request to command processor, and returns the response to the client, etc.
3 Implementing HttpCommands
Below we describe some HttpCommand subclasses you will implement.
We give a description of what the
command does, and the specification for the class' constructor.
- HttpCommandStatus
Description - reply with the context object's status.
Constructor - HttpCommandStatus(HttpCommandProcessor context);
- HttpCommandName
Description - reply with the context object's name.
Constructor - HttpCommandName(HttpCommandProcessor context);
- HttpCommandHelp
Description - reply with the all commands supported by
the context object..
Constructor - HttpCommandName(HttpCommandProcessor context);
- HttpCommandInfo
Description - reply with the string representation of the incoming HttpRequest.
Constructor - HttpCommandInfo();
- HttpCommandEcho
Description - reply with the arguments given to the command. (Provided to you
as an example).
Constructor - HttpCommandEcho();
- HttpCommandLogEnable
Description - enables logging
Constructor - HttpCommandLogOn(HttpFileLogger logger);
- HttpCommandLogDisable
Description - disables logging .
Constructor - HttpCommandLog(HttpFileLogger logger);
- HttpCommandLogClear
Description - log contents are cleared.
Constructor - HttpCommandLog(HttpFileLogger logger);
- HttpCommandLogGet
Description - reply to client with contents of log.
Constructor - HttpCommandLog(HttpFileLogger logger);
-
HttpCommandStart
Parameters - handlerClass, name [, pos].
Description - Create an object of class handlerClass, named name, attach
object to appropriate HttpRequestDispatcher at position pos if provided.
If pos is omitted, the new Handler should be appended.
Constructor - HttpCommandStart(HttpRequestDispatcher context, HttpHandlerFactory
fac);
-
HttpCommandKill
Parameters - pos
Description - Remove the HttpRequestProcessor at position pos from appropriate HttpRequestDispatcher.
Constructor - HttpCommandKill(HttpRequestDispatcher context);
- HttpCommandMove
Parameters - oldPos newPos
Description - Remove the HttpRequestProcessor at position oldPos. Attach it at position newPos.
Constructor - HttpCommandMove(HttpRequestDispatcher context);
- HttpProxySetTarget
Parameters - URL
Description - sets proxy's target to URL.
Constructor - HttpProxySetTarget(HttpProxyHandler context);
We've already given you the HttpHandlerFactory interface as well as a simple
implementation- BasicHandlerFactory, which knows how to create some of the
Handlers you are required to support. When testing we will use factories
that typically only know how to create minimal sets of Handlers, so that if
there is one handler you can't get to work it won't fail all of your
tests. This
approach uses the Factory pattern.
public interface HttpHandlerFactory {
public HttpRequestProcessor create(String type, String name)
}
4. Implementing handlers
Handlers define the part of each server that differs. In our project, we
will always use HTTP as the means of communication, but rather than just
retrieve HTML files, we also do more sophisticated processing. This way, we can build HTTP-based servers that
do different things by reusing the BasicServer class, but attaching
different HttpRequestProcessors.
- HttpRemoteManager (extends HttpRequestDispatcher). Executes commands, then forwards requests to attached HttpRequestProcessors.
Supports the: HttpCommandStatus, HttpCommandName, HttpCommandHelp, HttpCommandEcho,HttpCommandInfo,
HttpCommandStartup,
HttpCommandKill, and HttpCommandMove commands. After each operation, the current status should
reflect the current configuration and status of all attached
HttpRequestProcessors. Write constructor as:
HttpRemoteManager
(String name);
- HttpRemoteFileHandler(extends HttpCommandProcessor). Attempts to retrieve target filename from remote web
server using the special supported command 'get'. An example request
would be
/?name/get,server/file
The handler attempts to create a connection to the specified server to
request and return the contents of the specified file. Supports the HttpCommandStatus, HttpCommandName, HttpCommandHelp
and HttpCommandEcho, HttpCommandInfo commands, as well as an internal Get
command which is always bound to the string "get". After each operation, the current status should
indicate the last target filename, whether or not it was found, and the total
number of requests received since startup.
HttpRemoteFileHandler
(String name);
- HttpProxyHandler (extends HttpCommandProcessor). Implements the Proxy
pattern for HttpRemoteFileHandlers. When a HttpProxyHandler receives a local
file request, it prepends the proxy target to the local file name (creating a
new remote file request) along with the correct command identifier (/?remoteName/get,)
for the HttpRemoteFileHandler instance it is wrapping and then forwards the
modified event. Responses must be transmitted back to the original
event. Supports the HttpCommandStatus,
HttpCommandName, HttpCommandHelp, HttpCommandEcho,HttpCommandInfo and HttpProxySetTarget
commands. After each operation, the current status should indicate the current
proxy target in
addition to the status of the wrapped RemoteFileHandler. Write constructor as:
HttpProxyHandler (String name, HttpRemoteFileHandler h);
- HttpFileLogger (extends HttpCommandProcessor). When enabled, writes the
incoming httpRequestEvent to log file. Logging can be externally enabled or
disabled and the contents of the log file can be cleared or retrieved.
Supports the HttpCommandStatus, HttpCommandName, HttpCommandHelp, HttpCommandEcho,HttpCommandInfo, HttpCommandLogEnable,
HttpCommandLogDisable, HttpCommandLogClear
and HttpCommandLogGet commands. After each operation, the current status
should indicate whether or not logging is enabled. Write constructor as:
HttpFileLogger (String name, String logFileName);
5 Test Applications
Using these handlers, we will write several server applications. These
servers will be accessed by HTTP clients, like Netscape. For each
application we will do the following:
-
Create a BasicServer instance.
- Create all necessary handlers.
- Attach the handlers to the server.
- Invoke the server's go() method.
- Send requests to BasicServer
Each application will be encapsulated within a class. Each application class
and the necessary handlers are described below. Note that in all cases, the public static main() method for your server should get the port
number to pass to BasicServer's constructor from the command line
(i.e., start the server with java -Dport=portnum ServerClassName). The port number can be extracted in your server
with the Integer.getInteger() method. For testing purposes, so
everyone uses different ports, test your server using port x33yy, where x is your section number (either 1 or 2) and yy is the last two digits
of your account number.