Javalite 框架 - 定位现有 FreeMarker 模板的问题

Javalite framework - problem with locating existing FreeMarker templates

我正在开发一个简单的 Web 应用程序,它提供 RestFul API 来管理多个资源。

我正在使用 java 标准版 - openjdk version "1.8.0_191 和以下 javalite 依赖项:

我使用嵌入式 jetty v9.4.1 作为 Web 服务器,整个项目打包为带有依赖项的 jar,并且 运行 使用以下命令:

java -Dconfig=/etc/project/config.properties -jar jarname.jar

Web 应用程序设计为仅提供 JSON 服务,所有 FreeMarker 模板都构造 json 消息。这是我的 FreeMarker 配置 class:

public class FreeMarkerConfig extends AbstractFreeMarkerConfig {
    @Override
    public void init() {
        // this is to override a strange FreeMarker default processing of numbers
        Configuration config = this.getConfiguration();
        config.setNumberFormat("0.##");

        config.setClassicCompatible(true);
        config.setClassForTemplateLoading(this.getClass(), "webapp/WEB-INF/views");
    }
}

打包后jar里面的结构如下(这是一个简化版,只有一个资源):

├── META-INF
│   ├── MANIFEST.MF
│   └── maven
│       └── project.control
│           └── panel
│               ├── pom.properties
│               └── pom.xml
├── activejdbc_models.properties
├── app
│   ├── config
│   │   ├── AppBootstrap.class
│   │   ├── AppControllerConfig.class
│   │   ├── DbConfig.class
│   │   ├── FreeMarkerConfig.class
│   │   └── RouteConfig.class
│   ├── controllers
│   │   ├── APIController.class
│   │   ├── CatchAllFilter.class
│   │   ├── OfficesController.class
│   └── models
│       ├── Office.class
├── config.properties
├── project
│   └── control
│       └── panel
│           ├── Launcher.class
│           ├── dao
│           │   ├── query
│           │   │   ├── Query.class
│           │   │   ├── QueryBuilder.class
│           │   │   ├── QueryStringBuilder.class
│           │   │   ├── StatsParamsHandler.class
│           │   └── validators
│           │       ├── OfficeValidator.class
│           ├── exceptions
│           │   └── ResourceNotFoundException.class
│           └── util
│               ├── Config.class
│               ├── JsonHelper.class
└── webapp
    └── WEB-INF
        ├── views
        │   ├── offices
        │   │   ├── _comma.ftl
        │   │   ├── _office.ftl
        │   │   ├── _office_agency.ftl
        │   │   ├── _office_agent.ftl
        │   │   └── index.ftl
        │   ├── layouts
        │   │   └── default_layout.ftl
        │   ├── shared
        │   │   ├── _paging.ftl
        │   │   └── message.ftl
        │   ├── system
        │   │   ├── 404.ftl
        │   │   └── error.ftl
        └── web.xml

大多数时候一切似乎都运行正常,没有任何问题。但是在某些时候发生了一些事情,FreeMarker 无法找到以前多次提供的模板。

我无法在本地主机上重现该行为,因此无法对其进行调试。

在服务器上 运行 时发生了几次。我唯一的观察是它发生在几个空闲小时之后——即几个小时没有请求,下一个请求失败,因为 FreeMarker 找不到所需的模板。这是在请求 /offices 时抛出的确切异常:


2019-06-24 15:22:50 - INFO  LazyList:164 - {"sql":"SELECT * FROM offices  ORDER BY id  LIMIT 20  OFFSET 0 ","params":[],"duration_millis":2,"cache":"miss"}
2019-06-24 15:22:50 - INFO  DB:164 - {"sql":"SELECT COUNT(*) FROM offices","params":[],"duration_millis":0}
2019-06-24 15:22:50 - INFO  FreeMarkerTemplateManager:81 - Rendering template: '/offices/index.ftl' without layout.
2019-06-24 15:22:50 - INFO  FreeMarkerTemplateManager:81 - Rendering template: '/shared/message.ftl' without layout.
2019-06-24 15:22:50 - INFO  RequestDispatcher:360 - {"controller":"app.controllers.OfficesController","duration_millis":15,"remote_ip":"127.0.0.1","method":"GET","action":"index","error":"Failed to render template: '/shared/message.ftl' without layout.  Template not found for name \\"/shared/message.ftl\\".\nThe name was interpreted by this TemplateLoader: WebappTemplateLoader(subdirPath=\\"/WEB-INF/views/\\", servletContext={contextPath=\\"\\", displayName=\\"activeweb\\"}).","url":"http://127.0.0.1:5050/offices","status":404}
2019-06-24 15:22:50 - INFO  FreeMarkerTemplateManager:81 - Rendering template: '/system/404.ftl' with layout: '/layouts/default_layout.ftl'.
2019-06-24 15:22:50 - ERROR RequestDispatcher:290 - ActiveWeb internal error:
org.javalite.activeweb.ViewMissingException: Failed to render template: '/system/404.ftl' with layout: '/layouts/default_layout.ftl'.  Template not found for name "/system/404.ftl".
The name was interpreted by this TemplateLoader: WebappTemplateLoader(subdirPath="/WEB-INF/views/", servletContext={contextPath="", displayName="activeweb"}).
  at org.javalite.activeweb.freemarker.FreeMarkerTemplateManager.merge(FreeMarkerTemplateManager.java:109)
  at org.javalite.activeweb.RenderTemplateResponse.doProcess(RenderTemplateResponse.java:88)
  at org.javalite.activeweb.ControllerResponse.process(ControllerResponse.java:67)
  at org.javalite.activeweb.RequestDispatcher.renderSystemError(RequestDispatcher.java:283)
  at org.javalite.activeweb.RequestDispatcher.doFilter(RequestDispatcher.java:219)
  at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1613)
  at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:541)
  at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
  at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:548)
  at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
  at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:190)
  at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1584)
  at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:188)
  at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1228)
  at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:168)
  at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:481)
  at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1553)
  at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:166)
  at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1130)
  at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
  at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
  at org.eclipse.jetty.server.Server.handle(Server.java:564)
  at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:320)
  at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:251)
  at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:279)
  at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:112)
  at org.eclipse.jetty.io.ChannelEndPoint.run(ChannelEndPoint.java:124)
  at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:672)
  at org.eclipse.jetty.util.thread.QueuedThreadPool.run(QueuedThreadPool.java:590)
  at java.lang.Thread.run(Thread.java:748)

如有需要,可以提供其他详细信息。

可能导致此类问题的原因 - 虽然一切看起来都可以正常运行,但 FreeMaker 突然无法找到之前提供的没有任何问题的模板。

任何可能有助于调试问题的建议都将不胜感激。

谢谢!

经过更详细的调查,jetty 似乎从 jar 中提取了 FreeMarker 模板并将它们放在 /tmp 文件夹中。该文件夹具有以下模式

/tmp/jetty-<host>-<port>-<resourceBase>-<context>-<virtualhost>-<randomdigits>.dir

示例: jetty-0.0.0.0-5050-webapp-_-any-35239075401795634.dir

基于 Unix 的操作系统有清理 /tmp 文件夹的策略,文件夹被删除后 - 显然找不到模板。

解决方案是将jetty 的WebAppContext 配置为使用另一个目录来存放此类数据。这可以通过 setTempDirectory 方法完成:

String webViewsPath = Launcher.class.getResource("/webapp").toString();
WebAppContext webapp = new WebAppContext(webViewsPath, "/");
webapp.setTempDirectory(new File("/data/templates"));
server.setHandler(webapp);

可以在此处找到有关码头临时目录的更多信息: https://www.eclipse.org/jetty/documentation/9.4.x/ref-temporary-directories.html