Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Tomcat #27

Closed
mvysny opened this issue Aug 30, 2024 · 9 comments · Fixed by #29
Closed

Add support for Tomcat #27

mvysny opened this issue Aug 30, 2024 · 9 comments · Fixed by #29
Assignees
Labels
enhancement New feature or request

Comments

@mvysny
Copy link
Owner

mvysny commented Aug 30, 2024

Starting an embedded Tomcat looks pretty easy too: https://devcenter.heroku.com/articles/create-a-java-web-application-using-embedded-tomcat

Perhaps I could add a way for the user to choose between Jetty and Tomcat.

@mvysny mvysny self-assigned this Aug 30, 2024
@mvysny mvysny added the enhancement New feature or request label Aug 30, 2024
@mvysny
Copy link
Owner Author

mvysny commented Aug 31, 2024

This is a working example: https://vaadin.com/forum/t/vaadin-10-tomcat-embedded/157777

Some shortcomings - needs investigating:

  1. No auto-detection of servlets defined in the project itself - this becomes a problem when there's an additional servlet such as javalin
  2. No way to add static files as classpath resources? This could be a blocker, but we could extract the jar file to a directory?

Otherwise it seems to be working.

Things to test:

  1. production mode - launching from a zip file
  2. adding javalin rest servlet
  3. static resources being served
  4. push & websockets

@mvysny
Copy link
Owner Author

mvysny commented Aug 31, 2024

Push works. Only the following dependencies are necessary:

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>10.1.28</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-websocket</artifactId>
            <version>10.1.28</version>
        </dependency>

The app prints this to stderr but otherwise works:

SEVERE: Servlet [jsp] in web application [] threw load() exception
java.lang.ClassNotFoundException: org.apache.jasper.servlet.JspServlet
	at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1332)
	at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1144)
	at org.apache.catalina.core.DefaultInstanceManager.loadClass(DefaultInstanceManager.java:491)
	at org.apache.catalina.core.DefaultInstanceManager.loadClassMaybePrivileged(DefaultInstanceManager.java:473)
	at org.apache.catalina.core.DefaultInstanceManager.newInstance(DefaultInstanceManager.java:143)
	at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:758)
	at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:698)
	at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:4172)
	at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:4458)
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1203)
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1193)
	at java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:317)

We can either manually remove the JSP servlet somehow (since it's unnecessary), or just add necessary jar to the classpath ( both options are acceptable).,

@mvysny
Copy link
Owner Author

mvysny commented Aug 31, 2024

Adding servlet such as

@WebServlet(urlPatterns = {"/*"})
public class MyServlet extends VaadinServlet {
}

removes the need for having to register the servlet manually:

        tomcat.addServlet("", "", VaadinServlet.class.getName());
        ctx.addServletMappingDecoded("/*", "");

@mvysny
Copy link
Owner Author

mvysny commented Aug 31, 2024

The ClassNotFoundException: org.apache.jasper.servlet.JspServlet issue can be fixed by adding org.apache.tomcat.embed:tomcat-embed-jasper to the classpath. It transitively pulls in org.eclipse.jdt:ecj which is a whopping 3mb jar, but we can exclude it without affecting Vaadin. We can also exclude transitively pulled org.apache.tomcat.embed:tomcat-embed-el leaving only the org.apache.tomcat.embed:tomcat-embed-jasper jar on the classpath, which is a 700kb JAR which is acceptable.

@mvysny
Copy link
Owner Author

mvysny commented Aug 31, 2024

Sounds like tomcat only performs servlet scanning of classes located in the /WEB-INF/classes of the WAR file virtually created for embedded tomcat. That means that:

  1. During development mode you need to map target/classes there (or other folder for Gradle)
  2. During production, you need to map the contents of the app jar there

The code is something like:

        File additionWebInfClasses = new File("target/classes").getAbsoluteFile();
        if (additionWebInfClasses.exists()) {
            WebResourceRoot resources = new StandardRoot(ctx);
            resources.addPreResources(new DirResourceSet(resources, "/WEB-INF/classes",
                    additionWebInfClasses.getAbsolutePath(), "/"));
            ctx.setResources(resources);
        }
        File productionJar = new File("libs/vaadin-boot-example-maven-1.0-SNAPSHOT.jar").getAbsoluteFile();
        if (productionJar.exists()) {
            System.out.println("PRODUCTION!!!!! " +productionJar);
            WebResourceRoot resources = new StandardRoot(ctx);
            resources.addPreResources(new JarResourceSet(resources, "/WEB-INF/classes",
                    productionJar.getAbsolutePath(), "/"));
            ctx.setResources(resources);
        }

@mvysny
Copy link
Owner Author

mvysny commented Aug 31, 2024

Yup: manually registering the Vaadin servlet is easier than adding classes to virtual WAR; this code works both in dev mode and in production:

        tomcat.addServlet("", "", VaadinServlet.class.getName());
        ctx.addServletMappingDecoded("/*", "");

Adding back.

What's strange is that Tomcat is apparently capable of discovering Vaadin's LookupServletContainerInitializer class even though it's not in the virtual WAR class loader... strange.

@mvysny
Copy link
Owner Author

mvysny commented Aug 31, 2024

Uh-oh - we might need to enable the classpath scanning after all, otherwise @WebListener classes are ignored.

mvysny added a commit that referenced this issue Aug 31, 2024
mvysny added a commit that referenced this issue Aug 31, 2024
mvysny added a commit that referenced this issue Aug 31, 2024
mvysny added a commit that referenced this issue Aug 31, 2024
mvysny added a commit that referenced this issue Aug 31, 2024
mvysny added a commit that referenced this issue Aug 31, 2024
mvysny added a commit that referenced this issue Aug 31, 2024
@mvysny
Copy link
Owner Author

mvysny commented Sep 1, 2024

Initial implementation is ready.

  1. push & websockets is verified to be working
  2. The servlet auto-detection is working: the launcher adds either target/classes (or build/classes) to WAR, or the final production app jar. In order to identify which jar file is the main app jar, you need to provide a regex matching the jar file name.
  3. The static resources are served either from src/dist/webapp (when in dev mode), or from ../webapp (when in production mode). This follows the structure of how Gradle application plugin packages the app (the contents of src/dist go to the resulting zip file as-is), and we may need to change it for Maven projects, but that remains to be seen - this will depend on how Maven AppPlugin packages the app.
  4. Javalin servlet - remains to be tested.

This means that the app structure is a bit different from when using the Jetty launcher: Jetty launcher expects webapp on classpath, in the /webapp class folder; while Tomcat launcher expects the resources in /src/dist/webapp.

An alternative would be to continue using the webapp-in-resources approach identical to Jetty, and then unpack the webapp folder to a temp folder when running in production mode (since Tomcat can't serve static resources from a jar file... or can it? Maybe we can populate the virtual WAR file by the jar file contents - explore).

mvysny added a commit that referenced this issue Sep 1, 2024
mvysny added a commit that referenced this issue Sep 1, 2024
mvysny added a commit that referenced this issue Sep 2, 2024
mvysny added a commit that referenced this issue Sep 2, 2024
mvysny added a commit that referenced this issue Sep 2, 2024
mvysny added a commit that referenced this issue Sep 2, 2024
mvysny added a commit that referenced this issue Sep 2, 2024
mvysny added a commit that referenced this issue Sep 2, 2024
mvysny added a commit that referenced this issue Sep 2, 2024
mvysny added a commit that referenced this issue Sep 2, 2024
mvysny added a commit that referenced this issue Sep 2, 2024
mvysny added a commit that referenced this issue Sep 2, 2024
mvysny added a commit that referenced this issue Sep 2, 2024
mvysny added a commit that referenced this issue Sep 2, 2024
mvysny added a commit that referenced this issue Sep 2, 2024
mvysny added a commit that referenced this issue Sep 2, 2024
mvysny added a commit that referenced this issue Sep 2, 2024
mvysny added a commit that referenced this issue Sep 2, 2024
mvysny added a commit that referenced this issue Sep 3, 2024
mvysny added a commit that referenced this issue Sep 3, 2024
mvysny added a commit that referenced this issue Sep 3, 2024
* #27 add the 'vaadin-boot-tomcat' module

* #27 initial impl of VaadinBoot for Tomcat

* #27 initial impl of VaadinBoot for Tomcat

* #27 Add testapp-tomcat

* #27 initial impl of VaadinBoot for Tomcat

* #27 Tomcat VaadinBoot: fix basedir configuration

* #27 Tomcat VaadinBoot: fix basedir configuration

* #27 Tomcat VaadinBoot: enable classpath scanning

* #27 minor build.gradle cleanup

* #27 honor VaadinBoot.contextRoot setting

* #27 fix production mode

* #27 minor

* #27 minor

* #27 extract common bits to the 'common' project

* #27 extract common bits to the 'common' project

* #27 javadoc

* #27 minor

* #27 documentation

* #27 documentation

* #27 documentation

* #27 TomcatWebServer: use standard JVM class-loading order

* #27 TomcatWebServer: refactoring

* #27 TomcatTest refactoring

* #27 JettyTest refactoring

* #27 WebServer: docs

* #27 fix compat with jdk 17

* #27 testapp-kotlin: more thorough tests

* #27 configure Tomcat to serve static files from classpath://webapp

* Fix tests on Windows

* Fix tests on Windows

* #27 Env: implement Env.findResourcesJarOrFolder() to also support classpath URLs pointing into JAR files

* #27 minor

* readme

* readme

* readme

* #27 common: add tests

* #27 refactor existing Jetty VaadinBoot on top of common VaadinBootBase

* #27 minor

* #27 todo

* #27 fix flaky tests

* #27 fix flaky tests

* #27 TomcatWebServer: detect main jar via the 'webapp' resource scan

* #27 Add testapp-kotlin-tomcat

* #27 TomcatWebServer: minor refactoring

* #27 v bump to 13.0-SNAPSHOT

* #27 improve detection of app's class folder

* #27 enable logging
@mvysny mvysny closed this as completed in #29 Sep 3, 2024
@mvysny
Copy link
Owner Author

mvysny commented Sep 3, 2024

Support for Tomcat will be added in Vaadin-Boot 13.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant