Search code examples
jarclassloaderembedded-jettyvaadin-flow

Vaadin Flow 14, Jetty embedded and static files


I'm trying to create app based on Jetty 9.4.20 (embedded) and Vaadin Flow 14.0.12.

It based on very nice project vaadin14-embedded-jetty.

I want to package app with one main-jar and all dependency libs must be in folder 'libs' near main-jar.

I remove maven-assembly-plugin, instead use maven-dependency-plugin and maven-jar-plugin. In maven-dependency-plugin i add section <execution>get-dependencies</execution> where i unpack directories META-INF/resources/,META-INF/services/ from Vaadin Flow libs to the result JAR.

In this case app work fine. But if i comment section <execution>get-dependencies</execution> then result package didn't contain that directories and app didn't work.

It just cannot give some static files from Vaadin Flow libs.

This error occurs only if i launch packaged app with ...

$ java -jar vaadin14-embedded-jetty-1.0-SNAPSHOT.jar

... but from Intellij Idea it launch correctly.

There was an opinion that is Jetty staring with wrong ClassLoader and cannot maintain requests to static files in Jar-libs.


Solution

  • The META-INF/services/ files MUST be maintained from the Jetty libs.

    That's important for Jetty to use java.util.ServiceLoader.

    If you are merging contents of JAR files into a single JAR file, that's called a "uber jar".

    There are many techniques to do this, but if you are using maven-assembly-plugin or maven-dependency-plugin to build this "uber jar" then you will not be merging critical files that have the same name across multiple JAR files.

    Consider using maven-shade-plugin and it's associated Resource Transformers to properly merge these files.

    The ServicesResourceTransformer is the one that merges META-INF/services/ files, use it.

    As for static content, that works fine, but you have to setup your Base Resource properly.

    Looking at your source, you do the following ...

    final URI webRootUri = ManualJetty.class.getResource("/webapp/").toURI();
    final WebAppContext context = new WebAppContext();
    context.setBaseResource(Resource.newResource(webRootUri));
    

    That won't work reliably in 100% of cases (as you have noticed when running in the IDE vs command line).

    The Class.getResource(String) is only reliable if you lookup a file (not a directory).

    Consider that the Jetty Project Embedded Cookbook recipes have techniques for this.

    See:

    Example:

    // Figure out what path to serve content from
    ClassLoader cl = ManualJetty.class.getClassLoader();
    // We look for a file, as ClassLoader.getResource() is not
    // designed to look for directories (we resolve the directory later)
    URL f = cl.getResource("webapp/index.html");
    if (f == null)
    {
        throw new RuntimeException("Unable to find resource directory");
    }
    
    // Resolve file to directory
    URI webRootUri = f.toURI().resolve("./").normalize();
    System.err.println("WebRoot is " + webRootUri);
    
    WebAppContext context = new WebAppContext();
    context.setBaseResource(Resource.newResource(webRootUri));