Vert.x Shell

Vert.x Shell 是一个基于Vert.x, 支持不同协议的统一终端的命令行接口

Vert.x Shell 提供了一系列命令来与 Vert.x 服务进行实时互动

Vert.x Shell 可以使用任何 Vert.x 支持的语言来扩展自定义命令

使用 Vert.x Shell

Vert.x Shell 是一个Vert.x服务,既可以通过 ShellService 以编码的方式启动, 也可以作为一个服务发布。

Shell 服务

shell服务可以直接以命令行的方式启动,也可以通过 Vert.x 发布启动

启动一个基于 Telnet 的 shell 服务
vertx run -conf '{"telnetOptions":{"port":5000}}' maven:io.vertx:vertx-shell:4.4.0

或者

启动一个基于 SSH 的 shell 服务
# 为 SSH 服务端创建密钥
keytool -genkey -keyalg RSA -keystore ssh.jks -keysize 2048 -validity 1095 -dname CN=localhost -keypass secret -storepass secret
# 创建授权配置文件
echo user.admin=password > auth.properties
# 启动 shell
vertx run -conf '{"sshOptions":{"port":4000,"keyPairOptions":{"path":"ssh.jks","password":"secret"},"authOptions":{"provider":"shiro","config":{"properties_path":"file:auth.properties"}}}}' maven:io.vertx:vertx-shell:4.4.0

或者

启动一个基于 HTTP 的shell服务
# 为 HTTP 服务端创建证书
keytool -genkey -keyalg RSA -keystore keystore.jks -keysize 2048 -validity 1095 -dname CN=localhost -keypass secret -storepass secret
# 创建授权配置文件
echo user.admin=password > auth.properties
vertx run -conf '{"httpOptions":{"port":8080,"ssl":true,"keyStoreOptions":{"path":"keystore.jks","password":"secret"},"authOptions":{"provider":""properties,"config":{"file":"file:auth.properties"}}}}' maven:io.vertx:vertx-shell:4.4.0

你也可以在你的 verticle 中发布 shell 服务:

vertx
  .deployVerticle("maven:{maven-groupId}:{maven-artifactId}:{maven-version}",
    new DeploymentOptions().setConfig(
      new JsonObject().put("telnetOptions",
        new JsonObject().
          put("host", "localhost").
          put("port", 4000))
    )
  );

或者

vertx
  .deployVerticle("maven:{maven-groupId}:{maven-artifactId}:{maven-version}",
    new DeploymentOptions().setConfig(new JsonObject().
      put("sshOptions", new JsonObject().
        put("host", "localhost").
        put("port", 5000).
        put("keyPairOptions", new JsonObject().
          put("path", "src/test/resources/ssh.jks").
          put("password", "wibble")).
        put("authOptions", new JsonObject().
          put("provider", "properties").
          put("config", new JsonObject().
            put("file", "file:/path/to/my/auth.properties"))))
    )
  );

或者

vertx
  .deployVerticle("maven:{maven-groupId}:{maven-artifactId}:{maven-version}",
    new DeploymentOptions().setConfig(new JsonObject().
      put("httpOptions", new JsonObject().
        put("host", "localhost").
        put("port", 8080).
        put("ssl", true).
        put("keyPairOptions", new JsonObject().
          put("path", "src/test/resources/server-keystore.jks").
          put("password", "wibble")).
        put("authOptions", new JsonObject().
          put("provider", "properties").
          put("config", new JsonObject().
            put("file", "file:/path/to/my/auth.properties"))))
    )
  );
注意
当你的 classpath 中已经包含了Vert.x Shell , 你可以使用 service:io.vertx.ext.shell 来代替 maven:io.vertx:vertx-shell:4.4.0

可编码的服务

使用 ShellService 来启动Vert.x Shell 实例。

启动一个基于 SSH 的 shell 服务:

ShellService service = ShellService.create(vertx,
  new ShellServiceOptions().setSSHOptions(
    new SSHTermOptions().
      setHost("localhost").
      setPort(5000).
      setKeyPairOptions(new JksOptions().
        setPath("server-keystore.jks").
        setPassword("wibble")
      ).
      setAuthOptions(
        new JsonObject()
          .put("provider", "properties")
          .put("type", "PROPERTIES")
          .put("config", new JsonObject().
            put("properties_path", "file:/path/to/my/auth.properties"))
      )
  )
);
service.start();

启动一个基于 Telnet 的 shell 服务:

ShellService service = ShellService.create(vertx,
  new ShellServiceOptions().setTelnetOptions(
    new TelnetTermOptions().
      setHost("localhost").
      setPort(4000)
  )
);
service.start();

Telnet server 基于 NetServer 实现, 其配置 TelnetTermOptions 继承自 Vert.x Core 模块中的 NetServerOptions

小心
Telnet 不提供任何身份授权和加密机制。

启动一个基于 HTTP 的 shell 服务:

ShellService service = ShellService.create(vertx,
  new ShellServiceOptions().setHttpOptions(
    new HttpTermOptions().
      setHost("localhost").
      setPort(8080)
  )
);
service.start();

身份认证

SSH 和 HTTP 连接管理器提供了一套基于 vertx-auth 的身份认证机制, 并支持以下授权方式:

  • properties: 提供 .properties 后端

  • ldap: 提供 LDAP 后端

  • mongo : 基于 MongoDB 的身份认证

  • jdbc : 基于 JDBC 的身份认证

  • shiro : (已过时) 正如演示中看到的,提供了 .properties 配置文件 和 LDAP 这两种认证方式

可以使用相关的类来直接创建配置项:

对于使用 Json 的外部服务配置,使用 authOptions 对象来表示授权的基本配置, 其 provider 属性用来区分不同的授权方式:

{
 ...
 "authOptions": {
   "provider":"properties",
   "config": {
     "file":"file:auth.properties"
   }
 }
 ...
}

Telnet 终端配置

Telnet 终端通过 setTelnetOptions 来配置, `TelnetTermOptions 继承自 NetServerOptions, 所以它们具有完全相同的配置。

SSH 终端配置

SSH 终端通过 setSSHOptions 来配置:

目前仅支持 username/password(用户名+密码) 这种授权认证方式,可以通过属性文件或者 LDAP 来进行配置, 获取更多详细信息请查看 Vert.x Auth 文档:

服务端密钥配置采用的是 Vert.x Core 提供的密钥对存储配置方案:

在 SSH 上发布基于 Mongo 身份授权的 Shell 服务
vertx
  .deployVerticle("maven:{maven-groupId}:{maven-artifactId}:{maven-version}",
    new DeploymentOptions().setConfig(new JsonObject().
      put("sshOptions", new JsonObject().
        put("host", "localhost").
        put("port", 5000).
        put("keyPairOptions", new JsonObject().
          put("path", "src/test/resources/ssh.jks").
          put("password", "wibble")).
        put("authOptions", new JsonObject().
          put("provider", "mongo").
          put("config", new JsonObject().
            put("connection_string", "mongodb://localhost:27018"))))
    )
  );
在 SSH 上运行基于 Mongo 身份授权的 Shell 服务
ShellService service = ShellService.create(vertx,
  new ShellServiceOptions().setSSHOptions(
    new SSHTermOptions().
      setHost("localhost").
      setPort(5000).
      setKeyPairOptions(new JksOptions().
        setPath("server-keystore.jks").
        setPassword("wibble")
      ).
      setAuthOptions(new JsonObject()
        .put("provider", "mongo")
        .put("config", new JsonObject()
          .put("connection_string", "mongodb://localhost:27018"))
      )
  )
);
service.start();
在 SSH 上发布基于 JDBC 身份授权的 Shell 服务
vertx
  .deployVerticle("maven:{maven-groupId}:{maven-artifactId}:{maven-version}",
    new DeploymentOptions().setConfig(new JsonObject().
      put("sshOptions", new JsonObject().
        put("host", "localhost").
        put("port", 5000).
        put("keyPairOptions", new JsonObject().
          put("path", "src/test/resources/ssh.jks").
          put("password", "wibble")).
        put("authOptions", new JsonObject().
          put("provider", "jdbc").
          put("config", new JsonObject()
            .put("url", "jdbc:hsqldb:mem:test?shutdown=true")
            .put("driver_class", "org.hsqldb.jdbcDriver"))))
    )
  );
在 SSH 上运行基于 JDBC 身份授权的 Shell 服务
ShellService service = ShellService.create(vertx,
  new ShellServiceOptions().setSSHOptions(
    new SSHTermOptions().
      setHost("localhost").
      setPort(5000).
      setKeyPairOptions(new JksOptions().
        setPath("server-keystore.jks").
        setPassword("wibble")
      ).
      setAuthOptions(new JsonObject()
        .put("provider", "jdbc")
        .put("config", new JsonObject()
          .put("url", "jdbc:hsqldb:mem:test?shutdown=true")
          .put("driver_class", "org.hsqldb.jdbcDriver"))
      )
  )
);
service.start();

HTTP 终端配置

HTTP 终端通过 setHttpOptions 来配置, 这个配置 继承自 HttpServerOptions,所以它们提供了完全相同的配置。

此外,它还提供了配置 HTTP 终端的其他选项:

在 HTTP 上发布基于 Mongo 身份授权的 Shell 服务
vertx
  .deployVerticle("maven:{maven-groupId}:{maven-artifactId}:{maven-version}",
    new DeploymentOptions().setConfig(new JsonObject().
      put("httpOptions", new JsonObject().
        put("host", "localhost").
        put("port", 8080).
        put("ssl", true).
        put("keyPairOptions", new JsonObject().
          put("path", "src/test/resources/server-keystore.jks").
          put("password", "wibble")).
        put("authOptions", new JsonObject().
          put("provider", "mongo").
          put("config", new JsonObject().
            put("connection_string", "mongodb://localhost:27018"))))
    )
  );
在 HTTP 上运行基于 Mongo 身份授权的 Shell 服务
ShellService service = ShellService.create(vertx,
  new ShellServiceOptions().setHttpOptions(
    new HttpTermOptions().
      setHost("localhost").
      setPort(8080).
      setAuthOptions(new JsonObject()
        .put("provider", "mongo")
        .put("config", new JsonObject()
          .put("connection_string", "mongodb://localhost:27018"))
      )
  )
);
service.start();
在 HTTP 上发布基于 JDBC 身份授权的 Shell 服务
vertx
  .deployVerticle("maven:{maven-groupId}:{maven-artifactId}:{maven-version}",
    new DeploymentOptions().setConfig(new JsonObject().
      put("httpOptions", new JsonObject().
        put("host", "localhost").
        put("port", 8080).
        put("ssl", true).
        put("keyPairOptions", new JsonObject().
          put("path", "src/test/resources/server-keystore.jks").
          put("password", "wibble")).
        put("authOptions", new JsonObject().
          put("provider", "jdbc").
          put("config", new JsonObject()
            .put("url", "jdbc:hsqldb:mem:test?shutdown=true")
            .put("driver_class", "org.hsqldb.jdbcDriver"))))
    )
  );
在 HTTP 上运行基于 JDBC 身份授权的 Shell 服务
ShellService service = ShellService.create(vertx,
  new ShellServiceOptions().setHttpOptions(
    new HttpTermOptions().
      setHost("localhost").
      setPort(8080).
      setAuthOptions(new JsonObject()
        .put("provider", "jdbc")
        .put("config", new JsonObject()
          .put("url", "jdbc:hsqldb:mem:test?shutdown=true")
          .put("driver_class", "org.hsqldb.jdbcDriver"))
      )
  )
);
service.start();

按键映射配置

shell 使用默认的按键映射配置, 可以使用各种终端配置对象的 inputrc 属性来进行覆盖。

inputrc 必须通过 classloader 或者文件系统指定一个存在的文件。

inputrc 只能进行函数绑定,目前可用的函数有:

  • backward-char

  • forward-char

  • next-history

  • previous-history

  • backward-delete-char

  • backward-delete-char

  • backward-word

  • end-of-line

  • beginning-of-line

  • delete-char

  • delete-char

  • complete

  • accept-line

  • accept-line

  • kill-line

  • backward-word

  • forward-word

  • backward-kill-word

注意
要想添加额外的函数,必须要实现一个`Term.d` 项目中的函数,这个项目包含在 Vert.x Shell 中。 例如,首先实现一个 reverse function 函数, 然后在 META-INF/services/io.termd.core.readline.Function 中添加实现声明,最后 shell 会以 SPI 的方式加载该函数。

基本命令

你可以使用内置命令 help 来获取当前可用的命令:

  1. Verticle 命令

    1. verticle-ls: 列出所有已发布的 verticle

    2. verticle-undeploy: 卸载一个verticle

    3. verticle-deploy: 以JSON字符串作为配置来发布 verticle

    4. verticle-factories: 列出所有已知的 verticle 工厂

  2. 文件系统命令

    1. ls

    2. cd

    3. pwd

  3. Event Bus 命令

    1. bus-tail: 显示 Event Bus 地址上所有接收到的消息

    2. bus-send: 在 Event Bus 上发送消息

  4. 网络命令

    1. net-ls: 列出所有存在网络服务端,包括HTTP服务端

  5. 共享数据命令

    1. local-map-put

    2. local-map-get

    3. local-map-rm

  6. 其他命令

    1. echo

    2. sleep

    3. help

    4. exit

    5. logout

  7. 任务控制

    1. fg

    2. bg

    3. jobs

注意
这个命令列表应该会在下个 Vert.x Shell 版本中得到改进,以便其他 Vert.x 项目可以对 Vert.x Shell 进行扩展, 例如 Dropwizard Metrics。

扩展 Vert.x Shell

在任何支持代码生成的语言 中,Vert.x Shell 都可以通过自定义命令的方式来进行扩展。

可以使用 CommandBuilder.command 方法来创建命令: 通过 processHandler 为命令指定 处理器 , 这个 处理器 会在命令执行的时候被 Shell 调用。

CommandBuilder builder = CommandBuilder.command("my-command");
builder.processHandler(process -> {

  // Write a message to the console
  process.write("Hello World");

  // End the process
  process.end();
});

// Register the command
CommandRegistry registry = CommandRegistry.getShared(vertx);
registry.registerCommand(builder.build(vertx));

在命令创建完成后,还需要注册到 CommandRegistry 中,这个 命令注册表保存着 Vert.x 实例的所有命令。

一旦通过 unregisterCommand 方法进行注销,该命令就会失效。 如果命令注册在 Verticle 中,那么卸载 verticle 之时,便是该命令注销之时。

注意
当在注册表中注册命令时,将在 {@literal io.vertx.core.Context} 中调用命令的回调函数。 当你想要在命令中保持状态尤其需要注意这一点。

CommandProcess 对象可以用于与 shell 互动。

命令参数

args 方法可以返回命令参数:

command.processHandler(process -> {

  for (String arg : process.args()) {
    // 在控制台上打印所有的参数
    process.write("Argument " + arg);
  }

  process.end();
});

除此之外,也可以使用 Vert.x CLI 来创建命令: 这种方式更容易对写入的命令行参数进行解析:

  • optionargument 解析

  • 参数 校验

  • 生成命令的 帮助文档

CLI cli = CLI.create("my-command").
  addArgument(new Argument().setArgName("my-arg")).
  addOption(new Option().setShortName("m").setLongName("my-option"));
CommandBuilder command = CommandBuilder.command(cli);
command.processHandler(process -> {

  CommandLine commandLine = process.commandLine();

  String argValue = commandLine.getArgumentValue(0);
  String optValue = commandLine.getOptionValue("my-option");
  process
    .write("The argument is " + argValue + " and the option is " + optValue);

  process.end();
});

当一个名为 help 选项被添加到 CLI 对象中时, shell 将负责在该选项激活时生成命令的帮助文档:

CLI cli = CLI.create("my-command").
  addArgument(new Argument().setArgName("my-arg")).
  addOption(new Option()
    .setArgName("help")
    .setShortName("h")
    .setLongName("help"));

CommandBuilder command = CommandBuilder.command(cli);
command.processHandler(process -> {
  // ...
});

命令不仅提供了 process 与 shell 互动, 还提供了继承自 TtyCommandProcess 来与 terminal 互动。

终端用法

终端 I/O

当终端接收到数据时,stdinHandler 注册的 处理器 会被调用, 比如用户的键盘输入:

tty.stdinHandler(data -> System.out.println("Received " + data));

命令可以使用 write 写入数据到标准输出流中。

tty.write("Hello World");

终端窗口大小

当前的终端窗口大小可以使用 widthheight 来获取。

tty.write("Current terminal size: (" + tty.width() + ", " + tty.height() + ")");

窗口尺寸调整事件

当终端的窗口大小改变,resizehandler 将会被调用, 新的终端窗口大小可以通过 widthheight 来获得。

tty.resizehandler(v ->
  System.out.println("terminal resized : " + tty.width() + " " + tty.height()));

终端类型

终端类型被用来给远程的终端发送转义符号: type 方法返回当前终端的类型,如果终端没有指定该值,则可能返回为 null。

System.out.println("terminal type : " + tty.type());

Shell 会话

shell 是一个有连接的服务,所以它需要维护与客户端之间的会话,命令可以使用这个会话来限制数据的有效范围。 任何命令都可以使用 session 来获取当前的会话。

command.processHandler(process -> {

  Session session = process.session();

  if (session.get("my_key") == null) {
    session.put("my key", "my value");
  }

  process.end();
});

进程结束

调用 end 方法即可结束当前的处理进程, 它可以在处理进程中的任何时候进行调用:

command.processHandler(process -> {
  Vertx vertx = process.vertx();

  // Set a timer
  vertx.setTimer(1000, id -> {

    // End the command when the timer is fired
    process.end();
  });
});

进程事件

命令可以订阅一些进程事件.

中断事件

当用户在命令执行的过程中键入 Ctrl+C,该中断事件将会被触发,随后该进程被中断, interruptHandler 指定的 处理器 即被调用。 该 handler 可以用来中断命令对 CLI 的 阻塞,然后优雅地结束命令进程。

command.processHandler(process -> {
  Vertx vertx = process.vertx();

  // 在控制台每隔1s打印一次信息
  long periodicId = vertx.setPeriodic(1000, id -> process.write("tick\n"));

  // 当用户按下 Ctrl+C:取消定时器的并结束当前进程
  process.interruptHandler(v -> {
    vertx.cancelTimer(periodicId);
    process.end();
  });
});

如果没有注册任何中断 处理器, 输入 Ctrl+C 不会对当前的命令进程产生丝毫影响, 并且该事件很可能会被 shell 延期处理,就好像是在控制台换行一样。

挂起/恢复 事件

如果用户对正在运行的命令进行输入 Ctrl+Z ,那么由 suspendHandler 注册的 处理器 即被调用, 该命令则会被 挂起

  • 如果命令注册了挂起事件,那么它将接收到该事件的触发。

  • 命令不会收到标准输入的任何数据

  • shell 会提示用户继续输入

  • 命令可以收到中断事件和结束事件

当用户键入 fg,当前命令处理进程即可恢复, 而 resumeHandler 注册的 处理器 也会被调用:

  • 如果命令注册了恢复事件,那么它可以收到该事件的触发

  • 如果命令已经注册了 stdin 处理器,那么它可以再一次从标准输入流中获取到数据

command.processHandler(process -> {

  // 命令被挂起
  process.suspendHandler(v -> System.out.println("Suspended"));

  // 命令恢复
  process.resumeHandler(v -> System.out.println("Resumed"));
});

结束事件

当正在运行或者挂起的命令进程被终止时, endHandler 将会被调用, 比如 shell 会话关闭或者命令 结束

command.processHandler(process -> {

  // 命令终止
  process.endHandler(v -> System.out.println("Terminated"));
});

一旦执行 end 方法,结束 处理器 即被调用。

处理器 对于命令终止时的一些资源清理是非常有用的,比如关闭连接或者定时器等。

命令完成

命令提供了完成 处理器 ,以便于实现上下文命令行的完成接口。

由于其实现是基于 Vert.x 服务,completion handler 与其他处理 handler一样都是非阻塞的,比如文件系统。

lineTokens 返回从行起点到光标位置的 tokens 列表, 如果光标位于行起点, 那么这个列表可能为空。

rawLine 返回当前完成列表从行起点到光标位置的原始格式的数据, 即没有任何字符转义。

通过调用 complete 来结束当前的完成事件。

Shell 服务端

无论是以编码的方式,还是作为一个 Vert.x 服务,Shell 服务提供了便利的方式来启动一个预配置的 shell。 如果需要更大的灵活性,那么可以使用 ShellServer 来代替 shell 服务。

例如 shell http 终端可以配置成使用现有的路由,而不是启动一个自己的 http 服务端。

要想使用 shell 服务端需要配置明确,但也更加灵活,一个 shell 服务端配置需要以下几步:

ShellServer server = ShellServer.create(vertx); (1)

Router shellRouter = Router.router(vertx); (2)
router.route("/shell/*").subRouter(shellRouter);
TermServer httpTermServer = TermServer.createHttpTermServer(vertx, router);

TermServer sshTermServer = TermServer.createSSHTermServer(vertx); (3)

server.registerTermServer(httpTermServer); (4)
server.registerTermServer(sshTermServer);

server.registerCommandResolver(CommandResolver.baseCommands(vertx)); (5)

server.listen(); (6)
  1. 创建一个 shell 服务端

  2. 创建一个 HTTP 终端服务器挂载到现有的路由

  3. 创建一个 SSH 终端的服务器

  4. 注册终端服务器

  5. 注册所有基本命令

  6. 最后启动 shell 服务端

除此之外,shell 服务端还可以用于创建处理进程中shell会话:提供了一个可编码的交互式 shell 。

使用 createShell 来创建处理进程中 shell 会话:

Shell shell = shellServer.createShell();

主要的作用是运行和命令测试:

Shell shell = shellServer.createShell();

// 为当前命令创建一个任务
Job job = shell.createJob("my-command 1234");

// 创建一个伪终端
Pty pty = Pty.create();
pty.stdoutHandler(data -> System.out.println("Command wrote " + data));

// 运行命令
job.setTty(pty.slave());
job.statusUpdateHandler(status ->
  System.out.println("Command terminated with status " + status));

当 shell 运行时, Pty 伪终端是与命令互动的主要手段:

  • 使用标准的 输入/输出 来写入和读取数据

  • 调整终端的窗口大小

当使用 close 关闭 shell 时, 它将会结束当前 shell 会话中的所有任务。

终端服务器

Vert.x Shell 还为那些需要写纯终端应用的开发者提供了裸终端服务器。

Term处理器 必须在终端服务器启动前配置好, 当用户连接时,这个 处理器 将处理每个终端的数据。

{@code Auth*Options} 可以通过 SSHTermOptionsHttpTermOptions 来设置。 或者,在终端服务器启动之前通过 set 直接设置 AuthenticationProvider

SSH 终端

终端服务器提供了 Term 处理器 来接受传入的终端连接。 当一个远程终端进行连接时, Term 可以用来与当前连接的终端互动。

TermServer server = TermServer
  .createSSHTermServer(vertx,
    new SSHTermOptions().setPort(5000).setHost("localhost"));

server.termHandler(term -> term.stdinHandler(term::write));
server.listen();

Term 同时也是一个 Tty, 本节将介绍如何使用 tty。

Telnet 终端

TermServer server = TermServer
  .createTelnetTermServer(vertx,
    new TelnetTermOptions().setPort(5000).setHost("localhost"));

server.termHandler(term -> term.stdinHandler(term::write));
server.listen();

HTTP 终端

使用 TermServer.createHttpTermServer 方法创建一个 HTTP终端服务器, 该服务器基于 Vert.x Web ,使用 SockJS 协议。

TermServer server = TermServer
  .createHttpTermServer(vertx,
    new HttpTermOptions().setPort(5000).setHost("localhost"));

server.termHandler(term -> term.stdinHandler(term::write));
server.listen();

一个 HTTP终端可以启动自己的 HTTP 服务器,也可以复用 Vert.x Web 中已存在的 Router

shell 可以在 /shell.html 中找到。

TermServer server = TermServer
  .createHttpTermServer(vertx,
    router,
    new HttpTermOptions().setPort(5000).setHost("localhost"));

server.termHandler(term -> term.stdinHandler(term::write));
server.listen();

如果 HTTP shell 集成到现有的 HTTP 服务器中,那么后面的配置将会很方便。

HTTP 终端服务器提供了一些默认配置:

  • shell.html 访问页面

  • term.js 客户端js

  • the vertxshell.js 客户端js

vertxshell.js 集成 term.js 作为 HTTP终端客户端连接的一部分。

它将 term.js 与 SockJS 集成,并且需要 HTTP 终端服务器的地址作为访问地址:

window.addEventListener('load', function () {
 var url = 'http://localhost/shell';
 new VertxTerm(url, {
   cols: 80,
   rows: 24
  });
});

也可以使用更直接的 websockets,如果是这样,远程的终端 URL 需要以 /websocket 开始:

window.addEventListener('load', function () {
 var url = 'ws://localhost/shell/websocket';
 new VertxTerm(url, {
   cols: 80,
   rows: 24
  });
});

为了满足更多定制化需求,这些资源统一集中在 Vert.x Shell 模块下面的 io.vertx.ext.shell 包中, 可以直接复制和修改。

命令发现

命令发现可以用来在没有明确注册的情况下添加新的命令到 Vert.x 中。

比如,在 Dropwizard metrics 服务中,通过这种方式动态地添加特定的 metrics 命令到 shell 服务中。

这一切都是通过扩展自 java.util.ServiceLoaderCommandResolverFactory 实现的。

public class CustomCommands implements CommandResolverFactory {

 public void resolver(Vertx vertx, Handler<AsyncResult<CommandResolver>> resolverHandler) {
   resolverHandler.handler(() -> Arrays.asList(myCommand1, myCommand2));
 }
}

resolver 方法是异步的,因为在命令被执行之前, resolver 可能需要等待一些条件。

shell 服务发现使用了服务加载机制:

服务提供的加载文件 META-INF/services/io.vertx.ext.shell.spi.CommandResolverFactory
my.CustomCommands

这只对 ShellService 有效, ShellServer 并没有使用该机制。

命令包

命令包是一个提供了新的 Vert.x Shell 命令的 jar 包文件。

该 jar 文件只需要放在 classpath 目录下,然后就会被 Vertx. Shell 发现。

public class CommandPackExample implements CommandResolverFactory {

  @Override
  public void resolver(Vertx vertx, Handler<AsyncResult<CommandResolver>> resolveHandler) {
    List<Command> commands = new ArrayList<>();

    // 添加一个命令
    commands.add(Command.create(vertx, JavaCommandExample.class));

    // 添加另一个命令
    commands.add(CommandBuilder.command("another-command").processHandler(process -> {
      // 命令处理逻辑
    }).build(vertx));

    // 用命令来处理
    resolveHandler.handle(Future.succeededFuture(() -> commands));
  }
}

该命令包使用了命令发现机制,所以需要一些服务描述:

META-INF/services/io.vertx.ext.shell.spi.CommandResolverFactory 文件中添加内容
examples.pack.CommandPackExample