gitlab.com/SiaPrime/SiaPrime@v1.4.1/node/api/api.go (about) 1 package api 2 3 import ( 4 "encoding/json" 5 "net/http" 6 "strings" 7 "sync" 8 9 "gitlab.com/SiaPrime/SiaPrime/build" 10 "gitlab.com/SiaPrime/SiaPrime/modules" 11 ) 12 13 // Error is a type that is encoded as JSON and returned in an API response in 14 // the event of an error. Only the Message field is required. More fields may 15 // be added to this struct in the future for better error reporting. 16 type Error struct { 17 // Message describes the error in English. Typically it is set to 18 // `err.Error()`. This field is required. 19 Message string `json:"message"` 20 21 // TODO: add a Param field with the (omitempty option in the json tag) 22 // to indicate that the error was caused by an invalid, missing, or 23 // incorrect parameter. This is not trivial as the API does not 24 // currently do parameter validation itself. For example, the 25 // /gateway/connect endpoint relies on the gateway.Connect method to 26 // validate the netaddress. However, this prevents the API from knowing 27 // whether an error returned by gateway.Connect is because of a 28 // connection error or an invalid netaddress parameter. Validating 29 // parameters in the API is not sufficient, as a parameter's value may 30 // be valid or invalid depending on the current state of a module. 31 } 32 33 // Error implements the error interface for the Error type. It returns only the 34 // Message field. 35 func (err Error) Error() string { 36 return err.Message 37 } 38 39 // HttpGET is a utility function for making http get requests to sia with a 40 // whitelisted user-agent. A non-2xx response does not return an error. 41 func HttpGET(url string) (resp *http.Response, err error) { 42 req, err := http.NewRequest("GET", url, nil) 43 if err != nil { 44 return nil, err 45 } 46 req.Header.Set("User-Agent", "SiaPrime-Agent") 47 return http.DefaultClient.Do(req) 48 } 49 50 // HttpGETAuthenticated is a utility function for making authenticated http get 51 // requests to sia with a whitelisted user-agent and the supplied password. A 52 // non-2xx response does not return an error. 53 func HttpGETAuthenticated(url string, password string) (resp *http.Response, err error) { 54 req, err := http.NewRequest("GET", url, nil) 55 if err != nil { 56 return nil, err 57 } 58 req.Header.Set("User-Agent", "SiaPrime-Agent") 59 req.SetBasicAuth("", password) 60 return http.DefaultClient.Do(req) 61 } 62 63 // HttpPOST is a utility function for making post requests to sia with a 64 // whitelisted user-agent. A non-2xx response does not return an error. 65 func HttpPOST(url string, data string) (resp *http.Response, err error) { 66 req, err := http.NewRequest("POST", url, strings.NewReader(data)) 67 if err != nil { 68 return nil, err 69 } 70 req.Header.Set("User-Agent", "SiaPrime-Agent") 71 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 72 return http.DefaultClient.Do(req) 73 } 74 75 // HttpPOSTAuthenticated is a utility function for making authenticated http 76 // post requests to sia with a whitelisted user-agent and the supplied 77 // password. A non-2xx response does not return an error. 78 func HttpPOSTAuthenticated(url string, data string, password string) (resp *http.Response, err error) { 79 req, err := http.NewRequest("POST", url, strings.NewReader(data)) 80 if err != nil { 81 return nil, err 82 } 83 req.Header.Set("User-Agent", "SiaPrime-Agent") 84 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 85 req.SetBasicAuth("", password) 86 return http.DefaultClient.Do(req) 87 } 88 89 // API encapsulates a collection of modules and implements a http.Handler 90 // to access their methods. 91 type API struct { 92 cs modules.ConsensusSet 93 explorer modules.Explorer 94 gateway modules.Gateway 95 host modules.Host 96 miner modules.Miner 97 renter modules.Renter 98 tpool modules.TransactionPool 99 wallet modules.Wallet 100 pool modules.Pool 101 stratumminer modules.StratumMiner 102 index modules.Index 103 104 downloadMu sync.Mutex 105 downloads map[string]func() 106 router http.Handler 107 routerMu sync.RWMutex 108 109 requiredUserAgent string 110 requiredPassword string 111 Shutdown func() error 112 spdConfig *modules.SpdConfig 113 } 114 115 // api.ServeHTTP implements the http.Handler interface. 116 func (api *API) ServeHTTP(w http.ResponseWriter, r *http.Request) { 117 api.routerMu.RLock() 118 api.router.ServeHTTP(w, r) 119 api.routerMu.RUnlock() 120 } 121 122 // SetModules allows for replacing the modules in the API at runtime. 123 func (api *API) SetModules(cs modules.ConsensusSet, e modules.Explorer, g modules.Gateway, h modules.Host, m modules.Miner, r modules.Renter, tp modules.TransactionPool, w modules.Wallet, p modules.Pool, sm modules.StratumMiner) { 124 api.cs = cs 125 api.explorer = e 126 api.gateway = g 127 api.host = h 128 api.miner = m 129 api.renter = r 130 api.tpool = tp 131 api.wallet = w 132 api.stratumminer = sm 133 api.pool = p 134 api.buildHTTPRoutes() 135 } 136 137 // New creates a new Sia API from the provided modules. The API will require 138 // authentication using HTTP basic auth for certain endpoints of the supplied 139 // password is not the empty string. Usernames are ignored for authentication. 140 func New(cfg *modules.SpdConfig, requiredUserAgent string, requiredPassword string, cs modules.ConsensusSet, e modules.Explorer, g modules.Gateway, h modules.Host, m modules.Miner, r modules.Renter, tp modules.TransactionPool, w modules.Wallet, p modules.Pool, sm modules.StratumMiner, index modules.Index) *API { 141 api := &API{ 142 cs: cs, 143 explorer: e, 144 gateway: g, 145 host: h, 146 miner: m, 147 renter: r, 148 tpool: tp, 149 wallet: w, 150 pool: p, 151 stratumminer: sm, 152 index: index, 153 downloads: make(map[string]func()), 154 requiredUserAgent: requiredUserAgent, 155 requiredPassword: requiredPassword, 156 spdConfig: cfg, 157 } 158 159 // Register API handlers 160 api.buildHTTPRoutes() 161 162 return api 163 } 164 165 // UnrecognizedCallHandler handles calls to unknown pages (404). 166 func UnrecognizedCallHandler(w http.ResponseWriter, req *http.Request) { 167 WriteError(w, Error{"404 - Refer to API.md"}, http.StatusNotFound) 168 } 169 170 // WriteError an error to the API caller. 171 func WriteError(w http.ResponseWriter, err Error, code int) { 172 w.Header().Set("Content-Type", "application/json; charset=utf-8") 173 w.WriteHeader(code) 174 encodingErr := json.NewEncoder(w).Encode(err) 175 if _, isJsonErr := encodingErr.(*json.SyntaxError); isJsonErr { 176 // Marshalling should only fail in the event of a developer error. 177 // Specifically, only non-marshallable types should cause an error here. 178 build.Critical("failed to encode API error response:", encodingErr) 179 } 180 } 181 182 // WriteJSON writes the object to the ResponseWriter. If the encoding fails, an 183 // error is written instead. The Content-Type of the response header is set 184 // accordingly. 185 func WriteJSON(w http.ResponseWriter, obj interface{}) { 186 w.Header().Set("Content-Type", "application/json; charset=utf-8") 187 err := json.NewEncoder(w).Encode(obj) 188 if _, isJsonErr := err.(*json.SyntaxError); isJsonErr { 189 // Marshalling should only fail in the event of a developer error. 190 // Specifically, only non-marshallable types should cause an error here. 191 build.Critical("failed to encode API response:", err) 192 } 193 } 194 195 // WriteSuccess writes the HTTP header with status 204 No Content to the 196 // ResponseWriter. WriteSuccess should only be used to indicate that the 197 // requested action succeeded AND there is no data to return. 198 func WriteSuccess(w http.ResponseWriter) { 199 w.WriteHeader(http.StatusNoContent) 200 }