0

I've been trying, for some time now, to create a local process that will act as a CLI for my server. The idea is similar to what Drush does for Drupal servers.

I haven't created the CLI interface yet (will probably use a 3rd party code) but I wanted to share my solution to my biggest obstacle in the matter: Transferring messages between a local process to my running and active server without using REST services because they add security risk with some commands.

Charles
  • 50,943
  • 13
  • 104
  • 142
Eyal
  • 758
  • 6
  • 23

1 Answers1

0

Note: This code is written in Scala but can be converted to Java

First we'll need to create a servlet class that extends HttpServlet. The servlet class, for every GET request, will check if our thread (explained later) is open and if not tries to start it up. Note that we are using a try catch with the start method because if the state of the thread is TERMINATED isAlive will return true (I have no idea why).

I'm also implementing the destroy method to kill our thread if the servlet dies. I'm not sure what happens to running threads if the servlet dies but just in case...

Servlet.scala:

package com.example

import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

class Servlet extends HttpServlet {

    override def doGet(req: HttpServletRequest, resp: HttpServletResponse) = {
        if (!CLIThread.isAlive) {
            try {
                CLIThread.start
            }
            catch {
                case _ => resp.getOutputStream().println("Error: thread state is, " + CLIThread.getState)
            }
        }
        resp.getOutputStream().println(CLIThread.pool)
    }

    override def destroy(): Unit = {
        super.destroy()
        CLIThread.shutdown
    }
}

Our thread (CLIThread) is scala object extending the Thread class.

CLIThread has 2 methods, pool & shutdown, and both of them are passed to our Runnable implementation.

CLIRunnable is the Runnable implemented object that is passed to the Thread constructor.

CLIRunnable has a nulled ServerSocket (listener), Socket (socket) & InputStream (in), a true Boolean (keepAlive) and an empty String (_pool) as variables.

The run method:

  1. Assigns a new ServerSocket to the listener variable. I use a while loop, a boolean, a try-catch block and a random function to assign a serverSocket that listens to an empty port, otherwise it would have thrown an Exception if the port was used
  2. Assign the accepted socket from the listener object (this method holds the thread until a a socket connects to the listener, which is why we use a thread)
  3. Assigns the socket input stream to in
  4. As long as keepAlive is true checks if the input stream is not empty and if so it fills the _pool variable with it.

The pool method:

  • If the in variable is not null (CLIThread was started and a socket connected to our port), we return the _pool String and empty it.
  • Otherwise If the above is false but listener is not null (a socket was not yet accepted) we print out the value of the listener for the user to use to connect to our port.
  • If all the above fails than listener is nulled which means the thread never started, we print the string "listener == null..."

The shutdown method:

  1. Sets keep alive to false (releases the thread from the while loop)
  2. If listener is not null:
    1. If no socket connected to our port, creates a new socket that connects and closes it, to release our listener from it's loop.
    2. If socket is not null closes it.
    3. Closes the listener

CLIThread.scala

package com.example

import java.io.InputStream
import java.net.ServerSocket
import java.net.Socket

object CLIThread extends Thread(CLIRunner) {
    def pool: String = CLIRunner.pool
    def shutdown() = CLIRunner.shutdown()
}

protected final object CLIRunner extends Runnable {
    var listener: ServerSocket = null
    var socket: Socket = null
    var in: InputStream = null
    private var keepAlive = true

    private var _pool = ""

    def run: Unit = {
        var ok = false
        while (!ok) {
            try {
                listener = new ServerSocket((math.random * 10000).toInt)
                ok = true;
            }
            catch {
                case _ => {}
            }
        }
        socket = listener.accept()
        in = socket.getInputStream
        while (keepAlive) {
            while (in.available() > 0) _pool += in.read().toChar
        }

    }
    def pool: String = if (in != null) {
        val temp = _pool
        _pool = ""
        return temp
    }
    else if (listener != null) (listener.getInetAddress, listener.getLocalPort).toString
    else "listener == null..."

    def shutdown() {
        keepAlive = false
        if (listener != null) {
            if (socket == null)
                (new Socket(listener.getInetAddress, listener.getLocalPort)).close()
            if (socket != null)
                socket.close()
            listener.close()
        }
    }
}

CLI.scala

package com.example

import java.net.Socket
import java.net.URL

object CLI extends App {
    val addr = args(0) // The server address (example.com:8080)

    val addrUrl = new URL("http://" + addr + "/~cli/addr")
    var con = addrUrl.openConnection()
    var in = con.getInputStream()
    var cliAddr = ""
    while (in.available() > 0)
        cliAddr += in.read.toChar

    val portUrl = new URL("http://" + addr + "/~cli/port")
    con = portUrl.openConnection()
    in = con.getInputStream()
    var cliPort = ""
    while (in.available() > 0)
        cliPort += in.read.toChar

    val socket = new Socket(cliAddr, Integer.valueOf(cliPort))

    implicit def stringToByteArray(s: String) = s.toCharArray.map(c => c.toByte)

    socket.getOutputStream().write("Hellllo from CLI process")
}

CliAddr.scala

package org.sdms

import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

class CliAddr extends HttpServlet {
    override def doGet(req: HttpServletRequest, resp: HttpServletResponse) {
        resp.getWriter.print(CLIRunner.listener.getInetAddress.getHostAddress)
    }
}

CliPort.scala

package com.example

import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

class CliPort extends HttpServlet {
    override def doGet(req: HttpServletRequest, resp: HttpServletResponse) {
        resp.getWriter.print(CLIRunner.listener.getLocalPort)
    }
}
Community
  • 1
  • 1
Eyal
  • 758
  • 6
  • 23