How do you get embedded Jetty 9 to successfully resolve the JSTL URI?

自作多情 提交于 2019-12-03 00:37:21

So here is yet another solution. I was struggling with very similar problem only I have a separate war file and a simple embedder class which creates a Jetty server for me and starts it up pointing to possibly any war file. Here is how this all work.

  1. The war file doesn't have any tld library in WEB-INF/lib and is completely separated from the loader miniapplication.

  2. The Loader application Main class which starts the server and points it to any war file has following dependencies (maven):

        <!-- Jetty webapp -->
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-webapp</artifactId>
            <version>${jettyVersion}</version>
        </dependency>
    
        <!-- With JSP support -->
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-jsp</artifactId>
            <version>${jettyVersion}</version>
        </dependency>
    

  3. The loading class itself looks as:

    Server server = new Server(cncPort);
    
    WebAppContext webApp = new WebAppContext();
    
    webApp.setContextPath(applicationContext);
    webApp.setWar(jettyHome + "/" + warFile);
    server.setHandler(webApp);
    
    try {
        server.start();
        server.join();
    } catch (Exception ex) {
        System.out.println("Failed to start server.");
        ex.printStackTrace();
    } 
    
  4. The resulting package in my case looks as follows:

    + EmbedderApp
    |
    +-- lib
      - EmbeddedApp.jar <-- JAR with the embedder class
      - com.sun.el-2.2.0.v201303151357.jar
      - javax.el-2.2.0.v201303151357.jar
      - javax.servlet-3.0.0.v201112011016.jar
      - javax.servlet.jsp-2.2.0.v201112011158.jar
      - javax.servlet.jsp.jstl-1.2.0.v201105211821.jar
      - jetty-http-9.0.6.v20130930.jar
      - jetty-io-9.0.6.v20130930.jar
      - jetty-jsp-9.0.6.v20130930.jar
      - jetty-security-9.0.6.v20130930.jar
      - jetty-server-9.0.6.v20130930.jar
      - jetty-servlet-9.0.6.v20130930.jar
      - jetty-util-9.0.6.v20130930.jar
      - jetty-webapp-9.0.6.v20130930.jar
      - jetty-xml-9.0.6.v20130930.jar
      - org.apache.jasper.glassfish-2.2.2.v201112011158.jar
      - org.apache.taglibs.standard.glassfish-1.2.0.v201112081803.jar
      - org.eclipse.jdt.core-3.8.2.v20130121.jar
    
  5. My dependecies were simply added via assembly plugin as

    <dependencySet>
        <outputDirectory>lib</outputDirectory>
        <scope>runtime</scope>
    </dependencySet>
    
  6. I have a shell start script launching the embedding class and here comes what took me ages to figure out.

    I used a manifest with classpath embedded within the jar and setting my CLASSPATH=<PATH_TO_APP>\lib\EmbeddedApp.jar assuming the reset of the dependencies are part of my classpath through the manifest. And I was gettign the same unresolvable URI error.

    Once I added changed the CLASSPATH variable within my script to contain all the jars explicitely it started to work.

    for jar in ${APP_ROOT}/lib/*.jar; do CLASSPATH=$jar:${CLASSPATH}; done 
    

Hope this can save someones time :-)

I had the same problem starting Jetty from a Surefire test; the problem was that Jetty 9 doesn't look at the manifests of any jar files except in WEB-INF, which is incompatible with the way I was writing my tests.

To work around the problem, I wrote a little bit of code to find the jar files from the manifest, and put them into a new intermediate URLClassLoader.

This is what my functional test setup function ended up looking like to get it to work with Jetty 9:

@Before @SuppressWarnings("unchecked")
public void setUp() throws Exception {

    WebAppContext ctx = new WebAppContext("src/main/webapp", "/");

    Server server = new Server(9000);

    ctx.setServer(server);
    server.setHandler(ctx);

    ctx.preConfigure();

    ctx.addOverrideDescriptor("src/main/webapp/WEB-INF/tests-web.xml");      

    // Replace classloader with a new classloader with all URLs in manifests 
    // from the parent loader bubbled up so Jasper looks at them.
    ClassLoader contextClassLoader = ctx.getClassLoader();
    ClassLoader parentLoader = contextClassLoader.getParent();
    if (contextClassLoader instanceof WebAppClassLoader &&
        parentLoader instanceof URLClassLoader) {
      LinkedList<URL> allURLs =
          new LinkedList<URL>(Arrays.asList(((URLClassLoader)parentLoader).getURLs()));
      for (URL url : ((LinkedList<URL>)allURLs.clone())) {
        try {
          URLConnection conn = new URL("jar:" + url.toString() + "!/").openConnection();
          if (!(conn instanceof JarURLConnection))
            continue;
          JarURLConnection jconn = (JarURLConnection)conn;
          Manifest jarManifest = jconn.getManifest();
          String[] classPath = ((String)jarManifest.getMainAttributes().getValue("Class-Path")).split(" ");

          for (String cpurl : classPath)
            allURLs.add(new URL(url, cpurl));
        } catch (IOException e) {} catch (NullPointerException e) {}
      }

      ctx.setClassLoader(
          new WebAppClassLoader(
              new URLClassLoader(allURLs.toArray(new URL[]{}), parentLoader),
              ((WebAppClassLoader)contextClassLoader).getContext()));
    }

    server.start();
}

My code sample is placed in the public domain - you may use it in your own code (attribution appreciated but not required).

After fiddling around with a1kmm's solution and ending up with NullPointers I noticed I did not set a Classloader on the WebAppContext. Using the following line I no longer need the custom classloading/manifest scanning setup.

webAppContext.setClassLoader(new WebAppClassLoader(getClass().getClassLoader(), webAppContext));

I was having this exact problem. And I solved it in the most unusual way.

I am using Maven as the build tool, but here is how I was building my self-exec WAR.

<profile>
        <id>Jetty_9</id>
        <properties>
            <jetty9.version>9.0.4.v20130625</jetty9.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>${logback.version}</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-access</artifactId>
                <version>${logback.version}</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-core</artifactId>
                <version>${logback.version}</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>${slf4j.version}</version>
                <scope>provided</scope>
            </dependency>

            <dependency>
                <groupId>org.eclipse.jetty.orbit</groupId>
                <artifactId>javax.servlet</artifactId>
                <version>3.0.0.v201112011016</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-webapp</artifactId>
                <version>${jetty9.version}</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-plus</artifactId>
                <version>${jetty9.version}</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-jsp</artifactId>
                <version>${jetty9.version}</version>
            </dependency>            
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>2.3.2</version>
                    <configuration>
                        <source>${compileSource}</source>
                        <target>${compileSource}</target>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-antrun-plugin</artifactId>
                    <version>1.7</version>
                    <executions>
                        <execution>
                            <id>main-class-placement</id>
                            <phase>prepare-package</phase>
                            <configuration>
                                <target>
                                    <move todir="${project.build.directory}/${project.build.finalName}/">
                                        <fileset dir="${project.build.directory}/classes/">
                                            <include name="Main.class"/>
                                        </fileset>
                                    </move>
                                </target>
                            </configuration>
                            <goals>
                                <goal>run</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-dependency-plugin</artifactId>
                    <version>2.6</version>
                    <executions>
                        <execution>
                            <id>jetty-classpath</id>
                            <phase>prepare-package</phase>
                            <goals>
                                <goal>unpack-dependencies</goal>
                            </goals>
                            <configuration>
                                <includeGroupIds>
                                    org.eclipse.jetty,org.slf4j,ch.qos
                                </includeGroupIds>                                    
                                <includeScope>provided</includeScope>
                                <excludes>META-INF/*.SF,META-INF/*.RSA,about.html, about_files/**, readme.txt,
                                    plugin.properties, jetty-dir.css
                                </excludes>
                                <outputDirectory>
                                    ${project.build.directory}/${project.build.finalName}
                                </outputDirectory>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>2.3</version>
                    <configuration>
                        <archive>
                            <manifest>
                                <mainClass>Main</mainClass>
                            </manifest>
                        </archive>
                    </configuration>
                    <executions>
                        <execution>
                            <id>default-war</id>
                            <phase>package</phase>
                            <goals>
                                <goal>war</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>

But like yourself I was getting that error. After Googling the death out of it after 2 days I found this - http://internna.blogspot.co.uk/2011/08/step-by-step-executable-war-files.html

By switching out the jetty-jsp dependency for this one:

 <dependency>
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>jsp-2.1-glassfish</artifactId>
            <version>2.1.v20100127</version>
 </dependency>

It all started magically working!

As of this moment I can't explain why it does work. But I am keen to find out

I've just created simple jsp with ta taglib as you use. Then created server app and it works. So, I think it is not a taglib that causes the problem.

public static void main(String[] args) throws Exception {
    Server server = new Server(8680);
    HandlerCollection handlers = new HandlerCollection();
    server.setHandler(handlers);
    ContextHandlerCollection chc = new ContextHandlerCollection();
    handlers.addHandler(chc);
    WebAppContext webapp = new WebAppContext();
    webapp.getInitParams().put("org.eclipse.jetty.servlet.Default.useFileMappedBuffer",    
    "false");
    webapp.setContextPath("/WebAppMng01");
    //URL url = ManagedServer.class.getResource("/WebAppMng01/build/web");
    URL url = ManagedServer.class.getResource("/WebAppMng01/dist/WebAppMng01.war");
    if (url != null) {
        webapp.setWar(url.toExternalForm());
        chc.addHandler(webapp);
    }
    server.start();
    server.join();
}   

Sadly, none of the answers so far have worked for me. But I finally found a solution to my issue. This is going to sound like a hack, and it definitely feels like one.

But if I get everything set up just as I described in my question, up to the point where the JSTL won't resolve, I can then take one step that makes everything work, almost like magic.

That cringe-inducing step is changing the extension on the .war file to .jar. Once I do that, the JSTL resolves just fine, and everything works.

So right now I produce a .war that you can stick into a servlet container or you can rename to .jar and run standalone. And it works on both Unix and Windows, whereas the way I was doing this before wouldn't work on Unix due to a bug present in the jsp-2.1-glassfish library mentioned by James Cook.

The relevant bits from my pom:

<properties>
    <jetty.version>9.0.5.v20130815</jetty.version>
    <war.class>com.domain.package.DeployWebapp</war.class>
    <war.class.path>com/domain/package</war.class.path>
</properties>    

<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-server</artifactId>
    <version>${jetty.version}</version>
    <scope>provided</scope>
</dependency>

<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-webapp</artifactId>
    <version>${jetty.version}</version>
    <scope>provided</scope>
</dependency>

<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-jsp</artifactId>
    <version>${jetty.version}</version>
    <scope>provided</scope>
</dependency>

<plugin>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>1.7</version>
    <executions>
        <execution>
            <id>main-class-placement</id>
            <phase>prepare-package</phase>
            <configuration>
                <tasks>
                    <move todir="${project.build.directory}/${project.artifactId}-${project.version}/${war.class.path}">
                    <fileset dir="${project.build.directory}/classes/${war.class.path}">
                        <include name="DeployWebapp.class" />
                    </fileset>
                </move>
                        </tasks>
                </configuration>
                <goals>
                    <goal>run</goal>
                </goals>
            </execution>
    </executions>
</plugin>

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>2.3</version>
    <executions>
        <execution>
            <id>jetty-classpath</id>
            <phase>prepare-package</phase>
            <goals>
                <goal>unpack-dependencies</goal>
            </goals>
            <configuration>
                <includeGroupIds>org.eclipse.jetty,javax.servlet,javax.el,org.glassfish.web,org.eclipse.jetty.orbit,org.ow2.asm,javax.annotation</includeGroupIds>
                <outputDirectory>
                    ${project.build.directory}/${project.artifactId}-${project.version}
                </outputDirectory>
            </configuration>
        </execution>
    </executions>
</plugin>

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.4</version>
    <configuration>
        <attachClasses>true</attachClasses>
        <archiveClasses>true</archiveClasses>
        <archive>
            <manifest>
                <mainClass>${war.class}</mainClass>
            </manifest>
        </archive>
        <packagingExcludes>META-INF/*.SF</packagingExcludes>
        <packagingExcludes>META-INF/*.DSA</packagingExcludes>
        <packagingExcludes>META-INF/*.RSA</packagingExcludes>
    </configuration>
</plugin>

It seems that embedded jetty 9 doesn't like to automatically use any class path entries from your main executable jar file. This includes taglib libraries. Adding class path entries directly to the webappclassloader doesn't seem to work either. For whatever reason the class path entries must added to a parent of the webappclassloader.

The simple solution doesn't work for me:

    webAppContext.setClassLoader(new WebAppClassLoader(getClass().getClassLoader(), webAppContext));

But scanning the manifests by hand did. I re-wrote the above scanning example just I could see what's going on, and try out different things which I'll include here.

//========== create WebAppClassloader with a new parent classloader with all URLs in manifests=================== 
    //search for any secondary class path entries
    Vector<URL> secondayClassPathURLVector = new Vector<>();
    ClassLoader parentClassLoader = this.getClass().getClassLoader();
    if(parentClassLoader instanceof URLClassLoader)
    {
        URL[] existingURLs = ((URLClassLoader)parentClassLoader).getURLs(); 
        for (URL parentURL : existingURLs)
        {
            //if it doesn't end in .jar, then it's probably not going to have Manifest with a Class-Path entry 
            if(parentURL.toString().endsWith(".jar"))
            {
                JarURLConnection jarURLConnection = (JarURLConnection) new URL("jar:" + parentURL.toString() + "!/").openConnection();
                Manifest jarManifest = jarURLConnection.getManifest();
                String classPath = jarManifest.getMainAttributes().getValue("Class-Path");
                if(classPath != null)
                {
                    //Iterate through all of the class path entries and create URLs out of them                                                
                    for (String part : classPath.split(" "))
                    {
                        //add our full path to the jar file to our classpath list
                        secondayClassPathURLVector.add(new URL(parentURL,part));                     
                    }
                }
            }                
        }
    }
    //use our class path entries to create a parent for the webappclassloader that knows how to use or referenced class paths
    URLClassLoader internalClassPathUrlClassLoader = new URLClassLoader(secondayClassPathURLVector.toArray(new URL[secondayClassPathURLVector.size()]), parentClassLoader);
    //create a new webapp class loader as a child of our  internalClassPathUrlClassLoader. For whatever reason Jetty needs to have a WebAppClassLoader be it's main class loader,
    //while all of our classpath entries need to be added to the parent class loader   
    WebAppClassLoader webAppClassLoader = new WebAppClassLoader(internalClassPathUrlClassLoader,context);
    context.setClassLoader(webAppClassLoader);

This should replace the embedded-jetty-jsp example which just doesn't work for whatever reason, but should, as any class-path entries should automatically included in the classpath. It does say however, that JSP requires a NON system class loader. So I can only assume that JSP stops scanning the class path once it reaches the System classpath loader. Which is why we effectively have to replace it.

WebAppContext context = new WebAppContext();      
final URL url = getClass().getProtectionDomain().getCodeSource().getLocation();
if (url != null) {
    context.getMetaData().addWebInfJar(JarResource.newResource(url));
}

Add the fat-jar as a WEB-INF jar, let the MetaInfConfiguration to find the *.tld files.

Reference:

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!