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

Grails rest-api application only show directories in war file. #609

Closed
abcfy2 opened this issue Nov 29, 2021 · 4 comments
Closed

Grails rest-api application only show directories in war file. #609

abcfy2 opened this issue Nov 29, 2021 · 4 comments
Assignees

Comments

@abcfy2
Copy link

abcfy2 commented Nov 29, 2021

Hi there. I try to build a Grails rest-api application and deploy to unit, but why only show directories in war ?

Here is what I do:

  • download a sample project: curl -O https://start.grails.org/restPproject.zip -d profile=rest-api
  • unzip and build war file: unzip restPproject.zip ; cd restPproject && chmod +x gradlew && sed -i 's/org.grails.plugins:views-gradle:2.1.0/org.grails.plugins:views-gradle:2.1.1/' build.gradle && ./gradlew assemble
  • deploy to unit follow the spring boot config: https://unit.nginx.org/howto/springboot/

But the result is:

$ curl -s localhost
<a href="META-INF">META-INF</a><br>
<a href="WEB-INF">WEB-INF</a><br>
<a href="org">org</a><br>

But java -jar /path/to/my.war is working, also deploy war to tomcat is also working.

What's wrong with me?

@mar0x
Copy link
Contributor

mar0x commented Dec 2, 2021

Hello.

I've tried to reproduce the issue. I've got same response from Unit. When running as jar it also works for me.
But I'm unable to use this war in Tomcat (got 404) and in Jetty (got same response as from Unit).

Could you please share more details how you deploy it in Tomcat? It would be nice if you try Jetty too, because Unit is more like Servlet Container than fully featured Java Application Server.

@abcfy2
Copy link
Author

abcfy2 commented Dec 3, 2021

Grails is based on spring boot and used embed tomcat as starter by default. So it can run in servlet container such as tomcat, jetty, undertow: https://docs.grails.org/latest/guide/deployment.html#deploymentContainer

Rename to ROOT.war:

mkdir webapps
mv build/libs/restPproject-0.1-plain.war webapps/ROOT.war

Run in tomcat:

docker run --rm -p 8080:8080 -v `pwd`/webapps:/usr/local/tomcat/webapps/ tomcat:9-jre11-openjdk-slim
$ curl -s localhost:8080
{"message":"Welcome to Grails!","environment":"production","appversion":"0.1","grailsversion":"5.0.1","appprofile":"rest-api","groovyversion":"3.0.7","jvmversion":"11.0.13","reloadingagentenabled":false,"artefacts":{"controllers":1,"domains":0,"services":0},"controllers":[{"name":"restpproject.ApplicationController","logicalPropertyName":"application"}],"plugins":[{"name":"i18n","version":"5.0.1"},{"name":"jsonView","version":"unspecified"},{"name":"core","version":"5.0.1"},{"name":"dataSource","version":"5.0.1"},{"name":"eventBus","version":"SNAPSHOT"},{"name":"restResponder","version":"5.0.1"},{"name":"codecs","version":"5.0.1"},{"name":"controllers","version":"5.0.1"},{"name":"urlMappings","version":"5.0.1"},{"name":"controllersAsync","version":"SNAPSHOT"},{"name":"domainClass","version":"5.0.1"},{"name":"converters","version":"5.0.1"},{"name":"hibernate","version":"7.1.0"},{"name":"interceptors","version":"5.0.1"},{"name":"services","version":"5.0.1"},{"name":"cache","version":"4.0.3"}]}

To deploy under jetty, should change implementation "org.springframework.boot:spring-boot-starter-tomcat" to providedRuntime "org.springframework.boot:spring-boot-starter-jetty" in build.gradle, build again:

./gradlew assemble
mv build/libs/restPproject-0.1.war webapps/ROOT.war

Run in jetty:

docker run --rm -p 8080:8080 -v `pwd`/webapps:/var/lib/jetty/webapps/ jetty:10-jre11-slim

Both tomcat and jetty should not run a high version because spring boot only support servlet 4.0: https://docs.spring.io/spring-boot/docs/2.5.x/reference/html/getting-started.html#getting-started.system-requirements

@mar0x
Copy link
Contributor

mar0x commented Dec 20, 2021

During investigation 3 issues were found in series:

  1. Unit stops calling SCI after first successful call (this was an attempt to mitigate issue 3 below).
    The fix is:
diff --git a/src/java/nginx/unit/Context.java b/src/java/nginx/unit/Context.java
--- a/src/java/nginx/unit/Context.java
+++ b/src/java/nginx/unit/Context.java
@@ -1558,7 +1563,6 @@ public class Context implements ServletC
 
         try {
             sci.onStartup(handles_classes, this);
-            metadata_complete_ = true;
         } catch(Exception e) {
             System.err.println("loadInitializer: exception caught: " + e.toString());
         }
  1. Exception Unable to find the main class to restart from Restarter which failed to find main entry point.
    I'll try to find the correct way to disable Restarter implicitly. For now Restarter can be disabled with global Java option in application:
            "options": [ "-Dspring.devtools.restart.enabled=false" ]
  1. Failed to initialise nginx.unit.websocket.server.WsFilter because application contains tomcat-embed-websocket.jar with org.apache.tomcat.websocket.server.WsSci which overrides javax.websocket.server.ServerContainer context attribute for it's own org.apache.tomcat.websocket.server.WsServerContainer incompatible with Unit class. Removing this jar should help to avoid collision. If removal is not an option, the issue can be solved in Unit.
    Unit WebSocket server container is the copy of Tomcat container and to avoid conflicts original Tomcat initialiser should be explicitly ignored:
diff --git a/src/java/nginx/unit/Context.java b/src/java/nginx/unit/Context.java
--- a/src/java/nginx/unit/Context.java
+++ b/src/java/nginx/unit/Context.java
@@ -1514,6 +1514,11 @@ public class Context implements ServletC
     {
         trace("loadInitializer: initializer: " + sci.getClass().getName());
 
+        if (sci.getClass().getName().equals("org.apache.tomcat.websocket.server.WsSci")) {
+            trace("loadInitializer: ignore");
+            return;
+        }
+
         HandlesTypes ann = sci.getClass().getAnnotation(HandlesTypes.class);
         if (ann == null) {
             trace("loadInitializer: no HandlesTypes annotation");

@mar0x
Copy link
Contributor

mar0x commented Dec 20, 2021

After inspecting the Spring Boot source code I've discovered the way how it detects the development environment (I would say it is annoying to catch this):

	protected boolean isDevelopmentClassLoader(ClassLoader classLoader) {
		return classLoader.getClass().getName().contains("AppClassLoader");
	}

So, the development class loader has AppClassLoader substring in the name 👏. The solution on Unit side is to eliminate the AppClassLoader substring.

diff --git a/src/java/nginx/unit/Context.java b/src/java/nginx/unit/Context.java
--- a/src/java/nginx/unit/Context.java
+++ b/src/java/nginx/unit/Context.java
@@ -422,7 +422,7 @@ public class Context implements ServletC
 
         processWebXml(root);
 
-        loader_ = new AppClassLoader(urls,
+        loader_ = new UnitClassLoader(urls,
             Context.class.getClassLoader().getParent());
 
         Class wsSession_class = WsSession.class;
@@ -531,7 +531,7 @@ public class Context implements ServletC
         }
     }
 
-    private static class AppClassLoader extends URLClassLoader
+    private static class UnitClassLoader extends URLClassLoader
     {
         static {
             ClassLoader.registerAsParallelCapable();
@@ -547,7 +547,7 @@ public class Context implements ServletC
 
         private ClassLoader system_loader;
 
-        public AppClassLoader(URL[] urls, ClassLoader parent)
+        public UnitClassLoader(URL[] urls, ClassLoader parent)
         {
             super(urls, parent);
 

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

No branches or pull requests

2 participants