sbt 服务器是 sbt 1.x 中新引入的功能,它仍在开发中。你可能最初认为服务器是运行在远程服务器上的东西,并能完成很多很棒的事情,但就目前而言,sbt 服务器并不是那样。
实际上,sbt 服务器只是在 sbt 的 shell 命令中添加了网络访问,因此除了接受来自终端的输入之外,服务器还可以接受来自网络的输入。这允许多个客户端连接到一个 *单个 sbt 会话*。我们对客户端的主要用例是工具集成,例如编辑器和 IDE。请参见 IDE 集成 页面。
有几个设置可用于配置服务器。以下列出了一些设置及其默认值。可以针对每个项目更改设置,也可以在 ~/.sbt/1.0/global.sbt
中设置值。
// If set to a defined value, sbt server will exit if it goes at least the
// specified duration without receiving any commands.
Global / serverIdleTimeout := Some(new FiniteDuration(5, TimeUnit.MINUTES))
我们使用的线协议是 语言服务器协议 3.0 (LSP),它基于 JSON-RPC。
基本协议由标头和内容部分组成(类似于 HTTP)。标头和内容部分用 \r\n
分隔。
目前支持以下标头字段
Content-Length
:内容部分的长度(以字节为单位)。如果你没有提供此标头,我们将读取直到行尾。Content-Type
:必须设置为 application/vscode-jsonrpc; charset=utf-8
或省略。以下是一个示例
Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n
Content-Length: ...\r\n
\r\n
{
"jsonrpc": "2.0",
"id": 1,
"method": "textDocument/didSave",
"params": {
...
}
}
一个 JSON-RPC 请求由一个 id
号、一个 method
名称和一个可选的 params
对象组成。因此所有 LSP 请求都是方法名称和 params
JSON 的对。
对 JSON-RPC 请求的示例响应是
Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n
Content-Length: ...\r\n
\r\n
{
"jsonrpc": "2.0",
"id": 1,
"result": {
...
}
}
或者服务器可能返回错误响应
Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n
Content-Length: ...\r\n
\r\n
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "some error message"
}
}
除了响应之外,服务器还可以发送事件(在 LSP 术语中称为“通知”)。
Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n
Content-Length: ...\r\n
\r\n
{
"jsonrpc": "2.0",
"method": "textDocument/publishDiagnostics",
"params": {
...
}
}
Sbt 服务器可以运行在两种模式下,这两种模式在线协议和初始化方面有所不同。自 sbt 1.1.x 以来,默认模式是 *域套接字模式*,它使用 Unix 域套接字(在 Unix 上)或命名管道(在 Windows 上)进行服务器和客户端之间的数据传输。此外,还有一种 *TCP 模式*,它使用 TCP 进行数据传输。
sbt 服务器启动的模式由键 serverConnectionType
决定,该键可以设置为 ConnectionType.Local
(用于域套接字/命名管道模式),也可以设置为 ConnectionType.Tcp
(用于 TCP 模式)。
为了发现正在运行的服务器,我们使用 *端口文件*。
默认情况下,当 sbt shell 会话处于活动状态时,sbt 服务器将运行。当服务器启动时,它将创建一个名为端口文件的文件。端口文件位于 ./project/target/active.json
。端口文件的内容将根据服务器是在 TCP 模式下运行还是在域套接字/命名管道模式下运行而有所不同。它们看起来像这样
在域套接字/命名管道模式下,在 Unix 上
{"uri":"local:///Users/someone/.sbt/1.0/server/0845deda85cb41abdb9f/sock"}
其中 uri
键将包含一个以 local://
开头的字符串,后跟 sbt 服务器正在监听的套接字地址。
在域套接字/命名管道模式下,在 Windows 上,它看起来像这样
{"uri":"local:sbt-server-0845deda85cb41abdb9f"}
其中 uri
键将包含一个以 local:
开头的字符串,后跟命名管道的名称。在此示例中,命名管道的路径将为 \.\pipe\sbt-server-0845deda85cb41abdb9f
。
在 TCP 模式下,它看起来像这样
{
"uri":"tcp://127.0.0.1:5010",
"tokenfilePath":"/Users/xxx/.sbt/1.0/server/0845deda85cb41abdb9f/token.json",
"tokenfileUri":"file:/Users/xxx/.sbt/1.0/server/0845deda85cb41abdb9f/token.json"
}
在这种情况下,uri
键将包含一个 TCP uri,其中包含服务器正在监听的地址。在此模式下,端口文件将包含两个额外的键,tokenfilePath
和 tokenfileUri
。它们指向 *令牌文件* 的位置。
令牌文件的位置在运行之间不会改变。它的内容看起来像这样
{
"uri":"tcp://127.0.0.1:5010",
"token":"12345678901234567890123456789012345678"
}
uri
字段相同,token
字段包含一个 128 位非负整数。
为了启动与 sbt 服务器的通信,客户端(例如 VS Code 等工具)必须首先发送一个 `initialize` 请求。这意味着客户端必须发送一个方法设置为“initialize”、params
字段为 InitializeParams
数据类型的请求。
如果服务器在 TCP 模式下运行,则为了验证自己,必须在 initializationOptions
中传递令牌,如下所示
type InitializationOptionsParams {
token: String!
}
在 telnet 上,它看起来像这样
$ telnet 127.0.0.1 5010
Content-Type: application/vscode-jsonrpc; charset=utf-8
Content-Length: 149
{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "initializationOptions": { "token": "84046191245433876643612047032303751629" } } }
如果服务器在命名管道模式下运行,则不需要令牌,initializationOptions
应该为空对象 {}
。
在 Unix 上,使用 netcat 在域套接字/命名管道模式下发送初始化消息看起来像这样
$ nc -U /Users/foo/.sbt/1.0/server/0845deda85cb41abcdef/sock
Content-Length: 99^M
^M
{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "initializationOptions": { } } }^M
当服务器在命名管道模式下运行时,对服务器的连接是独占的,只允许第一个连接到套接字或管道的进程访问。
sbt 接收请求后,将发送一个 `initialized` 事件。
textDocument/publishDiagnostics
事件 编译器警告和错误使用 textDocument/publishDiagnostics
事件发送到客户端。
textDocument/publishDiagnostics
以下是一个示例输出(省略了 JSON-RPC 标头)
{
"jsonrpc": "2.0",
"method": "textDocument/publishDiagnostics",
"params": {
"uri": "file:/Users/xxx/work/hellotest/Hello.scala",
"diagnostics": [
{
"range": {
"start": {
"line": 2,
"character": 0
},
"end": {
"line": 2,
"character": 1
}
},
"severity": 1,
"source": "sbt",
"message": "')' expected but '}' found."
}
]
}
}
textDocument/didSave
事件 从 sbt 1.1.0 开始,sbt 将在收到 textDocument/didSave
通知后执行 compile
任务。此行为可能会发生变化。
sbt/exec
请求 sbt/exec
请求模拟用户在 shell 中输入内容。
sbt/exec
type SbtExecParams {
commandLine: String!
}
在 telnet 上,它看起来像这样
Content-Length: 91
{ "jsonrpc": "2.0", "id": 2, "method": "sbt/exec", "params": { "commandLine": "clean" } }
请注意,可能还有其他命令正在构建上运行,在这种情况下,请求将被排队。
sbt/setting
请求 sbt/setting
请求可用于查询设置。
sbt/setting
type SettingQuery {
setting: String!
}
在 telnet 上,它看起来像这样
Content-Length: 102
{ "jsonrpc": "2.0", "id": 3, "method": "sbt/setting", "params": { "setting": "root/scalaVersion" } }
Content-Length: 87
Content-Type: application/vscode-jsonrpc; charset=utf-8
{"jsonrpc":"2.0","id":"3","result":{"value":"2.12.2","contentType":"java.lang.String"}}
与命令执行不同,它会立即响应。
sbt/completion
请求 (sbt 1.3.0+)
sbt/completion
请求用于模拟 sbt shell 的制表符补全。
sbt/completion
` type CompletionParams { query: String! }
`在 telnet 上,它看起来像这样
Content-Length: 100
{ "jsonrpc": "2.0", "id": 15, "method": "sbt/completion", "params": { "query": "testOnly org." } }
Content-Length: 79
Content-Type: application/vscode-jsonrpc; charset=utf-8
{"jsonrpc":"2.0","id":15,"result":{"items":["testOnly org.sbt.ExampleSpec"]}}
它将根据 sbt 的最后一个可用状态立即响应。
sbt/cancelRequest
(sbt 1.3.0+)
sbt/cancelRequest
请求可用于终止正在进行的任务的执行。
sbt/cancelRequest
` type CancelRequestParams { id: String! }
`在 telnet 上,它看起来像这样(假设一个名为“foo”的任务正在运行)
Content-Length: 93
{ "jsonrpc": "2.0", "id": "bar", "method": "sbt/cancelRequest", "params": { "id": "foo" } }
Content-Length: 126
Content-Type: application/vscode-jsonrpc; charset=utf-8
{"jsonrpc":"2.0","id":"bar","result":{"status":"Task cancelled","channelName":"network-1","execId":"foo","commandQueue":[]}}
它将响应操作的结果。