src/Pure/Tools/server.scala
changeset 67809 a5fa8d854e5e
parent 67807 331619e6c8b0
child 67811 33199d033505
equal deleted inserted replaced
67808:9cb7f5f0bf41 67809:a5fa8d854e5e
     1 /*  Title:      Pure/Tools/server.scala
     1 /*  Title:      Pure/Tools/server.scala
     2     Author:     Makarius
     2     Author:     Makarius
     3 
     3 
     4 Resident Isabelle servers.
     4 Resident Isabelle servers.
       
     5 
       
     6 Message formats:
       
     7 
       
     8   - short message (single line):
       
     9       NAME ARGUMENT
       
    10 
       
    11   - long message (multiple lines):
       
    12       BYTE_LENGTH
       
    13       NAME ARGUMENT
     5 */
    14 */
     6 
    15 
     7 package isabelle
    16 package isabelle
     8 
    17 
     9 
    18 
    14 
    23 
    15 object Server
    24 object Server
    16 {
    25 {
    17   /* protocol */
    26   /* protocol */
    18 
    27 
    19   def split_line(line: String): (String, String) =
    28   def split_message(msg: String): (String, String) =
    20   {
    29   {
    21     val head = line.takeWhile(c => Symbol.is_ascii_letter(c) || Symbol.is_ascii_letdig(c))
    30     val head = msg.takeWhile(c => Symbol.is_ascii_letter(c) || Symbol.is_ascii_letdig(c))
    22     val rest = line.substring(head.length).dropWhile(Symbol.is_ascii_blank(_))
    31     val rest = msg.substring(head.length).dropWhile(Symbol.is_ascii_blank(_))
    23     (head, proper_string(rest) getOrElse "{}")
    32     (head, proper_string(rest) getOrElse "{}")
    24   }
    33   }
    25 
    34 
    26   val commands: Map[String, PartialFunction[(Server, JSON.T), JSON.T]] =
    35   val commands: Map[String, PartialFunction[(Server, JSON.T), JSON.T]] =
    27     Map(
    36     Map(
    31 
    40 
    32   object Reply extends Enumeration
    41   object Reply extends Enumeration
    33   {
    42   {
    34     val OK, ERROR, NOTE = Value
    43     val OK, ERROR, NOTE = Value
    35 
    44 
    36     def unapply(line: String): Option[(Reply.Value, JSON.T)] =
    45     def unapply(msg: String): Option[(Reply.Value, JSON.T)] =
    37     {
    46     {
    38       if (line == "") None
    47       if (msg == "") None
    39       else {
    48       else {
    40         val (reply, output) = split_line(line)
    49         val (reply, output) = split_message(msg)
    41         try { Some((withName(reply), JSON.parse(output, strict = false))) }
    50         try { Some((withName(reply), JSON.parse(output, strict = false))) }
    42         catch {
    51         catch {
    43           case _: NoSuchElementException => None
    52           case _: NoSuchElementException => None
    44           case Exn.ERROR(_) => None
    53           case Exn.ERROR(_) => None
    45         }
    54         }
    63     def close() { socket.close }
    72     def close() { socket.close }
    64 
    73 
    65     val in = new BufferedInputStream(socket.getInputStream)
    74     val in = new BufferedInputStream(socket.getInputStream)
    66     val out = new BufferedOutputStream(socket.getOutputStream)
    75     val out = new BufferedOutputStream(socket.getOutputStream)
    67 
    76 
    68     def read_line(): Option[String] =
    77     def read_message(): Option[String] =
    69       try { Bytes.read_line(in).map(_.text) }
    78       try {
       
    79         Bytes.read_line(in).map(_.text) match {
       
    80           case Some(Value.Int(n)) =>
       
    81             Bytes.read_block(in, n).map(bytes => Library.trim_line(bytes.text))
       
    82           case res => res
       
    83         }
       
    84       }
    70       catch { case _: SocketException => None }
    85       catch { case _: SocketException => None }
    71 
    86 
    72     def write_line(msg: String)
    87     def write_message(msg: String)
    73     {
    88     {
    74       require(split_lines(msg).length <= 1)
    89       val b = UTF8.bytes(msg)
    75       out.write(UTF8.bytes(msg))
    90       if (b.length > 100 || b.contains(10)) {
       
    91         out.write(UTF8.bytes((b.length + 1).toString))
       
    92         out.write(10)
       
    93       }
       
    94       out.write(b)
    76       out.write(10)
    95       out.write(10)
    77       try { out.flush() } catch { case _: SocketException => }
    96       try { out.flush() } catch { case _: SocketException => }
    78     }
    97     }
    79 
    98 
    80     def reply(r: Server.Reply.Value, t: JSON.T)
    99     def reply(r: Server.Reply.Value, t: JSON.T)
    81     {
   100     {
    82       write_line(if (t == JSON.empty) r.toString else r.toString + " " + JSON.Format(t))
   101       write_message(if (t == JSON.empty) r.toString else r.toString + " " + JSON.Format(t))
    83     }
   102     }
    84 
   103 
    85     def reply_ok(t: JSON.T) { reply(Server.Reply.OK, t) }
   104     def reply_ok(t: JSON.T) { reply(Server.Reply.OK, t) }
    86     def reply_error(t: JSON.T) { reply(Server.Reply.ERROR, t) }
   105     def reply_error(t: JSON.T) { reply(Server.Reply.ERROR, t) }
    87     def reply_error_message(message: String, more: (String, JSON.T)*): Unit =
   106     def reply_error_message(message: String, more: (String, JSON.T)*): Unit =
   101       "server " + quote(name) + " = " + print(port, password)
   120       "server " + quote(name) + " = " + print(port, password)
   102 
   121 
   103     def connection(): Connection =
   122     def connection(): Connection =
   104     {
   123     {
   105       val connection = Connection(new Socket(InetAddress.getByName("127.0.0.1"), port))
   124       val connection = Connection(new Socket(InetAddress.getByName("127.0.0.1"), port))
   106       connection.write_line(password)
   125       connection.write_message(password)
   107       connection
   126       connection
   108     }
   127     }
   109 
   128 
   110     def active(): Boolean =
   129     def active(): Boolean =
   111       try {
   130       try {
   112         using(connection())(connection =>
   131         using(connection())(connection =>
   113           {
   132           {
   114             connection.socket.setSoTimeout(2000)
   133             connection.socket.setSoTimeout(2000)
   115             connection.read_line() == Some(Reply.OK.toString)
   134             connection.read_message() == Some(Reply.OK.toString)
   116           })
   135           })
   117       }
   136       }
   118       catch {
   137       catch {
   119         case _: IOException => false
   138         case _: IOException => false
   120         case _: SocketException => false
   139         case _: SocketException => false
   202   {
   221   {
   203     using(SQLite.open_database(Data.database))(db =>
   222     using(SQLite.open_database(Data.database))(db =>
   204       db.transaction {
   223       db.transaction {
   205         find(db, name) match {
   224         find(db, name) match {
   206           case Some(server_info) =>
   225           case Some(server_info) =>
   207             using(server_info.connection())(_.write_line("shutdown"))
   226             using(server_info.connection())(_.write_message("shutdown"))
   208             while(server_info.active) { Thread.sleep(50) }
   227             while(server_info.active) { Thread.sleep(50) }
   209             true
   228             true
   210           case None => false
   229           case None => false
   211         }
   230         }
   212       })
   231       })
   271 
   290 
   272   override def toString: String = Server.print(port, password)
   291   override def toString: String = Server.print(port, password)
   273 
   292 
   274   private def handle(connection: Server.Connection)
   293   private def handle(connection: Server.Connection)
   275   {
   294   {
   276     connection.read_line() match {
   295     connection.read_message() match {
   277       case Some(line) if line == password =>
   296       case Some(msg) if msg == password =>
   278         connection.reply_ok(JSON.empty)
   297         connection.reply_ok(JSON.empty)
   279         var finished = false
   298         var finished = false
   280         while (!finished) {
   299         while (!finished) {
   281           connection.read_line() match {
   300           connection.read_message() match {
   282             case None => finished = true
   301             case None => finished = true
   283             case Some("") =>
   302             case Some("") =>
   284               connection.notify_message("Command 'help' provides list of commands")
   303               connection.notify_message("Command 'help' provides list of commands")
   285             case Some(line) =>
   304             case Some(msg) =>
   286               val (cmd, input) = Server.split_line(line)
   305               val (cmd, input) = Server.split_message(msg)
   287               Server.commands.get(cmd) match {
   306               Server.commands.get(cmd) match {
   288                 case None => connection.reply_error("Bad command " + quote(cmd))
   307                 case None => connection.reply_error("Bad command " + quote(cmd))
   289                 case Some(body) =>
   308                 case Some(body) =>
   290                   input match {
   309                   input match {
   291                     case JSON.Format(arg) =>
   310                     case JSON.Format(arg) =>
   297                         connection.reply_error_message(
   316                         connection.reply_error_message(
   298                           "Bad argument for command", "command" -> cmd, "argument" -> arg)
   317                           "Bad argument for command", "command" -> cmd, "argument" -> arg)
   299                       }
   318                       }
   300                     case _ =>
   319                     case _ =>
   301                       connection.reply_error_message(
   320                       connection.reply_error_message(
   302                         "Malformed command-line", "command" -> cmd, "input" -> input)
   321                         "Malformed message", "command" -> cmd, "input" -> input)
   303                   }
   322                   }
   304               }
   323               }
   305           }
   324           }
   306         }
   325         }
   307       case _ =>
   326       case _ =>