Computing: Website and Database Programming

Web development environment setup on MS Windows.


8.7. Using Tomcat in cooperation with Apache.
  Back to the main Tomcat page.
  On the Tomcat-Apache HOWTO page of The Jakarta Project site, they write:
  "While it is entirely possible to have Tomcat serve both your static and dynamic document provision needs, there are several reasons why you might not want want to do this. With respect to the Apache web server,
  1. Tomcat is not as fast as Apache when it comes to static pages.
  2. Tomcat is not as configurable as Apache.
  3. Tomcat is not as robust as Apache.
  4. Tomcat may not address many sites' need for functionality found only in Apache modules (e.g. Perl, PHP, etc.).
For all these reasons it is recommended that real-world sites use an industrial-strength web server, such as Apache, for serving static content, and use Tomcat as a Servlet/JSP add-on."
  An alternative to this cooperation between Tomcat and Apache would be to run both servers, without any interaction between them, sending JSP and servlet requests directly to Tomcat and sending all other requests to Apache. It's obvious that this isn't really a solution and is very rarely done in real life.
  Tomcat running as Servlet/JSP add-on, we said. What does that mean? In brief:
  • All web requests are addressed (by default on standard port 80) to Apache.
  • Just as Apache checks if a request is a Perl CGI script and if so, passes it to the Perl interpreter, it checks if it is a JSP page or a servlet and if it is, forwards it (or more correctly lets forward it; cf. further down in the text) to Tomcat.
  • If the requested URL is such, that it leads to a web application defined within an actually deployed context, Tomcat handles this file (the same way it would do, when the request would be directly sent to itself).
  • The resulting web page (JSP page or servlet output) is served to the web browser.
  1. Defining an AJP 1.3 Connector in Tomcat.
  The first point to consider is how to implement the communication between Apache and Tomcat. This is actually realized using a so-called adapter (connector), that allows Apache to communicate with Tomcat, using a certain protocol, such as Apache JServ Protocol (AJP) and a given port, the default for AJP being port 8009.
  We will use a connector, that uses the AJP v1.3 protocol. This connector has to be defined in server.xml, located in the Tomcat conf directory. The configuration of the AJP v1.3 connector is included by default in server.xml, but commented out. However, just removing the comments, as told in most Tomcat tutorials, you find on the web, is not enough. With all the rest of the configuration done, but using the AJP connector settings, as given in the original server.xml, and trying to access our JSP page test.jsp via Apache, you get a service unavailable message. You'll find the reason for this by having a look at the Tomcat standard error log file, logs/tomcat10-stderr.{actual-date}.log, where you see an entry like:
    Caused by: java.lang.IllegalArgumentException: The AJP Connector is configured with secretRequired="true" but the secret attribute is either null or "". This combination is not valid.
  The solution consists in setting secretRequired="true". Below my AJP 1.3 Connector settings. Note, that I used the IP of my Windows 10 machine instead of the localhost address. This is necessary to access Tomcat content via Apache using a http://wk-win10 URL (otherwise you'll get a "service unavailable" message).
      <!-- Define an AJP 1.3 Connector on port 8009 -->
    <Connector protocol="AJP/1.3"
        address="192.168.40.1"
        port="8009"
        redirectPort="8443"
        secretRequired="false" />
  To note that, just as we checked if our "ws" context has actually be deployed, we can check if the AJP service has actually been started. If so, having a look at the file logs/catalina.{actual-date}, you should see an entry like (192.168.40.1 being the address set in server.xml):
    {timestamp} INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-nio-192.168.40.1-8009"]
  2. Installing the Apache-Tomcat Connector.
  The Apache-Tomcat Connector is an adapter module used by Apache to communicate with Tomcat, using the AJP v1.3 protocol through TCP port 8009. With Apache having been installed as binaries, downloaded from Apache Lounge (cf. Web development environment setup on MS Windows: 1. Apache webserver on my site), you should download the connector at their site, too. Here the link to the Apache Lounge download page. As I actually use Apache Lounge 2.4.46 64bit, build with Visual Studio C++ 2019 (VS16), I had to use a 64bit connector, build with VS16, too. I actually use mod_jk-1.2.48-win64-VS16. The download is a ZIP archive, containing, among others, the module file mod_jk.so, to be placed in the Apache modules directory (in my case: C:\Programs\Apache24\modules).
  The mod_jk module must be loaded into Apache, so we have to change httpd.conf. As mod_jk needs several configuration settings, we'll create (cf. next section) a file called mod_jk.conf, placed in the Tomcat conf directory. This file will contain the instruction to load the module as well as the module's settings. To load the content of this configuration file into Apache, we can use the Apache Include directive. Just add the following lines to httpd.conf (somewhere at the end of the file, together with other includes):
      # Include Tomcat configuration
    Include "C:/Program Files/Tomcat 10.0/conf/mod_jk.conf"
  3. Configuring mod_jk.
  As said above, we create the file with the settings for the Apache-Tomcat Connector mod_jk.conf in the Tomcat conf directory. This file will contain the instruction to load the mod_jk.so module, general settings for the module (not discussed here), the path to the workers properties file (to communicate with Tomcat, Apache uses the service of one or several workers, whose properties are defined in the file referred to in mod_jk.conf; cf. next section), and two JKMount statements for each web context, that is to be forwarded from Apache to Tomcat. Do you see what these JKMount statements are for? At the begin of this tutorial, we saw that Apache has to check whether a request has to be forwarded to Tomcat, or handled by itself. The question is, how to tell Apache, what requests have to be treated by Tomcat. Have a look at the requests, that Apache handles itself. Some of these have to be passed to a special handler, such, for example PHP scripts, that Apache recognizes by the file extension (.php and possibly others, as stated in the handler definition). CGI scripts are passed to an external program, defined within the script itself (the she-bang line in a Perl script contains the path to the Perl interpreter). In this case, Apache does not look at the file extension, but considers as a script any URL that references the /cgi-bin folder (more exactly the folder defined with the ScriptAlias directive). For Java applications, like JSP pages and servlets, we do in a similar way as with Perl: telling the httpd server that any URL referencing a given web context, has to be forwarded to Tomcat. I chose to call this web context "java-apps".
  Here the content of my mod_jk.conf file:
      # Load mod_jk module
    LoadModule jk_module modules/mod_jk.so
    # Where to find workers.properties
    JkWorkersFile "C:/Program Files/Tomcat 10.0/conf/workers.properties"
    # Where to put jk logs
    JkLogFile "C:/Program Files/Tomcat 10.0/logs/mod_jk.logs"
    # Set the jk log level [debug/error/info]
    JkLogLevel info
    # Select the log format
    JkLogStampFormat "[%a %b %d %H:%M:%S %Y]"
    # Various options
    JkOptions +ForwardKeySize +ForwardURICompat -ForwardDirectories
    # JkRequestLogFormat set the request format
    JkRequestLogFormat "%w %V %T"
    # Send everything for context /java-apps to worker ajp13
    JkMount /java-apps ajp13
    JkMount /java-apps/* ajp13
  Concerning the last two lines of the configuration file, please, note that "java-apps" is the web context that I want to forward to Tomcat and that "ajp13" is the name of the worker that Apache communicates with (this worker will have to be defined in the workers.properties file mentioned above).
  4. Configuring the AJP workers.
  It is possible to define several workers listening on different ports and even using different protocols. And forwarding a given web context to one or another of them. We'll here define a single worker called ajp13, using the AJP v1.3 protocol on port 8009. Note, that the worker name, we use here, must be the one used in the JkMount statements in the mod_jk.conf file. The workers are defined in a file called workers.properties (here again, the name must be the one used in mod_jk.conf), located in the Tomcat conf directory. Here the content of my file (remembering that 192.168.40.1 is the IP of my Windows 10 computer):
      # Define 1 real worker named ajp13
    worker.list=ajp13
    # Set properties for worker named ajp13 to use AJP v1.3 protocol and run on port 8009
    worker.ajp13.type=ajp13
    worker.ajp13.host=192.168.40.1
    worker.ajp13.port=8009
    worker.ajp13.lbfactor=50
    worker.ajp13.socket_keepalive=1
    worker.ajp13.socket_timeout=300
  5. Defining the forwarded context in Tomcat.
  So, what do we have until now? If Apache receives a request for some resource belonging to the context "java-apps", it passes this request to worker ajp13, that forwards it to Tomcat. But, does Tomcat know how to handle this request? No! Because the context "java-apps" is unknown to him. This means that we have to define this context in the Tomcat server.xml file, setting the context root (document base directory) to the folder where the Java applications, that may be accessed via Apache, are located. What actually is the directory webapps/ws, that we created at the beginning of the Tomcat tutorial.
  Here the content of my server.xml file, only showing the "most relevant" parts (the omitted parts being predefined default settings):
      <Server port="-1" shutdown="SHUTDOWN">
        .....
        <Service name="Catalina">
            <!-- Define a non-SSL/TLS HTTP/1.1 Connector on port 8080 -->
            <Connector port="8080" protocol="HTTP/1.1"
                connectionTimeout="20000"
                redirectPort="8443" />
            <!-- Define an AJP 1.3 Connector on port 8009 -->
            <<Connector protocol="AJP/1.3"
                address="192.168.40.1"
                port="8009"
                redirectPort="8443"
                secretRequired="false" />
            <!-- Define the engine -->
            <Engine name="Catalina" defaultHost="localhost">
                .....
                <!-- Define the host -->
                <Host name="localhost" appBase="webapps"
                    unpackWARs="true" autoDeploy="true">
                    <!-- Define the forwarded context -->
                    <Context path="/java-apps/"
                        docBase="/ws"/>
                    .....
                </Host>
            </Engine>
        </Service>
    </Server>
  6. Accessing our Java applications.
  In part 5 and 6 of the Tomcat tutorial, we have created the JSP page test.jsp and the servlet sayhello, both belonging to the context "ws" ("ws" being a subdirectory of the Tomcat "webapps" folder). First lets access the applications directly on Tomcat. Try the following:
  • http://wk-win10:8080/ws/test.jsp (screenshot on the left)
  • http://wk-win10:8080/java-apps/sayhello (screenshot on the right)
 
Accessing Tomcat directly [1]
Accessing Tomcat directly [2]
  Success in both cases. "ws" is the context, our Java applications belong to. And the context "java-apps" has been defined with appBase = "ws", so no problem for Tomcat to find the applications when using a "java-apps" based URL.
  Now, try to access the applications by sending the requests to Apache. Try the following:
  • http://wk-win10/java-apps/sayhello (screenshot on the left)
  • http://wk-win10/ws/sayhello (screenshot on the right)
 
Accessing Tomcat via Apache: Success
Accessing Tomcat via Apache: Error 404 (Resource not existing on Apache)
  Success when using the "java-apps" context: We have configured mod_jk to forward all requests for this context to Tomcat and Tomcat can handle this forwarded request as it did handle the equivalent direct request in the previous example. On the other side, using the "ws" context, the request fails. There is no /ws directory on my Apache server and the mod_jk configuration contains nothing about a context of this name. Thus, Apache trying to handle the request itself and not finding a resource with the name "sayhello", issues an error 404 (not found).
  Two remarks, concerning the access of Tomcat content by sending the request to Apache:
  • The access of Tomcat via Apache is lots slower than accessing Tomcat directly. At least, that's what I noticed on my system. However, it's not to exclude, that this isn't necessarily the case and that my configuration is not optimal.
  • If the request succeeds, the user is not aware of the Tomcat server handling it. If you look at the address, displayed in the web browser, when the file has been served, it is the Apache URL, you entered, and not the "real" URL of the file, that is actually located on the Tomcat server.
  Two final screenshots. In both cases, the request concerns a resource belonging to the "java-apps" context, thus, it is forwarded to Tomcat. The screenshot on the left shows the request of a not existing resource, what results in an error 404 (not found). As a difference with the error 404 in the previous example, the error message is issued in this case by Tomcat (not by Apache). The screenshot on the right shows the case, where Tomcat is offline. Apache can't forward the request and issues a "service unavailable" message.
 
Accessing Tomcat via Apache: Error 404 (Resource not existing on Tomcat)
Accessing Tomcat via Apache: Service not available (because Tomcat is offline)

If you find this text helpful, please, support me and this website by signing my guestbook.