Last Modified: Thu, 05 May 2016 19:29:59 +0000 ; Created: Fri, 01 Jul 2011 19:21:45 +0000
All code is Copyright 2016 Rodney Beede So Jetty has the ability to java -jar YourWebApp.war which runs the Jetty server. I tried using Jetty 8 to do this with an application that had JSP files, but support just isn't there yet. I wanted the latest JSP APIs too. I decided to try Tomcat 7's new embedded support. I used some known tricks from my Jetty experience to get it working correctly. This allows me to embed Tomcat 7 into my war file for easy deployment to others with java -jar WebApp.war. I used Maven for all the building which makes grabbing dependency jars much easier. The embedded version reads the web application configuration from the normal WEB-INF/web.xml. Here is a rundown: pom.xmlStandard stuff for any web application project. Your build type should be a war not a jar.
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <tomcat.version>7.0.69</tomcat.version> </properties> <dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>apache-log4j-extras</artifactId> <version>1.2.17</version> </dependency> <!-- Maven Central and other repos don't have consistent locations for versions of the J2EE APIs This guide was helpful for older versions, but it doesn't seem to be followed anymore in repos: http://maven.apache.org/guides/mini/guide-coping-with-sun-jars.html You can also search http://jcp.org/en/jsr/all to get the middle point (x.'#'.z) latest JSR spec version --> <!-- http://www.java.net/forum/topic/glassfish/glassfish/javaxservlet-api-version --> <!-- http://java.net/projects/servlet-spec --> <!-- Found in Maven central repository --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency> <!-- http://jsp.java.net/downLoad.html --> <!-- Found in Maven central repository --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.2.1</version> <scope>provided</scope> </dependency> <!-- Find the latest version with groupId and artifactId per http://jstl.java.net/download.html --> <!-- Found in Maven central repository --> <dependency> <groupId>org.glassfish.web</groupId> <artifactId>javax.servlet.jsp.jstl</artifactId> <version>1.2.4</version> </dependency> <!-- These allow embedding of Tomcat 7 with JSP support --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> <version>${tomcat.version}</version> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <version>${tomcat.version}</version> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-logging-juli</artifactId> <version>${tomcat.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.4.3</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <artifactSet> <excludes> <!-- Must have Excluding javax.servlet:servlet-api:jar:2.5 from the shaded jar to avoid conflict with tomcat-embed-core --> <exclude>javax.servlet:servlet-api:*</exclude> <!-- For the warning about [WARNING] log4j-1.2.17.jar, apache-log4j-extras-1.2.17.jar define 43 overlapping classes we can ignore since it is okay. --> </excludes> </artifactSet> <createDependencyReducedPom>true</createDependencyReducedPom> <minimizeJar>false</minimizeJar> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>EmbeddedTomcatMain</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> <!-- The main class needs to be in the root of the war in order to be runnable --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-antrun-plugin</artifactId> <version>1.8</version> <executions> <execution> <id>move-main-class</id> <phase>compile</phase> <configuration> <tasks> <move todir="${project.build.directory}/${project.artifactId}-${project.version}"> <fileset dir="${project.build.directory}/classes/"> <include name="EmbeddedTomcatMain.class" /> </fileset> </move> </tasks> </configuration> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin> EmbeddedTomcatMain.java (HTTP listener example 1)
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; import java.security.ProtectionDomain; import javax.servlet.ServletException; import org.apache.catalina.LifecycleException; import org.apache.catalina.connector.Connector; import org.apache.catalina.startup.Tomcat; import org.apache.tomcat.util.http.fileupload.IOUtils; /** * @author rbeede * * Supports Tomcat 7 * */ public class EmbeddedTomcatMain { public static void main(final String[] args) throws ServletException, LifecycleException, URISyntaxException, IOException { final Tomcat tomcat = new Tomcat(); // Extract the pre-packaged SSL keys final File[] certificateStores = extractCertificateStores(); tomcat.setPort(80); // Default connector addConnector(82, false, tomcat, null); addConnector(443, true, tomcat, certificateStores); // Load the war (assumes this class in in root of war file) final ProtectionDomain domain = EmbeddedTomcatMain.class.getProtectionDomain(); final URL location = domain.getCodeSource().getLocation(); System.out.println("Using webapp at " + location.toExternalForm()); tomcat.addWebapp("/", location.toURI().getPath()); tomcat.start(); tomcat.getServer().await(); } private static void addConnector(final int port, final boolean https, final Tomcat tomcat, final File[] certificateStores) throws IOException { final Connector connector = new Connector(); connector.setScheme((https) ? "https" : "http"); connector.setPort(port); connector.setProperty("maxPostSize", "0"); // unlimited connector.setProperty("xpoweredBy", "true"); if(https) { connector.setSecure(true); connector.setProperty("SSLEnabled","true"); connector.setProperty("keyPass", "123456"); connector.setProperty("keystoreFile", certificateStores[0].getCanonicalPath()); connector.setProperty("keystorePass", "123456"); connector.setProperty("truststoreFile", certificateStores[1].getCanonicalPath()); connector.setProperty("truststorePass", "123456"); } tomcat.getService().addConnector(connector); } /** * @param tomcat * @return 0 = keystoreFile, 1 = truststoreFile * @throws IOException */ private static File[] extractCertificateStores() throws IOException { //FIXME Not secure in creation because Java 6 and before provide no platform independent API for setting file permissions & ownership. Java 7 will. final File keystoreFile = File.createTempFile("ETM", null); final File truststoreFile = File.createTempFile("ETM", null); keystoreFile.deleteOnExit(); truststoreFile.deleteOnExit(); final FileOutputStream fosKeystore = new FileOutputStream(keystoreFile); final FileOutputStream fosTruststore = new FileOutputStream(truststoreFile); // Assumes .jks files were in root of project resources which causes them to be under /WEB-INF/classes/ inside the war IOUtils.copy(EmbeddedTomcatMain.class.getResourceAsStream("/WEB-INF/classes/keyStore.jks"), fosKeystore); IOUtils.copy(EmbeddedTomcatMain.class.getResourceAsStream("/WEB-INF/classes/trustStore.jks"), fosTruststore); fosKeystore.close(); fosTruststore.close(); return new File[] {keystoreFile, truststoreFile}; } } EmbeddedTomcatMain.java (AJP listener only example 2)
import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Path; import java.security.ProtectionDomain; import java.util.UUID; import javax.servlet.ServletException; import org.apache.catalina.LifecycleException; import org.apache.catalina.connector.Connector; import org.apache.catalina.core.StandardHost; import org.apache.catalina.startup.Tomcat; /** * @author rbeede * * Supports Tomcat 7 * * Enforces secure options but does not handle https in preference of localhost binding AJP and letting external * service handle user identity authorization and authentication. * * Since this is embedded inside a WAR file it must be found in the root of the packaged & assembled WAR * */ public class EmbeddedTomcatMain { public static void main(final String[] args) throws ServletException, LifecycleException, URISyntaxException, IOException { final Tomcat tomcat = new Tomcat(); // Tomcat 7 must have a BaseDir for temporary files (like work, wars, etc) final Path tempBaseDir = java.nio.file.Files.createTempDirectory(EmbeddedTomcatMain.class.getSimpleName()); tempBaseDir.toFile().deleteOnExit(); tomcat.setBaseDir(tempBaseDir.toString()); System.out.println("Using temporary base directory of: " + tempBaseDir); // Tomcat 7 API has no tomcat.getBaseDir() method tomcat.getHost().setAutoDeploy(false); tomcat.getHost().setCreateDirs(false); tomcat.getHost().setDeployOnStartup(false); tomcat.setPort(-1); // Not using default connector; Undocumented API trick // Tomcat 8 supports unpackWARS=false with untouched war file // Tomcat 7 even with unpackWARS=false still has to extract Java libs and temp files for JSP compile // 2015-04-06 https://wiki.apache.org/tomcat/RemoveUnpackWARs ((StandardHost) tomcat.getHost()).setUnpackWARs(false); // Setup AJP connector addAJPConnector(8009, tomcat); // Logging is not yet available System.out.println("Listening via AJP on port 8009"); // Load the war (assumes this class is inside a war file) final ProtectionDomain domain = EmbeddedTomcatMain.class.getProtectionDomain(); final URL location = domain.getCodeSource().getLocation(); System.out.println("Using webapp at " + location.toExternalForm()); // Disable the shutdown port tomcat.getServer().setShutdown(UUID.randomUUID().toString()); // Just in case future API doesn't disable it tomcat.getServer().setPort(-1); // Not officially API documented trick System.out.println("Adding web application at " + location.toURI().getPath()); tomcat.addWebapp("", location.toURI().getPath()); tomcat.getServer().start(); // Do not call tomcat.start() as that adds a default connector we do not want for(final Connector connector : tomcat.getService().findConnectors()) { System.out.println("Connector: " + connector.toString()); System.out.println("Connector Port: " + connector.getPort()); } tomcat.getServer().await(); } private static void addAJPConnector(final int port, final Tomcat tomcat) throws IOException { final Connector connector = new Connector(); connector.setProtocol("AJP/1.3"); // "The standard protocol value for an AJP connector is AJP/1.3" https://tomcat.apache.org/tomcat-7.0-doc/config/ajp.html connector.setPort(port); connector.setAllowTrace(false); connector.setEnableLookups(false); connector.setXpoweredBy(true); connector.setSecure(false); // AJP does not encrypt in-transit connector.setProperty("address", "127.0.0.1"); //IPv4 only connector.setProperty("connectionTimeout", Integer.toString(10 * 1000)); // 10 seconds //We do not use requiredSecret connector.setProperty("tomcatAuthentication", Boolean.toString(false)); connector.setProperty("tomcatAuthorization", Boolean.toString(false)); tomcat.getService().addConnector(connector); } } |
|