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  }