Habr shell: we build a cross-platform ssh server in java application
I'll tell you how to implement an ssh server into an existing java application that can output data about the best articles from habrahabr to the terminal. This is just an example, but based on it, you can get an additional tool to administer your program and extend the behavior of any commands, without changing the source code and rebuilding the application.
Screencast of work can be seen at the end of the article. ')
We use RaSH as ssh server - this is one of the implementations of the command interpreter on java. It allows you to create a server in the java process that executes commands via ssh / telnet protocols and includes a set of ready-made commands with the ability to write new ones on groovy. Out of the box there are ready-made commands for working with JMX, access to the database, java streams, monitoring heap, changes in the logging level.
Perhaps this project is familiar if you used Spring Boot, Mulesoft Enterprise Service Bus, Play Framework, Vert.x. Now you can even embed it in corporate legacy on java!
So, let's start with the implementation of the habr command. The implementation is written in groovy. For those who are familiar with java, writing a new implementation will not be difficult.
Let's save our command for Habr in the sonarqube-5.1.2 / cmd / habrahabrShell.groovy file:
import org.crsh.cli.Usage import org.crsh.cli.Command import org.crsh.cli.Argument import org.crsh.cli.Required @Usage("Example for habrahabr")classhabrahabrShell{ @Usage("Display best topics from habrahabr")@Commandpublic void displayBest() { def feedContent = "http://habrahabr.ru/rss/best/".toURL().text def rss = new XmlSlurper().parseText(feedContent) rss.channel.item.each{ out << "$it.title [$it.link]\n" } } @Usage("Say hello")@Commandpublic void sayHello(@Required@Argument String message) { out << "Hello habrahabr $message" } }
Note that the command interpreter will use the command name based on the file name, not the class name. @Usage annotations allow CRaSH to print help on the command and its parameters. This command, which displays the Hello habrahabr console with your parameter, is not particularly useful and has a rather motivating effect and shows that writing commands is quite simple. The displayBest command is more difficult to implement: we retrieve the contents of the rss feed using toURL (). Text, parse it with XmlSlurper (). ParseText () and output to the console a list of the best articles for the day and links to them, iterating over the elements of channel.item from the feed .
Our experimental application for the implementation of CRaSH remains the SonarQube. How to download it, configure it and stuff a aspectj with an agent in detail told and shown in the last article . The configuration of -javaagent is left unchanged, and in org.aspectj.weaver.loadtime.configuration we specify the new script file config: file: /home/igor/dev/projects/sonar-demo/scripts/shell-console.xml
Contents of the shell-console.xml file:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><configuration><aspects><name>com.github.igorsuhorukov.rashubSsh</name><type>AFTER</type><pointcut>execution(* org.sonar.server.app.WebServer.start(..))</pointcut><artifacts><artifact>org.crashub:crash.connectors.ssh:1.3.1</artifact><classRefs><variable>Bootstrap</variable><className>org.crsh.standalone.Bootstrap</className></classRefs><classRefs><variable>Builder</variable><className>org.crsh.vfs.FS$Builder</className></classRefs><classRefs><variable>ClassPathMountFactory</variable><className>org.crsh.vfs.spi.url.ClassPathMountFactory</className></classRefs><classRefs><variable>FileMountFactory</variable><className>org.crsh.vfs.spi.file.FileMountFactory</className></classRefs></artifacts><process><expression> classLoader = Bootstrap.getClassLoader(); classpathDriver = new ClassPathMountFactory(classLoader); otherCmd = new FileMountFactory(new java.io.File(System.getProperty("user.dir"))); cmdFS = new Builder().register("classpath", classpathDriver).register("file", otherCmd).mount("classpath:/crash/commands/").mount("file:cmd/").build(); confFS = new Builder().register("classpath", classpathDriver).mount("classpath:/crash/").build(); bootstrap = new Bootstrap(classLoader, confFS, cmdFS); config = new java.util.Properties(); config.put("crash.ssh.port", "2000"); config.put("crash.ssh.auth_timeout", "300000"); config.put("crash.ssh.idle_timeout", "300000"); config.put("crash.auth", "simple"); config.put("crash.auth.simple.username", "admin"); config.put("crash.auth.simple.password", "admin"); bootstrap.setConfig(config); bootstrap.bootstrap(); //bootstrap.shutdown(); </expression></process></aspects></configuration>
This configuration allows the agent, after executing the start () method of the org.sonar.server.app.WebServer class, download from the maven repository the artifact org.crashub: crash.connectors.ssh: 1.3.1, configure the server and start it by calling bootstrap.bootstrap () Our server will find our command in the cmd directory, which we specified during initialization with mount (“file: cmd /”). Well, authentication will be using the username and password specified by us admin / admin, ssh server will listen to port 2000 - put ("crash.ssh.port", "2000")
This is implemented on MVEL - java-like script using the AspectJ-scripting agent and the appropriate configuration. The agent is available in the central maven repository.
On September 9, my open session on aspect-oriented programming will be held in Moscow. Admission is free, after I publish report materials in Habré. Register openevent9sept2015.questionpro.com
All the magic of this article is implemented using aspect-oriented programming and consists only of 2 files: habrahabrShell.groovy and shell-console.xml and a parameter indicating the jvm agent. Isn't it easy !?