英文:
Golang jsonrpc2 server where is it listending?
问题
我想在golang中创建一个简单的LSP服务器,到目前为止,这是我编写的代码:
package main
import (
"context"
"fmt"
"os"
"sync"
"github.com/sourcegraph/jsonrpc2"
)
type LSPServer struct {
// 对称连接
conn jsonrpc2.Conn
// 检查连接是否可用
connMutex sync.Mutex
// 关闭
shutdown bool
}
func NewLSPServer() *LSPServer {
return &LSPServer{}
}
func (s *LSPServer) Initialize(ctx context.Context) error {
// 实现
return nil
}
func (s *LSPServer) Handle(context.Context, *jsonrpc2.Conn, *jsonrpc2.Request) (result interface{}, err error) {
fmt.Println("处理请求...")
// 实现
return nil, nil
}
func (s *LSPServer) Serve(ctx context.Context) {
fmt.Println("启动LSP服务器...")
// 该服务器监听的端口是多少?
// 它监听在4389端口上
// 创建一个新的jsonrpc2流服务器
handler := jsonrpc2.HandlerWithError(s.Handle)
// 创建一个新的jsonrpc2流服务器
<-jsonrpc2.NewConn(
context.Background(),
jsonrpc2.NewBufferedStream(os.Stdin, jsonrpc2.VSCodeObjectCodec{}),
handler).DisconnectNotify()
}
func main() {
// 创建一个新的LSP服务器
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': 'demo@example.com','Subject': 'Hello', 'Content': 'Hi!!!'}
smsArgs = {'Number': '381641234567', 'Content': 'Sms!!!'}
print(rpc_call(url, 'email.SendEmail', emailArgs))
print(rpc_call(url, 'sms.SendSMS', smsArgs))
我认为这是正确的,因为我从另一个stackoverflow问题中获取了这个客户端。
英文:
I want to create a simple LSP server in golang, and so far this is the code I wrote:
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())
}
It runs, but I don't know what port it is running on, or how to call it with a client in general. Does someone have some ideas?
I think it should be port 4389, but it is not that one
I am testing with this script:
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': 'demo@example.com','Subject': 'Hello', 'Content': 'Hi!!!'}
smsArgs = {'Number': '381641234567', 'Content': 'Sms!!!'}
print(rpc_call(url, 'email.SendEmail', emailArgs))
print(rpc_call(url, 'sms.SendSMS', smsArgs))
I think it is correct since I took this client from another stackoverflow question
答案1
得分: 2
我了解了。你的代码使用标准输入和输出(stdin/stdout)进行 JSON-RPC,而不是通过网络连接。当你将 os.Stdin
作为参数传递给 jsonrpc2.NewBufferedStream
时,你指定输入应该来自运行服务器的进程的标准输入,响应将发送到标准输出。
因此,服务器不会监听任何网络端口。它与直接发送到其标准输入和输出的数据进行交互。这通常用于进程间通信,例如当你希望一个进程调用服务器进程并接收响应时。例如,参考“Go: bidirectional communication with another process?”或 davidelorenzoli/stdin-stdout-ipc
。
如果你希望 JSON-RPC 服务器监听网络端口,你需要在 Go 中使用 net
包 建立网络连接。你还需要修改客户端脚本,将请求发送到正确的网络端口,而不是向 URL 发送 HTTP 请求。
以下是一个基本示例,其中 Serve
方法创建一个 TCP 监听器,监听本地主机的端口 4389。然后进入循环,等待连接,当接收到连接时,它会启动一个新的 goroutine 来使用你的 JSON-RPC 服务器处理该连接。
在客户端,你需要打开一个 TCP 连接到服务器,将 JSON-RPC 请求写入该连接,然后读取响应。你不能像在 Python 脚本中那样使用 requests
库,因为它是用于 HTTP 请求,而不是原始的 TCP 连接。你需要在 Python 中使用 socket
库,或者在客户端的语言中使用类似的库,来创建 TCP 连接并在其上发送/接收数据。
但请注意,LSP(Language Server Protocol) 是通过 stdin/stdout 进行操作,而不是网络套接字。这是因为 LSP 服务器通常由编辑器/IDE 作为子进程启动,并直接通过这些通道进行通信。因此,根据你的用例,原始的 stdin/stdout 方法可能更合适。
英文:
I see:
HandlerWithError(s.Handle)
// Create a new jsonrpc2 stream server
<-jsonrpc2.NewConn(
context.Background(),
jsonrpc2.NewBufferedStream(os.Stdin, jsonrpc2.VSCodeObjectCodec{}),
handler).DisconnectNotify()
}
That means your code is using JSON-RPC over standard input and output (stdin/stdout), not over a network connection.
When you use os.Stdin
as a parameter to jsonrpc2.NewBufferedStream
, you are specifying that input should come from the standard input of the process running the server. And the response will be sent to the standard output.
So, the server is not listening on any network port. It is interacting with data that is sent directly to its standard input and output. That is often used for inter-process communication, for example, when you want one process to call the server process and receive a response.
See for instance "Go: bidirectional communication with another process?" or davidelorenzoli/stdin-stdout-ipc
.
If you want your JSON-RPC server to listen on a network port, you will need to set up a network connection in Go using the net
package. You will also need to modify your client script to send its requests to the correct network port, rather than sending an HTTP request to a URL.
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
}
That is a basic example, where the Serve
method creates a TCP listener that listens on port 4389 of the localhost. It then enters a loop where it waits for connections, and when it gets one, it starts a new goroutine to handle that connection using your JSON-RPC server.
On the client side, you would need to open a TCP connection to the server, write your JSON-RPC request to that connection, then read the response.
You cannot use the requests
library as in your Python script because that is for HTTP requests, not raw TCP connections.
You will need to use the socket
library in Python, or a similar library in your client's language, to create a TCP connection and send/receive data over it.
But keep in mind, an LSP (Language Server Protocol) operates over stdin/stdout rather than network sockets.
That is because LSP servers are typically launched as subprocesses by the editor/IDE and communicate directly through these channels. So depending on your use-case, the original stdin/stdout method might be more appropriate.
答案2
得分: 2
这是对@VonC的出色答案的补充。这个答案提供了一个stdioReadWriteCloser
,使原始的stdin/stdout方法能够工作。请先阅读@VonC的答案。
编译原始问题中的源代码:
go build -o lspserver .
然后将此消息发送到lspserver
的stdin
:
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
(注意:Content-Length: %d\r\n\r\n
是由VSCodeObjectCodec要求的)
注意错误消息。它试图写入stdin
。这是不正确的。我们必须从stdin
和stdout
创建一个io.ReadWriteCloser
,像这样:
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
没有提供这样的包装器。
英文:
This is an addition to @VonC's excellent answer. This answer provides a stdioReadWriteCloser
to make the original stdin/stdout method work. Please read @VonC's answer first.
Build the source code in the original question:
go build -o lspserver .
And then send this message to lspserver
's stdin
:
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
(Note: Content-Length: %d\r\n\r\n
is required by the VSCodeObjectCodec)
Pay attention to the error message. It tries to write to stdin
. That's incorrect. We have to create an io.ReadWriteCloser
from stdin
and stdout
like this:
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
}
And use stdioReadWriteCloser
like this:
<-jsonrpc2.NewConn(
context.Background(),
jsonrpc2.NewBufferedStream(stdioReadWriteCloser{}, jsonrpc2.VSCodeObjectCodec{}),
handler).DisconnectNotify()
Build lspserver
and try again:
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"}
Now it works as expected!
It's weird that github.com/sourcegraph/jsonrpc2
does not provide such a wrapper out of the box.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论