Golang jsonrpc2 服务器 listending 在哪里?

Golang jsonrpc2 server where is it listending?

提问人:Matteo Possamai 提问时间:7/16/2023 更新时间:7/16/2023 访问量:129

问:

我想在 golang 中创建一个简单的 LSP 服务器,到目前为止,这是我编写的代码:

package main

import (
    "context"
    "fmt"
    "os"
    "sync"

    "github.com/sourcegraph/jsonrpc2"
)

type LSPServer struct {
    // The symmetric connection
    conn jsonrpc2.Conn

    // Check if the connection is available
    connMutex sync.Mutex

    // shutdown
    shutdown bool
}

func NewLSPServer() *LSPServer {
    return &LSPServer{}
}

func (s *LSPServer) Initialize(ctx context.Context) error {
    // to implement
    return nil
}

func (s *LSPServer) Handle(context.Context, *jsonrpc2.Conn, *jsonrpc2.Request) (result interface{}, err error) {
    fmt.Println("Handling request...")
    // to implement
    return nil, nil
}

func (s *LSPServer) Serve(ctx context.Context) {
    fmt.Println("Starting LSP server...")
    // what port is this server listening on?
    // it is listening on port 4389

    // Create a new jsonrpc2 stream server
    handler := jsonrpc2.HandlerWithError(s.Handle)

    // Create a new jsonrpc2 stream server
    <-jsonrpc2.NewConn(
        context.Background(),
        jsonrpc2.NewBufferedStream(os.Stdin, jsonrpc2.VSCodeObjectCodec{}),
        handler).DisconnectNotify()
}

func main() {

    // Create a new LSP server
    server := NewLSPServer()
    server.Serve(context.Background())

}

它运行,但我不知道它在哪个端口上运行,或者通常如何使用客户端调用它。有人有什么想法吗?

我认为应该是端口 4389,但不是那个

我正在用这个脚本进行测试:

import json
import requests

def rpc_call(url, method, args):
    headers = {'content-type': 'application/json'}
    payload = {
        "method": method,
        "params": [args],
        "jsonrpc": "2.0",
        "id": 1,
    }
    response = requests.post(url, data=json.dumps(payload), headers=headers).json()
    return response['result']

url = 'http://localhost:4389/'

emailArgs = {'To': '[email protected]','Subject': 'Hello', 'Content': 'Hi!!!'}
smsArgs = {'Number': '381641234567', 'Content': 'Sms!!!'}
print(rpc_call(url, 'email.SendEmail', emailArgs))
print(rpc_call(url, 'sms.SendSMS', smsArgs))

我认为这是正确的,因为我从另一个 stackoverflow 问题中获取了这个客户端

json go json-rpc

评论


答:

2赞 VonC 7/16/2023 #1

明白了:

HandlerWithError(s.Handle)

    // Create a new jsonrpc2 stream server
    <-jsonrpc2.NewConn(
        context.Background(),
        jsonrpc2.NewBufferedStream(os.Stdin, jsonrpc2.VSCodeObjectCodec{}),
        handler).DisconnectNotify()
}

这意味着您的代码在标准输入和输出 (stdin/stdout) 上使用 JSON-RPC,而不是通过网络连接。
当您用作 jsonrpc2 的参数时。NewBufferedStream,则指定输入应来自运行服务器的进程的标准输入。响应将被发送到标准输出。
os.Stdin

因此,服务器未侦听任何网络端口。它与直接发送到其标准输入和输出的数据进行交互。这通常用于进程间通信,例如,当您希望一个进程调用服务器进程并接收响应时。
例如,请参阅“Go: bidirectional communication with another process?”davidelorenzoli/stdin-stdout-ipc


如果您希望 JSON-RPC 服务器侦听网络端口,则需要使用 net在 Go 中设置网络连接。您还需要修改客户端脚本,将其请求发送到正确的网络端口,而不是向 URL 发送 HTTP 请求。

package main

import (
    "context"
    "net"
    "log"
    "sync"

    "github.com/sourcegraph/jsonrpc2"
)

type LSPServer struct {
    // The symmetric connection
    conn jsonrpc2.Conn

    // Check if the connection is available
    connMutex sync.Mutex

    // shutdown
    shutdown bool
}

func NewLSPServer() *LSPServer {
    return &LSPServer{}
}

func (s *LSPServer) Initialize(ctx context.Context) error {
    // Initialize here if needed
    return nil
}

func (s *LSPServer) Handle(context.Context, *jsonrpc2.Conn, *jsonrpc2.Request) (result interface{}, err error) {
    fmt.Println("Handling request...")
    // Handle something
    return nil, nil
}

func (s *LSPServer) Serve(ctx context.Context) {
    fmt.Println("Starting LSP server...")
    
    // Listen on TCP port 4389 on all available unicast and
    // anycast IP addresses of the local system.
    l, err := net.Listen("tcp", "localhost:4389")
    if err != nil {
        log.Fatal(err)
    }
    defer l.Close()

    for {
        // Wait for a connection.
        conn, err := l.Accept()
        if err != nil {
            log.Fatal(err)
        }

        // Handle the connection in a new goroutine.
        go func(c net.Conn) {
            // Create a new jsonrpc2 stream server
            handler := jsonrpc2.HandlerWithError(s.Handle)
            <-jsonrpc2.NewConn(
                ctx,
                jsonrpc2.NewBufferedStream(c, jsonrpc2.VSCodeObjectCodec{}),
                handler).DisconnectNotify()
            c.Close()
        }(conn)
    }
}

func main() {
    // Create a new LSP server
    server := NewLSPServer()
    go server.Serve(context.Background()) // run Serve in a separate goroutine
    select {} // wait forever
}

这是一个基本示例,其中该方法创建一个 TCP 侦听器,该侦听器侦听本地主机的端口 4389。然后,它进入一个循环,等待连接,当它获得连接时,它会启动一个新的 goroutine 来使用您的 JSON-RPC 服务器处理该连接。Serve


在客户端,您需要打开与服务器的 TCP 连接,将 JSON-RPC 请求写入该连接,然后读取响应。

您不能像在 Python 脚本中那样使用 requests,因为它用于 HTTP 请求,而不是原始 TCP 连接。
您需要使用 Python 中的套接字库或客户端语言中的类似库来创建 TCP 连接并通过它发送/接收数据。


但请记住,LSP(语言服务器协议)通过 stdin/stdout 而不是网络套接字运行。
这是因为 LSP 服务器通常由编辑器/IDE 作为子进程启动,并直接通过这些通道进行通信。因此,根据您的用例,原始的 stdin/stdout 方法可能更合适。

2赞 Zeke Lu 7/16/2023 #2

这是对@VonC出色回答的补充。此答案提供了使原始 stdin/stdout 方法起作用的方法。请先阅读@VonC的回答。stdioReadWriteCloser

在原始问题中构建源代码:

go build -o lspserver .

然后将此消息发送到 的 :lspserverstdin

echo 'Content-Length: 70\r\n\r\n{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}' | ./lspserver
Starting LSP server...
Handling request...
2023/07/16 16:16:37 jsonrpc2 handler: sending response 2: write /dev/stdin: bad file descriptor

(注意:VSCodeObjectCodec 需要Content-Length: %d\r\n\r\n)

请注意错误消息。它尝试写入 。这是不正确的。我们必须创建一个 from,如下所示:stdinio.ReadWriteCloserstdinstdout

type stdioReadWriteCloser struct{}

var _ io.ReadWriteCloser = (*stdioReadWriteCloser)(nil)

func (c stdioReadWriteCloser) Read(p []byte) (n int, err error) {
    return os.Stdin.Read(p)
}

func (c stdioReadWriteCloser) Write(p []byte) (n int, err error) {
    return os.Stdout.Write(p)
}

func (c stdioReadWriteCloser) Close() error {
    return nil
}

并像这样使用:stdioReadWriteCloser

<-jsonrpc2.NewConn(
    context.Background(),
    jsonrpc2.NewBufferedStream(stdioReadWriteCloser{}, jsonrpc2.VSCodeObjectCodec{}),
    handler).DisconnectNotify()

生成并重试:lspserver

echo 'Content-Length: 70\r\n\r\n{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 2}' | ./lspserver
Starting LSP server...
Handling request...
Content-Length: 38

{"id":2,"result":null,"jsonrpc":"2.0"}

现在它按预期工作!

奇怪的是,没有提供这样的开箱即用的包装器。github.com/sourcegraph/jsonrpc2

评论

0赞 VonC 7/16/2023
不错:这确实说明了 LSP 工作的 stdin/stdout 预期方式。点赞。
0赞 Zeke Lu 7/16/2023
谢谢VonC!我从一开始就支持你的答案 <3.这很有帮助!