我应该如何从 Java 应用程序运行 NodeJS?
Posted
技术标签:
【中文标题】我应该如何从 Java 应用程序运行 NodeJS?【英文标题】:How should I run NodeJS from a Java application? 【发布时间】:2015-12-12 22:49:22 【问题描述】:我正在编写一个 Java 库,实际上是一个 Clojure 库,但是对于这个问题,重要的是它运行在 JVM 上。这个库需要执行一些 javascript。 I tried with Nashorn 但我遇到了一些可能难以克服的限制。作为替代方案,我想尝试 NodeJS。
我希望我的库是自包含的,不依赖于独立运行 NodeJS 的系统,因此需要特定的部署机制将 Java 和 NodeJS 工件放置在正确的位置,以供两个不同的网络服务器获取。但是,这种方法会带来一些问题。
我将通过 HTTP 与 NodeJS 交谈,但我不希望 NodeJS 打开特定端口。我想找到一个随机未使用的,这样就不会发生冲突。我还想控制来自 NodeJS 的日志的去向,以便将它们与我的应用程序的其余部分一起保存。最后,我的应用应该能够检测到 NodeJS 何时崩溃并重新运行它或报告错误信息。
解决这个问题的最佳方法是什么?是否有任何 Java 库可以帮助以这种方式管理子进程?我应该从 NodeJS 方面做任何特别的事情(我对 NodeJS 很陌生,我以前从未使用过它)。
【问题讨论】:
我想这取决于您的库打算如何被使用,但是让库启动重量级服务器进程以完成其工作似乎是代码异味。 你听说过头像吗?我也遇到了一些问题,我无法使用一些 nodejs 模块,但头像填补了空白:strongloop.com/strongblog/… 只是为了缩小答案范围......您到底遇到了什么限制?使用 nashorn 的服务器端脚本增加了 6ms 到 avg。最后你的帖子中的响应时间,所以我猜性能不是其中之一 【参考方案1】:我最终的解决方案是像这样使用 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 导致它的输出转到当前进程的输出,该进程有效地合并了 stdout 和 stderr。
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 想我会注意到在 0 上监听的 Node.js 可能并不总是按预期运行。见here @Stanimir 搞不懂nodyn.io到底有多完整,好像不是很完整,项目正式放弃了。【参考方案2】:关于如何在 java 中运行 javascript,有一个很好的答案 here。对于您的情况,这样的事情是否可行?如果没有,这里有一些资源:
Random port in nodejs 您可以在构建过程中点击另一个服务来查找开放端口,或者让您的节点应用根据它获取的端口向您的 java 服务器发送一个 http 请求。 Winston 是我发现的最好的日志库,您应该不会有任何问题记录到同一路径。 Forever 和 PM2 是保持节点运行的常用节点进程管理器。我目前更喜欢永远(不知道为什么)听起来您将在节点中使用大量 cpu。如果是这种情况,您可能会想要使用cluster module(这样nodejs 可以利用多个内核)。如果您阻止事件循环(基于 cpu 的处理将,那么您将只能执行每个分叉进程的 1 个并发请求)。
【讨论】:
关于您指出的其他问题,不,我已经尝试过 Nashorn,但这还不够。我需要类似 NodeJS 的东西。【参考方案3】:我一直处于类似的位置,我不得不从 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);
通过设置,您可以将参数传递给您的节点程序并获得响应。虽然我不确定您使用 node.js 程序的目的是什么,但不确定这是否有帮助。
【讨论】:
使用此方法需要注意的一点是,每次调用启动节点进程大约需要 30 毫秒。 另外,我知道 OP 不想依赖系统上安装的节点【参考方案4】:Nashorn 确实有一些我也遇到过的问题,例如查找有关他们的一些 API 的信息(文档还有很多不足之处),以及启动缓慢。
我的建议是:将您的服务器端渲染视为服务,而不是子进程。
换句话说,您可以在内部网络上的端口10015
上运行 Node.js 实例,并且只允许本地连接到它(您也可以将日志发送到任何您想要的地方)。然后,您可以使用要呈现的页面向服务发出请求,例如 localhost:10015/posts/
,并让该 Node.js 应用在无头浏览器中呈现页面(使用 Phantom 或 Slimer 之类的东西)。
为了让您的 Node 服务器保持正常运行,您可以使用 Forever
或 supervisord
,为了帮助您更快地获得关注,您可以查看 Prerender 团队所做的工作:
https://github.com/prerender/prerender-node
【讨论】:
以上是关于我应该如何从 Java 应用程序运行 NodeJS?的主要内容,如果未能解决你的问题,请参考以下文章