anders.com: technology
Interesting Technology Pictures Projects Lectures The name "Anders" Store
 

Tomcat 5.5 / Apache 2.0 / PostgreSQL 8.1 Configuration

Sample server.xml, web.xml, httpd.conf, workers.properties and pg_hba.conf files that work.

Few configuration problems have taken me more time to figure out than getting database pooling working under Apache Tomcat 5.5. For some reason, configuring Tomcat is a horribly convoluded process. What you state in one config file might not be listened to because some other setting somewhere obscure is overriding it. It would seem that if you don't want to do exactly what Tomcat suggests in their sample config files, you are basically on your own. Heck, sometimes even if you do what they suggest, things don't work! Its amazing Tomcat has the takeup rate it does! But I digress.

What I wanted from Tomcat was a configuration suitable for servlet development and another for production that both support database pooling. I wanted to serve pages through Apache's http server using mod_jk for the dynamic pages. I would think this would be a common problem that has been solved many times over, but a quick scan of the net has revealed numerous pages just like this one where people list various configuration examples tied to specific versions of Tomcat for doing what I'm trying to do. It seems like every other example I have come across expects that I'm deploying WAR files and shows off the auto deploy feature. Well, I don't use WAR files and I don't want to auto deploy anything. I'm developing an application that I probably will never package up like that. All I will ever do is copy a class tree over to production. In my development framework, I want to have class files sitting right next to source files because that's where the compiler puts them. (and I'm lazy and I don't want to have to move things around)

What follows is a configuration I slaved over for about 2 weeks. I wanted to use Apache's HTTP Server and Apache's Tomcat 5.5 servlet runner. With the HTTP Server, I wanted to use mod_jk to trap specific requests and have Tomcat fulfill them. In Tomcat I wanted to be able to describe a global database resource and then have one or more host configurations use them. I didn't want to use the WAR auto deploy capability of Tomcat because I wanted to have a development friendly setup.

Generally I serve more than one domain from an Apache setup so I have a directory off of the root called /sites that contains one directory per virtual server named the FQDN. (ie: /sites/www.example.com) Within each directory are directories named "web" for the document tree, "logs" for the access and error logs and "conf" for site specific configuration files such as htaccess and htpasswd.

Apache HTTP Server 2.0 Setup

In this example, HTTP Server is installed in /usr/local/apache2 and serves documents from /sites/www.example.com/web. Here we load the JK Module and define a virtual host for www.example.com with JkMount directives. (most of the standard httpd.conf file is stripped out of this example)

/usr/local/apache2/conf/httpd.conf
...

# load and configure JK Module

LoadModule      jk_module       modules/mod_jk.so
JkWorkersFile   /usr/local/apache2/conf/jk-workers.properties
JkLogFile       /usr/local/apache2/logs/mod_jk.log

# Virtual hosts

<VirtualHost www.example.com:80>
    ServerAdmin webmaster@example.com
    DocumentRoot /sites/www.example.com/web
    ServerName www.example.com
    ErrorLog /sites/www.example.com/logs/error.log
    CustomLog /sites/www.example.com/logs/access.log common

    JkMount /*.jsp web01-worker
    JkMount /index.html web01-worker
    JkMount /login web01-worker
    JkMount /logout web01-worker
    JkMount /show/* web01-worker
</VirtualHost>

The jk-workers.properties file describes the attributes of the worker we defined in the httpd.conf.

/usr/local/apache2/conf/jk-workers.properties
worker.list=web01-worker

worker.web01-worker.type=ajp13
worker.web01-worker.host=localhost
worker.web01-worker.port=8908

PostgreSQL 8.1 Setup

I'm not going to go into this much as it's covered elsewhere, but you will need the JDBC driver for your version of PostgreSQL in the common library area. Mine is in:

/usr/local/tomcat/common/lib/postgresql-8.1-404.jdbc2.jar

Your pg_hba.conf file that controlls access to PostgreSQL might open up to all hosts supplying a password:

/usr/local/pgsql/data/pg_hba.conf
# TYPE  DATABASE    USER        CIDR-ADDRESS          METHOD
local   all         all                               password
host    all         all         0.0.0.0/0             password

Apache Tomcat 5.5 Setup

Tomcat is installed in /usr/local/tomcat and generally configured with two files plus a web.xml for each instance. Server-wide configuration is done in /usr/local/tomcat/conf/server.xml and /usr/local/tomcat/conf/web.xml and site specific configuration is done in /sites/www.example.com/web/WEB-INF/web.xml. I have tried to mantain this seperation as much as possible but Tomcat idiosyncrasies have limited me. If you find a better and more clear way of doing this that works universally, please let me know via the email address at the bottom right of this page.

In the server.xml file we define the Global Naming Resource "jdbc/testdb-global". Any number of Host declarations (here we have one) can refrence that global resource and make it available under a new name with ResourceLink. In this example, we call the database resource "jdbc/testdb". I also use a random string and a non-standard port for server shutdown so its a little harder for local users to shut things down and cause problems.

/usr/local/tomcat/conf/server.xml
<?xml version='1.0' encoding='utf-8'?>
 <Server port="8907" shutdown="2FA35BC73A9C65A985C8E45CA4B97ABC" 
  debug="0">
  <Listener 
   className="org.apache.catalina.mbeans.ServerLifecycleListener" 
   debug="0"/>
  <Listener 
    className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" 
    debug="0"/>
  <GlobalNamingResources>
   <Resource name="jdbc/testdb-global" auth="Container" 
     type="javax.sql.DataSource" driverClassName="org.postgresql.Driver" 
     url="jdbc:postgresql://1.2.3.4/testdb" 
     username="testdb" password="superSecretPassword" />
  </GlobalNamingResources>
  <Service name="service">
   <Connector name="connector" address="127.0.0.1" port="8908" 
     protocol="AJP/1.3" enableLookups="false" />
   <Engine name="engine" defaultHost="www.example.com" 
     debug="0">

    <Host name="www.example.com" debug="0" 
      appBase="/sites/www.example.com/web" unpackWARs="false" 
      autoDeploy="false">
     <Logger className="org.apache.catalina.logger.FileLogger" 
       directory="/sites/www.example.com/logs" 
       prefix="tomcat-" suffix=".log" timestamp="true"/>
     <Context path="" docBase="" reloadable="true" 
       swallowOutput="true">
      <WatchedResource>WEB-INF/web.xml</WatchedResource>
      <Logger className="org.apache.catalina.logger.FileLogger" 
        prefix="www-example-com-log." suffix=".txt" 
        timestamp="true"/>
      <ResourceLink name="jdbc/testdb" global="jdbc/testdb-global" 
        type="javax.sql.DataSource"/>
     </Context>
    </Host>

   </Engine>
  </Service>
 </Server>

We set some server-wide conventions in Tomcat's main web.xml file. In this example, we set some servlet mappings for common things like jsp and set some other usefull defaults.

/usr/local/tomcat/conf/web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
    http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
  <servlet>
    <servlet-name>jsp</servlet-name>
    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
    <init-param>
      <param-name>fork</param-name>
      <param-value>false</param-value>
    </init-param>
    <init-param>
      <param-name>xpoweredBy</param-name>
      <param-value>false</param-value>
    </init-param>
    <load-on-startup>3</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>jsp</servlet-name>
    <url-pattern>*.jsp</url-pattern>
  </servlet-mapping>

  <servlet-mapping>
    <servlet-name>jsp</servlet-name>
    <url-pattern>*.jspx</url-pattern>
  </servlet-mapping>

  <session-config>
    <session-timeout>30</session-timeout>
  </session-config>

  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>

</web-app>

For each site, we create a site specific web.xml file. We reiterate our jndi resource "jdbc/testdb" and set up our main servlet with some URLs mapped to it.

/sites/www.example.com/web/WEB-INF/web.xml
<web-app>
  <display-name>Example Application</display-name>
  <description>Just serves as an example.</description>

  <resource-ref>
    <description>
      Resource reference to a factory for java.sql.Connection
      instances that may be used for talking to the database
      that is configured in server.xml.
    </description>
    <res-ref-name>jdbc/testdb</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
  </resource-ref>

  <servlet>
    <servlet-name>jsp</servlet-name>
    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
    <init-param>
      <param-name>fork</param-name>
      <param-value>false</param-value>
    </init-param>
    <init-param>
      <param-name>xpoweredBy</param-name>
      <param-value>false</param-value>
    </init-param>
    <load-on-startup>3</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>jsp</servlet-name>
    <url-pattern>*.jsp</url-pattern>
  </servlet-mapping>

  <servlet>
    <servlet-name>handler</servlet-name>
    <description>Handles everything thrown at it.</description>
    <servlet-class>com.example.handler</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>handler</servlet-name>
    <url-pattern>/index.html</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>handler</servlet-name>
    <url-pattern>/login</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>handler</servlet-name>
    <url-pattern>/logout</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>handler</servlet-name>
    <url-pattern>/show/*</url-pattern>
  </servlet-mapping>

  <session-config>
    <session-timeout>30</session-timeout> 
  </session-config>

</web-app>

Notes:
All your class files would live in /sites/www.example.com/web/WEB-INF/classes in this example. For example, the handler servlet described in the example would live in:

/sites/www.example.com/web/WEB-INF/classes/com/example/handler.class

You will find the output of System.err.println( "..." ) in:

/usr/local/tomcat/logs/catalina.out

Ideally STDERR and STDOUT would be split up by site and live in the logs directory of each site (/sites/www.example.com/logs) but we are still working on a configuration that makes that happen reliably. If you have any input on this, please contact me through the address at the bottom right of this page. I'll update this as soon as we have a working solution.

If you can't get the resource ref going in /usr/local/tomcat/conf/server.xml, try declaring it in the /usr/local/tomcat/conf/context.xml file. In my example above, I don't use a context.xml file but this seems to be needed in one of our instances. To be clear, my example above comes from Tomcat 5.5.12 and when Tomcat 5.5.12 is installed on another instance, the context.xml seems to be required. We can't seem to figure out what the critical differience is though we haven't done an exhaustive search.

I'm trying to clean this up right now but haven't delved much into this. If anyone has tested and working examples of a configuration that writes logs into the site specific log directory on a site by site basis, I'd be interested to hear from you.

Some of my jsp definitions can probably be stripped out of the site specific web.xml and moved into the server-wide web.xml in this example. I'm still working on cleaning these things up but all code on this page comes from running production instances so it can be considered fully tested. I'll change this page once I clean up my code and test to make sure everything is still working.

Keep in mind that the url-pattern in the servlet-mapping section is horribly restricted. You would think you could trap a URL like /abc*.html with it but you can't. You are allowed to have an asterisk as either the first character or the last, but nowhere else. The JkMount directive in the Apache httpd configuration is significantly more flexible. If you need to trap a URL such as /abc*.html, you can trap /abc*.html in the Apache httpd configuration and then trap just *.html in the url-pattern in your WEB-INF/web.xml file. Its somewhat of a hack but it can get you around the servlet-mapping limitations in some situations.



user:   pass:   © 1995-2006