The connector in Chapter 3 worked fine and could have been perfected to achieve much more. However, it was designed as an educational tool, an introduction to Tomcat 4's default connector. Understanding the connector in Chapter 3 is key to understanding the default connector that comes with Tomcat 4. Chapter 4 will now discuss what it takes to build a real Tomcat connector by dissecting the code of Tomcat 4's default connector.
Note: The "default connector" in this chapter refers to Tomcat 4's default connector. Even though the default connector has now been deprecated, replaced by a faster connector code-named Coyote, it is still a great learning tool.
A Tomcat connector is an independent module that can be plugged into a servlet container. There are already many connectors in existence. Examples include Coyote, mod_jk, mod_jk2, and mod_webapp. A Tomcat connector must meet the following requirements:
Tomcat 4's default connector works similarly to the simple connector in Chapter 3. It waits for incoming HTTP requests, creates request and response objects, then passes the request and response objects to the container. A connector passes the request and response objects to the container by calling the org.apache.catalina.Container interface's invoke method, which has the following signature.
public void invoke( org.apache.catalina.Request request, org.apache.catalina.Response response);
Inside the invoke method, the container loads the servlet class, call its service method, manage sessions, log error messages, etc.The default connector also employs a few optimizations not used in Chapter 3's connector. The first is to provide a pool of various objects to avoid the expensive object creation. Secondly, in many places it uses char arrays instead of strings.
The application in this chapter is a simple container that will be associated with the default connector. However, the focus of this chapter is not this simple container but the default connector. Containers will be discussed in Chapter 5. Nevertheless, the simple container will be discussed in the section "The Simple Container Application" towards the end of this chapter, to show how to use the default connector.
Another point that needs attention is that the default connector implements all features new to HTTP 1.1 as well as able to serve HTTP 0.9 and HTTP 1.0 clients. To understand the new features in HTTP 1.1, you first need to understand these features, which we will explain in the first section of this chapter. Thereafter, we discuss the org.apache.catalina.Connector, interface and how to create the request and response objects. If you understand how the connector in Chapter 3 works, you should not find any problem understanding the default connector.
This chapter starts with three new features in HTTP 1.1. Understanding them is crucial to understanding the internal working of the default connector. Afterwards, it introduces org.apache.catalina.Connector, the interface that all connectors must implement. You then will find classes you have encountered in Chapter 3, such as HttpConnector, HttpProcessor, etc. This time, however, they are more advanced than the similar classes in Chapter 3.
This section explains three new features of HTTP 1.1. Understanding them is crucial to understanding how the default connector processes HTTP requests.
Prior to HTTP 1.1, whenever a browser connected to a web server, the connection was closed by the server right after the requested resource was sent. However, an Internet page can contain other resources, such as image files, applets, etc. Therefore, when a page is requested, the browser also needs to download the resources referenced by the page. If the page and all resources it references are downloaded using different connections, the process will be very slow. That's why HTTP 1.1 introduced persistent connections. With a persistent connection, when a page is downloaded, the server does not close the connection straight away. Instead, it waits for the web client to request all resources referenced by the page. This way, the page and referenced resources can be downloaded using the same connection. This saves a lot of work and time for the web server, client, and the network, considering that establishing and tearing down HTTP connections are expensive operations.
The persistent connection is the default connection of HTTP 1.1. Also, to make it explicit, a browser can send the request header connection with the value keep-alive:
connection: keep-alive
The consequence of establishing a persistent connection is that the server can send byte streams from multiple resources, and the client can send multiple requests using the same connection. As a result, the sender must send the content length header of each request or response so that the recipient would know how to interpret the bytes. However, often the case is that the sender does not know how many bytes it will send. For example, a servlet container can start sending the response when the first few bytes become available and not wait until all of them ready. This means, there must be a way to tell the recipient how to interpret the byte stream in the case that the content-length header cannot be known earlier.
Even without having to send multiple requests or many responses, a server or a client does not necessarily know how much data it will send. In HTTP 1.0, a server could just leave out the content-length header and keep writing to the connection. When it was finished, it would simply close the connection. In this case, the client would keep reading until it got a -1 as an indication that the end of file had been reached.
HTTP 1.1 employs a special header called transfer-encoding to indicate that the byte stream will be sent in chunks. For every chunk, the length (in hexadecimal) followed by CR/LF is sent prior to the data. A transaction is marked with a zero length chunk. Suppose you want to send the following 38 bytes in 2 chunks, the first with the length of 29 and the second 9.
I'm as helpless as a kitten up a tree.
You would send the following:
1D\r\n
I'm as helpless as a kitten u 9\r\n
p a tree.
0\r\n
1D, the hexadecimal of 29, indicates that the first chunk consists of 29 bytes. 0\r\n indicates the end of the transaction.
HTTP 1.1 clients may send the Expect: 100-continue header to the server before sending the request body and wait for acknowledgement from the server. This normally happens if the client is going to send a long request body but is not sure that the server is willing to accept it. It would be a waste if the client sent the long body just to find out the server turned it down.
Upon receipt of the Expect: 100-continue header, the server responds with the following 100-continue header if it is willing to or can process the request, followed by two pairs of CRLF characters.
HTTP/1.1 100 Continue
The server should then continue reading the input stream.
A Tomcat connector must implement the org.apache.catalina.Connector interface. Of many methods in this interface, the most important are getContainer, setContainer, createRequest, and createResponse.
setContainer is used to associate the connector with a container. getContainer returns the associated container. createRequest constructs a request object for the incoming HTTP request and createResponse creates a response object.
The org.apache.catalina.connector.http.HttpConnector class is an implementation of the Connector interface and is discussed in the next section, "The HttpConnector Class". Now, take a close look at Figure 4.1 for the UML class diagram of the default connector. Note that the implementation of the Request and Response interfaces have been omitted to keep the diagram simple. The org.apache.catalina prefix has also been omitted from the type names, except for the SimpleContainer class.
Figure 4.1: The default connector class diagram
Therefore, Connector should be read org.apache.catalina.Connector,util.StringManager org.apache.catalina.util.StringManager, etc.
A Connector has one-to-one relationship with a Container. The navigability of the arrow representing the relationship reveals that the Connector knows about the Container but not the other way around. Also note that, unlike in Chapter 3, the relationship between HttpConnector and HttpProcessor is one-to-many.
You already know how this class works because the simplified version of org.apache.catalina.connector.http.HttpConnector was explained in Chapter 3. It implements org.apache.catalina.Connector (to make it eligible to work with Catalina), java.lang.Runnable (so that its instance can work in its own thread), and org.apache.catalina.Lifecycle. The Lifecycle interface is used to maintain the life cycle of every Catalina component that implements it.
Lifecycle is explained in Chapter 6 and for now you don't have to worry about it except to know this: by implementing Lifecycle, after you have created an instance of HttpConnector, you should call its initialize and start methods. Both methods must only called once during the life time of the component. We will now look at those aspects that are different from the HttpConnector class in Chapter 3: how HttpConnector creates a server socket, how it maintains a pool of HttpProcessor, and how it serves HTTP requests.
The initialize method of HttpConnector calls the open private method that returns an instance of java.net.ServerSocket and assigns it to serverSocket. However, instead of calling the java.net.ServerSocket constructor, the open method obtains an instance of ServerSocket from a server socket factory. If you want to know the details of this factory, read the ServerSocketFactory interface and the DefaultServerSocketFactory class in the org.apache.catalina.net package. They are easy to understand.
In Chapter 3, the HttpConnector instance had only one instance of HttpProcessor at a time, so it can only process one HTTP request at a time. In the default connector, the HttpConnector has a pool of HttpProcessor objects and each instance of HttpProcessor has a thread of its own. Therefore, the HttpConnector can serve multiple HTTP requests simultaneously.
The HttpConnector maintains a pool of HttpProcessor instances to avoid creating HttpProcessor objects all the time. The HttpProcessor instances are stored in a java.io.Stack called processors:
private Stack processors = new Stack();
In HttpConnector, the number of HttpProcessor instances created is determined by two variables: minProcessors and maxProcessors. By default, minProcessors is set to 5 and maxProcessors 20, but you can change their values through the setMinProcessors and setMaxProcessors methods.
protected int minProcessors = 5;
private int maxProcessors = 20;
Initially, the HttpConnector object creates minProcessors instances of HttpProcessor. If there are more requests than the HttpProcessor instances can serve at a time, the HttpConnector creates more HttpProcessor instances until the number of instances reaches maxProcessors. After this point is reached and there are still not enough HttpProcessor instances, the incoming HTTP requests will be ignored. If you want the HttpConnector to keep creating HttpProcessor instances, set maxProcessors to a negative number. In addition, the curProcessors variable keeps the current number of HttpProcessor instances.
Here is the code that creates an initial number of HttpProcessor instances in the HttpConnector class's start method:
while (curProcessors < minProcessors) {
if ((maxProcessors > 0) && (curProcessors >= maxProcessors))
break;
HttpProcessor processor = newProcessor();
recycle(processor);
}
The newProcessor method constructs a new HttpProcessor object and increments curProcessors. The recycle method pushes the HttpProcessor back to the stack.
Each HttpProcessor instance is responsible for parsing the HTTP request line and headers and populates a request object. Therefore, each instance is associated with a request object and a response object. The HttpProcessor class's constructor contains calls to the HttpConnector class's createRequest and createResponse methods.
The HttpConnector class has its main logic in its run method, just like in Chapter 3. The run method contains a while loop where the server socket waits for an HTTP request until the HttpConnector is stopped.
while (!stopped) {
Socket socket = null;
try {
socket = serverSocket.accept();
...
For each incoming HTTP request, it obtains an HttpProcessor instance by calling the createProcessor private method.
HttpProcessor processor = createProcessor();
However, most of the time the createProcessor method does not create a new HttpProcessor object. Instead, it gets one from the pool. If there is still an HttpProcessor instance in the stack, createProcessor pops one. If the stack is empty and the maximum number of HttpProcessor instances has not been exceeded, createProcessor creates one. However, if the maximum number has been reached, createProcessor returns null. If this happens, the socket is simply closed and the incoming HTTP request is not processed.
if (processor == null) {
try {
log(sm.getString("httpConnector.noProcessor"));
socket.close();
}
... continue;
If createProcessor does not return null, the client socket is passed to the HttpProcessor class's assign method:
processor.assign(socket);
It's now the HttpProcessor instance's job to read the socket's input stream and parse the HTTP request. An important note is this. The assign method must return straight away and not wait until the HttpProcessor finishes the parsing, so the next incoming HTTP request can be served. Since each HttpProcessor instance has a thread of its own for the parsing, this is not very hard to achieve. You will see how this is done in the next section, "The HttpProcessor Class".
The HttpProcessor class in the default connector is the full version of the similarly named class in Chapter 3. You've learned how it worked and in this chapter we're most interested in knowing how the HttpProcessor class makes its assign method asynchronous so that the HttpConnector instance can serve many HTTP requests at the same time.
Note: Another important method of the HttpProcessor class is the private process method which parses the HTTP request and invoke the container's invoke method. We'll have a look at it in the section, "Processing Requests" later in this chapter.
In Chapter 3, the HttpConnector runs in its own thread. However, it has to wait for the currently processed HTTP request to finish before it can process the next: request. Here is part of the HttpConnector class's run method in Chapter 3:
public void run() {
...
while (!stopped) {
Socket socket = null;
try {
socket = serversocket.accept();
} catch (Exception e) { continue;}
// Hand this socket off to an Httpprocessor
}
}
The process method of the HttpProcessor class in Chapter 3 is synchronous. Therefore, its run method waits until the process method finishes before accepting another request.
In the default connector, however, the HttpProcessor class implements java.lang.Runnable and each instance of HttpProcessor runs in its own thread, which we call the "processor thread". For each HttpProcessor instance the HttpConnector creates, its start method is called, effectively starting the "processor thread" of the HttpProcessor instance. Listing 4.1 presents the run method in the HttpProcessor class in the default connector:
Listing 4.1: The HttpProcessor class's run method.
public void run() {
// Process requests until we receive a shutdown signal
while (!stopped) {
// Wait for the next socket to be assigned
Socket socket = await();
if (socket == null)
continue;
// Process the request from this socket
try {
process(socket);
}catch (Throwable t) {
log("process.invoke", t);
}
// Finish up this request
connector.recycle(this);
}
// Tell threadStop() we have shut ourselves down successfully
synchronized (threadSync) { threadSync.notifyAll();
} }
The while loop in the run method keeps going in this order: gets a socket, process it, calls the connector's recycle method to push the current HttpProcessor instance back to the stack. Here is the HttpConenctor class's recycle method:
void recycle(HttpProcessor processor) {
processors.push(processor);
}
Notice that the while loop in the run method stops at the await method. The await method holds the control flow of the "processor thread" until it gets a new socket from the HttpConnector. In other words, until the HttpConnector calls the HttpProcessor instance's assign method. However, the await method runs on a different thread than the assign method. The assign method is called from the run method of the HttpConnector. We name the thread that the HttpConnector instance's run method runs on the "connector thread". How does the assign method tell the await method that it has been called? By using a boolean called available, and by using the wait and notifyAll methods of java.lang.Object.
Note The wait method causes the current thread to wait until another thread invokes the notify or the notifyAll method for this object.
Here is the HttpProcessor class's assign and await methods:
synchronized void assign(Socket socket) {
// Wait for the processor to get the previous socket
while(available) {
try {
wait();
}catch (InterruptedException e) { }
}
// Store the newly available Socket and notify our thread
this.socket = socket;
available = true;
notifyAll();
...
}
private synchronized Socket await() {
// Wait for the Connector to provide a new Socket
while (!available) { try {
wait();
}catch (InterruptedException e) { } }
// Notify the Connector that we have received this Socket
Socket socket = this.socket;
available = false;
notifyAll();
if ((debug >= 1) && (socket != null))
log(" The incoming request has been awaited");
return (socket);
}
The program flows of both methods are summarized in Table 4.1.
Table 4.1: Summary of the await and assign method
The processor thread (the await method) The connector thread (the assign method)
while (!available) { while (available) { wait(); wait(); }}
Socket socket = this.socket; available = false;
notifyAll();
return socket; // to the run // method
this.socket = socket; available = true; notifyAll();
...
Initially, when the "processor thread" has just been started, available is false, so the thread waits inside the while loop (see Column 1 of Table 4.1). It will wait until another thread calls notify or notifyAll. This is to say that calling the wait method causes the "processor thread" to pause until the "connector thread" invokes the notifyAll method for the HttpProcessor instance.
Now, look at Column 2. When a new socket is assigned, the "connector thread" calls the HttpProcessor's assign method. The value of available is false, so the while loop is skipped and the socket is assigned to the HttpProcessor instance's socket variable:
this.socket = socket;
The "connector thread" then sets available to true and calls notifyAll. This wakes up the processor thread and now the value of available is true so the program controls goes out of the while loop: assigning the instance's socket to a local variable, sets available to false, calls notifyAll, and returns the socket, which eventually causes the socket to be processed.
Why does the await method need to use a local variable (socket) and not return the instance's socket variable? So that the instance's socket variable can be assigned to the next incoming socket before the current socket gets processed completely.
Why does the await method need to call notifyAll? Just in case another socket arrives when the value of available is true. In this case, the "connector thread" will stop inside the assign method's while loop until the nofifyAll call from the "processor thread" is received.
The HTTP Request object in the default connector is represented by the org.apache.catalina.Request interface. This interface is directly implemented by the RequestBase class, which is the parent of HttpRequest. The ultimate implementation is HttpRequestImpl, which extends HttpRequest. Like in Chapter 3, there are facade classes: RequestFacade and HttpRequestFacade. The UML diagram for the Request interface and its implementation classes is given in Figure 4.2. Note that except for the types belonging to the javax.servlet and javax.servlet.http packages, the prefix org.apache.catalina has been omitted.
Figure 4.2: The Request interface and related types
If you understand about the request object in Chapter 3, you should not have problems understanding the diagram.
The UML diagram of the Response interface and its implementation classes is given in Figure 4.3.
Figure 4.3: The Response interface and its implementation classes
At this point, you already understand about the request and response objects and how the HttpConnector object creates them. Now is the last bit of the process. In this section we focus on the process method of the HttpProcessor class, which is called by the HttpProcessor class's run method after a socket is assigned to it. The process method does the following:
Each operation is discussed in the sub-sections of this section after the process method is explained.
The process method uses the boolean ok to indicate that there is no error during the process and the boolean finishResponse to indicate that the finishResponse method of the Response interface should be called.
boolean ok = true;
boolean finishResponse = true;
In addition, the process method also uses the instance boolean variables keepAlive, stopped, and http11.keepAlive indicates that the connection is persistent, stopped indicates that the HttpProcessor instance has been stopped by the connector so that the process method should also stop, and http11 indicates that the HTTP request is coming from a web client that supports HTTP 1.1.
Like in Chapter 3, a SocketInputStream instance is used to wrap the socket's input stream. Note that, the constructor of SocketInputStream is also passed the buffer size from the connector, not from a local variable in the HttpProcessor class. This is because HttpProcessor is not accessible by the user of the default connector. By putting the buffer size in the Connector interface, this allows anyone using the connector to set the buffer size.
SocketInputStream input = null;
OutputStream output = null;
// Construct and initialize the objects we will need
try {
input = new SocketInputStream(socket.getInputstream(), connector.getBufferSize());
} catch (Exception e) {
ok = false;
}
Then, there is a while loop which keeps reading the input stream until the HttpProcessor is stopped, an exception is thrown, or the connection is closed.
keepAlive = true;
while (!stopped && ok && keepAlive) {
... }
Inside the while loop, the process method starts by setting finishResponse to true and obtaining the output stream and performing some initialization to the request and response objects.
finishResponse = true; try {
request.setStream(input);
request.setResponse(response);
output = socket.getOutputStream();
response.setStream(output);
response.setRequest(request);
((HttpServletResponse) response.getResponse()).setHeader
("Server", SERVER_INFO);
} catch (Exception e) {
log("process.create", e);
//logging is discussed in Chapter 7
ok = false;
}
Afterwards, the process method start parsing the incoming HTTP request by calling the parseConnection, parseRequest, and parseHeaders methods, all of which are discussed in the sub-sections in this section.
try {
if (ok) {
parseConnection(socket);
parseRequest(input, output);
if (!request.getRequest().getProtocol()
.startsWith("HTTP/0"))
parseHeaders(input);
The parseConnection method obtains the value of the protocol, which can be HTTP 0.9, HTTP 1.0 or HTTP 1.1. If the protocol is HTTP 1.0, the keepAlive boolean is set to false because HTTP 1.0 does not support persistent connections. The parseHeaders method will set the sendAck boolean to true if an Expect: 100-continue header is found in the HTTP request.
If the protocol is HTTP 1.1, it will respond to the Expect: 100-continue header, if the web client sent this header, by calling the ackRequest method. It will also check if chunking is allowed.
if (http11) {
// Sending a request acknowledge back to the client if
// requested.
ackRequest(output);
// If the protocol is HTTP/1.1, chunking is allowed.
if (connector.isChunkingAllowed())
response.setAllowChunking(true);
}
The ackRequest method checks the value of sendAck and sends the following string if sendAck is true:
HTTP/1.1 100 Continue\r\n\r\n
During the parsing of the HTTP request, one of the many exceptions might be thrown. Any exception will set ok or finishResponse to false. After the parsing, the process method passes the request and response objects to the container's invoke method.
try {
((HttpServletResponse) response).setHeader
("Date", FastHttpDateFormat.getCurrentDate());
if (ok) {
connector.getContainer().invoke(request, response);
}
}
Afterwards, if finishResponse is still true, the response object's finishResponse method and the request's object finishRequest methods are called, and the output is flushed.
if (finishResponse) {
...
response.finishResponse();
...
request.finishRequest();
...
output.flush();
The last part of the while loop checks if the response's Connection header has been set to close from inside the servlet or if the protocol is HTTP 1.0. If this is the case, keepAlive is set to false. Also, the request and response objects are then recycled.
if ( "close".equals(response.getHeader("Connection")) ) {
keepAlive = false;
}
// End of request processing
status = Constants.PROCESSOR_IDLE;
// Recycling the request and the response objects
request.recycle();
response.recycle();
}
At this stage, the while loop will start from the beginning if keepAlive is true, there is no error during the previous parsing and from the container's invoke method, or the HttpProcessor instance has not been stopped. Otherwise, the shutdownInput method is called and the socket is closed.
try {
shutdownInput(input);
socket.close();
} ...
The shutdownInput method checks if there are any unread bytes. If there are, it skips those bytes.
The parseConnection method obtains the Internet address from the socket and assigns it to the HttpRequestImpl object. It also checks if a proxy is used and assigns the socket to the request object. The parseConnection method is given in Listing 4.2.
Listing 4.2: The parseConnection method
private void parseConnection(Socket socket) throws IOException, ServletException {
if (debug >= 2)
log(" parseConnection: address=" + socket.getInetAddress() + ", port=" + connector.getPort());
((HttpRequestImpl) request).setInet(socket.getInetAddress());
if (proxyPort != 0)
request.setServerPort(proxyPort);
else
request.setServerPort(serverPort);
request.setSocket(socket);
}
The parseRequest method is the full version of the similar method in Chapter 3. If you understand Chapter 3 well, you should be able to understand how this method works by reading the method.
The parseHeaders method in the default connector uses the HttpHeader and DefaultHeaders classes in the org.apache.catalina.connector.http package. The HttpHeader class represents an HTTP request header. Instead of working with strings like in Chapter 3, the HttpHeader class uses character arrays to avoid expensive string operations. The DefaultHeaders class is a final class containing the standard HTTP request headers in character arrays:
static final char[] AUTHORIZATION_NAME = "authorization".toCharArray();
static final char[] ACCEPT_LANGUAGE_NAME = "accept-language".toCharArray();
static final char[] COOKIE_NAME = "cookie".toCharArray();
...
The parseHeaders method contains a while loop that keeps reading the HTTP request until there is no more header to read. The while loop starts by calling the allocateHeader method of the request object to obtain an instance of empty HttpHeader. The instance is passed to the readHeader method of SocketInputStream.
HttpHeader header = request.allocateHeader();
// Read the next header
input.readHeader(header);
If all headers have been read, the readHeader method will assign no name to the HttpHeader instance, and this is time for the parseHeaders method to return.
if (header.nameEnd == 0) {
if (header.valueEnd == 0) {
return;
} else {
throw new ServletException
(sm.getString("httpProcessor.parseHeaders.colon"));
}
}
If there is a header name, there must also be a header value:
String value = new String(header.value, 0, header.valueEnd);
Next, like in Chapter 3, the parseHeaders method compares the header name with the standard names in DefaultHeaders. Note that comparison is performed between two character arrays, not between two strings.
if (header.equals(DefaultHeaders.AUTHORIZATION_NAME)) {
request.setAuthorization(value);
}
else if (header.equals(DefaultHeaders.ACCEPT_LANGUAGE_NAME)) {
parseAcceptLanguage(value);
}
else if (header.equals(DefaultHeaders.COOKIE_NAME)) {
// parse cookie
}
else if (header.equals(DefaultHeaders.CONTENT_LENGTH_NAME)) {
// get content length
}
else if (header.equals(DefaultHeaders.CONTENT_TYPE_NAME)) {
request.setContentType(value);
}
else if (header.equals(DefaultHeaders.HOST_NAME)) {
// get host name
}
else if (header.equals(DefaultHeaders.CONNECTION_NAME)) {
if (header.valueEquals(DefaultHeaders.CONNECTION_CLOSE_VALUE)) {
keepAlive = false;
response.setHeader("Connection", "close");
}

}
else if (header.equals(DefaultHeaders.EXPECT_NAME)) {
if (header.valueEquals(DefaultHeaders.EXPECT_100_VALUE))
sendAck = true;
else
throw new ServletException(sm.getstring
("httpProcessor.parseHeaders.unknownExpectation"));
}
else if (header.equals(DefaultHeaders.TRANSFER_ENCODING_NAME)) {
//request.setTransferEncoding(header);
}
request.nextHeader();
The main purpose of the application in this chapter is to show how to use the default connector. It consists of two classes: ex04.pyrmont.core.SimpleContainer and ex04 pyrmont.startup.Bootstrap. The SimpleContainer class implements org.apache.catalina.container so that it can be associated with the connector. The Bootstrap class is used to start the application, we have removed the connector module and the ServletProcessor and StaticResourceProcessor classes in the application accompanying Chapter 3, so you cannot request a static page.
The SimpleContainer class is presented in Listing 4.3.
Listing 4.3: The SimpleContainer class
package ex04.pyrmont.core;
import java.beans.PropertyChangeListener;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import java.io.File;
import java.io.IOException;
import javax.naming.directory.DirContext;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.Cluster;
import org.apache.catalina.Container;
import org.apache.catalina.ContainerListener;
import org.apache.catalina.Loader;
import org.apache.catalina.Logger;
import org.apache.catalina.Manager;
import org.apache.catalina.Mapper;
import org.apache.catalina.Realm;
import org.apache.catalina.Request;
import org.apache.catalina.Response;
public class SimpleContainer implements Container {
public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";
public SimpleContainer() { } public String getInfo() {
return null;
}
public Loader getLoader() {
return null;
}
public void setLoader(Loader loader) { }
public Logger getLogger() {
return null;
}
public void setLogger(Logger logger) { }
public Manager getManager() { return null; }
public void setManager(Manager manager) { }
public Cluster getCluster() { return null; }
public void setCluster(Cluster cluster) { }
public String getName() { return null; }
public void setName(String name) { }
public Container getParent() { return null; }
public void setParent(Container container) { }
public ClassLoader getParentClassLoader() { return null;}
public void setParentClassLoader(ClassLoader parent) { }
public Realm getRealm() { return null; }
public void setRealm(Realm realm) { }
public DirContext getResources() { return null; }
public void setResources(DirContext resources) { }
public void addChild(Container child) { }
public void addContainerListener(ContainerListener listener) { }
public void addMapper(Mapper mapper) { }
public void addPropertyChangeListener(PropertyChangeListener listener) { }
public Container findchild(String name) { return null; }
public Container[] findChildren() { return null;}
public ContainerListener[] findContainerListeners() { return null; }
public Mapper findMapper(String protocol) { return null;}
public Mapper[] findMappers() { return null; }
public void invoke(Request request, Response response)throws IoException, ServletException {
string servletName = ( (Httpservletrequest)
request).getRequestURI();
servletName = servletName.substring(servletName.lastIndexof("/") +
1);
URLClassLoader loader = null;
try {
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classpath = new File(WEB_ROOT);
}
string repository = (new URL("file",null,
classpath.getCanonicalpath() + File.separator)).toString();
urls[0] = new URL(null, repository, streamHandler);
loader = new URLClassLoader(urls);
}catch (IOException e) {
System.out.println(e.toString() );
Class myClass = null;
try {
myClass = loader.loadclass(servletName);
} catch (classNotFoundException e) {
System.out.println(e.toString());
}
servlet servlet = null;
try {
servlet = (Servlet) myClass.newInstance();
servlet.service((HttpServletRequest) request,
(HttpServletResponse) response);
}catch (Exception e) {
System.out.println(e.toString());
}catch (Throwable e) {
System.out.println(e.toString());
}
}
public Container map(Request request, boolean update) { return null; }
public void removeChild(Container child) { }
public void removeContainerListener(ContainerListener listener) { }
public void removeMapper(Mapper mapper) { }
public void removoPropertyChangeListener(
PropertyChangeListener listener) { }
}
I only provide the implementation of the invoke method in the SimpleContainer class because the default connector will call this method. The invoke method creates a class loader, loads the servlet class, and calls its service method. This method is very similar to the process method in the ServletProcessor class in Chapter 3.
The Bootstrap class is given in Listing 4.4.
Listing 4.4: The ex04.pyrmont.startup.Bootstrap class
package ex04.pyrmont.startup;
import ex04.pyrmont.core.simplecontainer;
import org.apache.catalina.connector.http.HttpConnector;
public final class Bootstrap {
public static void main(string[] args) {
HttpConnector connector = new HttpConnector(); SimpleContainer
container = new SimpleContainer();
connector.setContainer(container);
try {
connector.initialize();
connector.start();
// make the application wait until we press any key.
System in.read();
}catch (Exception e) {
e.printStackTrace();
}
}
}
The main method of the Bootstrap class constructs an instance of org.apache.catalina.connector.http.HttpConnector and a SimpleContainer instance. It then associates the connector with the container by calling the connector's setContainer method, passing the container. Next, it calls the connector's initialize and start methods. This will make the connector ready for processing any HTTP request on port 8080.
You can terminate the application by pressing a key on the console.
To run the application in Windows, from the working directory, type the following:
java -classpath ./lib/servlet.jar;./ ex04.pyrmont.startup.Bootstrap
In Linux, you use a colon to separate two libraries.
java -classpath ./lib/servlet.jar:./ ex04.pyrmont.startup.Bootstrap
You can invoke PrimitiveServlet and ModernServlet the way you did in Chapter 3. Note that you cannot request the index.html file because there is no processor for static resources.
This chapter showed what it takes to build a Tomcat connector that can work with Catalina. It dissected the code of Tomcat 4's default connector and built a small application that used the connector. All applications in the upcoming chapters use the default connector.