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(), &params); 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(), &params); 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  }