Using Tomcat And Apache HTTP Server

There are three options to choose from for configuring the Apache HTTP Server as a front-end proxy server for the TDS. They differ in their communication protocol, features, and ease of use:

  1. The Apache HTTP Server’s mod_proxy/mod_proxy_http with Tomcat’s HTTP connector

    This method is perhaps the simplest to implement, may not require much additional configuration in both Tomcat and the Apache HTTP Server, and is appropriate for basic reverse proxy scenarios.

  2. The Apache HTTP Server’s mod_proxy/mod_proxy_ajp modules with Tomcat’s AJP connector

    Utilizing mod_proxy_ajp with mod_proxy allows you to leverage the Apache JServ Protocol (AJP) to faciliate communication between the Apache HTTP Server and Tomcat. AJP typically offers improving performance, load balancing handling, static content serving, and security enhancements.

  3. The Apache Tomcat Connectors project’s mod_jk module with the Tomcat AJP connector

    mod_jk offers the performance advantages of AJP, in addition to advanced load balancing capabilities and node failure detection. This module does not come with the Apache HTTP Server and must be build and maintained separately.

Tomcat-Apache Proxy Documentation

Implementing The Tomcat-Apache Proxy Using HTTP

The following example shows how to implement a reverse proxy using the Apache HTTP Server and the Tomcat Servlet Container using HTTP via mod_proxy/mod_proxy_http.

Configure Apache HTTP Server To Use mod_proxy and mod_proxy_http

  1. Ensure your Apache HTTP Server installation includes the mod_proxy and mod_proxy_http modules.

    If you are uncertain, you can check to see if these modules are included either as a Dynamic Shared Object module or directly compiled into the httpd binary file by running the apachectl (a.k.a, Apache Control) command with the -M option:

       # /usr/local/apache/bin/apachectl -M
       Loaded Modules:
        core_module (static)
        ...
        proxy_module (shared)
        proxy_http_module (shared)
        ...
    
  2. Configure the reverse proxy in the Apache HTTP Server.

    Utilize the ProxyPass and ProxyPassReverse directives to tell Apache to forward http://yourhost.edu/thredds/* to the Tomcat HTTP connector listening on port 8080 on localhost:

    ProxyPass         /thredds  http://localhost:8080/thredds
    ProxyPassReverse  /thredds  http://localhost:8080/thredds
    

    Or if you are setting up a reverse proxy to a TDS hosted on a different server other than localhost:

    ProxyPass         /thredds  https://tds_host_location:8443/thredds
    ProxyPassReverse  /thredds  https://tds_home_location:8443/thredds
    

    Notice the use of the HTTPS protocol and port in the above example.

Configure Tomcat For The proxy

Configure the Tomcat HTTP Connector (or SSL/TLS HTTP/1.1 Connector) with the appropriate proxy settings:

   <Connector port="8080"
              protocol="HTTP/1.1"
              ...
              proxyName="yourhost.edu"
              proxyPort="80"/>

Implementing The Tomcat-Apache Proxy Using AJP

There are two options available for implementing a reverse proxy using the Apache HTTP Server and the Tomcat Servlet Container via AJP:

  • Option A: using the mod_proxy/mod_proxy_ajp modules; or
  • Option B: using the mod_jk module.

Option A: Configure Apache HTTP Server To Use mod_proxy and mod_proxy_ajp

  1. Ensure your Apache HTTP Server installation includes the mod_proxy and mod_proxy_ajp modules.

    If you are uncertain, you can check to see if these modules are included either as a Dynamic Shared Object module or directly compiled into the httpd binary file by running the apachectl (a.k.a, Apache Control) command with the -M option:

       # /usr/local/apache/bin/apachectl -M
       Loaded Modules:
        core_module (static)
        ...
        proxy_module (shared)
        proxy_ajp_module (shared)
        ...
    
  2. Configure the reverse proxy in the Apache HTTP Server.

    Utilize the ProxyPass and ProxyPassReverse directives to tell Apache to forward http://yourhost.edu/thredds/* to the Tomcat AJP connector listening on port 8080 on localhost:

    ProxyPass         /thredds  ajp://localhost:8080/thredds
    ProxyPassReverse  /thredds  ajp://localhost:8080/thredds
    

    Notice the use of the AJP protocol (ajp://) in the above example.

Option B: Configure Apache HTTP Server To Use mod_jk

Build and Install mod_jk

  1. Download the latest version of Tomcat’s mod_jk module.

  2. Build and install the mod_jk module as per the installation instructions that come bundled with the download. The build and installation will need to be done as either root, sudo, or as user with privileges to modify Apache.

     # tar xvfz tomcat-connectors-1.2.xx-src.tar.gz
     # cd tomcat-connectors-1.2.xx-src/native
     # ./configure --with-apxs=/usr/bin/apxs  <--- path to your apache apxs
     # make
     # make install
    

    Confirm the module was added to the directory in which the Apache HTTP Server stores its modules (/usr/local/apache/modules in this example):

     # cd /usr/local/apache/modules
     # ls -l  mod_jk.so
        
     -rwxr-xr-x 1 root root 1147204 Oct  8 12:34 mod_jk.so
    

Configure Apache HTTP Server To Use mod_jk To Communicate With Tomcat

  1. Update Apache configurations to use the mod_jk module.

    mod_jk was built as a DSO module, therefore you will need to update your Apache configurations to enable this 3rd-party module:

    The following example shows adding mod_jk configurations to a Apache HTTP Server built from source.
    Modify the main Apache server configuration file (usually httpd.conf) in the following manner (in this example /usr/local/apache/conf is where the Apache configuration files are located):

    # cd /usr/local/apache/conf
    # vi http.conf
    

    Add the following configurations to enable the mod_jk module, restrict access to web application WEB-INF and META-INF directories, and tell Apache where to find the workers.properties file using the JkWorkersFile directive. (The worker.properties file is discussed in more detail below.)

    # Third party modules
    LoadModule jk_module    modules/mod_jk.so
     
    <IfModule jk_module>
        JkWorkersFile "conf/workers.properties"
        JkShmFile "logs/mod_jk.shm"
        JkLogFile "logs/mod_jk.log"
        JkLogLevel warn
        #Tomcat Security Section
        <LocationMatch "/WEB-INF/">
            Require all denied
        </LocationMatch>
        
        <LocationMatch "/META-INF/">
            Require all denied
        </LocationMatch>
    </IfModule>
    
  2. Create a workers.properties file.

    The mod_jk modules in the Apache HTTP Server uses the workers.properties file to relevant map requests to the TDS using Tomcat’s AJP connector.

    Use your favorite text editor to create a workers.properties file in the Apache configuration directory that you specified in the previous step using the JkWorkersFile directive:

     # cd /usr/local/apache/conf
     # vi http.conf
    

    Add the following configurations to the workers.properties module to define a worker that will handle communication between the Apache HTTP Server and Tomcat.

     # workers.properties
     # To allow tomcat and apache to talk to each other.
     # needed by mod_jk
        
     # Define workers
     worker.list=worker1
        
     # Define workers using ajp13 protocol to forward requests to the Tomcat processes.
     worker.worker1.type=ajp13
        
     # TDS app
     # worker1 will talk to Tomcat listening on localhost at port 8009
     worker.worker1.host=localhost
     worker.worker1.port=8009
    
  3. Configure your Apache host to send the appropriate requests to TDS via mod_jk and Tomcat AJP.

    You will use the JkMount directives that come with the mod_jk to specify/match which URL requests should be proxied to the TDS in the Tomcat Servlet Container.

    The following shows an example of configuring an Apache VirtualHost with the JkMount directives.
    Note that these directives reference the worker configured in the workers.properties file in the previous step.

    <VirtualHost IP_ADDRESS:PORT>
        ServerName thredds.unidata.ucar.edu 
        ...
           
        # Proxy requests to the TDS
        JkMount /thredds* worker1
        JkMount /thredds worker1
        # Proxy requests to the Manager App
        JkMount /manager* worker1
        JkMount /manager worker1
           
        ...
    </VirtualHost>
    

Configure Tomcat And The TDS For The Proxy

Configuring Tomcat and the TDS for the reverse proxy will be the same regardless of what option for the Apache HTTP Server.

  1. Modify the Tomcat AJP Connector

    In the ${tomcat_home}/conf/server.xml file, locate the AJP Connector (uncommented and enabled by default) and add the following additional configuration to it:

    <!-- AJP 1.3 Connector on port 8009 -->
        <Connector port="8009" 
                   enableLookups="false"
                   useBodyEncodingForURI="true"
                   connectionTimeout="20000"
                   protocol="AJP/1.3" />
    
  2. Disable any active Java HTTP/1.1 Connector and the SSL HTTP/1.1 Connector Tomcat connectors.

    This will prevent direct communication to Tomcat via ports 8080 and 8443 ensuring the AJP proxy via Apache is the only HTTP method by which to access Tomcat and the TDS.

    Locate the Java HTTP/1.1 Connector listening on port 8080 and the active SSL HTTP/1.1 Connector listening on port 8443 and comment them out:

      <!--
        <Connector port="8080" 
                   protocol="HTTP/1.1"
                   connectionTimeout="20000"
                   redirectPort="8443" />
      -->
      ...
      <!--
        <Connector port="8443" 
                   protocol="org.apache.coyote.http11.Http11NioProtocol" 
                   maxThreads="150" 
                   SSLEnabled="true">
           <SSLHostConfig>
               <Certificate certificateKeystoreFile="conf/localhost-rsa.jks" 
                            type="RSA" />
           </SSLHostConfig>
        </Connector>
       -->
    
  3. Configure the TDS to relinquish control of TLS/SSL to Apache

    The TDS deployment descriptor (${tomcat_home}/webapps/thredds/WEB-INF/web.xml) is configured to only allow access parts of the TDS application via TLS/SSL.
    Because we’ve disabled Tomcat’s handling of the TLS/SSL, we need to update these configurations.

    Use your favorite editor to open the TDS ${tomcat_home}/webapps/thredds/WEB-INF/web.xml file.
    Around line 106 you’ll start seeing configurations that look like the following:

    <!-- tdsConfig with HTTPS needed for /admin access  -->
      <security-constraint>
         <web-resource-collection>
           <web-resource-name>sensitive read access</web-resource-name>
           <url-pattern>/admin/*</url-pattern>
         </web-resource-collection>
         <auth-constraint>
           <role-name>tdsConfig</role-name>
         </auth-constraint>
         <user-data-constraint>
           <transport-guarantee>CONFIDENTIAL</transport-guarantee>
         </user-data-constraint>
       </security-constraint>
     ...
    

    These configs restrict access to the url in the <url-pattern> tags to the role in the <role-name> tags (which correspond to the roles defined in the ${tomcat_home}/conf/tomcat-users.xml file).
    The <transport-guarantee>CONFIDENTIAL</transport-guarantee> says access must take place via HTTPS.

    For any restricted part of the TDS you want to access that is listed here, you’ll need to comment out the configurations in the <user-data-constraint> tags:

    <!-- tdsConfig with HTTPS needed for /admin access  -->
      <security-constraint>
         <web-resource-collection>
           <web-resource-name>sensitive read access</web-resource-name>
           <url-pattern>/admin/*</url-pattern>
         </web-resource-collection>
         <auth-constraint>
           <role-name>tdsConfig</role-name>
         </auth-constraint>
         <!-- do not use tomcat https
           <user-data-constraint>
             <transport-guarantee>CONFIDENTIAL</transport-guarantee>
           </user-data-constraint>
         -->
       </security-constraint>
     ...
    

Changing The TDS Context Path (/thredds)

We do not recommend changing the TDS context path (the /thredds part of the URL path). However, if your network configuration requires that you use a different context path (e.g., /my/thredds) or you are proxying two TDS installations and need to differentiate them with different context paths (e.g., /thredds1 and /thredds2 ), you will need to make the following changes:

  1. Rename the thredds.war file to match the desired context path before you deploy it to Tomcat. Tomcat and other servlet engines direct incoming requests to a particular web application when the beginning of the request URL path matches the context path of that particular webapp. The easiest way to let Tomcat (or any other servlet engine) know what context path to use for a given webapp is to rename that webapp’s .war file before deploying it to Tomcat.

    For instance, if you want all URLs starting with /thredds2 to be handled by your TDS install, rename the thredds.war file to thredds2.war before you deploy it to Tomcat.

    If the desired context path is a multi-level context path (e.g., /my/thredds ), you must use a pound sign (“#”) in the .war filename to encode the slash (/). In this case, the thredds.war file would need to be renamed to my#thredds.war.

  2. Edit the TDS web.xml file and change the value of the ContextPath parameter to match the desired context path.

    The TDS uses the value of the ContextPath context parameter (as defined in the TDS web.xml file) when generating TDS URLs in certain situations. To make sure all generated URLs are consistent, you must change the value of the ContextPath parameter to match the desired context path.

    (Changing the value of ContextPath will no longer be necessary in a future release once we require Tomcat 6.0 (Servlet 2.5).

    The TDS web.xml file is located in ${tomcat_home}/webapps/<contextPath>/WEB-INF/web.xml, where <contextPath> is the value of the desired context path. The ContextPath context parameter is defined in the web.xml file (starting at line 12):

    <context-param>
      <param-name>ContextPath</param-name>
      <param-value>thredds</param-value>
    </context-param>
    

    For the /thredds2 example, it should be changed to:

    <context-param>
      <param-name>ContextPath</param-name>
      <param-value>thredds2</param-value>
    </context-param>
    

    And for the /my/thredds example, it should be changed to:

    <context-param>
      <param-name>ContextPath</param-name>
      <param-value>my/thredds</param-value>
    </context-param>
    
  3. Edit your TDS configuration catalogs and change the service base URLs to start with the desired context path.

    So that users will receive the correct data access URLs for datasets served by your TDS, the base URLs given by the service elements in your TDS configuration catalogs must match the desired context path.

    An OPeNDAP service element on a TDS with the context path of /thredds2 would need to look similar to this:

    <service name="odap" serviceType="OPeNDAP" base="/thredds2/dodsC/"/>
    

    And, similarly, an OPeNDAP service element on a TDS with the context path of /my/thredds would need to look similar to this:

    <service name="odap" serviceType="OPeNDAP" base="/my/thredds/dodsC/"/>
    

Troubleshooting Tips

  • Check the catalog URL in the title of the HTML view of catalogs matches the requested URL.
  • Check the Data Access URL in the OPeNDAP Data Access Form matches the requested URL (minus the .html suffix).
  • If you have TDS Remote Management configured, go to the TDS debug page (e.g., http://localhost:8080/thredds/admin/debug) and follow the “Show HTTP Request info” link.
  • Once there, check the values listed for server name and port and the context path all match the appropriate values from the request URL, e.g., for the URL http://localhost:8080/thredds/admin/debug?General/showRequest , the values should be:

     req.getServerName(): localhost
     req.getServerPort(): 8080
     req.getContextPath(): /thredds