Samples

SamplesXml

Code

Config

API Docs

Download

Neolectric

Configuring Apache/mod_jk2/Tomcat to run dxp pages

First we'll setup a couple of Virtual Hosts hostname.com and ssl.hostname.com and configure Apache to deliver their static files. This assumes we have created directories for the host files.

for host hostname.com
/home/hostname
/home/hostname/log
/home/hostname/prop
/home/hostname/htdocs
/home/hostname/htdocs/error
/home/hostname/htdocs/WEB-INF

for host ssl.hostname.com
/home/sslhostname
/home/sslhostname/log
/home/sslhostname/prop
/home/sslhostname/htdocs
/home/sslhostname/htdocs/error
/home/sslhostname/htdocs/WEB-INF

Create customized error pages and put them in the error directory of each host. Apache and Tomcat will return these when specific errors occur.

If the host domain belongs to an outside company with a webmaster or developer I give them a restricted user account and set /home/hostname as their default home directory. Apache and Tomcat must be able to read files in htdocs. You can enhance security by running Apache and Tomcat under different user accounts and setting file and directory permissions to limit access to specific users and groups. See the User and Groups section later.

Next, we'll modify the main Apache config file httpd.conf to set up a Virtual Host for hostname.com.

file: /usr/local/apache/conf/httd.conf 
(perhaps /etc/httpd/conf/httpd.conf if you installed from rpm)

# put in the IP address here
NameVirtualHost xxx.xxx.xxx.xxx.

<VirtualHost xxx.xxx.xxx.xxx>
 ServerName hostname.com
 ServerAlias www.hostname.com
 ServerAdmin jdoe@mycopr.com
 DocumentRoot /home/hostname/htdocs
 ErrorLog /home/hostname/log/error_log
 ErrorDocument 401 /error/notauthorized.html
 ErrorDocument 403 /error/forbidden.html
 ErrorDocument 404 /error/notfound.html
 ErrorDocument 500 /error/internalerr.html
 ErrorDocument 503 /error/unavailable.html
 CustomLog /home/hostname/log/access_log common
 <Directory "/home/hostname/htdocs">
    Options FollowSymLinks
    AllowOverride AuthConfig
 </Directory>
 <Location /WEB-INF/ >
    AllowOverride None
    deny from all
 </Location>
</VirtualHost>

Then we'll do something similar to set up a default ssl host for ssl.hostname.com by modifying the apache config file ssl.conf.

file: /usr/local/apache/conf/ssl.conf
(perhaps /etc/httpd/conf/ssl.conf if you installed from rpm)

# Modify this VitrualHost section to specify your document root and error docs:
<VirtualHost _default_:443>

DocumentRoot "/home/sslhostname/htdocs"
ServerName ssl.hostname.com:443
ServerAdmin jdoek@hostname.com
ErrorLog /home/sslhostname/log/error_log
TransferLog /home/sslhostname/log/access_log
ErrorDocument 401 /error/notauthorized.html
ErrorDocument 403 /error/forbidden.html
ErrorDocument 404 /error/notfound.html
ErrorDocument 500 /error/internalerr.html
ErrorDocument 503 /error/unavailable.html
<Directory "/home/sslhostname/htdocs">
    Options FollowSymLinks
    AllowOverride AuthConfig
</Directory>
<Location /WEB-INF/ >
    AllowOverride None
    deny from all
</Location>

When Apache starts up it loads various modules. You can tell Apache to pass requests for certain file types and file paths to Tomcat using the jk2 module. First you need to compile the module and store it in the modules directory of your apache installation. Then you need to add this line to your main apache config file httpd.conf.

LoadModule jk2_module modules/mod_jk2.so
The jk2_module will look for the file workers2.properties in the same directory as httd.conf. Here is a sample that is set to pass requests for .dxp pages and the SnoopServlet to Tomcat.

file: /usr/local/apache/conf/workers2.properties
(perhaps /etc/httpd/conf/workers2.properties if you installed apache from rpm)

# A minimal JK2 connector configuration file to pass .dxp requests to Tomcat
[logger]
info=Native logger
level=ERROR

[config:]
file=${serverRoot}/conf/workers2.properties
debug=0
debugEnv=0

[uriMap:]
info=Maps the requests.
debug=0

[shm:]
info=Scoreboard. Required for reconfiguration and status with multiprocess servers
file=anonymous
debug=0

[workerEnv:]
info=Global server options
timing=0
debug=0

[lb:lb]
info=Default load balancer.
debug=0

[channel.socket:localhost:8009]
info=Ajp13 forwarding over socket
debug=0
tomcatId=localhost:8009

# pass requests for .dxp pages to Tomcat
[uri:/*.dxp]
info=dxp mapping
debug=0

# ucomment if you run .jsp pages
# [uri:/*.jsp]
# info=jsp servlet
# debug=0

In the rest of the discussion I will refer to the Tomcat installation directory as $TOMCAT_HOME. In my case this is /usr/local/jakarta-tomcat-5.x.x where x is the minor update number.I normally put this into /usr/local and create a symlink called tomcat.

cd /usr/local
ln -s jakarta-tomcat-5.x.x tomcat

When Tomcat starts up it reads the file $TOMCAT_HOME/conf/server.xml to find out what you want it to do. Here is an example that loads application zones in couple of Virtual Hosts: hostname.com and ssl.hostname.com. We tell Tomcat to listen for connections from an Apache module on port 8009 and not to accept http connections from the outside. We skip loading the default Tomcat apps and pass requests to the Virtual Hosts.

file: $TOMCAT_HOME/conf/server.xml

<Server port="8005" shutdown="SHUTDOWN">
  <Service name="Catalina">
  	
  <!-- Define a Coyote/JK2 AJP 1.3 Connector (for apache) on port 8009 -->
  <Connector port="8009" enableLookups="false"
   redirectPort="8443" debug="0" protocol="AJP/1.3" />
			   
  <Engine name="Catalina" defaultHost="hostname.com" debug="4">
   <Logger className="org.apache.catalina.logger.FileLogger" />

   <!-- hostname.com host -->
   <Host name="hostname.com" debug="4" appBase="/home/hostname/htdocs"
    autoDeploy="false" xmlValidation="false" xmlNamespaceAware="true">
    <Alias>www.hostname.com</Alias>
    <Context path="" docBase="/home/hostname/htdocs" debug="0" reloadable="false"/>
   </Host>
	  
   <!-- ssl.hostname.com host -->
   <Host name="ssl.hostname.com" debug="4" appBase="/home/sslhostname/htdocs"
    autoDeploy="true" xmlValidation="false" xmlNamespaceAware="true">
    <Context path="" docBase="/home/sslhostname/htdocs" debug="4" reloadable="false"/>
   </Host>
	  	  
  </Engine>
 </Service>
</Server>

Based on the path you specified for appBase and docBase in server.xml, Tomcat will look for a "webapp" configuration file called web.xml in each Virtual Host that tells it which servlets to load. For the hosts listed above, the config files will be:

/home/hostname/htdocs/WEB-INF/web.xml 
/home/sslhostname/htdocs/WEB-INF/web.xml

Tomcat hard codes the location WEB-INF/web.xml to the appBase so you must configure Apache to stay out of the WEB-INF directory because you don't want apache to deliver web.xml to browsers. That's why we put this block into the Virtual Host sections of httpd.conf and ssl.conf above.

<Location /WEB-INF/ >
  AllowOverride None
  deny from all
</Location>

Now we'll configure each Virtual Host to run servlets and classes that support the dxp application framework. The classes must be added to Tomcat's classpath. The easiest way to do this is to add the required jar files to the directory $TOMCAT_HOME/shared/lib

mysql-connector-java-3.x.x-production-bin.jar
xercesImpl.jar
dxp.jar

The mysql jar file provides a JDBC connectivity to MySQL and is available from mysql.com. The xerces jar may be found in the $TOMCAT_HOME/common/endorsed directory. If not there, you can find it at the Xerces site at xml.apache.org. The dxp.jar file is available from the Neolectric Download page. Make sure you have MySQL installed and create the required tables before you attempt to create database pools or do authentication.

Here is a web.xml file for hostname.com that tells Tomcat to load the ContextServlet and DxpServlet for this host. It also tells Tomcat to return specific error documents for some error types. These are the same files we told Apache to return for those errors. The xml tag format used to set parameters in server.xml extreemly verbose.

file: /home/hostname/htdocs/WEB-INF/web.xml

<?xml version="1.0" encoding="ISO-8859-1"?>

<!-- Servlet config file ready by Tomcat for host hostname.com.
 Tomcat expects this file to be stored in the filepath ../WEB-INF/web.xml
 which is relative to the appBase defined in $TOMCAT_HOME/conf/server.xml
 for this Host. -->

<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> <!-- ContextServlet loads service classes -->
  <servlet-name>ctxtservlet</servlet-name>
  <servlet-class>com.neolectric.servlet.ContextServlet</servlet-class>
  <init-param> <!-- file that describes service classes -->
   <param-name>contextFile</param-name>
   <param-value>/home/hostname/prop/context.xml</param-value>
  </init-param>
  <init-param> <!-- xml parser that reads config values -->
   <param-name>parser</param-name>
   <param-value>org.apache.xerces.parsers.SAXParser</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup> <!-- load this servlet first --> 
 </servlet>
	
 <servlet> <!-- DxpServlet delivers dynamic dxp pages -->
  <servlet-name>dxpservlet</servlet-name>
  <servlet-class>com.neolectric.servlet.DxpServlet</servlet-class>
  <init-param>
   <param-name>maxPost</param-name>
   <param-value>8192</param-value>
  </init-param>
  <init-param>
   <param-name>debug</param-name>
   <param-value>false</param-value>
  </init-param>
  <init-param> <!-- how often a cached page is checked for changes to the src file -->
   <param-name>updateInterval</param-name>
   <param-value>5</param-value> <!-- sec -->
  </init-param>
  <init-param>
   <param-name>docroot</param-name>
   <param-value>/home/hostname/htdocs</param-value>
   </init-param>
  <init-param>
   <param-name>errdoc</param-name>
   <param-value>/home/hostname/htdocs/dxperrdoc.html</param-value>
  </init-param>
  <init-param>
   <param-name>notauthdoc</param-name>
   <param-value>/home/hostname/htdocs/error/notauthorized.html</param-value>
  </init-param>
  <init-param>
   <param-name>excludeDir</param-name> <!-- comma delim list -->
   <param-value>/home/hostname/htdocs/WEB-INF</param-value>
  </init-param>
  <init-param>
   <param-name>errtag</param-name>
   <param-value>[msg]</param-value>
  </init-param>
  <init-param>
   <param-name>parser</param-name>
   <param-value>org.apache.xerces.parsers.SAXParser</param-value>
  </init-param>
  <load-on-startup>2</load-on-startup>
 </servlet>
 <servlet-mapping> 
  <servlet-name>dxpservlet</servlet-name> 
  <url-pattern>*.dxp</url-pattern> 
 </servlet-mapping>
	
 <!-- docs returned by servlet runner for various http errors;
 these are located in /home/hostname/htdocs/error -->
 <error-page>
  <error-code>401</error-code>
  <location>/error/notauthorized.html</location>
 </error-page>
 <error-page>
  <error-code>403</error-code>
  <location>/error/forbidden.html</location>
 </error-page>
 <error-page>
  <error-code>404</error-code>
  <location>/error/notfound.html</location>
 </error-page>
 <error-page>
  <error-code>500</error-code>
  <location>/error/internalerr.html</location>
 </error-page>
 <error-page>
  <error-code>503</error-code>
  <location>/error/unavailable.html</location>
</error-page>
	
</web-app>

Note that the <load-on-startup> parameter for the ContextServlet is set to 1 while the value for the DxpServlet is 2. This tells Tomcat to load the ContextServlet first so it can create and store service classes required by the DxpServlet and similar servlets.

The ContexServlet is a custom servlet and has an <init-param> called contextFile normally called context.xml, that lists the services to load and their parameters. IMPORTANT: this file contains sensitive information. Make sure that Apache can't deliver it by accident. The path is not hard coded (unlike web.xml) so you can store it outside the Apache DocumentRoot (outside /home/hostname/htdocs). If you run Tomcat under a differt user account that Apache (see Users and Groups section) set the file permission so Tomcat can read it but Apache can't. You may also want to keep the webmaster of the domain out of this file too.

file: /home/hostname/prop/context.xml

<?xml version='1.0' encoding='utf-8'?>

 <!-- This file is read by the ContextServlet to load service 
 classes that are used in this Virtual Host. It contains sensitive
 information and should be stored outside the directory path that
 Apache or Tomcat serve pages from.
 -->
<context name="hostname.com"> 
 
 <!-- Load pools of database connections in this host zone.
  Do this before other services that depend on connections.
  You can load multiple DbServices but they should each have
  a distinct pool-name, user-name and password.
 -->
 <DbService class="com.neolectric.dbutil.DbService">
  <pool>
   <name>hostname</name> <!-- named used by DbService to store this connection pool -->
   <driver>org.gjt.mm.mysql.Driver</driver>
   <connections>4</connections> <!-- how many active connections to open -->
   <queue>4</queue>             <!-- how many request can wait for a free connection -->
   <ping>select 1</ping>        <!-- sql stmt used to see if a connection is live -->
   <user>bozo</user>	        <!-- user stored in database permission table -->
   <password>xxxxxxxx</password><!-- password stored in database permission table -->
   <url>jdbc:mysql://srvname:3306/hostname</url> <!-- connection to database server -->
  </pool>
  <pool>
   <name>ubiz</name>            
   <driver>org.gjt.mm.mysql.Driver</driver>
   <connections>3</connections>
   <queue>4</queue>            
   <ping>select 1</ping>       
   <user>jdoe</user>           
   <password>xxxxxxxx</password>
   <url>jdbc:mysql://srvname/ubiz</url>
  </pool>
  <pool>
   <name>userdb</name>
   <driver>org.gjt.mm.mysql.Driver</driver>
   <connections>4</connections>
   <queue>4</queue>            
   <ping>select 1</ping>       
   <user>admin</user> 	       
   <password>xxxxxxxx</password>
   <url>jdbc:mysql://srvname:3306/userdb</url>
  </pool>
 </DbService>
 
 <!-- AuthService is used by dxp tags and some Servlets to check user privileges. 
 LocalAuthService uses a direct db connection and requires the name of a db pool
 that will connect it to realm tables. The pool should have been loaded by the
 DataService above. The ContextServlet will use a Timer to call the AuthService
 periodically and tell it to remove stale sessions. 
 --> 
 <AuthService class="com.neolectric.servlet.util.LocalAuthService"> 
  <max-items>1024</max-items> <!-- set max UserSessions allowed in cache -->
  <min-items>4</min-items>    <!-- skip cleaning if min sessions not exceeded -->
  <period>31</period>         <!-- (min) call run() at this interval to clean cache -->
  <refresh>30</refresh>       <!-- (min) a session idle for this long is stale -->
  <database>userdb</database> <!-- name of DbPool that has access realm tables -->
  <debug>true</debug>
 </AuthService> <!-- NOTE: may want a max-refresh-count value -->
 
 <!-- DbSessionManager used by AuthService for anonymous sessions and sessionIDs -->
 <DbSessionManager class="com.neolectric.servlet.util.DbSessionManager">
  <database>userdb</database> <!-- database that holds session table -->
  <threshold>64</threshold>   <!-- how many new sessions before clean is called -->
 </DbSessionManager>
 
 <!-- RealmManager (commented out) is used to manage the user database records.
 This can be used on an ssl host so that all the info is encrypted during transfer.
 <RealmManager class="com.neolectric.security.RealmManager">
  <database>userdb</database>
  <dir-access>/home/hostname</dir-access>
 </RealmManager>
 -->
 
 <!-- Cache used by DxpServlet to store compiled dxp pages. The ContextServlet
 will use a Timer to call ItemCache.run() periodically to clean out stale items.
 -->
 <PageCache class="com.neolectric.util.ItemCache">
  <max-items>64</max-items> <!-- set max pages allowed in cache -->
  <min-items>8</min-items>  <!-- skip cleaning if min items not exceeded -->
  <period>60</period>       <!-- (min) call run() at this interval to clean cache -->
  <refresh>10</refresh>     <!-- (min) page idle for this long is stale -->
  <debug>true</debug>       <!-- print cleaning msgs -->
 </PageCache>
 
</context>

 

Users and Groups - applies to Unix type systems

When you set up a Virtual Host directory structure you should think about who owns the files what the file and directory permissions are should be. There are 3 user accounts to consider: the user who runs Apache, the user who runs Tomcat and the user who can upload static and dynamic pages for the company that owns the website.

Apache is started by user root but it normally switches to user nobody when running. You can set the user and group name that Apache runs under in httpd.conf. These accounts must exist on your system. Check /etc/passwd and /etc/group or ask the sysadmin who manages your server.

Tomcat runs as the user account that started it. I prefer to run Tomcat under a different user account than Apache and never run Tomcat as root. I create a special user that I'll call tomcat for this discussion. Then I create a group called webuser and add tomcat and nobody to this group. If you need help ask your local Linux User Group or sysadmin who manages your server.

I give webuser permission to enter each /home/hostname directory and read files in the htdocs directory. My Tomcat setup reads a special config file /home/hostname/prop/context.xml that contains sensitive information so I store it outside htdocs and set the file permission so that only tomcat can read it.

If a Virtual Host has an outside webmaster or developer I create a restricted user account that I'll call this vhostuser and set /home/hostname as their default home directory. I allow the group webuser to enter /home/hostname by setting the group permission to webuser but I block other users by setting the other permission to off. Here's one way to do this. As root:

chown vhostuser:webuser /home/hostname
chmod o-wrx /home/hostname

IMPORTANT: don't give vhostuser a normal shell account that can run arbitray commands on the system. On many Linux distros you can do this by setting their default shell to /sbin/nologin or some other shell script that prints an error msg when they try to login. In many cases you can use an ftp server or other service that permits uploading but restricts shell command the user can run. If you need help, ask your sysadmin or Linux User Group.