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': '[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))
I think it is correct since I took this client from another stackoverflow question
2
Answers
I see:
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 tojsonrpc2.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.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.
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:
And then send this message to
lspserver
‘sstdin
:(Note:
Content-Length: %drnrn
is required by the VSCodeObjectCodec)Pay attention to the error message. It tries to write to
stdin
. That’s incorrect. We have to create anio.ReadWriteCloser
fromstdin
andstdout
like this:And use
stdioReadWriteCloser
like this:Build
lspserver
and try again:Now it works as expected!
It’s weird that
github.com/sourcegraph/jsonrpc2
does not provide such a wrapper out of the box.