github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/lsprpc/lsprpc.go (about) 1 // Copyright 2020 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package lsprpc implements a jsonrpc2.StreamServer that may be used to 6 // serve the LSP on a jsonrpc2 channel. 7 package lsprpc 8 9 import ( 10 "context" 11 "encoding/json" 12 "fmt" 13 "log" 14 "net" 15 "os" 16 "strconv" 17 "strings" 18 "sync" 19 "sync/atomic" 20 "time" 21 22 "github.com/jhump/golang-x-tools/internal/event" 23 "github.com/jhump/golang-x-tools/internal/jsonrpc2" 24 "github.com/jhump/golang-x-tools/internal/lsp" 25 "github.com/jhump/golang-x-tools/internal/lsp/cache" 26 "github.com/jhump/golang-x-tools/internal/lsp/command" 27 "github.com/jhump/golang-x-tools/internal/lsp/debug" 28 "github.com/jhump/golang-x-tools/internal/lsp/debug/tag" 29 "github.com/jhump/golang-x-tools/internal/lsp/protocol" 30 errors "golang.org/x/xerrors" 31 ) 32 33 // Unique identifiers for client/server. 34 var serverIndex int64 35 36 // The StreamServer type is a jsonrpc2.StreamServer that handles incoming 37 // streams as a new LSP session, using a shared cache. 38 type StreamServer struct { 39 cache *cache.Cache 40 // daemon controls whether or not to log new connections. 41 daemon bool 42 43 // serverForTest may be set to a test fake for testing. 44 serverForTest protocol.Server 45 } 46 47 // NewStreamServer creates a StreamServer using the shared cache. If 48 // withTelemetry is true, each session is instrumented with telemetry that 49 // records RPC statistics. 50 func NewStreamServer(cache *cache.Cache, daemon bool) *StreamServer { 51 return &StreamServer{cache: cache, daemon: daemon} 52 } 53 54 func (s *StreamServer) Binder() *ServerBinder { 55 newServer := func(ctx context.Context, client protocol.ClientCloser) protocol.Server { 56 session := s.cache.NewSession(ctx) 57 server := s.serverForTest 58 if server == nil { 59 server = lsp.NewServer(session, client) 60 debug.GetInstance(ctx).AddService(server, session) 61 } 62 return server 63 } 64 return NewServerBinder(newServer) 65 } 66 67 // ServeStream implements the jsonrpc2.StreamServer interface, by handling 68 // incoming streams using a new lsp server. 69 func (s *StreamServer) ServeStream(ctx context.Context, conn jsonrpc2.Conn) error { 70 client := protocol.ClientDispatcher(conn) 71 session := s.cache.NewSession(ctx) 72 server := s.serverForTest 73 if server == nil { 74 server = lsp.NewServer(session, client) 75 debug.GetInstance(ctx).AddService(server, session) 76 } 77 // Clients may or may not send a shutdown message. Make sure the server is 78 // shut down. 79 // TODO(rFindley): this shutdown should perhaps be on a disconnected context. 80 defer func() { 81 if err := server.Shutdown(ctx); err != nil { 82 event.Error(ctx, "error shutting down", err) 83 } 84 }() 85 executable, err := os.Executable() 86 if err != nil { 87 log.Printf("error getting gopls path: %v", err) 88 executable = "" 89 } 90 ctx = protocol.WithClient(ctx, client) 91 conn.Go(ctx, 92 protocol.Handlers( 93 handshaker(session, executable, s.daemon, 94 protocol.ServerHandler(server, 95 jsonrpc2.MethodNotFound)))) 96 if s.daemon { 97 log.Printf("Session %s: connected", session.ID()) 98 defer log.Printf("Session %s: exited", session.ID()) 99 } 100 <-conn.Done() 101 return conn.Err() 102 } 103 104 // A Forwarder is a jsonrpc2.StreamServer that handles an LSP stream by 105 // forwarding it to a remote. This is used when the gopls process started by 106 // the editor is in the `-remote` mode, which means it finds and connects to a 107 // separate gopls daemon. In these cases, we still want the forwarder gopls to 108 // be instrumented with telemetry, and want to be able to in some cases hijack 109 // the jsonrpc2 connection with the daemon. 110 type Forwarder struct { 111 dialer *AutoDialer 112 113 mu sync.Mutex 114 // Hold on to the server connection so that we can redo the handshake if any 115 // information changes. 116 serverConn jsonrpc2.Conn 117 serverID string 118 } 119 120 // NewForwarder creates a new Forwarder, ready to forward connections to the 121 // remote server specified by rawAddr. If provided and rawAddr indicates an 122 // 'automatic' address (starting with 'auto;'), argFunc may be used to start a 123 // remote server for the auto-discovered address. 124 func NewForwarder(rawAddr string, argFunc func(network, address string) []string) (*Forwarder, error) { 125 dialer, err := NewAutoDialer(rawAddr, argFunc) 126 if err != nil { 127 return nil, err 128 } 129 fwd := &Forwarder{ 130 dialer: dialer, 131 } 132 return fwd, nil 133 } 134 135 // QueryServerState queries the server state of the current server. 136 func QueryServerState(ctx context.Context, addr string) (*ServerState, error) { 137 serverConn, err := dialRemote(ctx, addr) 138 if err != nil { 139 return nil, err 140 } 141 var state ServerState 142 if err := protocol.Call(ctx, serverConn, sessionsMethod, nil, &state); err != nil { 143 return nil, errors.Errorf("querying server state: %w", err) 144 } 145 return &state, nil 146 } 147 148 // dialRemote is used for making calls into the gopls daemon. addr should be a 149 // URL, possibly on the synthetic 'auto' network (e.g. tcp://..., unix://..., 150 // or auto://...). 151 func dialRemote(ctx context.Context, addr string) (jsonrpc2.Conn, error) { 152 network, address := ParseAddr(addr) 153 if network == AutoNetwork { 154 gp, err := os.Executable() 155 if err != nil { 156 return nil, errors.Errorf("getting gopls path: %w", err) 157 } 158 network, address = autoNetworkAddress(gp, address) 159 } 160 netConn, err := net.DialTimeout(network, address, 5*time.Second) 161 if err != nil { 162 return nil, errors.Errorf("dialing remote: %w", err) 163 } 164 serverConn := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn)) 165 serverConn.Go(ctx, jsonrpc2.MethodNotFound) 166 return serverConn, nil 167 } 168 169 func ExecuteCommand(ctx context.Context, addr string, id string, request, result interface{}) error { 170 serverConn, err := dialRemote(ctx, addr) 171 if err != nil { 172 return err 173 } 174 args, err := command.MarshalArgs(request) 175 if err != nil { 176 return err 177 } 178 params := protocol.ExecuteCommandParams{ 179 Command: id, 180 Arguments: args, 181 } 182 return protocol.Call(ctx, serverConn, "workspace/executeCommand", params, result) 183 } 184 185 // ServeStream dials the forwarder remote and binds the remote to serve the LSP 186 // on the incoming stream. 187 func (f *Forwarder) ServeStream(ctx context.Context, clientConn jsonrpc2.Conn) error { 188 client := protocol.ClientDispatcher(clientConn) 189 190 netConn, err := f.dialer.dialNet(ctx) 191 if err != nil { 192 return errors.Errorf("forwarder: connecting to remote: %w", err) 193 } 194 serverConn := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn)) 195 server := protocol.ServerDispatcher(serverConn) 196 197 // Forward between connections. 198 serverConn.Go(ctx, 199 protocol.Handlers( 200 protocol.ClientHandler(client, 201 jsonrpc2.MethodNotFound))) 202 203 // Don't run the clientConn yet, so that we can complete the handshake before 204 // processing any client messages. 205 206 // Do a handshake with the server instance to exchange debug information. 207 index := atomic.AddInt64(&serverIndex, 1) 208 f.mu.Lock() 209 f.serverConn = serverConn 210 f.serverID = strconv.FormatInt(index, 10) 211 f.mu.Unlock() 212 f.handshake(ctx) 213 clientConn.Go(ctx, 214 protocol.Handlers( 215 f.handler( 216 protocol.ServerHandler(server, 217 jsonrpc2.MethodNotFound)))) 218 219 select { 220 case <-serverConn.Done(): 221 clientConn.Close() 222 case <-clientConn.Done(): 223 serverConn.Close() 224 } 225 226 err = nil 227 if serverConn.Err() != nil { 228 err = errors.Errorf("remote disconnected: %v", serverConn.Err()) 229 } else if clientConn.Err() != nil { 230 err = errors.Errorf("client disconnected: %v", clientConn.Err()) 231 } 232 event.Log(ctx, fmt.Sprintf("forwarder: exited with error: %v", err)) 233 return err 234 } 235 236 // TODO(rfindley): remove this handshaking in favor of middleware. 237 func (f *Forwarder) handshake(ctx context.Context) { 238 // This call to os.Execuable is redundant, and will be eliminated by the 239 // transition to the V2 API. 240 goplsPath, err := os.Executable() 241 if err != nil { 242 event.Error(ctx, "getting executable for handshake", err) 243 goplsPath = "" 244 } 245 var ( 246 hreq = handshakeRequest{ 247 ServerID: f.serverID, 248 GoplsPath: goplsPath, 249 } 250 hresp handshakeResponse 251 ) 252 if di := debug.GetInstance(ctx); di != nil { 253 hreq.Logfile = di.Logfile 254 hreq.DebugAddr = di.ListenedDebugAddress() 255 } 256 if err := protocol.Call(ctx, f.serverConn, handshakeMethod, hreq, &hresp); err != nil { 257 // TODO(rfindley): at some point in the future we should return an error 258 // here. Handshakes have become functional in nature. 259 event.Error(ctx, "forwarder: gopls handshake failed", err) 260 } 261 if hresp.GoplsPath != goplsPath { 262 event.Error(ctx, "", fmt.Errorf("forwarder: gopls path mismatch: forwarder is %q, remote is %q", goplsPath, hresp.GoplsPath)) 263 } 264 event.Log(ctx, "New server", 265 tag.NewServer.Of(f.serverID), 266 tag.Logfile.Of(hresp.Logfile), 267 tag.DebugAddress.Of(hresp.DebugAddr), 268 tag.GoplsPath.Of(hresp.GoplsPath), 269 tag.ClientID.Of(hresp.SessionID), 270 ) 271 } 272 273 func ConnectToRemote(ctx context.Context, addr string) (net.Conn, error) { 274 dialer, err := NewAutoDialer(addr, nil) 275 if err != nil { 276 return nil, err 277 } 278 return dialer.dialNet(ctx) 279 } 280 281 // handler intercepts messages to the daemon to enrich them with local 282 // information. 283 func (f *Forwarder) handler(handler jsonrpc2.Handler) jsonrpc2.Handler { 284 return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error { 285 // Intercept certain messages to add special handling. 286 switch r.Method() { 287 case "initialize": 288 if newr, err := addGoEnvToInitializeRequest(ctx, r); err == nil { 289 r = newr 290 } else { 291 log.Printf("unable to add local env to initialize request: %v", err) 292 } 293 case "workspace/executeCommand": 294 var params protocol.ExecuteCommandParams 295 if err := json.Unmarshal(r.Params(), ¶ms); err == nil { 296 if params.Command == command.StartDebugging.ID() { 297 var args command.DebuggingArgs 298 if err := command.UnmarshalArgs(params.Arguments, &args); err == nil { 299 reply = f.replyWithDebugAddress(ctx, reply, args) 300 } else { 301 event.Error(ctx, "unmarshaling debugging args", err) 302 } 303 } 304 } else { 305 event.Error(ctx, "intercepting executeCommand request", err) 306 } 307 } 308 // The gopls workspace environment defaults to the process environment in 309 // which gopls daemon was started. To avoid discrepancies in Go environment 310 // between the editor and daemon, inject any unset variables in `go env` 311 // into the options sent by initialize. 312 // 313 // See also golang.org/issue/37830. 314 return handler(ctx, reply, r) 315 } 316 } 317 318 // addGoEnvToInitializeRequest builds a new initialize request in which we set 319 // any environment variables output by `go env` and not already present in the 320 // request. 321 // 322 // It returns an error if r is not an initialize requst, or is otherwise 323 // malformed. 324 func addGoEnvToInitializeRequest(ctx context.Context, r jsonrpc2.Request) (jsonrpc2.Request, error) { 325 var params protocol.ParamInitialize 326 if err := json.Unmarshal(r.Params(), ¶ms); err != nil { 327 return nil, err 328 } 329 var opts map[string]interface{} 330 switch v := params.InitializationOptions.(type) { 331 case nil: 332 opts = make(map[string]interface{}) 333 case map[string]interface{}: 334 opts = v 335 default: 336 return nil, fmt.Errorf("unexpected type for InitializationOptions: %T", v) 337 } 338 envOpt, ok := opts["env"] 339 if !ok { 340 envOpt = make(map[string]interface{}) 341 } 342 env, ok := envOpt.(map[string]interface{}) 343 if !ok { 344 return nil, fmt.Errorf(`env option is %T, expected a map`, envOpt) 345 } 346 goenv, err := getGoEnv(ctx, env) 347 if err != nil { 348 return nil, err 349 } 350 for govar, value := range goenv { 351 env[govar] = value 352 } 353 opts["env"] = env 354 params.InitializationOptions = opts 355 call, ok := r.(*jsonrpc2.Call) 356 if !ok { 357 return nil, fmt.Errorf("%T is not a *jsonrpc2.Call", r) 358 } 359 return jsonrpc2.NewCall(call.ID(), "initialize", params) 360 } 361 362 func (f *Forwarder) replyWithDebugAddress(outerCtx context.Context, r jsonrpc2.Replier, args command.DebuggingArgs) jsonrpc2.Replier { 363 di := debug.GetInstance(outerCtx) 364 if di == nil { 365 event.Log(outerCtx, "no debug instance to start") 366 return r 367 } 368 return func(ctx context.Context, result interface{}, outerErr error) error { 369 if outerErr != nil { 370 return r(ctx, result, outerErr) 371 } 372 // Enrich the result with our own debugging information. Since we're an 373 // intermediary, the jsonrpc2 package has deserialized the result into 374 // maps, by default. Re-do the unmarshalling. 375 raw, err := json.Marshal(result) 376 if err != nil { 377 event.Error(outerCtx, "marshaling intermediate command result", err) 378 return r(ctx, result, err) 379 } 380 var modified command.DebuggingResult 381 if err := json.Unmarshal(raw, &modified); err != nil { 382 event.Error(outerCtx, "unmarshaling intermediate command result", err) 383 return r(ctx, result, err) 384 } 385 addr := args.Addr 386 if addr == "" { 387 addr = "localhost:0" 388 } 389 addr, err = di.Serve(outerCtx, addr) 390 if err != nil { 391 event.Error(outerCtx, "starting debug server", err) 392 return r(ctx, result, outerErr) 393 } 394 urls := []string{"http://" + addr} 395 modified.URLs = append(urls, modified.URLs...) 396 go f.handshake(ctx) 397 return r(ctx, modified, nil) 398 } 399 } 400 401 // A handshakeRequest identifies a client to the LSP server. 402 type handshakeRequest struct { 403 // ServerID is the ID of the server on the client. This should usually be 0. 404 ServerID string `json:"serverID"` 405 // Logfile is the location of the clients log file. 406 Logfile string `json:"logfile"` 407 // DebugAddr is the client debug address. 408 DebugAddr string `json:"debugAddr"` 409 // GoplsPath is the path to the Gopls binary running the current client 410 // process. 411 GoplsPath string `json:"goplsPath"` 412 } 413 414 // A handshakeResponse is returned by the LSP server to tell the LSP client 415 // information about its session. 416 type handshakeResponse struct { 417 // SessionID is the server session associated with the client. 418 SessionID string `json:"sessionID"` 419 // Logfile is the location of the server logs. 420 Logfile string `json:"logfile"` 421 // DebugAddr is the server debug address. 422 DebugAddr string `json:"debugAddr"` 423 // GoplsPath is the path to the Gopls binary running the current server 424 // process. 425 GoplsPath string `json:"goplsPath"` 426 } 427 428 // ClientSession identifies a current client LSP session on the server. Note 429 // that it looks similar to handshakeResposne, but in fact 'Logfile' and 430 // 'DebugAddr' now refer to the client. 431 type ClientSession struct { 432 SessionID string `json:"sessionID"` 433 Logfile string `json:"logfile"` 434 DebugAddr string `json:"debugAddr"` 435 } 436 437 // ServerState holds information about the gopls daemon process, including its 438 // debug information and debug information of all of its current connected 439 // clients. 440 type ServerState struct { 441 Logfile string `json:"logfile"` 442 DebugAddr string `json:"debugAddr"` 443 GoplsPath string `json:"goplsPath"` 444 CurrentClientID string `json:"currentClientID"` 445 Clients []ClientSession `json:"clients"` 446 } 447 448 const ( 449 handshakeMethod = "gopls/handshake" 450 sessionsMethod = "gopls/sessions" 451 ) 452 453 func handshaker(session *cache.Session, goplsPath string, logHandshakes bool, handler jsonrpc2.Handler) jsonrpc2.Handler { 454 return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error { 455 switch r.Method() { 456 case handshakeMethod: 457 // We log.Printf in this handler, rather than event.Log when we want logs 458 // to go to the daemon log rather than being reflected back to the 459 // client. 460 var req handshakeRequest 461 if err := json.Unmarshal(r.Params(), &req); err != nil { 462 if logHandshakes { 463 log.Printf("Error processing handshake for session %s: %v", session.ID(), err) 464 } 465 sendError(ctx, reply, err) 466 return nil 467 } 468 if logHandshakes { 469 log.Printf("Session %s: got handshake. Logfile: %q, Debug addr: %q", session.ID(), req.Logfile, req.DebugAddr) 470 } 471 event.Log(ctx, "Handshake session update", 472 cache.KeyUpdateSession.Of(session), 473 tag.DebugAddress.Of(req.DebugAddr), 474 tag.Logfile.Of(req.Logfile), 475 tag.ServerID.Of(req.ServerID), 476 tag.GoplsPath.Of(req.GoplsPath), 477 ) 478 resp := handshakeResponse{ 479 SessionID: session.ID(), 480 GoplsPath: goplsPath, 481 } 482 if di := debug.GetInstance(ctx); di != nil { 483 resp.Logfile = di.Logfile 484 resp.DebugAddr = di.ListenedDebugAddress() 485 } 486 return reply(ctx, resp, nil) 487 488 case sessionsMethod: 489 resp := ServerState{ 490 GoplsPath: goplsPath, 491 CurrentClientID: session.ID(), 492 } 493 if di := debug.GetInstance(ctx); di != nil { 494 resp.Logfile = di.Logfile 495 resp.DebugAddr = di.ListenedDebugAddress() 496 for _, c := range di.State.Clients() { 497 resp.Clients = append(resp.Clients, ClientSession{ 498 SessionID: c.Session.ID(), 499 Logfile: c.Logfile, 500 DebugAddr: c.DebugAddress, 501 }) 502 } 503 } 504 return reply(ctx, resp, nil) 505 } 506 return handler(ctx, reply, r) 507 } 508 } 509 510 func sendError(ctx context.Context, reply jsonrpc2.Replier, err error) { 511 err = errors.Errorf("%v: %w", err, jsonrpc2.ErrParse) 512 if err := reply(ctx, nil, err); err != nil { 513 event.Error(ctx, "", err) 514 } 515 } 516 517 // ParseAddr parses the address of a gopls remote. 518 // TODO(rFindley): further document this syntax, and allow URI-style remote 519 // addresses such as "auto://...". 520 func ParseAddr(listen string) (network string, address string) { 521 // Allow passing just -remote=auto, as a shorthand for using automatic remote 522 // resolution. 523 if listen == AutoNetwork { 524 return AutoNetwork, "" 525 } 526 if parts := strings.SplitN(listen, ";", 2); len(parts) == 2 { 527 return parts[0], parts[1] 528 } 529 return "tcp", listen 530 }