我应该如何从 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 服务器保持正常运行,您可以使用 Foreversupervisord,为了帮助您更快地获得关注,您可以查看 Prerender 团队所做的工作: https://github.com/prerender/prerender-node

【讨论】:

以上是关于我应该如何从 Java 应用程序运行 NodeJS?的主要内容,如果未能解决你的问题,请参考以下文章

从nodejs运行bash命令[重复]

如何从 NodeJS 运行 CLI 命令? [复制]

如何在非关系数据模型上构建关系 Java 对象模型?

如何使用 Geoip 在 NodeJs 上获取当前城市名称

如何从nodejs刷新子进程

如何在 nodejs 中运行 bootstrap 和 jquery