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. 7package lsprpc 8 9import ( 10 "context" 11 "encoding/json" 12 "fmt" 13 "log" 14 "net" 15 "os" 16 "strconv" 17 "sync/atomic" 18 "time" 19 20 "golang.org/x/tools/internal/event" 21 "golang.org/x/tools/internal/gocommand" 22 "golang.org/x/tools/internal/jsonrpc2" 23 "golang.org/x/tools/internal/lsp" 24 "golang.org/x/tools/internal/lsp/cache" 25 "golang.org/x/tools/internal/lsp/debug" 26 "golang.org/x/tools/internal/lsp/debug/tag" 27 "golang.org/x/tools/internal/lsp/protocol" 28 errors "golang.org/x/xerrors" 29) 30 31// AutoNetwork is the pseudo network type used to signal that gopls should use 32// automatic discovery to resolve a remote address. 33const AutoNetwork = "auto" 34 35// Unique identifiers for client/server. 36var serverIndex int64 37 38// The StreamServer type is a jsonrpc2.StreamServer that handles incoming 39// streams as a new LSP session, using a shared cache. 40type StreamServer struct { 41 cache *cache.Cache 42 // daemon controls whether or not to log new connections. 43 daemon bool 44 45 // serverForTest may be set to a test fake for testing. 46 serverForTest protocol.Server 47} 48 49// NewStreamServer creates a StreamServer using the shared cache. If 50// withTelemetry is true, each session is instrumented with telemetry that 51// records RPC statistics. 52func NewStreamServer(cache *cache.Cache, daemon bool) *StreamServer { 53 return &StreamServer{cache: cache, daemon: daemon} 54} 55 56// ServeStream implements the jsonrpc2.StreamServer interface, by handling 57// incoming streams using a new lsp server. 58func (s *StreamServer) ServeStream(ctx context.Context, conn jsonrpc2.Conn) error { 59 client := protocol.ClientDispatcher(conn) 60 session := s.cache.NewSession(ctx) 61 server := s.serverForTest 62 if server == nil { 63 server = lsp.NewServer(session, client) 64 } 65 // Clients may or may not send a shutdown message. Make sure the server is 66 // shut down. 67 // TODO(rFindley): this shutdown should perhaps be on a disconnected context. 68 defer func() { 69 if err := server.Shutdown(ctx); err != nil { 70 event.Error(ctx, "error shutting down", err) 71 } 72 }() 73 executable, err := os.Executable() 74 if err != nil { 75 log.Printf("error getting gopls path: %v", err) 76 executable = "" 77 } 78 ctx = protocol.WithClient(ctx, client) 79 conn.Go(ctx, 80 protocol.Handlers( 81 handshaker(session, executable, s.daemon, 82 protocol.ServerHandler(server, 83 jsonrpc2.MethodNotFound)))) 84 if s.daemon { 85 log.Printf("Session %s: connected", session.ID()) 86 defer log.Printf("Session %s: exited", session.ID()) 87 } 88 <-conn.Done() 89 return conn.Err() 90} 91 92// A Forwarder is a jsonrpc2.StreamServer that handles an LSP stream by 93// forwarding it to a remote. This is used when the gopls process started by 94// the editor is in the `-remote` mode, which means it finds and connects to a 95// separate gopls daemon. In these cases, we still want the forwarder gopls to 96// be instrumented with telemetry, and want to be able to in some cases hijack 97// the jsonrpc2 connection with the daemon. 98type Forwarder struct { 99 network, addr string 100 101 // goplsPath is the path to the current executing gopls binary. 102 goplsPath string 103 104 // configuration for the auto-started gopls remote. 105 remoteConfig remoteConfig 106} 107 108type remoteConfig struct { 109 debug string 110 listenTimeout time.Duration 111 logfile string 112} 113 114// A RemoteOption configures the behavior of the auto-started remote. 115type RemoteOption interface { 116 set(*remoteConfig) 117} 118 119// RemoteDebugAddress configures the address used by the auto-started Gopls daemon 120// for serving debug information. 121type RemoteDebugAddress string 122 123func (d RemoteDebugAddress) set(cfg *remoteConfig) { 124 cfg.debug = string(d) 125} 126 127// RemoteListenTimeout configures the amount of time the auto-started gopls 128// daemon will wait with no client connections before shutting down. 129type RemoteListenTimeout time.Duration 130 131func (d RemoteListenTimeout) set(cfg *remoteConfig) { 132 cfg.listenTimeout = time.Duration(d) 133} 134 135// RemoteLogfile configures the logfile location for the auto-started gopls 136// daemon. 137type RemoteLogfile string 138 139func (l RemoteLogfile) set(cfg *remoteConfig) { 140 cfg.logfile = string(l) 141} 142 143func defaultRemoteConfig() remoteConfig { 144 return remoteConfig{ 145 listenTimeout: 1 * time.Minute, 146 } 147} 148 149// NewForwarder creates a new Forwarder, ready to forward connections to the 150// remote server specified by network and addr. 151func NewForwarder(network, addr string, opts ...RemoteOption) *Forwarder { 152 gp, err := os.Executable() 153 if err != nil { 154 log.Printf("error getting gopls path for forwarder: %v", err) 155 gp = "" 156 } 157 158 rcfg := defaultRemoteConfig() 159 for _, opt := range opts { 160 opt.set(&rcfg) 161 } 162 163 fwd := &Forwarder{ 164 network: network, 165 addr: addr, 166 goplsPath: gp, 167 remoteConfig: rcfg, 168 } 169 return fwd 170} 171 172// QueryServerState queries the server state of the current server. 173func QueryServerState(ctx context.Context, network, address string) (*ServerState, error) { 174 if network == AutoNetwork { 175 gp, err := os.Executable() 176 if err != nil { 177 return nil, errors.Errorf("getting gopls path: %w", err) 178 } 179 network, address = autoNetworkAddress(gp, address) 180 } 181 netConn, err := net.DialTimeout(network, address, 5*time.Second) 182 if err != nil { 183 return nil, errors.Errorf("dialing remote: %w", err) 184 } 185 serverConn := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn)) 186 serverConn.Go(ctx, jsonrpc2.MethodNotFound) 187 var state ServerState 188 if err := protocol.Call(ctx, serverConn, sessionsMethod, nil, &state); err != nil { 189 return nil, errors.Errorf("querying server state: %w", err) 190 } 191 return &state, nil 192} 193 194// ServeStream dials the forwarder remote and binds the remote to serve the LSP 195// on the incoming stream. 196func (f *Forwarder) ServeStream(ctx context.Context, clientConn jsonrpc2.Conn) error { 197 client := protocol.ClientDispatcher(clientConn) 198 199 netConn, err := f.connectToRemote(ctx) 200 if err != nil { 201 return errors.Errorf("forwarder: connecting to remote: %w", err) 202 } 203 serverConn := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn)) 204 server := protocol.ServerDispatcher(serverConn) 205 206 // Forward between connections. 207 serverConn.Go(ctx, 208 protocol.Handlers( 209 protocol.ClientHandler(client, 210 jsonrpc2.MethodNotFound))) 211 // Don't run the clientConn yet, so that we can complete the handshake before 212 // processing any client messages. 213 214 // Do a handshake with the server instance to exchange debug information. 215 index := atomic.AddInt64(&serverIndex, 1) 216 serverID := strconv.FormatInt(index, 10) 217 var ( 218 hreq = handshakeRequest{ 219 ServerID: serverID, 220 GoplsPath: f.goplsPath, 221 } 222 hresp handshakeResponse 223 ) 224 if di := debug.GetInstance(ctx); di != nil { 225 hreq.Logfile = di.Logfile 226 hreq.DebugAddr = di.ListenedDebugAddress 227 } 228 if err := protocol.Call(ctx, serverConn, handshakeMethod, hreq, &hresp); err != nil { 229 // TODO(rfindley): at some point in the future we should return an error 230 // here. Handshakes have become functional in nature. 231 event.Error(ctx, "forwarder: gopls handshake failed", err) 232 } 233 if hresp.GoplsPath != f.goplsPath { 234 event.Error(ctx, "", fmt.Errorf("forwarder: gopls path mismatch: forwarder is %q, remote is %q", f.goplsPath, hresp.GoplsPath)) 235 } 236 event.Log(ctx, "New server", 237 tag.NewServer.Of(serverID), 238 tag.Logfile.Of(hresp.Logfile), 239 tag.DebugAddress.Of(hresp.DebugAddr), 240 tag.GoplsPath.Of(hresp.GoplsPath), 241 tag.ClientID.Of(hresp.SessionID), 242 ) 243 clientConn.Go(ctx, 244 protocol.Handlers( 245 forwarderHandler( 246 protocol.ServerHandler(server, 247 jsonrpc2.MethodNotFound)))) 248 249 select { 250 case <-serverConn.Done(): 251 clientConn.Close() 252 case <-clientConn.Done(): 253 serverConn.Close() 254 } 255 256 err = nil 257 if serverConn.Err() != nil { 258 err = errors.Errorf("remote disconnected: %v", err) 259 } else if clientConn.Err() != nil { 260 err = errors.Errorf("client disconnected: %v", err) 261 } 262 event.Log(ctx, fmt.Sprintf("forwarder: exited with error: %v", err)) 263 return err 264} 265 266func (f *Forwarder) connectToRemote(ctx context.Context) (net.Conn, error) { 267 return connectToRemote(ctx, f.network, f.addr, f.goplsPath, f.remoteConfig) 268} 269 270func ConnectToRemote(ctx context.Context, network, addr string, opts ...RemoteOption) (net.Conn, error) { 271 rcfg := defaultRemoteConfig() 272 for _, opt := range opts { 273 opt.set(&rcfg) 274 } 275 // This is not strictly necessary, as it won't be used if not connecting to 276 // the 'auto' remote. 277 goplsPath, err := os.Executable() 278 if err != nil { 279 return nil, fmt.Errorf("unable to resolve gopls path: %v", err) 280 } 281 return connectToRemote(ctx, network, addr, goplsPath, rcfg) 282} 283 284func connectToRemote(ctx context.Context, inNetwork, inAddr, goplsPath string, rcfg remoteConfig) (net.Conn, error) { 285 var ( 286 netConn net.Conn 287 err error 288 network, address = inNetwork, inAddr 289 ) 290 if inNetwork == AutoNetwork { 291 // f.network is overloaded to support a concept of 'automatic' addresses, 292 // which signals that the gopls remote address should be automatically 293 // derived. 294 // So we need to resolve a real network and address here. 295 network, address = autoNetworkAddress(goplsPath, inAddr) 296 } 297 // Attempt to verify that we own the remote. This is imperfect, but if we can 298 // determine that the remote is owned by a different user, we should fail. 299 ok, err := verifyRemoteOwnership(network, address) 300 if err != nil { 301 // If the ownership check itself failed, we fail open but log an error to 302 // the user. 303 event.Error(ctx, "unable to check daemon socket owner, failing open", err) 304 } else if !ok { 305 // We successfully checked that the socket is not owned by us, we fail 306 // closed. 307 return nil, fmt.Errorf("socket %q is owned by a different user", address) 308 } 309 const dialTimeout = 1 * time.Second 310 // Try dialing our remote once, in case it is already running. 311 netConn, err = net.DialTimeout(network, address, dialTimeout) 312 if err == nil { 313 return netConn, nil 314 } 315 // If our remote is on the 'auto' network, start it if it doesn't exist. 316 if inNetwork == AutoNetwork { 317 if goplsPath == "" { 318 return nil, fmt.Errorf("cannot auto-start remote: gopls path is unknown") 319 } 320 if network == "unix" { 321 // Sometimes the socketfile isn't properly cleaned up when gopls shuts 322 // down. Since we have already tried and failed to dial this address, it 323 // should *usually* be safe to remove the socket before binding to the 324 // address. 325 // TODO(rfindley): there is probably a race here if multiple gopls 326 // instances are simultaneously starting up. 327 if _, err := os.Stat(address); err == nil { 328 if err := os.Remove(address); err != nil { 329 return nil, errors.Errorf("removing remote socket file: %w", err) 330 } 331 } 332 } 333 args := []string{"serve", 334 "-listen", fmt.Sprintf(`%s;%s`, network, address), 335 "-listen.timeout", rcfg.listenTimeout.String(), 336 } 337 if rcfg.logfile != "" { 338 args = append(args, "-logfile", rcfg.logfile) 339 } 340 if rcfg.debug != "" { 341 args = append(args, "-debug", rcfg.debug) 342 } 343 if err := startRemote(goplsPath, args...); err != nil { 344 return nil, errors.Errorf("startRemote(%q, %v): %w", goplsPath, args, err) 345 } 346 } 347 348 const retries = 5 349 // It can take some time for the newly started server to bind to our address, 350 // so we retry for a bit. 351 for retry := 0; retry < retries; retry++ { 352 startDial := time.Now() 353 netConn, err = net.DialTimeout(network, address, dialTimeout) 354 if err == nil { 355 return netConn, nil 356 } 357 event.Log(ctx, fmt.Sprintf("failed attempt #%d to connect to remote: %v\n", retry+2, err)) 358 // In case our failure was a fast-failure, ensure we wait at least 359 // f.dialTimeout before trying again. 360 if retry != retries-1 { 361 time.Sleep(dialTimeout - time.Since(startDial)) 362 } 363 } 364 return nil, errors.Errorf("dialing remote: %w", err) 365} 366 367// forwarderHandler intercepts 'exit' messages to prevent the shared gopls 368// instance from exiting. In the future it may also intercept 'shutdown' to 369// provide more graceful shutdown of the client connection. 370func forwarderHandler(handler jsonrpc2.Handler) jsonrpc2.Handler { 371 return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error { 372 // The gopls workspace environment defaults to the process environment in 373 // which gopls daemon was started. To avoid discrepancies in Go environment 374 // between the editor and daemon, inject any unset variables in `go env` 375 // into the options sent by initialize. 376 // 377 // See also golang.org/issue/37830. 378 if r.Method() == "initialize" { 379 if newr, err := addGoEnvToInitializeRequest(ctx, r); err == nil { 380 r = newr 381 } else { 382 log.Printf("unable to add local env to initialize request: %v", err) 383 } 384 } 385 return handler(ctx, reply, r) 386 } 387} 388 389// addGoEnvToInitializeRequest builds a new initialize request in which we set 390// any environment variables output by `go env` and not already present in the 391// request. 392// 393// It returns an error if r is not an initialize requst, or is otherwise 394// malformed. 395func addGoEnvToInitializeRequest(ctx context.Context, r jsonrpc2.Request) (jsonrpc2.Request, error) { 396 var params protocol.ParamInitialize 397 if err := json.Unmarshal(r.Params(), ¶ms); err != nil { 398 return nil, err 399 } 400 var opts map[string]interface{} 401 switch v := params.InitializationOptions.(type) { 402 case nil: 403 opts = make(map[string]interface{}) 404 case map[string]interface{}: 405 opts = v 406 default: 407 return nil, fmt.Errorf("unexpected type for InitializationOptions: %T", v) 408 } 409 envOpt, ok := opts["env"] 410 if !ok { 411 envOpt = make(map[string]interface{}) 412 } 413 env, ok := envOpt.(map[string]interface{}) 414 if !ok { 415 return nil, fmt.Errorf(`env option is %T, expected a map`, envOpt) 416 } 417 goenv, err := getGoEnv(ctx, env) 418 if err != nil { 419 return nil, err 420 } 421 for govar, value := range goenv { 422 env[govar] = value 423 } 424 opts["env"] = env 425 params.InitializationOptions = opts 426 call, ok := r.(*jsonrpc2.Call) 427 if !ok { 428 return nil, fmt.Errorf("%T is not a *jsonrpc2.Call", r) 429 } 430 return jsonrpc2.NewCall(call.ID(), "initialize", params) 431} 432 433func getGoEnv(ctx context.Context, env map[string]interface{}) (map[string]string, error) { 434 var runEnv []string 435 for k, v := range env { 436 runEnv = append(runEnv, fmt.Sprintf("%s=%s", k, v)) 437 } 438 runner := gocommand.Runner{} 439 output, err := runner.Run(ctx, gocommand.Invocation{ 440 Verb: "env", 441 Args: []string{"-json"}, 442 Env: runEnv, 443 }) 444 if err != nil { 445 return nil, err 446 } 447 envmap := make(map[string]string) 448 if err := json.Unmarshal(output.Bytes(), &envmap); err != nil { 449 return nil, err 450 } 451 return envmap, nil 452} 453 454// A handshakeRequest identifies a client to the LSP server. 455type handshakeRequest struct { 456 // ServerID is the ID of the server on the client. This should usually be 0. 457 ServerID string `json:"serverID"` 458 // Logfile is the location of the clients log file. 459 Logfile string `json:"logfile"` 460 // DebugAddr is the client debug address. 461 DebugAddr string `json:"debugAddr"` 462 // GoplsPath is the path to the Gopls binary running the current client 463 // process. 464 GoplsPath string `json:"goplsPath"` 465} 466 467// A handshakeResponse is returned by the LSP server to tell the LSP client 468// information about its session. 469type handshakeResponse struct { 470 // SessionID is the server session associated with the client. 471 SessionID string `json:"sessionID"` 472 // Logfile is the location of the server logs. 473 Logfile string `json:"logfile"` 474 // DebugAddr is the server debug address. 475 DebugAddr string `json:"debugAddr"` 476 // GoplsPath is the path to the Gopls binary running the current server 477 // process. 478 GoplsPath string `json:"goplsPath"` 479} 480 481// ClientSession identifies a current client LSP session on the server. Note 482// that it looks similar to handshakeResposne, but in fact 'Logfile' and 483// 'DebugAddr' now refer to the client. 484type ClientSession struct { 485 SessionID string `json:"sessionID"` 486 Logfile string `json:"logfile"` 487 DebugAddr string `json:"debugAddr"` 488} 489 490// ServerState holds information about the gopls daemon process, including its 491// debug information and debug information of all of its current connected 492// clients. 493type ServerState struct { 494 Logfile string `json:"logfile"` 495 DebugAddr string `json:"debugAddr"` 496 GoplsPath string `json:"goplsPath"` 497 CurrentClientID string `json:"currentClientID"` 498 Clients []ClientSession `json:"clients"` 499} 500 501const ( 502 handshakeMethod = "gopls/handshake" 503 sessionsMethod = "gopls/sessions" 504) 505 506func handshaker(session *cache.Session, goplsPath string, logHandshakes bool, handler jsonrpc2.Handler) jsonrpc2.Handler { 507 return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error { 508 switch r.Method() { 509 case handshakeMethod: 510 // We log.Printf in this handler, rather than event.Log when we want logs 511 // to go to the daemon log rather than being reflected back to the 512 // client. 513 var req handshakeRequest 514 if err := json.Unmarshal(r.Params(), &req); err != nil { 515 if logHandshakes { 516 log.Printf("Error processing handshake for session %s: %v", session.ID(), err) 517 } 518 sendError(ctx, reply, err) 519 return nil 520 } 521 if logHandshakes { 522 log.Printf("Session %s: got handshake. Logfile: %q, Debug addr: %q", session.ID(), req.Logfile, req.DebugAddr) 523 } 524 event.Log(ctx, "Handshake session update", 525 cache.KeyUpdateSession.Of(session), 526 tag.DebugAddress.Of(req.DebugAddr), 527 tag.Logfile.Of(req.Logfile), 528 tag.ServerID.Of(req.ServerID), 529 tag.GoplsPath.Of(req.GoplsPath), 530 ) 531 resp := handshakeResponse{ 532 SessionID: session.ID(), 533 GoplsPath: goplsPath, 534 } 535 if di := debug.GetInstance(ctx); di != nil { 536 resp.Logfile = di.Logfile 537 resp.DebugAddr = di.ListenedDebugAddress 538 } 539 540 return reply(ctx, resp, nil) 541 case sessionsMethod: 542 resp := ServerState{ 543 GoplsPath: goplsPath, 544 CurrentClientID: session.ID(), 545 } 546 if di := debug.GetInstance(ctx); di != nil { 547 resp.Logfile = di.Logfile 548 resp.DebugAddr = di.ListenedDebugAddress 549 for _, c := range di.State.Clients() { 550 resp.Clients = append(resp.Clients, ClientSession{ 551 SessionID: c.Session.ID(), 552 Logfile: c.Logfile, 553 DebugAddr: c.DebugAddress, 554 }) 555 } 556 } 557 return reply(ctx, resp, nil) 558 } 559 return handler(ctx, reply, r) 560 } 561} 562 563func sendError(ctx context.Context, reply jsonrpc2.Replier, err error) { 564 err = errors.Errorf("%v: %w", err, jsonrpc2.ErrParse) 565 if err := reply(ctx, nil, err); err != nil { 566 event.Error(ctx, "", err) 567 } 568} 569