Chapter 8. Integrating Tomcat 5 and Apache 2

There are two ways (that I know of) to integrate Tomcat with Apache 2:

8.1. Using mod_jk2

I don't know if mod_jk still works for Tomcat 5. I do know that mod_webapp development seems to have lapsed, so I shall concentrate on mod_jk2 integration. If you know of other methods of integrating Tomcat and Apache, please let me know !

8.1.1. Getting the mod_jk2 connector

The Jakarta group released version 2.0.4 of the mod_jk2 connector on 26 March 2004. This release includes source packages and RPM packages for Fedora Core 1, Suse 9 and Suse SLES 8. I will only be covering the source package compilation on Fedora Core for this document.

Binary RPM packages are available for Fedora Core 1, SUSE 9.0 and SUSE SLES at the Jakarta download site or mirrors. I'm not going to be covering the RPM packages in this document.

This release cleans up a lot of the bugs and annoyances that used to plague users of the previous release. It's actually quite good -- but then, Henri Gomez does good work !

The source package cannot be accessed directly from the links on the Jakarta site (strangely enough!). And because the Jakarta site will redirect you to the nearest mirror, the location of the source package may vary from site to site. My local mirror has the source tarball in this location: http://apache.oss.eznetsols.org/jakarta/tomcat-connectors/jk2/source/

If you clicked on the link for the JK2 Binary Releases on the Jakarta site, you will need to click on "Parent Directory", then "source" directory. (Note: your location may vary!)

This document has been updated for : jakarta-tomcat-connectors-jk2-2.0.4-src.tar.gz

8.1.2. Server Environment and System Requirements

Because many of the complexities of setting up mod_jk2 come from the environment itself, such as the layout of the Apache 2 files, or the availability of libraries, I will need to define my environment for this exercise. If your environment differs, please note that you may have to make some changes to the instructions here.

Table 8-1. Server Components

Linux Distribution Java Virtual Machine Apache version Tomcat version mod_jk2 version
Fedora Core 1 IBM Java 2 SDK v1.4.1 SR1 2.0.48 5.0.18 2.0.4

The following libraries were also installed:

  • apr-util-0.9.4-2

  • apr-0.9.4-2

  • apr-util-devel-0.9.4-2

  • apr-devel-0.9.4-2

  • automake-1.7.8-1

  • autoconf-2.57-3

  • m4-1.4.1-14

  • libtool-libs-1.5-8

  • libtool-1.5-8

  • pcre-4.4-1

  • pcre-devel-4.4-1

I believe that they are all required for the compilation of the JK2 source packages. If you get a /lib/cpp sanity check failure when running the configure script, check that you have the following installed:

  • libstdc++-devel-3.3.2-1

  • gcc-c++-3.3.2-1

For all the above items, I am not sure if the version matters, but these versions worked for me.

8.1.3. Exercise Overview

Building the mod_jk2 binary and integrating Tomcat and Apache is easier if we define a set of steps.

Integrating Apache and Tomcat with JK2

  1. Install Tomcat and verify that it is working properly

  2. Install Apache 2 and verify that it is working properly

  3. Shutdown both Tomcat and Apache

  4. Download, build and install the mod_jk2 source files

  5. Edit the configuration files httpd.conf, jk2.properties, workers2.properties and server.xml.

  6. Start Tomcat

  7. Start Apache

  8. Verify that everything works

8.1.4. Building mod_jk2

8.1.4.1. Install Tomcat and verify that it is working properly

Install Tomcat and verify that all the servlet and JSP examples work properly. If you worked through the earlier sections, you should already have a working Tomcat setup.

8.1.4.2. Install Apache 2 and verify that it is working properly

I am using the RPM packages for Fedora Core for this exercise. I will not be covering the installation of the source tarball or other RPM packages. In any case, there are plenty of resources and documentation available for this task.

Verify that Apache 2 is working by starting and stopping the http daemon and try to access web pages on it.

8.1.4.3. Shutdown both Tomcat and Apache

If you are ready to build the mod_jk2 binary from the source package, you should shutdown both Tomcat and Apache. We will start them up momentarily, after we have finished the next step.

8.1.4.4. Download, compile and install the mod_jk2 source files

If you have already downloaded the package, navigate to the download directory and unpack the tarball.

[chongym@localhost mod_jk2]$ tar -zxvf jakarta-tomcat-connectors-jk2-2.0.4-src.tar.gz

Now, you will need to navigate to the build directory.

[chongym@localhost mod_jk2]$ cd jakarta-tomcat-connectors-jk2-2.0.4-src/jk/native2/
[chongym@localhost native2]$ ls
aclocal.m4      BUILD.txt     common        INSTALL.txt  scripts
apr             build.xml     configure     jni          server
autom4te.cache  CHANGES.html  configure.in  Makefile.in  STATUS.txt
buildconf.sh    CHANGES.txt   include       README.txt   tomcat

For the next few steps, you will need to be root user to build the JK2 binary.

[chongym@localhost native2]$ su root
Password: 
[root@localhost native2]# ./configure --with-apxs2=/usr/sbin/apxs \
--with-tomcat-41=/opt/tomcat \ 
--with-apr-lib=/usr/lib \
--with-java-home=/opt/IBMJava2-141 \ 
--with-jni

When you execute "configure", the switches that you will use will probably differ from mine if you are not using Fedora Core 1, or if you built your own Apache binaries. These are my settings:

APXS path: /usr/sbin/apxs

Tomcat path: /opt/IBMJava2-141 - yes, I am using the IBM Java 2 SDK

JAVA_HOME: I have enabled the JNI switch. Why? I'm not really sure. Just that every other documentation out there says so

If the configure completed successfully, you should see these lines at the end of the process:

config.status: creating server/apache13/Makefile
config.status: creating server/apache13/Makefile.apxs
config.status: creating server/apache2/Makefile
config.status: creating server/apache2/Makefile.apxs
config.status: creating ../build.properties
config.status: creating scripts/build/unix/dummy
config.status: executing depfiles commands

Now, you can execute the make command,

[root@localhost native2]# make

If it completes successfully, you should see something like this:

libtool: install: warning: remember to run `libtool --finish
/usr/lib/httpd/modules'
/bin/cp ../../../build/jk2/apache2//usr/lib/httpd/modules/libjkjni.so
../../../build/jk2/apache2/libjkjni.so
make[1]: Leaving directory
`/home/chongym/downloads/jk2/jakarta-tomcat-connectors-jk2-2.0.4-src/jk/native2/server/apache2'

We don't really need to run 'libtool', but for the sake of completeness, let's execute it as suggested by the messages above.

[root@localhost native2]# libtool --finish
----------------------------------------------------------------------
Libraries have been installed in:

If you ever happen to want to link against installed libraries
in a given directory, LIBDIR, you must either use libtool, and
specify the full pathname of the library, or use the `-LLIBDIR'
flag during linking and do at least one of the following:
   - add LIBDIR to the `LD_LIBRARY_PATH' environment variable
     during execution
   - add LIBDIR to the `LD_RUN_PATH' environment variable
     during linking
   - use the `-Wl,--rpath -Wl,LIBDIR' linker flag
   - have your system administrator add LIBDIR to `/etc/ld.so.conf'

See any operating system documentation about shared libraries for
more information, such as the ld(1) and ld.so(8) manual pages.
----------------------------------------------------------------------

Now, we run the APXS tool against the mod_jk2 shared library.

[root@localhost native2]# cd ../build/jk2/apache2
[root@localhost apache2]# /usr/sbin/apxs -n jk2 -i mod_jk2.so

If the apxs command executed successfully, you should see this:

/usr/lib/httpd/build/instdso.sh SH_LIBTOOL='/bin/sh /usr/lib/apr/build/libtool'
mod_jk2.so /usr/lib/httpd/modules
/bin/sh /usr/lib/apr/build/libtool --mode=install cp mod_jk2.so
/usr/lib/httpd/modules/
cp mod_jk2.so /usr/lib/httpd/modules/mod_jk2.so
Warning!  dlname not found in /usr/lib/httpd/modules/mod_jk2.so.
Assuming installing a .so rather than a libtool archive.
chmod 755 /usr/lib/httpd/modules/mod_jk2.so

At this point I would like to explain a little about what apxs is and what it does. According to the Apache documentation, APXS is the APache eXtenSion Tool. It's stated purpose is for building and installing extension modules to Apache by building Dynamic Shared Object (DSO) files from source files. What this means is that I can add extensions to the Apache web server without needing to re-compile it.

If the apxs command executed successfully, you should find both mod_jk2.so and libjkjni.so inside the current directory, which is: your_download_directory/jakarta-tomcat-connectors-jk2-2.0.4-src/jk/build/jk2/apache2

You should then copy those two files into /usr/lib/httpd/modules/. Note that you will have to rename libjkjni.so to jkjni.so.

[root@localhost apache2]# cp libjkjni.so /usr/lib/httpd/modules/jkjni.so
[root@localhost apache2]# cp mod_jk2.so /usr/lib/httpd/modules/mod_jk2.so

8.1.5. Edit Configuration Files httpd.conf, jk2.properties, workers2.properties and server.xml.

At this point, you have to make a choice about the type of connection channel you want to use to connect Apache with Tomcat. There are 4 types of connections.

  • Channel sockets

  • UNIX sockets

  • APR sockets

  • JNI channels

8.1.6. Configuring for UNIX sockets

I will only be covering the configuration for UNIX sockets in this document.

8.1.6.1. Environment Variables

You will need to set one environment variable inside catalina.sh. Locate the following code block inside the script:

# Get standard environment variables
PRGDIR=`dirname "$PRG"`
CATALINA_HOME=`cd "$PRGDIR/.." ; pwd`
if [ -r "$CATALINA_HOME"/bin/setenv.sh ]; then
  . "$CATALINA_HOME"/bin/setenv.sh
fi

Append below it the following lines and save the file.

# Set serverRoot
serverRoot=/etc/httpd2
export serverRoot

Next, you will need to edit your configuration files. Try not to use the default files that come with your source download. The default files have a lot of options inside them, and may not work.

You MUST ensure that Apache and Tomcat have been shutdown before editing these files because they may be over-written when either daemon is started.

There are 3 files you will need to edit and one file you will need to check:

Table 8-2. Configuration Files For UNIX sockets

Filename Location Action To Take
httpd.conf /etc/httpd/conf/ Edit this file
jk2.properties /opt/tomcat/conf Create this file
workers2.properties /etc/httpd/conf Create this file
server.xml /opt/tomcat/conf Check this file

8.1.6.2. httpd.conf

You will need to make Apache2 aware of the Tomcat connector. Add the following lines into httpd.conf, at the end of the LoadModule block:

LoadModule jk2_module /usr/lib/httpd/mod_jk2.so

8.1.6.3. jk2.properties

Backup the existing jk2.properties file in this directory, then create a new jk2.properties implementing UNIX sockets, as shown below :

# jk2.properties
# Configured for channel UNIX

# Set the desired handler list
handler.list=apr,request,channelUnix

# UNIX Domain socket location
channelUnix.file=/opt/tomcat/work/jk2.socket

# Dynamic Library
serverRoot=/etc/httpd
apr.NativeSo=/usr/lib/httpd/modules/jkjni.so

8.1.6.4. workers2.properties

A sample workers2.properties implementing UNIX sockets is shown below :

# workers2.properties

# Shared memory handling. Needs to be set.
[shm]
info=Scoreboard. Required for reconfiguration and status with multiprocess serve
rs
file=/opt/tomcat/logs/jk2.shm
size=1048576
debug=0
disabled=0

# UNIX domain socket
[channel.un:/opt/tomcat/work/jk2.socket]
tomcatId=localhost:8009
debug=0

# define the worker
[ajp13:/opt/tomcat/work/jk2.socket]
channel=channel.un:/opt/tomcat/work/jk2.socket

# Announce a "status" worker
[status:status]
info=Status worker. Displays runtime information.

[uri:/jkstatus/*]
group=status:status

# Uri mapping
[uri:/examples/*]

# Uri mapping for MyFirst
[uri:/MyFirst/*]

8.1.6.5. server.xml

You will need to ensure that the following code block in server.xml is uncommented. By default, it already is, but if you have been tinkering with your setup, you would probably want to check, just to be sure.

    <!-- Define a Coyote/JK2 AJP 1.3 Connector on port 8009 -->
    <Connector port="8009" 
               enableLookups="false" redirectPort="8443" debug="0"
               protocol="AJP/1.3" />

8.1.6.6. Check for jk2.shm

Earlier, you defined a file jk2.shm in workers2.properties. This file should be automatically created when Tomcat is run. Start Tomcat now, if you have not already done so and verify that the file is created and is writeable by the UNIX user that the Tomcat daemon runs under. For this exercise, that user would be 'tomcat'.

8.1.6.7. jk2.socket Permissions

This file is crucial for communications between Apache and Tomcat, and therefore must be readable and writeable by both the Apache user and the Tomcat user. This file is also automatically generated when Tomcat starts up. This file has read and write permissions for user and group. This means that you need to put both the Apache user and the Tomcat user in the same group. For this exercise, we will put Fedora's Apache user, called 'apache' inside the Tomcat group, called 'tomcat':

[root@localhost logs]# usermod -G apache,tomcat apache

8.1.6.8. Deploying Web Applications

For every web application, we must supply a Uri mapping. For example, for our MyFirst web application, we simply append to our workers2.properties the following:

[uri:/MyFirst/*]

Now, you are ready to test. Since both Apache and Tomcat are stopped, we will need to start them up.

8.1.7. Start Tomcat

For the configurations defined here, the startup processes for Apache 2 and Tomcat are "de-coupled", that is, when you start Apache, Tomcat does not automatically start up as well. This is important for troubleshooting. At this point we want to know if the basic configuration works at all.

The startup sequence for our configuration is : you start Tomcat first, then Apache. On startup, Tomcat reads its own configuration files such as server.xml and web.xml first, then reads jk2.properties, from which it extracts some important information :

  • Which handlers to use

  • The port it listens on

  • The hostname or IP address it expects the connection from Apache to originate from

To check on the progress of the startup, do this as tomcat user,

[tomcat@localhost tomcat]$ tail -f $CATALINA_HOME/logs/catalina.out

8.1.8. Start Apache

Once Tomcat has started up, you can start Apache. It would be a good idea to view the Apache logs for details and/or errors, at this point.

To do this, as root, execute the following command :

[root@localhost run]# tail -f /var/httpd2/logs/error_log

8.1.9. Verify that everything works

Open a browser and key in the URL. For our web application 'MyFirst', the URL to key in for the HelloWorld servlet would be http://localhost/MyFirst/HelloWorld. If you get an error, check to see if you followed the instructions closely. Another thing to check are the error logs.

If everything works, congratulations ! You have successfully integrated Apache and Tomcat !

8.2. Reverse Proxy with Apache

If you don't want to mess with the complexities of mod_jk2, but you need to put Apache in front of Tomcat, there is an alternative, and that is to setup reverse proxying in Apache.

According to the ApacheWeek website, "a reverse proxy is a gateway for servers, and enables one web server to provide content from another transparently". This can sometimes result in better response times, because of caching, and also better security for servers behind a firewall.

With all these advantages, why would one want to use mod_jk2, you may ask ? Well, that's a question I really am not qualified to answer. The only drawback that I know of, for proxying, is that your web application sees only one client, and that is the Apache server. This makes it difficult for auditing and forensics -- should the need arise. There are ways to resolve this programmatically, of course, but I will not be covering that here.

8.2.1. System Requirements

These instructions cover :

  • Fedora Core 2

  • httpd-2.0.49-4

If you choose this method of integration, be aware that you may need to know your server's DNS name and interfaces. This is because I will be using the name-based virtual hosting method on Apache. Be aware of this difficulty, especially if you do not come from a networking background.

8.2.2. Exercise Overview

This tour of reverse proxying assumes the following:

  • I will be setting up reverse proxy for a host called www.virago.com.sg. There is, of course no such host or domain on the public Internet. You can substitute your favorite domain and hostname for this name.

  • Tomcat and Apache exist on the same machine. If your deployment has Apache and Tomcat on separate machines, you will need to refer to the VirtualHost stanza and locate where it defines Tomcat's server hostname in the ProxyPass and ProxyPassReverse directives and make the appropriate changes.

8.2.3. Edit Tomcat's server.xml

You will need to locate the following stanza and uncomment it.

    <!-- Define a Proxied HTTP/1.1 Connector on port 8082 -->
    <!-- See proxy documentation for more information about using this. -->
    <!--
    <Connector port="8082" 
               maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
               enableLookups="false"
               acceptCount="100" debug="0" connectionTimeout="20000"
               proxyPort="80" disableUploadTimeout="true" />
    -->

Note that in the stanza above, we are using port 8082 as the reverse proxy port for Tomcat. You will need to remember this when we edit Apache's configuration.

8.2.4. Edit Apache's httpd.conf

You need to ensure that the following proxy directives inside httpd.conf are uncommented (which they normally are by default)

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_ftp_module modules/mod_proxy_ftp.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_connect_module modules/mod_proxy_connect.so

There are actually 2 ways that you can add reverse proxying to Apache. The first involves using the <Location> directive while the second involves setting up a Virtual Host definition. I will be covering the second option.

Suppose that we have a virtual host www.virago.com.sg, and its IP address is 192.168.0.200, and we want to use name-based virtual hosting.

We add the following line into httpd.conf:

NameVirtualHost 192.168.0.200:80

Then we setup the virtual host stanza like so:

<VirtualHost 192.168.0.200:80>
        ServerName www.virago.com.sg
        DocumentRoot /var/www/html
        DirectoryIndex index.html index.shtml
</VirtualHost>

We only need to add a couple of lines to enable reverse proxying. I'll just show the full virtual host stanza with the two lines included below:

<VirtualHost 192.168.0.200:80>
        ServerName www.virago.com.sg
        DocumentRoot /var/www/html
        DirectoryIndex index.html index.shtml
        ProxyPass  /MyFirst  http://www.virago.com.sg:8082/MyFirst
        ProxyPassReverse  /MyFirst  http://www.virago.com.sg:8082/MyFirst
</VirtualHost>

Before we continue, let me explain the setup above:

  • I am assuming that both Tomcat and Apache are installed on the same box. This is why the ProxyPass and ProxyPassReverse refer to http://www.virago.com.sg:8082 which is the URL to reach Tomcat. In your deployment, that is probably not going to be the case, so you need to replace that URL with the URL for your Tomcat server.

  • For the ProxyPass and ProxyPassReverse directives, note that I defined two "arguments": the first "argument", /MyFirst refers to the URL that a user keys in, while the second "argument" refers to where that URL maps to. So, if a user opens a browser and keys in http://www.virago.com.sg/MyFirst, that request gets sent to Tomcat server URL http://www.virago.com.sg:8082/MyFirst via Apache's proxy.

Note that if your server needs to be accessible from the public Internet, there may be a few things you need to change in your DNS entries, especially if you wish to expose the Tomcat server to the Internet. It is perfectly all right to hide Tomcat behind a firewall, while the Apache server sits on the DMZ, but you need to ensure that the Apache server can resolve Tomcat's address and reach it.

This is not a comprehensive explanation of virtual hosting. If you require more clarification, or if you encounter problems with this setup, or if this setup is not what you want, please refer to the Apache documentation on virtual hosting or search for it on the Internet.

8.2.5. Testing Apache's Reverse Proxy

To test this setup, simply start your Tomcat and Apache servers and open a browser. Type in the URL http://www.virago.com.sg/MyFirst/HelloWorld and you should see the Tomcat page come up.

A lot of the time, the reason for most difficulties is that Apache cannot reach the Tomcat server, either because of firewall rules that block access on the proxy port (i.e. 8082 is Tomcat's default. Check what your proxy port is in server.xml), or because of name resolution problems. To see if this is the case, you need to look inside Tomcat's logs. If you are using a clean system, with no special logging defined, the access log is localhost_log.(some_date).txt. Check if any requests are coming into Tomcat. If none are, then you know you have a network problem. Speak to your network administrator to resolve it.