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 }