decred.org/dcrdex@v1.0.5/docs/examples/rpcclient/main.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 main 5 6 import ( 7 "bytes" 8 "crypto/tls" 9 "crypto/x509" 10 "encoding/base64" 11 "encoding/json" 12 "fmt" 13 "io" 14 "net/http" 15 "net/url" 16 "os" 17 "path/filepath" 18 19 "decred.org/dcrdex/client/rpcserver" 20 "decred.org/dcrdex/dex/msgjson" 21 "github.com/decred/dcrd/dcrutil/v4" 22 "github.com/gorilla/websocket" 23 ) 24 25 const ( 26 user = "user" 27 pass = "pass" 28 addr = "127.0.0.1:5757" 29 dexAddr = "127.0.0.1:17273" 30 ) 31 32 func main() { 33 if err := run(); err != nil { 34 fmt.Fprintf(os.Stderr, "%v", err) 35 } 36 } 37 38 func run() error { 39 home := dcrutil.AppDataDir("dexc", false) 40 certFile := filepath.Join(home, "rpc.cert") 41 cfg := &config{ 42 RPCUser: user, 43 RPCPass: pass, 44 RPCAddr: addr, 45 RPCCert: certFile, 46 } 47 urlStr := "https://" + cfg.RPCAddr 48 49 // Create a new HTTP client. 50 client, err := newHTTPClient(cfg, urlStr) 51 if err != nil { 52 return err 53 } 54 55 rawParams := rpcserver.RawParams{Args: []string{"trade"}} 56 msg, err := msgjson.NewRequest(1, "help", rawParams) 57 b, err := json.Marshal(msg) 58 fmt.Println("http request:", string(b)) 59 if err != nil { 60 return err 61 } 62 63 // Send a one shot request over https. 64 res, err := sendPostRequest(b, urlStr, cfg, client) 65 if err != nil { 66 return err 67 } 68 fmt.Println("response: ", res) 69 70 // Create a new ws client. 71 wsc, err := newWSClient(cfg) 72 if err != nil { 73 return err 74 } 75 defer wsc.Close() 76 77 // Send a request to the websocket server. 78 type marketLoad struct { 79 Host string `json:"host"` 80 Base uint32 `json:"base"` 81 Quote uint32 `json:"quote"` 82 } 83 ml := marketLoad{ 84 Host: dexAddr, 85 Base: 42, 86 Quote: 0, 87 } 88 msg, err = msgjson.NewRequest(1, "loadmarket", ml) 89 b, err = json.Marshal(msg) 90 if err != nil { 91 return err 92 } 93 fmt.Println("ws request:", string(b)) 94 err = wsc.WriteMessage(1, b) 95 if err != nil { 96 return err 97 } 98 99 // Wait for a notification to come. 100 _, r, err := wsc.NextReader() 101 if err != nil { 102 return err 103 } 104 notification, err := io.ReadAll(r) 105 if err != nil { 106 return err 107 } 108 fmt.Println("ws notification:", string(notification)) 109 return nil 110 } 111 112 type config struct { 113 RPCUser string 114 RPCPass string 115 RPCAddr string 116 RPCCert string 117 } 118 119 // newHTTPClient returns a new HTTP client 120 func newHTTPClient(cfg *config, urlStr string) (*http.Client, error) { 121 // Configure TLS. 122 pem, err := os.ReadFile(cfg.RPCCert) 123 if err != nil { 124 return nil, err 125 } 126 127 uri, err := url.Parse(urlStr) 128 if err != nil { 129 return nil, fmt.Errorf("error parsing URL: %v", err) 130 } 131 132 pool := x509.NewCertPool() 133 if ok := pool.AppendCertsFromPEM(pem); !ok { 134 return nil, fmt.Errorf("invalid certificate file: %v", 135 cfg.RPCCert) 136 } 137 tlsConfig := &tls.Config{ 138 RootCAs: pool, 139 ServerName: uri.Hostname(), 140 } 141 142 // Create and return the new HTTP client potentially configured with a 143 // proxy and TLS. 144 client := http.Client{ 145 Transport: &http.Transport{ 146 TLSClientConfig: tlsConfig, 147 }, 148 } 149 return &client, nil 150 } 151 152 // newWSClient obtains a websocket connection. 153 func newWSClient(cfg *config) (*websocket.Conn, error) { 154 urlStr := "wss://" + cfg.RPCAddr + "/ws" 155 // Configure TLS. 156 pem, err := os.ReadFile(cfg.RPCCert) 157 if err != nil { 158 return nil, err 159 } 160 161 uri, err := url.Parse(urlStr) 162 if err != nil { 163 return nil, fmt.Errorf("error parsing URL: %v", err) 164 } 165 166 pool := x509.NewCertPool() 167 if ok := pool.AppendCertsFromPEM(pem); !ok { 168 return nil, fmt.Errorf("invalid certificate file: %v", 169 cfg.RPCCert) 170 } 171 tlsConfig := &tls.Config{ 172 RootCAs: pool, 173 ServerName: uri.Hostname(), 174 } 175 176 // Configure dialer with tls. 177 dialer := websocket.Dialer{ 178 TLSClientConfig: tlsConfig, 179 } 180 181 // Configure basic access authorization. 182 authStr := base64.StdEncoding.EncodeToString([]byte(cfg.RPCUser + ":" + cfg.RPCPass)) 183 header := http.Header{} 184 header.Set("Authorization", "Basic "+authStr) 185 conn, _, err := dialer.Dial(urlStr, header) 186 if err != nil { 187 return nil, err 188 } 189 return conn, nil 190 } 191 192 // sendPostRequest sends the marshalled JSON-RPC command using HTTP-POST mode 193 // to the server described in the passed config struct. It also attempts to 194 // unmarshal the response as a msgjson.Message response and returns either the 195 // response or error. 196 func sendPostRequest(marshalledJSON []byte, urlStr string, cfg *config, httpClient *http.Client) (*msgjson.Message, error) { 197 bodyReader := bytes.NewReader(marshalledJSON) 198 httpRequest, err := http.NewRequest("POST", urlStr, bodyReader) 199 if err != nil { 200 return nil, err 201 } 202 httpRequest.Close = true 203 httpRequest.Header.Set("Content-Type", "application/json") 204 205 // Configure basic access authorization. 206 httpRequest.SetBasicAuth(cfg.RPCUser, cfg.RPCPass) 207 208 httpResponse, err := httpClient.Do(httpRequest) 209 if err != nil { 210 return nil, err 211 } 212 213 // Read the raw bytes and close the response. 214 respBytes, err := io.ReadAll(httpResponse.Body) 215 httpResponse.Body.Close() 216 if err != nil { 217 err = fmt.Errorf("error reading json reply: %v", err) 218 return nil, err 219 } 220 221 // Handle unsuccessful HTTP responses 222 if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 { 223 // Generate a standard error to return if the server body is 224 // empty. This should not happen very often, but it's better 225 // than showing nothing in case the target server has a poor 226 // implementation. 227 if len(respBytes) == 0 { 228 return nil, fmt.Errorf("%d %s", httpResponse.StatusCode, 229 http.StatusText(httpResponse.StatusCode)) 230 } 231 return nil, fmt.Errorf("%s", respBytes) 232 } 233 234 // Unmarshal the response. 235 var resp *msgjson.Message 236 if err := json.Unmarshal(respBytes, &resp); err != nil { 237 return nil, err 238 } 239 return resp, nil 240 }