Chrome原生消息主机:如何屏蔽应用标准输出避免通信中断?
问题背景
在MacOS上使用Java 8开发Chrome原生消息主机应用,通过SunPKCS11读取加密USB令牌的数字证书,核心代码如下:
String providerConfig = "name = OpenSC1\nlibrary = /usr/local/lib/libcastle.1.0.0.dylib\nslot=1\n"; ByteArrayInputStream localByteArrayInputStream = new ByteArrayInputStream(providerConfig.getBytes()); SunPKCS11 pk11provider = new SunPKCS11(localByteArrayInputStream);
实例化SunPKCS11提供器时,Chrome扩展触发onDisconnect事件,错误信息显示:
Error when communicating with the native messaging host.
经排查,问题源于底层令牌驱动API往标准输出(stdout)写入内容,而stdout是Chrome扩展与原生消息主机的专属通信通道,额外输出违反协议导致连接中断。单独运行Jar程序可正常读取证书,但终端会打印大量USB令牌相关日志;尝试重写PrintStream覆盖stdout无效,底层驱动仍使用默认输出流。
可行解决方案
方案一:Java层临时重定向stdout到空设备
在实例化SunPKCS11前,将stdout重定向到MacOS的空设备/dev/null,吞掉驱动输出,完成初始化后再恢复原输出流:
// 提前保存原stdout PrintStream originalOut = System.out; // 重定向到/dev/null System.setOut(new PrintStream(new FileOutputStream("/dev/null"))); // 执行SunPKCS11初始化 SunPKCS11 pk11provider = new SunPKCS11(localByteArrayInputStream); // 恢复原stdout,保证后续和Chrome的通信正常 System.setOut(originalOut);
如果需要保留驱动日志用于排查,可同时重定向到日志文件和空设备(需引入Apache Commons IO的TeeOutputStream):
PrintStream logStream = new PrintStream(new FileOutputStream("pkcs11_init.log")); System.setOut(new PrintStream(new TeeOutputStream(logStream, new FileOutputStream("/dev/null"))));
方案二:通过原生主机配置重定向输出
修改Chrome原生消息主机的plist配置文件,在启动命令中直接将stdout和stderr重定向到空设备,从进程启动阶段就隔离驱动输出:
原配置中的启动命令:
<key>Program</key> <string>java -jar your-app.jar</string>
修改为:
<key>Program</key> <string>bash</string> <key>ProgramArguments</key> <array> <string>-c</string> <string>java -jar your-app.jar > /dev/null 2>&1</string> </array>
该方式会丢弃进程所有标准输出和错误输出,完全避免干扰Chrome的通信协议。
方案三:扩展端无法忽略输出(说明)
Chrome原生消息协议严格要求原生主机只能通过stdout发送符合格式的JSON消息,任何非法输出都会直接触发Chrome终止连接,扩展没有机会“忽略”这些输出,因此该方向不可行,只能从原生主机端解决。
补充验证
若不确定输出来源是stdout还是stderr,可使用dtrace工具追踪Java进程的输出:
dtrace -n 'syscall::write:entry { printf("%d: %s", pid, copyinstr(arg1, arg2)); }' -p <Java进程PID>
如果输出来自stderr,可单独重定向stderr到/dev/null,不影响stdout的通信通道。
内容的提问来源于stack exchange,提问作者Daniel Facciabene

