github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/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/powerman/golang-tools/internal/event" 23 "github.com/powerman/golang-tools/internal/jsonrpc2" 24 "github.com/powerman/golang-tools/internal/lsp" 25 "github.com/powerman/golang-tools/internal/lsp/cache" 26 "github.com/powerman/golang-tools/internal/lsp/command" 27 "github.com/powerman/golang-tools/internal/lsp/debug" 28 "github.com/powerman/golang-tools/internal/lsp/debug/tag" 29 "github.com/powerman/golang-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 request, 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 // We don't want to propagate GOWORK unless explicitly set since that could mess with 351 // path inference during cmd/go invocations, see golang/go#51825. 352 _, goworkSet := os.LookupEnv("GOWORK") 353 for govar, value := range goenv { 354 if govar == "GOWORK" && !goworkSet { 355 continue 356 } 357 env[govar] = value 358 } 359 opts["env"] = env 360 params.InitializationOptions = opts 361 call, ok := r.(*jsonrpc2.Call) 362 if !ok { 363 return nil, fmt.Errorf("%T is not a *jsonrpc2.Call", r) 364 } 365 return jsonrpc2.NewCall(call.ID(), "initialize", params) 366 } 367 368 func (f *Forwarder) replyWithDebugAddress(outerCtx context.Context, r jsonrpc2.Replier, args command.DebuggingArgs) jsonrpc2.Replier { 369 di := debug.GetInstance(outerCtx) 370 if di == nil { 371 event.Log(outerCtx, "no debug instance to start") 372 return r 373 } 374 return func(ctx context.Context, result interface{}, outerErr error) error { 375 if outerErr != nil { 376 return r(ctx, result, outerErr) 377 } 378 // Enrich the result with our own debugging information. Since we're an 379 // intermediary, the jsonrpc2 package has deserialized the result into 380 // maps, by default. Re-do the unmarshalling. 381 raw, err := json.Marshal(result) 382 if err != nil { 383 event.Error(outerCtx, "marshaling intermediate command result", err) 384 return r(ctx, result, err) 385 } 386 var modified command.DebuggingResult 387 if err := json.Unmarshal(raw, &modified); err != nil { 388 event.Error(outerCtx, "unmarshaling intermediate command result", err) 389 return r(ctx, result, err) 390 } 391 addr := args.Addr 392 if addr == "" { 393 addr = "localhost:0" 394 } 395 addr, err = di.Serve(outerCtx, addr) 396 if err != nil { 397 event.Error(outerCtx, "starting debug server", err) 398 return r(ctx, result, outerErr) 399 } 400 urls := []string{"http://" + addr} 401 modified.URLs = append(urls, modified.URLs...) 402 go f.handshake(ctx) 403 return r(ctx, modified, nil) 404 } 405 } 406 407 // A handshakeRequest identifies a client to the LSP server. 408 type handshakeRequest struct { 409 // ServerID is the ID of the server on the client. This should usually be 0. 410 ServerID string `json:"serverID"` 411 // Logfile is the location of the clients log file. 412 Logfile string `json:"logfile"` 413 // DebugAddr is the client debug address. 414 DebugAddr string `json:"debugAddr"` 415 // GoplsPath is the path to the Gopls binary running the current client 416 // process. 417 GoplsPath string `json:"goplsPath"` 418 } 419 420 // A handshakeResponse is returned by the LSP server to tell the LSP client 421 // information about its session. 422 type handshakeResponse struct { 423 // SessionID is the server session associated with the client. 424 SessionID string `json:"sessionID"` 425 // Logfile is the location of the server logs. 426 Logfile string `json:"logfile"` 427 // DebugAddr is the server debug address. 428 DebugAddr string `json:"debugAddr"` 429 // GoplsPath is the path to the Gopls binary running the current server 430 // process. 431 GoplsPath string `json:"goplsPath"` 432 } 433 434 // ClientSession identifies a current client LSP session on the server. Note 435 // that it looks similar to handshakeResposne, but in fact 'Logfile' and 436 // 'DebugAddr' now refer to the client. 437 type ClientSession struct { 438 SessionID string `json:"sessionID"` 439 Logfile string `json:"logfile"` 440 DebugAddr string `json:"debugAddr"` 441 } 442 443 // ServerState holds information about the gopls daemon process, including its 444 // debug information and debug information of all of its current connected 445 // clients. 446 type ServerState struct { 447 Logfile string `json:"logfile"` 448 DebugAddr string `json:"debugAddr"` 449 GoplsPath string `json:"goplsPath"` 450 CurrentClientID string `json:"currentClientID"` 451 Clients []ClientSession `json:"clients"` 452 } 453 454 const ( 455 handshakeMethod = "gopls/handshake" 456 sessionsMethod = "gopls/sessions" 457 ) 458 459 func handshaker(session *cache.Session, goplsPath string, logHandshakes bool, handler jsonrpc2.Handler) jsonrpc2.Handler { 460 return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error { 461 switch r.Method() { 462 case handshakeMethod: 463 // We log.Printf in this handler, rather than event.Log when we want logs 464 // to go to the daemon log rather than being reflected back to the 465 // client. 466 var req handshakeRequest 467 if err := json.Unmarshal(r.Params(), &req); err != nil { 468 if logHandshakes { 469 log.Printf("Error processing handshake for session %s: %v", session.ID(), err) 470 } 471 sendError(ctx, reply, err) 472 return nil 473 } 474 if logHandshakes { 475 log.Printf("Session %s: got handshake. Logfile: %q, Debug addr: %q", session.ID(), req.Logfile, req.DebugAddr) 476 } 477 event.Log(ctx, "Handshake session update", 478 cache.KeyUpdateSession.Of(session), 479 tag.DebugAddress.Of(req.DebugAddr), 480 tag.Logfile.Of(req.Logfile), 481 tag.ServerID.Of(req.ServerID), 482 tag.GoplsPath.Of(req.GoplsPath), 483 ) 484 resp := handshakeResponse{ 485 SessionID: session.ID(), 486 GoplsPath: goplsPath, 487 } 488 if di := debug.GetInstance(ctx); di != nil { 489 resp.Logfile = di.Logfile 490 resp.DebugAddr = di.ListenedDebugAddress() 491 } 492 return reply(ctx, resp, nil) 493 494 case sessionsMethod: 495 resp := ServerState{ 496 GoplsPath: goplsPath, 497 CurrentClientID: session.ID(), 498 } 499 if di := debug.GetInstance(ctx); di != nil { 500 resp.Logfile = di.Logfile 501 resp.DebugAddr = di.ListenedDebugAddress() 502 for _, c := range di.State.Clients() { 503 resp.Clients = append(resp.Clients, ClientSession{ 504 SessionID: c.Session.ID(), 505 Logfile: c.Logfile, 506 DebugAddr: c.DebugAddress, 507 }) 508 } 509 } 510 return reply(ctx, resp, nil) 511 } 512 return handler(ctx, reply, r) 513 } 514 } 515 516 func sendError(ctx context.Context, reply jsonrpc2.Replier, err error) { 517 err = errors.Errorf("%v: %w", err, jsonrpc2.ErrParse) 518 if err := reply(ctx, nil, err); err != nil { 519 event.Error(ctx, "", err) 520 } 521 } 522 523 // ParseAddr parses the address of a gopls remote. 524 // TODO(rFindley): further document this syntax, and allow URI-style remote 525 // addresses such as "auto://...". 526 func ParseAddr(listen string) (network string, address string) { 527 // Allow passing just -remote=auto, as a shorthand for using automatic remote 528 // resolution. 529 if listen == AutoNetwork { 530 return AutoNetwork, "" 531 } 532 if parts := strings.SplitN(listen, ";", 2); len(parts) == 2 { 533 return parts[0], parts[1] 534 } 535 return "tcp", listen 536 }