Spark can't load static files from webjars

1k views Asked by At

I am using Spark Framework in my application, and use

staticFileLocation("/META-INF/resources/");

so that I can use webjars, which contain css and js files in there. I also have my own resources put in my projects src/main/resources/META-INF/resources folder because my gradle build picks them up from there.

My build uses a fat-jar approach, where everything ends up in a single jar and all files are served perfectly by Spark.

My problem is that when I run some unit tests standalone from Eclipse, even though I ensured that the webjars are on classpath, they are not served by Spark, only my own project static resources are.

@Test
public void testStartup() throws InterruptedException {
    InputStream schemaIS = this.getClass().getClassLoader().getResourceAsStream("META-INF/resources/webjars/bootstrap/3.2.0/js/bootstrap.min.js");
    System.out.println(schemaIS == null);
    staticFileLocation("/META-INF/resources/");
    // depending on the trailing / the bootstrap js is found, but Spark never serves it
}

I think this has something to do with classloaders, but I am not finding the way to make this work. Looking at Spark code, it says The thread context class loader will be used for loading the resource. I also see that the code itself removes the trailing slash, which makes big difference in the plain getResourceAsStream.

Is it a bug in Spark, or is there any way to make it work properly?

1

There are 1 answers

0
Christian MICHON On BEST ANSWER

Note that removing the leading slash is required by jetty not by Spark.

Unfortunately with Spark you cannot mix static files (in a physical directory/folder) with files served as resources in a jar. And many jars will not work either in Spark.

I had a look at this a few weeks ago and came to a conclusion this is a minor weakness in Spark (or a bug if you may say).

The only way I found out was to reverse Spark and figure out how jetty works. I managed with the following Nashorn javascript snippets to make webjars and static files to work together.

Unless Spark author changes his code to allow inclusion of tailor made context handlers, this will not help you out. But if you wish to pursue in jetty instead, this code with adaptation can help you out.

This code is for Nashorn jjs (from JDK8) but can be easily ported to Java. With this code I was able to use 3 separate webjars jquery/bootstrap/angular and the rest of my client code was in a physical directory/folder public.

app.js:

with(new JavaImporter(
  org.eclipse.jetty.server
, org.eclipse.jetty.server.handler
)) {

  var server = new Server(4567);

  var ctxs = new ContextHandlerCollection();
  ctxs.setHandlers(Java.to([
    load('src/static.js')
  , load('src/webjars.js')
  ], Handler.class.getName().concat('[]')));
  server.setHandler(ctxs);

  server.start();
  server.join();

}

src/static.js:

(function () {
  var context;
  with(new JavaImporter(
    org.eclipse.jetty.server.handler
  , org.eclipse.jetty.util.resource
  )) {
    context = new ContextHandler();
    context.setContextPath("/");
    var handler = new ResourceHandler();
    handler.setBaseResource(Resource.newResource("public"));
    context.setHandler(handler);
  }
  return context;
})();

src/webjars.js:

(function () {
  var context;
  with(new JavaImporter(
    org.eclipse.jetty.server.handler
  , org.eclipse.jetty.util.resource
  )) {
    context = new ContextHandler();
    context.setContextPath("/");
    var handler = new (Java.extend(ResourceHandler, {
      getResource: function(req) {
        var path = req.getUri();
        var resource = Resource.newClassPathResource(path);
        if (resource == null || !resource.exists()) {
          resource = Resource.newClassPathResource("META-INF/resources/webjars" + path);
        }
        return resource;
      }
    }))();
    handler.setDirectoriesListed(true); // true when debugging, false in production
    context.setHandler(handler);
  }
  return context;
})();