github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/lsprpc/middleware.go (about)

     1  // Copyright 2021 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
     6  
     7  import (
     8  	"context"
     9  	"encoding/json"
    10  	"sync"
    11  
    12  	"github.com/powerman/golang-tools/internal/event"
    13  	jsonrpc2_v2 "github.com/powerman/golang-tools/internal/jsonrpc2_v2"
    14  	"golang.org/x/xerrors"
    15  )
    16  
    17  // Metadata holds arbitrary data transferred between jsonrpc2 peers.
    18  type Metadata map[string]interface{}
    19  
    20  // PeerInfo holds information about a peering between jsonrpc2 servers.
    21  type PeerInfo struct {
    22  	// RemoteID is the identity of the current server on its peer.
    23  	RemoteID int64
    24  
    25  	// LocalID is the identity of the peer on the server.
    26  	LocalID int64
    27  
    28  	// IsClient reports whether the peer is a client. If false, the peer is a
    29  	// server.
    30  	IsClient bool
    31  
    32  	// Metadata holds arbitrary information provided by the peer.
    33  	Metadata Metadata
    34  }
    35  
    36  // Handshaker handles both server and client handshaking over jsonrpc2. To
    37  // instrument server-side handshaking, use Handshaker.Middleware. To instrument
    38  // client-side handshaking, call Handshaker.ClientHandshake for any new
    39  // client-side connections.
    40  type Handshaker struct {
    41  	// Metadata will be shared with peers via handshaking.
    42  	Metadata Metadata
    43  
    44  	mu     sync.Mutex
    45  	prevID int64
    46  	peers  map[int64]PeerInfo
    47  }
    48  
    49  // Peers returns the peer info this handshaker knows about by way of either the
    50  // server-side handshake middleware, or client-side handshakes.
    51  func (h *Handshaker) Peers() []PeerInfo {
    52  	h.mu.Lock()
    53  	defer h.mu.Unlock()
    54  
    55  	var c []PeerInfo
    56  	for _, v := range h.peers {
    57  		c = append(c, v)
    58  	}
    59  	return c
    60  }
    61  
    62  // Middleware is a jsonrpc2 middleware function to augment connection binding
    63  // to handle the handshake method, and record disconnections.
    64  func (h *Handshaker) Middleware(inner jsonrpc2_v2.Binder) jsonrpc2_v2.Binder {
    65  	return BinderFunc(func(ctx context.Context, conn *jsonrpc2_v2.Connection) (jsonrpc2_v2.ConnectionOptions, error) {
    66  		opts, err := inner.Bind(ctx, conn)
    67  		if err != nil {
    68  			return opts, err
    69  		}
    70  
    71  		localID := h.nextID()
    72  		info := &PeerInfo{
    73  			RemoteID: localID,
    74  			Metadata: h.Metadata,
    75  		}
    76  
    77  		// Wrap the delegated handler to accept the handshake.
    78  		delegate := opts.Handler
    79  		opts.Handler = jsonrpc2_v2.HandlerFunc(func(ctx context.Context, req *jsonrpc2_v2.Request) (interface{}, error) {
    80  			if req.Method == handshakeMethod {
    81  				var peerInfo PeerInfo
    82  				if err := json.Unmarshal(req.Params, &peerInfo); err != nil {
    83  					return nil, xerrors.Errorf("%w: unmarshaling client info: %v", jsonrpc2_v2.ErrInvalidParams, err)
    84  				}
    85  				peerInfo.LocalID = localID
    86  				peerInfo.IsClient = true
    87  				h.recordPeer(peerInfo)
    88  				return info, nil
    89  			}
    90  			return delegate.Handle(ctx, req)
    91  		})
    92  
    93  		// Record the dropped client.
    94  		go h.cleanupAtDisconnect(conn, localID)
    95  
    96  		return opts, nil
    97  	})
    98  }
    99  
   100  // ClientHandshake performs a client-side handshake with the server at the
   101  // other end of conn, recording the server's peer info and watching for conn's
   102  // disconnection.
   103  func (h *Handshaker) ClientHandshake(ctx context.Context, conn *jsonrpc2_v2.Connection) {
   104  	localID := h.nextID()
   105  	info := &PeerInfo{
   106  		RemoteID: localID,
   107  		Metadata: h.Metadata,
   108  	}
   109  
   110  	call := conn.Call(ctx, handshakeMethod, info)
   111  	var serverInfo PeerInfo
   112  	if err := call.Await(ctx, &serverInfo); err != nil {
   113  		event.Error(ctx, "performing handshake", err)
   114  		return
   115  	}
   116  	serverInfo.LocalID = localID
   117  	h.recordPeer(serverInfo)
   118  
   119  	go h.cleanupAtDisconnect(conn, localID)
   120  }
   121  
   122  func (h *Handshaker) nextID() int64 {
   123  	h.mu.Lock()
   124  	defer h.mu.Unlock()
   125  
   126  	h.prevID++
   127  	return h.prevID
   128  }
   129  
   130  func (h *Handshaker) cleanupAtDisconnect(conn *jsonrpc2_v2.Connection, peerID int64) {
   131  	conn.Wait()
   132  
   133  	h.mu.Lock()
   134  	defer h.mu.Unlock()
   135  	delete(h.peers, peerID)
   136  }
   137  
   138  func (h *Handshaker) recordPeer(info PeerInfo) {
   139  	h.mu.Lock()
   140  	defer h.mu.Unlock()
   141  	if h.peers == nil {
   142  		h.peers = make(map[int64]PeerInfo)
   143  	}
   144  	h.peers[info.LocalID] = info
   145  }