How Servlet Containers Work
Pages: 1, 2, 3, 4
The ServletProcessor1 Class
The ServletProcessor1 class processes HTTP requests for
servlets. It is surprisingly simple, consisting only of the
process method. This method accepts two arguments: an instance of
javax.servlet.ServletRequest and an instance of
javax.servlet.ServletResponse. The process method
also constructs a java.net.URLClassLoader object and uses it to
load the servlet class file. Upon obtaining a Class object from
the class loader, the process method creates an instance of the
servlet and calls its service method.
The process method is given in Listing 2.4.
Listing 2.4. The ServletProcessor1 class'
process method
public void process(Request request, Response response) {
String uri = request.getUri();
String servletName = uri.substring(uri.lastIndexOf("/") + 1);
URLClassLoader loader = null;
try {
// create a URLClassLoader
URLStreamHandler streamHandler = null;
URL[] urls = new URL[1];
File classPath = new File(Constants.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((ServletRequest) request, (ServletResponse) response);
}
catch (Exception e) {
System.out.println(e.toString());
}
catch (Throwable e) {
System.out.println(e.toString());
}
}
The process method accepts two arguments: an instance of
ServletRequest and an instance of ServletResponse.
From the ServletRequest, it obtains the URI by calling the
getRequestUri method:
String uri = request.getUri();
Remember that the URI is in the following format:
/servlet/servletName
where servletName is the name of the servlet class.
To load the servlet class, we need to know the servlet name from the URI,
which we do using the next line of the process method:
String servletName = uri.substring(uri.lastIndexOf("/") + 1);
Next, the process method loads the servlet. To do this, you
need to create a class loader and tell this class loader the class' location.
This servlet container directs the class loader to look in the directory
pointed by Constants.WEB_ROOT. WEB_ROOT points to the
webroot/ directory under the working directory.
To load a servlet, use the java.net.URLClassLoader class, which
is an indirect child class of java.lang.ClassLoader. Once you have
an instance of the URLClassLoader class, use its
loadClass method to load a servlet class. Instantiating the
URLClassLoader class is straightforward. This class has three
constructors, the simplest one being:
public URLClassLoader(URL[] urls);
where urls is an array of java.net.URL objects
pointing to the locations on which searches will be conducted when loading a
class. Any URL that ends with a / is assumed to refer to a
directory. Otherwise, the URL is assumed to refer to a .jar file, which will be
downloaded and opened as needed.
In a servlet container, the location where a class loader can find servlet classes is called a repository.
In our application, there is only one location in which the class loader must
look — the webroot/ directory under the working directory.
Therefore, we start by creating an array of a single URL. The URL
class provides several constructors, so there are many ways to construct a
URL object. For this application, I used the same constructor used in another
class in Tomcat. The constructor has the following signature:
public URL(URL context, String spec, URLStreamHandler hander)
throws MalformedURLException
You can use this constructor by passing a specification for the second
argument and null for both the first and the third arguments.
However, there is another constructor that accepts three arguments:
public URL(String protocol, String host, String file)
throws MalformedURLException
Thus, the compiler will not know which constructor you mean if you write the following:
new URL(null, aString, null);
You can get around this by telling the compiler the type of the third argument, like this:
URLStreamHandler streamHandler = null;
new URL(null, aString, streamHandler);
For the second argument, pass the String containing the repository (the
directory where servlet classes can be found). Create it with this code:
String repository = (new URL("file", null,
classPath.getCanonicalPath() + File.separator)).toString();
Combining everything, here is the part of the process method
that constructs the right URLClassLoader instance:
// create a URLClassLoader
URLStreamHandler streamHandler = null;
URL[] urls = new URL[1];
File classPath = new File(Constants.WEB_ROOT);
String repository = (new URL("file", null,
classPath.getCanonicalPath() + File.separator)).toString() ;
urls[0] = new URL(null, repository, streamHandler);
loader = new URLClassLoader(urls);
The code that forms a repository comes from the
createClassLoader method in
org.apache.catalina.startup.ClassLoaderFactory, and the code for
forming the URL is taken from the addRepository method in
the org.apache.catalina.loader.StandardClassLoader class. However, you
don't have to worry about these classes at this stage.
Having a class loader, you can load a servlet class using the
loadClass method:
Class myClass = null;
try {
myClass = loader.loadClass(servletName);
}
catch (ClassNotFoundException e) {
System.out.println(e.toString());
}
Next, the process method creates an instance of the servlet
class loaded, downcasts it to javax.servlet.Servlet, and invokes
the servlet's service method:
Servlet servlet = null;
try {
servlet = (Servlet) myClass.newInstance();
servlet.service((ServletRequest) request, (ServletResponse) response);
}
catch (Exception e) {
System.out.println(e.toString());
}
catch (Throwable e) {
System.out.println(e.toString());
}
Compiling and Running the Application
To compile the application, type the following from the working directory:
javac -d . -classpath ./lib/servlet.jar src/ex02/pyrmont/*.java
To run the application on Windows, type the following command from the working directory:
java -classpath ./lib/servlet.jar;./ ex02.pyrmont.HttpServer1
in Linux, use a colon to separate between libraries.
java -classpath ./lib/servlet.jar:./ ex02.pyrmont.HttpServer1
To test the application, type the following in the URL or address box of your browser:
http://localhost:8080/index.html
or
http://localhost:8080/servlet/PrimitiveServlet
You will see the following text in your browser:
Hello. Roses are red.
Note that you can't see the second string (Violets are blue)
because only the first string is flushed to the browser. The applications
accompanying the later chapters of the How Tomcat Works book show you how to
fix this problem.