如何在Java应用程序中运行NodeJS?

22

我正在编写一个Java库,实际上是一个Clojure库,但对于这个问题来说,重要的是它在JVM上运行。该库需要执行一些JavaScript。我尝试使用Nashorn,但遇到了一些难以克服的限制。作为替代方案,我想尝试NodeJS。

我希望我的库是独立自包含的,不依赖于独立运行NodeJS的系统,因此需要特定的部署机制将Java和NodeJS工件放置在正确的位置,以被两个不同的网络服务器接收。然而,这种方法会带来一些问题。

我将通过HTTP与NodeJS通信,但我不希望NodeJS打开特定的端口。我想找到随机未使用的端口,以避免冲突。我还想控制NodeJS日志的存储位置,以便将其与应用程序的其他部分保持在一起。最后,我的应用程序应该能够检测到NodeJS崩溃并重新运行它或报告带有信息的错误。

如何最好地解决这个问题?是否有任何Java库可以帮助以这种方式管理子进程?从NodeJS方面有什么特别的事情要做(我以前从未使用过它)?


我想这取决于你的库的使用方式,但是让一个库启动一个重量级服务器进程来完成工作似乎不太好。 - Nick Urban
你听说过Avatar吗?我也遇到了一些问题,无法使用一些Node.js模块,但是Avatar填补了这个空缺:https://strongloop.com/strongblog/how-to-run-node-js-on-the-jvm-with-avatar-js-and-loopback/ - Tiago Lopo
只是为了缩小答案范围...你遇到的限制是什么?在你的帖子末尾,使用nashorn进行服务器端脚本编写会将平均响应时间增加6毫秒,因此我猜性能不是其中之一。 - cviejo
4个回答

11

我的解决方案最终是使用ProcessBuilder,如下所示:

(defn create-process-builder [js-engine]
  (doto (ProcessBuilder. ["node" (:path js-engine)
                          "--port-file" (:port-file js-engine)
                          "--default-ajax-host" (:default-ajax-host js-engine)
                          "--default-ajax-port" (str (:default-ajax-port js-engine))])
    .inheritIO))

然后在其中调用start方法。inheritIO会使其输出流输入到当前进程的输出流,有效地合并标准输出和标准错误。

此外,NodeJS通过将端口号指定为0来打开一个随机端口,并将其写入文件:

(let [app (-> (express)
              (.use (cookie-parser))
              (.get "/" (fn [_req res] (.send res "Universal JavaScript engine for server side pre-rendering single page applications.")))
              (.get "/render" render))
      server (.createServer http app)]
  (.listen server 0 (fn [] (.writeFile fs (:port-file options) (.-port (.address server)))))))))

然后Java端打开它(等待它出现):

(defn get-port-number [js-engine]
  (or (with-timeout
        (:start-timeout js-engine)
        (loop [port-number (read-port-file js-engine)]
          (if (string/blank? port-number)
            (if (is-running? js-engine)
              (do (Thread/sleep 100)
                  (recur (read-port-file js-engine)))
              (throw (Exception. (str "While waiting for port number, process died: " (:path js-engine)))))
            port-number)))
      (throw (Exception. (str "Waited for " (:start-timeout js-engine) " for " (:path js-engine) " to start and report its port number but it timed out.")))))

@Pablo 我想指出,Node.js监听0可能不总是按预期工作。请参见这里 - Cody Gustafson
@Stanimir 我无法确定nodyn.io的完整性,它似乎并不是很完整,并且该项目已经正式被放弃了。 - pupeno

5

这里有一个非常好的答案来介绍如何在Java中运行JavaScript,你可以参考这里。如果不适用于你的情况,这里提供一些资源:

  • 在Node.js中获取随机端口:您可以在构建期间访问另一个服务以查找空闲端口,或者让您的Node应用程序基于获取到的端口向Java服务器发送HTTP请求。
  • Winston 是我发现的最好的日志记录库,您不应该有任何问题将日志记录到相同的路径中。
  • ForeverPM2 是常见的 Node 进程管理器,可保持 Node 运行。我目前更喜欢 forever(不确定为什么)。

听起来你将会在 Node 中使用大量的 CPU。如果是这种情况,您可能需要使用集群模块(这样 Node.js 就能利用多个核心)。如果阻塞事件循环(CPU 处理会这样),则您仅能每个派生进程执行 1 个并发请求。


关于你指出的另一个问题,不,我已经尝试过Nashorn了,但它并不够用。我需要像NodeJS这样的东西。 - pupeno

4

我曾经处于类似的位置,需要从Python脚本中运行Fortran代码,所以这是我的建议。在Java中使用终端命令来运行你的Node.js脚本,方法如下:

Runtime rt = Runtime.getRuntime();
String[] commands = {"node example.js", "args"};
Process proc = rt.exec(commands);

BufferedReader stdInput = new BufferedReader(new 
     InputStreamReader(proc.getInputStream()));

BufferedReader stdError = new BufferedReader(new 
     InputStreamReader(proc.getErrorStream()));

// read the output from the command
System.out.println("Here is the standard output of the command:\n");
String s = null;
while ((s = stdInput.readLine()) != null) {
    System.out.println(s);
}

// read any errors from the attempted command
System.out.println("Here is the standard error of the command (if any):\n");
while ((s = stdError.readLine()) != null) {
    System.out.println(s);
}

使用设置(setup)可以向您的Node程序传递参数并获取响应。虽然我不确定您将Node.js程序用于什么,因此不确定这是否有帮助。


需要注意的是,使用这种方法每次调用将需要大约30毫秒来启动一个节点进程。 - Cody Gustafson
此外,我理解 OP 不想依赖于系统上安装的 Node。 - cviejo

3
Nashorn存在一些问题,例如难以找到有关某些API的信息(文档不太友好),开机速度缓慢等。我建议将服务器端呈现视为“服务”而不是“子进程”。换句话说,您可以在内部网络上运行一个Node.js实例,例如在10015端口,并仅允许本地连接(还可以将日志发送到任何位置)。随后,您可以使用像Phantom或Slimer这样的工具在无头浏览器中呈现页面,并通过向此服务提交请求进行渲染,例如localhost:10015/posts/。为了保持Node服务器处于启动状态,您可以使用Forever或supervisord;为了帮助更快地推出,您可以查看Prerender团队所做的:https://github.com/prerender/prerender-node

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接