Chapter about advanced configurations and the internal architecture of Tomcat
--Placeholder
Placeholder
In this section we will dig deeper under the hood of Tomcat. We will examine the architecture with a deepened focus on the Tomcat interceptor infrastructure. The Tomcat interceptors allow you to configure Tomcat for different runtime configurations just changing your interceptor configuration. You can also write your own interceptors to handle your specific needs.
Tomcat is built using a very modular architecture. Several main concepts are used such as a Context Manager, Context, Interceptor, Server Connector and more. Since Tomcat is an open source project, you will have the source code readily available to you if you want to learn more about the architecture of Tomcat as you read through this chapter. We will make references to actual source code in this document, and this source code can be found in your $TOMCAT_HOME/src directory in the org.apache.tomcat and org.apache.jasper packages. Below is a partial and simplified overview of the Tomcat source code structure. The Jasper packages are not included, since a JSP gets compiled into a servlet during runtime, and then invoked in the same way as a servlet. The org.apache.jasper package contains the logic for how to compile the JSP and generate the servlet class. A graphical view of the Tomcat packages in given in
Your application WAR file sits is built using the javax.servlet APIs and is portable across any servlet/JSP container that implements the Sun specification. In tomcat, these interfaces (javax.servlet), are implemented in the org.apache.tomcat.facade package
The classes in this package are developed using the facade pattern. For example the interface javax.servlet.http.HttpSession is implemented in org.apache.tomcat.facade.HttpSessionFacade class but is actually realized in the org.apache.tomcat.session.StandardSession class.
Although Tomcat provides a plug-n-play environment where you can add and remove interceptors in any way you want. This is not completely true, most of the interceptors are essential for Tomcat to run properly, so if you remove any of those, the system will not run as expected. Later on in this section, we will describe what you can do and can't do with the plug-n-play functionality of the interceptors.
Now here is an interesting thing, how does Tomcat create a new session when a user makes a request? This happens in the following steps:
Your servlet makes a request on the HttpServletRequestFacade object and calls the method getSession( boolean );
The HttpServletRequestFacade holds a reference to an org.apache.tomcat.core.Request implementation and makes a Request.getSession request.
The implementation org.apache.tomcat.core.RequestImpl holds a member variable serverSession which is the implementation of the HttpSession interface
If the instance variable is null, the RequestImpl calls ContextManager.doNewSessionRequest(this,...).
The context manager loops through all the RequestInterceptor objects and calls the newSessionRequest(...) method.
In a default Tomcat configuration, the StandardSessionInterceptor intercepts the request instantiates a new StandardSession object, and associates the session with the request object.
You have now witnessed your very first example of how an interceptor is used in Tomcat. Although this example doesn't enlighten you, in the next few sections you will become extremely familiar with how the interceptors work and how you can take advantage of this architecture in order to improve and customize your Tomcat installation.
So what is an interceptor anyway?
Intercept as defined by dictionary.com "To stop, deflect, or interrupt the progress or intended course of"
And as defined by Pattern-Oriented Software Architecture (POSA) "The Interceptor architectural pattern allows services to be added transparently to a framework and triggered automatically when certain events occur."
An interceptor is essentially a regular Java object that intercepts a request or an event. In Tomcat an interceptor can modify the request and can even abort the request by returning an error code.
Another example to illustrate how an interceptor works, is when the request http://localhost:8080/ is received by Tomcat
As always the HttpConnectionHandler receives the request from the network, and invokes the ContextManager.processConnection(...) method. After a series of method calls the ContextManager loops through all the request interceptors configured.
and calls RequestInterceptor.requestMap() on each request interceptor After each interceptor call, the context manager checks the status of the request. If the status is different from 0 (0==OK) it will abort the request and handle the status code. In this example we will demonstrate the org.apache.tomcat.request.StaticInterceptor. This interceptor handles all the requests for static files as opposed to dynamic (JSP and servlets). It also has the ability to do localization file searches for different locales.
The StaticInterceptor.requestMap() method will first retrieve the path information, which in our example is "/"
Then the interceptor translates the path to an absolute path, in our case $TOMCAT_HOME/webapps/ROOT/
The interceptor now instantiates a java.io.File object and checks if file.isFile() returns true, which it doesn''t
It will then locate the welcome file, (Tomcat defaults to index.jsp, index.html, index.htm in that order, but can be modified in the server.xml file)
If the welcome file doesn’t exist, the interceptor returns the 404 (file not found) error code, but in our case it, index.html, does exist so the interceptor continues to process this request
The interceptor now creates a redirect request, "/index.html", in our case, and returns the 302 (re-direct) error code
The ContextManager, that invoked the interceptor, traps the error code, since it differs from 0. The error code is a redirect, so the context manager compiles the data and sends the response to the browser with the new location information
The browser makes a request to http://localhost:8080/index.html and the sequence starts over again
Ok, we admit it, this still doesn’t enlighten you the way we intend to, but please be patient with us, you will get there, just keep going.
One important aspect to understand is that the tomcat interceptors are sequential as opposed to chained. The picture below shows the difference
In a chained interceptor design the interceptors are linked together like a linked list. This means that interceptor (A) doesn't return from its method call until interceptor (B) does the same. In a sequential interceptor design, the caller first invokes interceptor (A), when interceptor (A) has completed the invokation, it will then invoke interceptor (B), and so on.
In a later on example we will write an interceptor that let's you monitor the performance of your servlet and JSP request.
Ok, let's get down to business about interceptors. We've been chatting on about examples and how great this architecture is, now we will tell you everything you need to know about interceptors and probably a little bit more than you want to know.
There are two types of interceptors in Tomcat, context interceptors and request interceptors. As indicated by the name, these interceptors handle request events and context events. A context is as defined by a WAR file, or actually by the directory structure. For example the test.war results in a context being created named "test". So a context interceptor handles events related to a context, this can be for example, a context is initialized, a servlet is loaded, a servlet is reloaded, etc. And a request interceptor handles events generated by a HTTP request, an example for this is the InvokerInterceptor that determines whether to invoke a servlet or a static file.
In this document we are making the distinction between context and request interceptors. In the actual Java source code of Tomcat, almost all these interceptors extend the class org.apache.tomcat.core.BaseInterceptor that implements both interfaces. So theoretically any interceptor that extends the BaseInterceptor can be used both as a context interceptor as well as a request interceptor. But in real life, the interceptors developed in Tomcat, only perform the functionality of one of these interfaces.
The interceptors are configured in the server.xml file as described later in this chapter.
So let's start by describing the context interceptors and how they work.
The first method in the context interceptor is the engineInit method. This method gets called when the ContextManager has is initialized. This is the very first event that gets intercepted. This is basically the first thing that happens when Tomcat starts up, after the server.xml file has been parsed and the core engine is configured.
A few examples of interceptors that implement the engineInit method are:
AutoSetup - This interceptor expands all the war files and creates a new context for each file, it then notifies the context manager of the new context that has been created, and this spawns a series of other events, like the contextInit event.
PolicyInterceptor - This interceptor creates a security manager and installs it into the VM based on the configuration for this interceptor.
The addContext event is called Now you remember how a context got created, that's right, it got created in the AutoSetup interceptor during the engineInit event. So this becomes a little bit tricky, the context manager generated the event, engineInit, that got intercepted by the AutoSetup interceptor, a context got created but this interceptor, and the AutoSetup made a call back to the context manager, and this generated a new event, the addContext that again got intercepted by the context interceptors. So ended up in a nested loop between the context interceptor methods.
Example of a addContext interceptor:
WebXmlReader - reads the web.xml file and fills the context with the information from this file
The contextInit method is invoked (remember that all the interceptor events are invoked by the context manager), when a new context is initialized. After all the contexts have been added the context manager initializes them by invoking the contextInit method on each of the context interceptors. An example of an interceptor that intercepts this event is the LoaderInterceptor. This interceptor creates a new class loader in form of an AdaptiveClassLoader and serves as a class loader to load classes from the WEB-INF/classes and WEB-INF/lib repositories. By using the AdaptiveClassLoader Tomcat can reload servlets and JSPs when they have changed during runtime.
Taken from the JavaDocs of Tomcat 3.2.1 JavaDoc A new location was added to the server. A location is defined as a set of URL patterns with common properties. All servlet mappings and security constraints are in this category - with a common handler and a common set of authorized roles Remember how the addContext event was a result of nested callback to the context manager from the AutoSetup interceptor. Well, when the addContainer event is generated in a similar way. When the WebXmlReader interceptor parses the web.xml file during the context init event it will add containers to the context and this generates the add container event. A container is a location, as specified in the JavaDoc. To give a more understandable example, it is a mapping. You know how you can specify a servlet mapping in the web.xml file. The simplest mapping is *.jsp and a JSP servlet wrapper. A servlet wrapper is an object that invokes a servlet. So this means that the container(*.jsp, jsp-wrapper) will intercept any URL with the path *.jsp and map it to the appropriate servlet wrapper. An example of an interceptor that intercepts this event is the SimpleMapper1 interceptor and the AccessInterceptor.
This method is called when a servlet is initialized. Servlet initialized means first time the servlet is loaded. Some of the servlets are loaded upon startup, by the LoadOnStartupInterceptor, others are loaded during runtime. The Jdk12Interceptor intercepts the preServletInit event and sets the class loader to be the servlet class loader for the executing thread.
By now you have a pretty good understanding of what the context interceptors do and their functionality. We will not describe the rest of the methods (events) since they are the counterparts of the methods (events) that we already described. For example, postServletInit, is the counterpart method to preServletInit.
For you as a developer(if that is what you are :), the section about the request interceptors is probably more interesting. The request interceptors handle the events when an actual HTTP (or other protocol) request is handled inside of Tomcat.
The contextMap method is called in the beginning of the request and maps the request properly to a context. For example:
The SessionInterceptor extracts the session ID during URL rewriting and the
SimpleMapper1 maps the request to a container, and sets URI properly
After the contextMap method has been invoked on all the interceptors, the context manager invokes the requestMap method on all the request interceptors. There are several interceptors that intercept this call.
SimpleMapper1 makes sure we don't try to access WEB-INF/ etc. will throw a 404 if you do
The InvokerInterceptor intercepts the calls that are for servlets and associates the correct servlet wrapper with the request.
The StaticInterceptor - catches calls for static files and locates the welcome files if the URL is a directory.
The StandardSessionInterceptor - reads cookies etc for your sessions
The JspInterceptor - compiles the JSP if needed and then maps the request into an actual servlet class using the ServletWrapper
This method is self explanatory. It is invoked prior to the service() method is invoked on servlet or JSP (JspServlet).
Invoked before the content is written to the output stream. The HttpAdapter holds a buffer of all the data that will be written to the output stream. When the body is written to the response stream, the HttpAdapter notifies the ContextManager. The context manager then invokes the beforeBody methods on the interceptors.
The SessionInterceptor is important here. It sets the session cookies (if cookies are used) in the HTTP header. The SessionInterceptor will take into account that each context has its own session cookie by setting the path in the cookie
Invoked before the buffer is committed (flushed) to the output stream. Currently no interceptors do any significant when this method is called
Invoked after the body is written to the output stream, this event occurs after the beforeCommit event. Currently no interceptors do any significant when this method is called.
This event is described in the example in the beginning of this chapter and occurs when the servlet calls HttpServletRequest.getSession.
When an authentication is required to be performed, this happens when the servlet calls HttpServletRequest.getRemoteUser().
The JDBCRealm interceptor will make a connection and authenticate the request user against the database. If the user is authenticated it will set the remote user of the request.
Let's take a detailed look on how you configure the Tomcat interceptors
It is important to know how Tomcat reads the server.xml file and dynamically configures the interceptors during runtime. First of all, the interceptors are loaded using reflection, so the constructor being used is the one without parameters. It will not do you any good if you create a constructors that takes parameters. Instead, after the interceptor is instantiated, Tomcat calls a set of set methods to initialize it. The set methods name has to match the attribute name with the first character upper case and the "set" prefix. For example, if you configure an interceptor with an attribute name of myAttribute the interceptor has to have a method with the name setMyAttribute(String).
The general syntax for an interceptor is:
<InterceptorType classname="some.class.name" attr1="value1" attr2="value2"/> where: InterceptorType = ContextInterceptor | RequestInterceptor classname = the fully qualified classname for the interceptor. And then a list of attributes.
For example:
<ContextInterceptor className="org.apache.tomcat.context.AutoSetup" /> or <RequestInterceptor className="org.apache.tomcat.request.JDBCRealm" debug="99" driverName="sun.jdbc.odbc.JdbcOdbcDriver" connectionURL="jdbc:odbc:TOMCAT" userTable="users" userNameCol="user_name" userCredCol="user_pass" userRoleTable="user_roles" roleNameCol="role_name"/>
<RequestInterceptor className="org.apache.tomcat.request.JDBCRealm" debug="99" driverName="sun.jdbc.odbc.JdbcOdbcDriver"…/>
The Tomcat engine will use reflection and invoke the public method setDriverName( String ) and setDebug( String ) on the interceptor. So the tomcat engine will map you attributeName to the public method setAttributeName. If you don't include a public method with the appropriate naming convention in your interceptor, there is no way of your interceptor to receive the value of the attribute.
If the server.xml doesn't contain any interceptor, Tomcat will startup properly. It will use a hard coded set of interceptors as default. The list is below.
Context Interceptors LogEvents AutoSetup LoaderInterceptor DefaultCMSetter WorkDirInterceptor WebXmlReader LoadOnStartupInterceptor Request Interceptors SessionInterceptor SimpleMapper1 StandardSessionInterceptor
In this simple example we will show you how you can very easily implement a request interceptor that will measure the performance of your servlets. A very simple, and very useful interceptor, use it wisely :). Remember, when you create an interceptor that measures performance, you affect the performance of the system. However, the margin is small enough to make this interceptor very useful.
First of all, you need to decide what kind of interceptor you want to implement. Since our interceptor will measure the performance of the requests into a servlet or JSP, we will be building a request interceptor. The primary methods that we are interested in are preService() and postService().
When creating an interceptor, the first thing you have to do is to either extend the base class, BaseInterceptor, or implement one of the interceptor interfaces. We don't want to write to much code, so we extend the base class
package com.filip.interceptor; import org.apache.tomcat.core.BaseInterceptor; import org.apache.tomcat.core.Request; import org.apache.tomcat.core.Response; public class PerformanceInterceptor extends BaseInterceptor {
We are interested in measuring the servlet performance we will implement the preService() and postService() methods. Since we know that each request is handled by a separate thread, we can use the class java.lang.ThreadLocal to keep track of data associated with the request.
//keep track of the current threads timer state private ThreadLocal tLocal = new ThreadLocal(); //print as ms or seconds //1 = ms //2 = sec private int format = 1;
In our preService method will take the current timestamp and store it in our thread local.
public int preService(Request request, Response response) { long start = System.currentTimeMillis(); Long value = new Long(start); tLocal.set( value ); return 0; }
When the postService method is called, we will take another timestamp, calculate the delta (the length of the request), format it into a string and print the result, including the servlet path and class, to the screen. Printing to the System.out may not be the best idea since it is a synchronized call. However, the measurement will still be accurate since the delta is calculated prior to that. Use log4j for asynchronous logging instead.
//preService is invoked right after service() is invoked on the //actual servlet public int postService(Request request, Response response) { long stop = System.currentTimeMillis(); Long value = (Long)tLocal.get(); if ( value != null ) { long start = ((Long)value).longValue(); tLocal.set( null ); StringBuffer outdata = new StringBuffer ("["+Thread.currentThread().getName()+"] "); if ( format == 2 ) { float time = ((float)(stop - start))/1000; outdata.append(" {"+time+" sec.} "); } else { outdata.append(" {"+(stop - start)+" ms.} "); } outdata.append(request.getServletPath()) .append(":class="); outdata.append(request.getWrapper().getServletClass()); System.out.println(outdata); } return 0; }And that's it. The full source code of the performance interceptor can be found at PerformanceInterceptor.java
In order for Tomcat to recognize the interceptor we have to create a server.xml entry. There are three ways to configure the performance interceptor:
1. Print the result performance time formatted seconds. <RequestInterceptor className="com.filip.interceptor.PerformanceInterceptor" format="sec"/> or 2. Print the result performance time formatted milliseconds. <RequestInterceptor className="com.filip.interceptor.PerformanceInterceptor" format="ms"/> or 3. Leave the default - milliseconds <RequestInterceptor className="com.filip.interceptor.PerformanceInterceptor"/>
Create a jar file containing all the necessary classes (com.filip.interceptor.PerformanceInterceptor) and place it in $TOMCAT_HOME/lib and Tomcat will automatically pick up the jar in the start script. If the start script tomcat.bat/tomcat.sh doesn't include the jar in the classpath, you have to modify the start script to do so. The interceptor classes have to be in the system classpath in order to be properly loaded.
For most accuracy, put the PerformanceInterceptor at the very end of the interceptor list. Otherwise you will be measuring the performance of other interceptors as well as the actual servlet that you wanted to monitor.
As you can see, the interceptor design pattern becomes quite useful if you want to modify the Tomcat servlet engines functionality. You could easily develop your own interceptors to do any functionality, including your own session management.
We hope you have enjoyed reading the chapter about interceptors. For more questions, contact the author, Filip Hanik, at <mail@filip.net>