Spark 无法从 webjars 加载静态文件

Spark can't load static files from webjars

我在我的应用程序中使用 Spark 框架,并使用

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

这样我就可以使用 webjars,其中包含 css 和 js 文件。我也有自己的资源放在我的项目 src/main/resources/META-INF/resources 文件夹中,因为我的 gradle 构建从那里获取它们。

我的构建使用 fat-jar 方法,其中所有内容最终都在一个 jar 中,所有文件都由 Spark 完美提供。

我的问题是,当我 运行 独立于 Eclipse 进行一些单元测试时,即使我确保 webjars 在类路径中,Spark 也不会为它们提供服务,只有我自己的项目静态资源在。

@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
}

我认为这与类加载器有关,但我没有找到实现它的方法。查看 Spark 代码,它说 The thread context class loader will be used for loading the resource. 我还看到代码本身删除了尾部斜杠,这在普通 getResourceAsStream.

中有很大的不同

是不是Spark的bug,或者有什么方法可以让它正常工作吗?

注意 Jetty 需要删除前导斜杠,Spark 不需要。

不幸的是,对于 Spark,您不能将静态文件(在物理 directory/folder 中)与作为资源的文件混合在 jar 中。许多罐子在 Spark 中也无法工作。

几周前我看过这个并得出结论这是 Spark 中的一个小弱点(或者如果你可以说是一个错误)。

我发现的唯一方法是逆向 Spark 并弄清楚 jetty 是如何工作的。我设法使用以下 Nashorn javascript 片段使 webjars 和静态文件一起工作。

除非 Spark 作者更改他的代码以允许包含定制的上下文处理程序,否则这对您没有帮助。但是如果你想在 jetty 中追求,这个带有适配的代码可以帮助你。

此代码适用于 Nashorn jjs(来自 JDK8),但可以轻松移植到 Java。使用此代码,我能够使用 3 个单独的 webjars jquery/bootstrap/angular,而我的客户端代码的其余部分位于物理 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;
})();