decred.org/dcrdex@v1.0.5/tatanka/tcp/server.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package tcp
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"net/url"
    11  	"sync"
    12  	"sync/atomic"
    13  	"time"
    14  
    15  	clientcomms "decred.org/dcrdex/client/comms"
    16  	"decred.org/dcrdex/dex"
    17  	"decred.org/dcrdex/dex/msgjson"
    18  	"decred.org/dcrdex/server/comms"
    19  	"decred.org/dcrdex/tatanka/mj"
    20  	"decred.org/dcrdex/tatanka/tanka"
    21  )
    22  
    23  // linkWrapper wraps a comms.Link to create a tanka.Sender.
    24  type linkWrapper struct {
    25  	comms.Link
    26  }
    27  
    28  func (l *linkWrapper) Request(msg *msgjson.Message, respHandler func(*msgjson.Message)) error {
    29  	const requestTimeout = time.Second * 30
    30  	return l.Link.Request(msg, func(_ comms.Link, respMsg *msgjson.Message) {
    31  		respHandler(respMsg)
    32  	}, requestTimeout, func() { // expire
    33  		errMsg := mj.MustResponse(msg.ID, nil, msgjson.NewError(mj.ErrTimeout, "request timed out"))
    34  		respHandler(errMsg)
    35  	})
    36  }
    37  
    38  func (l *linkWrapper) RequestRaw(msgID uint64, rawMsg []byte, respHandler func(*msgjson.Message)) error {
    39  	const requestTimeout = time.Second * 30
    40  	return l.Link.RequestRaw(msgID, rawMsg, func(_ comms.Link, respMsg *msgjson.Message) {
    41  		respHandler(respMsg)
    42  	}, requestTimeout, func() { // expire
    43  		errMsg := mj.MustResponse(msgID, nil, msgjson.NewError(mj.ErrTimeout, "request timed out"))
    44  		respHandler(errMsg)
    45  	})
    46  }
    47  
    48  func (l *linkWrapper) PeerID() (peerID tanka.PeerID) {
    49  	copy(peerID[:], []byte(l.CustomID()))
    50  	return
    51  }
    52  
    53  func (l *linkWrapper) SetPeerID(peerID tanka.PeerID) {
    54  	l.SetCustomID(string(peerID[:]))
    55  }
    56  
    57  type wsConnWrapper struct {
    58  	clientcomms.WsConn
    59  	cm *dex.ConnectionMaster
    60  	id atomic.Value
    61  }
    62  
    63  func newWsConnWrapper(cl clientcomms.WsConn, cm *dex.ConnectionMaster) *wsConnWrapper {
    64  	w := &wsConnWrapper{
    65  		WsConn: cl,
    66  		cm:     cm,
    67  	}
    68  	w.id.Store(tanka.PeerID{})
    69  	return w
    70  }
    71  
    72  func (cl *wsConnWrapper) Disconnect() {
    73  	cl.cm.Disconnect()
    74  	cl.cm.Wait()
    75  }
    76  
    77  func (cl *wsConnWrapper) SetPeerID(id tanka.PeerID) {
    78  	cl.id.Store(id)
    79  }
    80  
    81  func (cl *wsConnWrapper) PeerID() tanka.PeerID {
    82  	return cl.id.Load().(tanka.PeerID)
    83  }
    84  
    85  // Server handles HTTP, HTTPS, WebSockets, and Tor communications protocols.
    86  type Server struct {
    87  	t   TankaCore
    88  	wg  *sync.WaitGroup
    89  	srv *comms.Server
    90  	log dex.Logger
    91  }
    92  
    93  type TankaCore interface {
    94  	// Config() *mj.TatankaConfig
    95  	Routes() []string
    96  	HandleMessage(tanka.Sender, *msgjson.Message) *msgjson.Error
    97  }
    98  
    99  func NewServer(cfg *comms.RPCConfig, t TankaCore, log dex.Logger) (*Server, error) {
   100  	srv, err := comms.NewServer(cfg)
   101  	if err != nil {
   102  		return nil, fmt.Errorf("NewServer error: %w", err)
   103  	}
   104  
   105  	s := &Server{
   106  		t:   t,
   107  		srv: srv,
   108  		log: log,
   109  	}
   110  
   111  	for _, r := range t.Routes() {
   112  		srv.Route(r, func(l comms.Link, msg *msgjson.Message) *msgjson.Error {
   113  			return t.HandleMessage(&linkWrapper{l}, msg)
   114  		})
   115  	}
   116  
   117  	return s, nil
   118  }
   119  
   120  func (s *Server) Connect(ctx context.Context) (*sync.WaitGroup, error) {
   121  	var wg sync.WaitGroup
   122  	s.wg = &wg
   123  
   124  	// Start WebSocket server
   125  	runner := dex.NewStartStopWaiter(s.srv)
   126  	runner.Start(ctx) // stopped with Stop
   127  
   128  	wg.Add(1)
   129  	go func() {
   130  		<-ctx.Done()
   131  		runner.Stop()
   132  		wg.Done()
   133  	}()
   134  
   135  	return &wg, nil
   136  }
   137  
   138  type RemoteNodeConfig struct {
   139  	URL  string `json:"url"`
   140  	Cert []byte `json:"cert"`
   141  }
   142  
   143  func (s *Server) ConnectBootNode(
   144  	ctx context.Context,
   145  	rawCfg json.RawMessage,
   146  	handleMessage func(cl tanka.Sender, msg *msgjson.Message),
   147  	disconnect func(),
   148  ) (tanka.Sender, error) {
   149  
   150  	var n RemoteNodeConfig
   151  	if err := json.Unmarshal(rawCfg, &n); err != nil {
   152  		return nil, fmt.Errorf("error reading boot node configuration: %w", err)
   153  	}
   154  
   155  	uri, err := url.Parse(n.URL)
   156  	if err != nil {
   157  		return nil, fmt.Errorf("url parse error: %w", err)
   158  	}
   159  
   160  	if uri.Path != "/ws" {
   161  		uri.Path = "/ws"
   162  	}
   163  
   164  	cl, err := clientcomms.NewWsConn(&clientcomms.WsCfg{
   165  		URL:      uri.String(),
   166  		PingWait: 20 * time.Second,
   167  		Cert:     n.Cert,
   168  		ConnectEventFunc: func(status clientcomms.ConnectionStatus) {
   169  			if status == clientcomms.Disconnected {
   170  				disconnect()
   171  			}
   172  		},
   173  		Logger:               dex.StdOutLogger(fmt.Sprintf("CL[%s]", n.URL), dex.LevelDebug),
   174  		DisableAutoReconnect: true,
   175  	})
   176  	if err != nil {
   177  		return nil, fmt.Errorf("failed to connect to bootnode %q: %v", n.URL, err)
   178  	}
   179  
   180  	cm := dex.NewConnectionMaster(cl)
   181  	if err := cm.ConnectOnce(ctx); err != nil {
   182  		return nil, fmt.Errorf("failed to connect to boot node %q. error: %w", n.URL, err)
   183  	}
   184  
   185  	sender := newWsConnWrapper(cl, cm)
   186  	go s.handleOutboudTatankaConnection(ctx, sender, handleMessage)
   187  
   188  	return sender, nil
   189  }
   190  
   191  func (s *Server) handleOutboudTatankaConnection(ctx context.Context, cl *wsConnWrapper, handleMessage func(cl tanka.Sender, msg *msgjson.Message)) {
   192  	msgs := cl.MessageSource()
   193  	for {
   194  		if ctx.Err() != nil {
   195  			return
   196  		}
   197  		select {
   198  		case msg, ok := <-msgs:
   199  			if !ok {
   200  				// Connection has been closed.
   201  				return
   202  			}
   203  			handleMessage(cl, msg)
   204  		case <-ctx.Done():
   205  			return
   206  		}
   207  	}
   208  }