mirror of https://github.com/sbt/sbt.git
Updated Client server discovery lifecycle (markdown)
parent
be27184c5b
commit
187398e978
|
|
@ -112,4 +112,118 @@ The interface has two methods. One responsible for starting the Server, and ano
|
|||
|
||||
Port bindings come in java.net.URI, which includes an address, a protocol and a port. The server will actually also write down the PID (process ID) of the server (assuming it can obtain the pid). This enables clients to "kill rogue sbt server" directly.
|
||||
|
||||
Clients should be able to be notified of all open ServerPort bindings created by a server.
|
||||
Clients should be able to be notified of all open ServerPort bindings created by a server.
|
||||
|
||||
## Sample code
|
||||
|
||||
The following accomplishes:
|
||||
|
||||
```text
|
||||
client: I want the application defined by boot.properties to run in cwd and use version X of protocol N
|
||||
server-service: the application is running and listening on port P
|
||||
```
|
||||
|
||||
```scala
|
||||
client:
|
||||
val bootProperties: File = ...
|
||||
val connectionConfiguration = Map("x" -> "y")
|
||||
val dir: File = ...
|
||||
val (output: String, err: String, exit: Int) =
|
||||
(stdout, stderr, exitCode) of { "java -jar serverProvider.jar bootProperties connectionConfiguration(as key=value args)".in(dir).!!! }
|
||||
if(exit == 0)
|
||||
println("Server running on port: " + output.toInt)
|
||||
else
|
||||
println("Server connection failed: " + err)
|
||||
|
||||
server-provider:
|
||||
def main(args: Array[String])
|
||||
{
|
||||
val bootProperties: File = new File(args(0))
|
||||
val cwd: File = (new File(".")).getAbsoluteFile
|
||||
val connectionConfiguration: Map[String, String] =
|
||||
args.drop(1).map(_.split("=", 2) match { case Array(key,value) => (key,value) }).toMap
|
||||
|
||||
val serverFile = serverFileLocation(bootProperties, cwd)
|
||||
val connection = withLockFile( serverFile ) {
|
||||
readServerInfo( serverFile ) match {
|
||||
case None => startAndConnect(boot.properties, cwd, connectionConfiguration)
|
||||
case Some(info) => connect(info, connectionConfiguration)
|
||||
}
|
||||
}
|
||||
System.out.println(connection.port)
|
||||
System.exit(0)
|
||||
}
|
||||
|
||||
def connect(info: ServerInfo, connectionConfiguration: Map[String,String]): ConnectionInfo =
|
||||
requestConnection(info, connectionConfiguration) match {
|
||||
case Left(NoServerPresent) => startAndConnect(bootProperties, cwd, connectionConfiguration)
|
||||
case Left(e) => fail(e.toString)
|
||||
case Right(newConnection) => newConnection
|
||||
}
|
||||
def requestConnection(info: ServerInfo, connectionConfiguration: Map[String, String]): Either[<error>, ConnectionInfo] =
|
||||
try {
|
||||
openSocket(info.port) match {
|
||||
case Left(err) => Left(NoServerPresent)
|
||||
case Right(socket) =>
|
||||
sendConfiguration(socket, connectionConfiguration)
|
||||
ConnectionInfo(receivePort(socket))
|
||||
}
|
||||
} catch {
|
||||
case e: Exception => Left(e)
|
||||
}
|
||||
|
||||
|
||||
def fail(msg: String) {
|
||||
System.err.println(msg)
|
||||
System.exit(1)
|
||||
}
|
||||
def startAndConnect(bootProperties: File, cwd: File, connectionConfiguration: Map[String,String]): ConnectionInfo =
|
||||
connect(newServer(bootProperties, cwd), connectionConfiguration)
|
||||
def newServer(bootProperties: File, cwd: File, connectionConfiguration: Map[String,String]): ServerInfo =
|
||||
{
|
||||
val process = "java -jar sbt-launch.jar @bootProperties".in(cwd).run
|
||||
// this line is not quite right- reading a line from std output would be part of configuring the above line
|
||||
val readPort = fromStdOut(process)
|
||||
val info = ServerInfo(readPort, "faked")
|
||||
if(code != 0) fail("Couldn't start server")
|
||||
writeServerInfo(serverFile, info)
|
||||
info
|
||||
}
|
||||
|
||||
def sendConfiguration(socket: Socket, connectionConfiguration: Map[String,String]): Unit
|
||||
def receivePort(socket: Socket): Int
|
||||
def withLockFile[T](file: File)(run: => T): T
|
||||
def serverFileLocation(bootProperties: File, cwd: File): File
|
||||
def writeServerInfo(file: File, info: ServerInfo): Unit
|
||||
def readServerInfo(file: File): Option[ServerInfo]
|
||||
final case class ServerInfo(port: Int, pid: String)
|
||||
final case class ConnectionInfo(port: Int)
|
||||
|
||||
server:
|
||||
val socket: ServerSocket = localSocket
|
||||
System.out.println(socket.getLocalPort)
|
||||
...
|
||||
// probably part of an sbt command that is run initially
|
||||
while(true) {
|
||||
val config: Map[String, String] = receiveConfiguration(socket)
|
||||
val port = newClient(config)
|
||||
writePort(socket, port)
|
||||
}
|
||||
|
||||
def receiveConfiguration(socket: ServerSocket): Map[String,String] =
|
||||
{
|
||||
// I forget the details- not sure if you have to recreate a ServerSocket
|
||||
// for every accept or if you can accept multiple times on the same ServerSocket
|
||||
val s = socket.accept()
|
||||
readConfiguration(s)
|
||||
}
|
||||
def localSocket: ServerSocket = new ServerSocket(0, 0, InetAddress.getByName(null))
|
||||
def readConfiguration(socket: Socket): Map[String,String]
|
||||
def writePort(socket: Socket, port: Int): Unit
|
||||
def newClient(config: Map[String, String]): Int = {
|
||||
val socket: ServerSocket = localSocket
|
||||
<start a thread listening on socket, connect it to command processing, etc...>
|
||||
socket.getLocalPort
|
||||
}
|
||||
|
||||
```
|
||||
Loading…
Reference in New Issue