gitlab.com/SiaPrime/SiaPrime@v1.4.1/node/api/server/server.go (about)

     1  // Package server provides a server that can wrap a node and serve an http api
     2  // for interacting with the node.
     3  package server
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"net"
     9  	"net/http"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"sync"
    14  	"syscall"
    15  	"time"
    16  
    17  	mnemonics "gitlab.com/NebulousLabs/entropy-mnemonics"
    18  	"gitlab.com/NebulousLabs/errors"
    19  
    20  	"gitlab.com/SiaPrime/SiaPrime/crypto"
    21  	"gitlab.com/SiaPrime/SiaPrime/modules"
    22  	"gitlab.com/SiaPrime/SiaPrime/node"
    23  	"gitlab.com/SiaPrime/SiaPrime/node/api"
    24  	"gitlab.com/SiaPrime/SiaPrime/types"
    25  )
    26  
    27  // A Server is a collection of SiaPrime modules that can be communicated with
    28  // over an http api.
    29  type Server struct {
    30  	api               *api.API
    31  	apiServer         *http.Server
    32  	done              chan struct{}
    33  	listener          net.Listener
    34  	node              *node.Node
    35  	requiredUserAgent string
    36  	serveErr          error
    37  	Dir               string
    38  
    39  	closeMu sync.Mutex
    40  }
    41  
    42  // serve listens for and handles API calls. It is a blocking function.
    43  func (srv *Server) serve() error {
    44  	// The server will run until an error is encountered or the listener is
    45  	// closed, via either the Close method or by signal handling.  Closing the
    46  	// listener will result in the benign error handled below.
    47  	err := srv.apiServer.Serve(srv.listener)
    48  	if err != nil && !strings.HasSuffix(err.Error(), "use of closed network connection") {
    49  		return err
    50  	}
    51  	return nil
    52  }
    53  
    54  // Close closes the Server's listener, causing the HTTP server to shut down.
    55  func (srv *Server) Close() error {
    56  	srv.closeMu.Lock()
    57  	defer srv.closeMu.Unlock()
    58  	// Stop accepting API requests.
    59  	err := srv.apiServer.Shutdown(context.Background())
    60  	// Wait for serve() to return and capture its error.
    61  	<-srv.done
    62  	if srv.serveErr != http.ErrServerClosed {
    63  		err = errors.Compose(err, srv.serveErr)
    64  	}
    65  	// Shutdown modules.
    66  	if srv.node != nil {
    67  		err = errors.Compose(err, srv.node.Close())
    68  	}
    69  	return errors.AddContext(err, "error while closing server")
    70  }
    71  
    72  // APIAddress returns the underlying node's api address
    73  func (srv *Server) APIAddress() string {
    74  	return srv.listener.Addr().String()
    75  }
    76  
    77  // GatewayAddress returns the underlying node's gateway address
    78  func (srv *Server) GatewayAddress() modules.NetAddress {
    79  	return srv.node.Gateway.Address()
    80  }
    81  
    82  // HostPublicKey returns the host's public key or an error if the node is no
    83  // host.
    84  func (srv *Server) HostPublicKey() (types.SiaPublicKey, error) {
    85  	if srv.node.Host == nil {
    86  		return types.SiaPublicKey{}, errors.New("can't get public host key of a non-host node")
    87  	}
    88  	return srv.node.Host.PublicKey(), nil
    89  }
    90  
    91  // ServeErr is a blocking call that will return the result of srv.serve after
    92  // the server stopped.
    93  func (srv *Server) ServeErr() <-chan error {
    94  	c := make(chan error)
    95  	go func() {
    96  		<-srv.done
    97  		close(c)
    98  	}()
    99  	return c
   100  }
   101  
   102  // Unlock unlocks the server's wallet using the provided password.
   103  func (srv *Server) Unlock(password string) error {
   104  	if srv.node.Wallet == nil {
   105  		return errors.New("server doesn't have a wallet")
   106  	}
   107  	var validKeys []crypto.CipherKey
   108  	dicts := []mnemonics.DictionaryID{"english", "german", "japanese"}
   109  	for _, dict := range dicts {
   110  		seed, err := modules.StringToSeed(password, dict)
   111  		if err != nil {
   112  			continue
   113  		}
   114  		validKeys = append(validKeys, crypto.NewWalletKey(crypto.HashObject(seed)))
   115  	}
   116  	validKeys = append(validKeys, crypto.NewWalletKey(crypto.HashObject(password)))
   117  	for _, key := range validKeys {
   118  		if err := srv.node.Wallet.Unlock(key); err == nil {
   119  			return nil
   120  		}
   121  	}
   122  	return modules.ErrBadEncryptionKey
   123  }
   124  
   125  // New creates a new API server from the provided modules. The API will
   126  // require authentication using HTTP basic auth if the supplied password is not
   127  // the empty string. Usernames are ignored for authentication. This type of
   128  // authentication sends passwords in plaintext and should therefore only be
   129  // used if the APIaddr is localhost.
   130  func New(APIaddr string, requiredUserAgent string, requiredPassword string, nodeParams node.NodeParams) (*Server, error) {
   131  	// Create the server listener.
   132  	listener, err := net.Listen("tcp", APIaddr)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	// Load the config file.
   138  	cfg, err := modules.NewConfig(filepath.Join(nodeParams.Dir, configName))
   139  	if err != nil {
   140  		return nil, errors.AddContext(err, "failed to load spd config")
   141  	}
   142  
   143  	// Create the api for the server.
   144  	api := api.New(cfg, requiredUserAgent, requiredPassword, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
   145  	// NOTE: SiaPrime original below
   146  	//  api := api.New(cfg, requiredUserAgent, requiredPassword, node.ConsensusSet, node.Explorer, node.Gateway, node.Host, node.Miner, node.Renter, node.TransactionPool, node.Wallet, node.MiningPool, node.StratumMiner, nil)
   147  	srv := &Server{
   148  		api: api,
   149  		apiServer: &http.Server{
   150  			Handler: api,
   151  
   152  			// set reasonable timeout windows for requests, to prevent the Sia API
   153  			// server from leaking file descriptors due to slow, disappearing, or
   154  			// unreliable API clients.
   155  
   156  			// ReadTimeout defines the maximum amount of time allowed to fully read
   157  			// the request body. This timeout is applied to every handler in the
   158  			// server.
   159  			ReadTimeout: time.Minute * 60,
   160  
   161  			// ReadHeaderTimeout defines the amount of time allowed to fully read the
   162  			// request headers.
   163  			ReadHeaderTimeout: time.Minute * 2,
   164  
   165  			// IdleTimeout defines the maximum duration a HTTP Keep-Alive connection
   166  			// the API is kept open with no activity before closing.
   167  			IdleTimeout: time.Minute * 5,
   168  		},
   169  		done:              make(chan struct{}),
   170  		listener:          listener,
   171  		requiredUserAgent: requiredUserAgent,
   172  		Dir:               nodeParams.Dir,
   173  	}
   174  
   175  	// Set the shutdown method to allow the api to shutdown the server.
   176  	api.Shutdown = srv.Close
   177  
   178  	// Spin up a goroutine that serves the API and closes srv.done when
   179  	// finished.
   180  	go func() {
   181  		srv.serveErr = srv.serve()
   182  		close(srv.done)
   183  	}()
   184  
   185  	// Create the Sia node for the server after the server was started.
   186  	node, err := node.New(nodeParams)
   187  	if err != nil {
   188  		if isAddrInUseErr(err) {
   189  			return nil, fmt.Errorf("%v; are you running another instance of spd?", err.Error())
   190  		}
   191  		return nil, errors.AddContext(err, "server is unable to create the SiaPrime node")
   192  	}
   193  
   194  	// Make sure that the server wasn't shut down while loading the modules.
   195  	srv.closeMu.Lock()
   196  	defer srv.closeMu.Unlock()
   197  	select {
   198  	case <-srv.done:
   199  		// Server was shut down. Close node and exit.
   200  		return srv, node.Close()
   201  	default:
   202  	}
   203  	// Server wasn't shut down. Add node and replace modules.
   204  	srv.node = node
   205  	api.SetModules(node.ConsensusSet, node.Explorer, node.Gateway, node.Host, node.Miner, node.Renter, node.TransactionPool, node.Wallet, node.MiningPool, node.StratumMiner)
   206  
   207  	return srv, nil
   208  }
   209  
   210  // isAddrInUseErr checks if the error corresponds to syscall.EADDRINUSE
   211  func isAddrInUseErr(err error) bool {
   212  	if opErr, ok := err.(*net.OpError); ok {
   213  		if syscallErr, ok := opErr.Err.(*os.SyscallError); ok {
   214  			return syscallErr.Err == syscall.EADDRINUSE
   215  		}
   216  	}
   217  	return false
   218  }