Tag Archives: tomcat

Spring Boot And The Embedded Tomcat Container

There’s one thing that has been bothering me with Spring Boot’s embedded Tomcat container on Windows: it forks a new process which doesn’t die when the the parent process is terminated.

Here’s an example of the problem:

  1. Run the app using maven mvn clean spring-boot:run command
  2. Container starts up, do some testing, changed controller code and it’s now time to recompile again..
  3. Clicked Eclipse’s stop and run button, and port has been used error came up. Now I have to go task manager, find the java process (among 3-4 other java processes) and kill it manually

This additional step really frustates me, identifying which java process you should kill isn’t always straight forward, you have to see the full command and see what it does.

Fortunately I found a workaround for this: scrap spring-boot-maven-plugin altogether and use the old-school tomcat7-maven-plugin. Here’s the step required to achieve this:

  1. Remove spring-boot-maven-plugin configuration on pom.xml
  2. Setup tomcat7-maven-plugin
    
      org.apache.tomcat.maven
      tomcat7-maven-plugin
      2.0
    
    
  3. Instead of SpringApplication.run(Application.class, args), bootstrap Spring Boot using SpringBootServletInitializer instead, eg:
    
    guration
    
    public class Application extends SpringBootServletInitializer {
    
      
      protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class);
      }
      
    }
    

    This is required such that SpringBoot can boot the container when tomcat starts up

Ok all good now, I started my app with mvn clean test tomcat7:run command, but wait.. there’s a weird exception being thrown:

Aug 22, 2014 4:01:46 PM org.apache.catalina.startup.ContextConfig parseWebXml
SEVERE: Parse error in application web.xml file at file:/C:/***/***/***/target/tomcat/conf/web.xml
org.xml.sax.SAXParseException; systemId: file:/C:/***/***/***/target/tomcat/conf/web.xml; lineNumber: 108; columnNumber: 15; Error at (108, 15) : org.apache.catalina.deploy.WebXml addServlet
  at org.apache.tomcat.util.digester.Digester.createSAXException(Digester.java:2687)
  at org.apache.tomcat.util.digester.Digester.createSAXException(Digester.java:2719)
  at org.apache.tomcat.util.digester.Digester.endElement(Digester.java:1054)
  at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(AbstractSAXParser.java:606)
...
Caused by: java.lang.NoSuchMethodException: org.apache.catalina.deploy.WebXml addServlet
  at org.apache.tomcat.util.IntrospectionUtils.callMethod1(IntrospectionUtils.java:855)
  at org.apache.tomcat.util.digester.SetNextRule.end(SetNextRule.java:201)
  at org.apache.tomcat.util.digester.Digester.endElement(Digester.java:1051)
  ... 27 more

Took me quite a while to figure out, but it seems this problem is caused by a classpath conflict. My app uses spring-boot-started-web dependency which pulls spring-boot-starter-tomcat, which pulls various tomcat jars as dependency.

After a few trial and errors, I found the solution by marking spring-boot-started-tomcat as provided in the dependencies in pom.xml


  org.springframework.boot
  spring-boot-starter-tomcat
  provided

Java Spring Web App Silent Self-Update

It’s unimaginable how many apps still force its user to manually update — they’re very annoying! Fortunately if your app is a Java Web App (eg: running in Tomcat) you can set it up to auto-update silently. This is very helpful if you deploy your web-app onsite on clients’ infrastructure.

This article assumes you have a basic understanding of Spring MVC. The article is written against Spring Framework version 4.0.3.RELEASE.

The UpdaterService

The first thing to setup is an UpdaterService that checks periodically if a new version is available. You will need a TaskScheduler to schedule the periodic job. Feel free to either use Java annotation or XML style:

// Java annotation

g
public class AppConfig {
  ...
}



Once setup, you can decorate a method with annotation and it will be invoked by the scheduler
thread every specified period.

In below example my UpdaterService class has a checkAndInstallUpdate() method that get invoked by the scheduler thread every 8 hours.


public class UpdaterService {
  (fixedDelay=8*3600)
  public void checkAndInstallUpdate() {
  }
}

I will not go into much detail on how to check and download the update. There are tons of way of doing it and one popular method is to use Apache HttpClient library.

Typically you would upload your war/jar artifact in your website and have a REST service for apps to check if new version is available. If so then download and install it.

Self Executing Tomcat Container

Here’s another trick I use on my self-updating app: use a self-executing Tomcat. Basically everything required to run tomcat is packaged inside a nested war/jar. If you use spring-boot it already have a support for this, but otherwise you can set it up painlessly using maven plugin.

The other thing you need to make sure is the war/jar file isn’t file-system-locked when the app is running so it can be overwritten by the new version. Self-executing Tomcat does not lock the jar.

Restarting Tomcat

Here comes the tricky bit. First use ProcessBuilder to spawn a new OS process

String myAppPath = System.getenv("MY_APP_PATH");
String command = myAppPath + File.separator + "run.bat";
ProcessBuilder builder = new ProcessBuilder(command);
builder.start();

In the example above it is assumed you have a windows script called run.bat and the value of environment variable MY_APP_PATH is set to the folder that contain the run script.

And directly after that, terminate the JVM using System.exit(0)

System.exit(0);

The termination of the old process will not affect the new ones. And yes, you need to start the new process before you terminate the old one, otherwise it wouldn’t have chance to do so.

See Also

Creating Self Executing Tomcat Jar

The tomcat maven plugin comes with handy exec-war-only goal that will bundle a standalone tomcat server in an executable jar. Add following configuration to your pom file:


  org.apache.tomcat.maven
  tomcat7-maven-plugin
  2.0
  
    
      tomcat-run
      
        exec-war-only
      
      package
      
        /
      
    
  

And when you run the package goal, another artifact called myapp-x.y-war-exec.jar will be created. To run this simply execute java -jar myapp-x.y-war-exec.jar on a terminal shell.

The jar also comes with several options of which you can view by giving –help flag. The -httpPort is often useful to setup few testing environments:

C:\myapp>java -jar myapp-1.0-war-exec.jar --help
usage: java -jar [path to your exec war jar]
 -ajpPort                      ajp port to use
 -clientAuth                            enable client authentication for
                                        https
 -D                                key=value
 -extractDirectory    path to extract war content,
                                        default value: .extract
 -h,--help                              help
 -httpPort                    http port to use
 -httpProtocol            http protocol to use: HTTP/1.1 or
                                        org.apache.coyote.http11.Http11Nio
                                        Protocol
 -httpsPort                  https port to use
 -keyAlias                    alias from keystore for ssl
 -loggerName                logger to use: slf4j to use slf4j
                                        bridge on top of jul
 -obfuscate                   obfuscate the password and exit
 -resetExtract                          clean previous extract directory
 -serverXmlPath          server.xml to use, optional
 -uriEncoding              connector uriEncoding default
                                        ISO-8859-1
 -X,--debug                             debug

Clustering Tomcat Using Static Membership

Source: http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=2009794

Purpose

This article gives an example of cluster configuration using static cluster membership (instead of determining it dynamically over multicast), and points out some important aspects of membership configuration. While multicast membership is simpler to set up, static membership is necessary on networks with multicast disabled.

Resolution

Example: This is an example element in server.xml from a node that is part of a 2-node static cluster. In this example, all nodes are on the same host. You can copy this example and modify hosts and ports as necessary for your own cluster.


  

  

    

    
      
    
    
    
    

    
          
          
          
    

  
  
  

  
  

This example differs from the default multicast configuration as discussed in the Apache Tomcat 6 Clustering how-to. These differences are important when creating your own non-multicast configuration:

  • The McastService element is removed.
  • The channelStartOptions=”3″ switch has been added to the Cluster element, to disable the multicast service. Even when not explicitly configured, the multicast service is enabled by default. If the multicast service is not disabled this way, and multicast is enabled on the network, your nodes could cluster with unexpected members. For more information about the Cluster element, see its entry in the Apache documentation.
  • The TcpPingInterceptor class is added. This interceptor pings other nodes so that all nodes can recognize when other nodes have left the cluster. Without this class, the cluster may appear to work fine, but session replication can break down when nodes are removed and re-introduced. For more information about the TcpPingInterceptor element, see its entry in the Apache API documentation.
  • The StaticMembershipInterceptor element is added at the end of the list of interceptors, specifying the other static members of the cluster. For more information about the StaticMembershipInterceptor element, see its entry in the Apache documentation.

Remember these points when modifying the configuration:

  • As with the default configuration, you can use either the DeltaManager or BackupManager by changing the element.
    The order of the interceptors is very important. Preserve the order presented here.

    Caution: Reversing the first two interceptors can results in a log full of messages similar to:

    WARNING: Unable to send ping from TCP ping thread.
    java.lang.NullPointerException
            at org.apache.catalina.tribes.group.interceptors.TcpPingInterceptor.sendPing(TcpPingInterceptor.java:121)
            at org.apache.catalina.tribes.group.interceptors.TcpPingInterceptor$PingThread.run(TcpPingInterceptor.java:166)
    
  • One member in the static list is commented out: the member that corresponds to the node on which this configuration file is located. Be sure not to include a node in its own cluster membership. If this were done, the node would sync to itself as if it were a different node in the cluster. With DeltaManager, that could lead to errors at startup like:
    org.apache.catalina.ha.session.DeltaManager waitForSendAllSessions
    SEVERE: Manager [localhost#/petcare]: No session state send at [time] received, timing out after 60,087 ms.
    org.apache.catalina.ha.session.DeltaManager getAllClusterSessions
    WARNING: Manager [localhost#/petcare]: Drop message SESSION-GET-ALL inside GET_ALL_SESSIONS sync phase start date [times]
    

    With the BackupManager this configuration is silently accepted, but the node would consider itself its own backup. Restarting the node would result in the loss of sessions not synced to other nodes.

Additional Information

This configuration is known to work with Tomcat 6.0.33. It does not work with Tomcat 7.0.22/23. For more information, see the Apache bug report.

Tomcat 7 JDBC Session Persistence

The default Tomcat session management strategy is in-memory session persisted into file when the server is shutdown gracefully. If the server dies in a cold fashion (eg: kill -9 or power outage), session data might be lost. One approach to mitigate this is to store session data into database using JDBC, aka JDBC Session Persistence.

JDBC Session Persistence can also aid load balancer failover scenario. I’d say this is an alternative to setting up (often cumbersome) TCP session replication. Note that if you have multiple cloud servers like Amazon EC2 it doesn’t come with TCP multicast feature — TCP session replication sounds like a nightmare to setup.

The Steps

  1. Ensure org.apache.catalina.session.StandardSession.ACTIVITY_CHECK or org.apache.catalina.STRICT_SERVLET_COMPLIANCE is set to true. Add line similar to following into your Tomcat’s startup.sh (if you’re on UNIX)
    export CATALINA_OPTS="-Dorg.apache.catalina.session.StandardSession.ACTIVITY_CHECK=true"

    Tomcat System Property Reference will explain what do each property means if you’re curious

  2. Create following SQL table (yes you need a database to store the session data)
    create table tomcat_sessions (
      session_id     varchar(100) not null primary key,
      valid_session  char(1) not null,
      max_inactive   int not null,
      last_access    bigint not null,
      app_name       varchar(255),
      session_data   mediumblob,
      KEY kapp_name(app_name)
    );
    
  3. Place a copy of mysql-connector-java.jar (or your DB’s JDBC driver) into $CATALINA_HOME/lib
  4. In your web app, add a META-INF/context.xml file. If you use standard maven layout you have to place it on src/main/webapp/META-INF/context.xml. You can copy the file from $CATALINA_HOME/conf/context.xml as a starting point. Then under element add following element
    
      
    
    

    Notice how the SQL column name corresponds to some of the settings above. In this configuration I used mysql database on localhost with database name “mytomcat” and username “root”. maxIdleBackup=”10″ specifies number of seconds before the in-memory session data is persisted into database.

    There are many other settings you can tweak, have a look at the Tomcat Manager Component Reference.

Fine Prints

This article is tested against Tomcat 7.0.39 but I guess it should also work with Tomcat 6. If you’ve jumped the ship from relational to MongoDB, . I haven’t got a chance to try it but it looks awesome.

Tomcat & Apache Reverse Proxy

I have to admit Apache HTTPD has one of the most confusing documentation among other tools out there. Here are my Apache & Tomcat requirements:

  • A domain name mycoolwebsite.com pointing to my hosting server
  • Tomcat running on http://localhost:8080 on my hosting (default)
  • Apache HTTP server running on http://localhost:80 on hosting (default)
  • Request to http://mycoolwebsite.com should be reverse proxied into Tomcat without the end-user noticing the port difference

After 2 hours of reading documentation, trial and error, I managed to get this setup working using following Apache Reverse Proxy and VirtualHost configuration:


   ServerName mycoolwebsite.com
   ProxyPass        / http://localhost:8080/
   ProxyPassReverse / http://localhost:8080/
   ProxyPassReverseCookieDomain localhost mycoolwebsite.com
   ProxyPreserveHost On

Configuration Explained

  1. ProxyPass directive will cause request to http://mycoolwebsite.com/foo/bar (and any other paths) be internally forwarded to http://localhost:8080/foo/bar. Internally here means the user will never know — it will look as if the response came from http://mycoolwebsite.com. Behind the screen (on the server) Apache forwards the request and obtain the response to/from Tomcat. The trailing slash (/) at the end of http://localhost:8080/ is important because without it request to http://mycoolwebsite.com/style.css will be internally translated to http://localhoststyle.css (which is wrong).
  2. ProxyPassReverse directive is useful when Tomcat sends HTTP 3xx redirect response. If Tomcat redirects http://localhost:8080/logout into http://localhost:8080/login, your end user will see http://localhost:8080/login unless you place this directive.
  3. ProxyPassReverseCookieDomain directive will cause the cookie domain be re-written into the intended domain. Without this end-user would see localhost as their cookie domain (which is again wrong).
  4. ProxyPreserveHost directive will cause the domain header be forwarded into Tomcat. Without this Tomcat will see the incoming request coming from localhost. This is handy when you’re planning to serve multiple host name under Tomcat.

You can try this on be adding the above configuration into your httpd.conf file (typically located on /etc/httpd/conf/httpd.conf). Once you’ve edited the config, restart apache using sudo apachectl -k restart command.