All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.reactors.debugger.WebServer.scala Maven / Gradle / Ivy

package io.reactors
package debugger



import _root_.com.github.mustachejava._
import java.io.BufferedReader
import java.io.StringReader
import java.io.StringWriter
import java.util.ArrayList
import java.util.HashMap
import java.util.concurrent.TimeUnit
import java.util.regex._
import org.apache.commons.io.IOUtils
import org.json4s._
import org.json4s.jackson.JsonMethods._
import org.rapidoid.gui._
import org.rapidoid.http._
import org.rapidoid.setup._
import scala.collection._
import scala.collection.JavaConverters._
import scala.concurrent.ExecutionContext.Implicits.global



class WebServer(val system: ReactorSystem, webapi: WebApi) {
  val setup = WebServer.createServer(system, webapi)

  def shutdown() {
    setup.shutdown()
  }
}


object WebServer {
  private[debugger] def createServer(system: ReactorSystem, webapi: WebApi): Setup = {
    val port = system.bundle.config.int("debug-api.port")

    def loadPage(path: String): String = {
      sealed trait NodeType
      case object Com extends NodeType
      case object Lib extends NodeType
      case object Style extends NodeType
      case object Page extends NodeType

      class Node(val path: String, val nodeType: NodeType, var content: String = "") {
        val deps = mutable.LinkedHashMap[String, Node]()
      }

      val libPattern = Pattern.compile("\\s*@@library\\((?.*)\\)")
      val stylePattern = Pattern.compile("\\s*@@style\\((?.*)\\)")
      val componentPattern = Pattern.compile("\\s*@@component\\((?.*)\\)")
      val seen = mutable.Map[String, Node]()

      def add(n: Node): Node = {
        seen(n.path) = n
        n
      }

      def interpolate(n: Node): String = {
        val scopes = new HashMap[String, Object]
        scopes.put("reactor-system.url",
          s"${system.bundle.urlMap("udp").url.host}:$port")
        scopes.put("reactor-system.version", "0.7")
        scopes.put("debugger-ui.configuration", "{}")
        scopes.put("debugger-ui.plugins", "")
        val imports = {
          val sb = new StringBuffer
          def traverse(n: Node): Unit = if (seen.contains(n.path)) {
            seen.remove(n.path)
            for ((path, d) <- n.deps) {
              traverse(d)
            }
            sb.append("\n\n")
            n.nodeType match {
              case Style =>
                sb.append("\n")
              case Lib =>
                sb.append("\n")
              case Com =>
                sb.append(n.content)
              case Page =>
            }
            sb.append("\n\n")
          }
          traverse(n)
          sb.toString
        }
        scopes.put("debugger-ui.imports", imports)

        val writer = new StringWriter
        val mf = new DefaultMustacheFactory()
        val mustache =
          mf.compile(new StringReader(n.content), s"${system.name}.template")
        mustache.execute(writer, scopes)
        writer.toString
      }

      def loadPage(path: String): Node = {
        def loadString(path: String) = {
          val stream = getClass.getResourceAsStream("/" + path)
          if (stream == null) sys.error(s"Cannot find path: $path")
          IOUtils.toString(stream, "UTF-8")
        }

        def absorbImports(n: Node, raw: String): String = {
          val reader = new BufferedReader(new StringReader(raw))
          val sb = new StringBuffer

          var line: String = null
          while ({ line = reader.readLine(); line != null }) {
            def path(p: Pattern, txt: String): String = {
              val m = p.matcher(txt)
              if (m.matches()) m.group("path") else null
            }

            val stylepath = path(stylePattern, line)
            if (stylepath != null) {
              n.deps(stylepath) = loadStyle(stylepath)
            }

            val libpath = path(libPattern, line)
            if (libpath != null) {
              n.deps(libpath) = loadLibrary(libpath)
            }

            val compath = path(componentPattern, line)
            if (compath != null) {
              n.deps(compath) = loadCom(compath)
            }

            if (libpath == null && stylepath == null && compath == null) {
              sb.append(line).append("\n")
            }
          }

          sb.toString
        }

        def loadLibrary(path: String): Node =
          if (seen.contains(path)) seen(path)
          else add(new Node(path, Lib, loadString(path)))

        def loadStyle(path: String): Node =
          if (seen.contains(path)) seen(path) else {
            val style = add(new Node(path, Style))
            val raw = loadString(path)
            style.content = absorbImports(style, raw)
            style
          }

        def loadCom(path: String, t: NodeType = Com): Node =
          if (seen.contains(path)) seen(path) else {
            val com = add(new Node(path, t))
            val raw = loadString(path)
            com.content = absorbImports(com, raw)
            com
          }

        loadCom(path, Page)
      }

      interpolate(loadPage(path))
    }

    val debuggerPage = loadPage("io/reactors/debugger/index.html")
    val s = Setup.create(system.name)

    // attributes
    s.port(port)

    // ui routes
    s.get("/").html(debuggerPage)
    s.req((req: Req) => {
      val stream = getClass.getResourceAsStream("/io/reactors/debugger/" + req.path)
      if (stream == null) sys.error(s"Cannot find path: ${req.path}")
      if (req.path.endsWith(".svg")) IOUtils.toString(stream)
      else IOUtils.toByteArray(stream)
    })

    // api routes
    s.post("/api/state").json((req: Req) => {
      val suid = req.posted.get("suid").asInstanceOf[String]
      val ts = req.posted.get("timestamp").asInstanceOf[Number].longValue
      val repluids = req.posted.get("repluids").asInstanceOf[ArrayList[String]].asScala
      asJsonNode(webapi.state(suid, ts, repluids.toList))
    })
    s.post("/api/breakpoint/add").json((req: Req) => {
      val suid = req.posted.get("suid").asInstanceOf[String]
      val pattern = req.posted.get("pattern").asInstanceOf[String]
      val tpe = req.posted.get("tpe").asInstanceOf[String]
      ???
    })
    s.post("/api/breakpoint/list").json((req: Req) => {
      val suid = req.posted.get("suid").asInstanceOf[String]
      ???
    })
    s.post("/api/breakpoint/remove").json((req: Req) => {
      val suid = req.posted.get("suid").asInstanceOf[String]
      val bid = req.posted.get("bid").asInstanceOf[Number].longValue
      ???
    })
    s.post("/api/repl/get").json((req: Req) => {
      val tpe = req.posted.get("tpe").asInstanceOf[String]
      req.async()
      webapi.replGet(tpe).onSuccess { case result =>
        req.response.json(asJsonNode(result))
        req.done()
      }
      req
    })
    s.post("/api/repl/eval").json((req: Req) => {
      val repluid = req.posted.get("repluid").asInstanceOf[String]
      val command = req.posted.get("cmd").asInstanceOf[String]
      req.async()
      webapi.replEval(repluid, command).onSuccess { case result =>
        req.response.json(asJsonNode(result))
        req.done()
      }
      req
    })
    s.post("/api/repl/close").json((req: Req) => {
      val repluid = req.posted.get("repluid").asInstanceOf[String]
      req.async()
      webapi.replClose(repluid).onSuccess { case result =>
        req.response.json(asJsonNode(result))
        req.done()
      }
      req
    })

    s
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy