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