decred.org/dcrdex@v1.0.5/client/cmd/bwctl/httpclient.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/json" 11 "fmt" 12 "io" 13 "net" 14 "net/http" 15 "net/url" 16 "os" 17 18 "decred.org/dcrdex/dex/msgjson" 19 "github.com/decred/go-socks/socks" 20 ) 21 22 // newHTTPClient returns a new HTTP client that is configured according to the 23 // proxy and TLS settings in the associated connection configuration. 24 func newHTTPClient(cfg *config, urlStr string) (*http.Client, error) { 25 // Configure proxy if needed. 26 var dial func(network, addr string) (net.Conn, error) 27 if cfg.Proxy != "" { 28 proxy := &socks.Proxy{ 29 Addr: cfg.Proxy, 30 Username: cfg.ProxyUser, 31 Password: cfg.ProxyPass, 32 } 33 dial = func(network, addr string) (net.Conn, error) { 34 c, err := proxy.Dial(network, addr) 35 if err != nil { 36 return nil, err 37 } 38 return c, nil 39 } 40 } 41 42 // Configure TLS. 43 pem, err := os.ReadFile(cfg.RPCCert) 44 if err != nil { 45 return nil, err 46 } 47 48 uri, err := url.Parse(urlStr) 49 if err != nil { 50 return nil, fmt.Errorf("error parsing URL: %v", err) 51 } 52 53 pool := x509.NewCertPool() 54 if ok := pool.AppendCertsFromPEM(pem); !ok { 55 return nil, fmt.Errorf("invalid certificate file: %v", 56 cfg.RPCCert) 57 } 58 tlsConfig := &tls.Config{ 59 RootCAs: pool, 60 ServerName: uri.Hostname(), 61 } 62 63 // Create and return the new HTTP client potentially configured with a 64 // proxy and TLS. 65 client := http.Client{ 66 Transport: &http.Transport{ 67 Dial: dial, 68 TLSClientConfig: tlsConfig, 69 }, 70 } 71 return &client, nil 72 } 73 74 // sendPostRequest sends the marshalled JSON-RPC command using HTTP-POST mode 75 // to the server described in the passed config struct. It also attempts to 76 // unmarshal the response as a msgjson.Message response and returns either the 77 // response or error. 78 func sendPostRequest(marshalledJSON []byte, cfg *config) (*msgjson.Message, error) { 79 // Generate a request to the configured RPC server. 80 urlStr := "https://" + cfg.RPCAddr 81 if cfg.PrintJSON { 82 fmt.Println(string(marshalledJSON)) 83 } 84 bodyReader := bytes.NewReader(marshalledJSON) 85 httpRequest, err := http.NewRequest("POST", urlStr, bodyReader) 86 if err != nil { 87 return nil, err 88 } 89 httpRequest.Close = true 90 httpRequest.Header.Set("Content-Type", "application/json") 91 92 // Configure basic access authorization. 93 httpRequest.SetBasicAuth(cfg.RPCUser, cfg.RPCPass) 94 95 // Create the new HTTP client that is configured according to the user- 96 // specified options and submit the request. 97 httpClient, err := newHTTPClient(cfg, urlStr) 98 if err != nil { 99 return nil, err 100 } 101 httpResponse, err := httpClient.Do(httpRequest) 102 if err != nil { 103 return nil, err 104 } 105 106 // Read the raw bytes and close the response. 107 respBytes, err := io.ReadAll(httpResponse.Body) 108 httpResponse.Body.Close() 109 if err != nil { 110 err = fmt.Errorf("error reading json reply: %v", err) 111 return nil, err 112 } 113 114 // Handle unsuccessful HTTP responses 115 if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 { 116 // Generate a standard error to return if the server body is 117 // empty. This should not happen very often, but it's better 118 // than showing nothing in case the target server has a poor 119 // implementation. 120 if len(respBytes) == 0 { 121 return nil, fmt.Errorf("%d %s", httpResponse.StatusCode, 122 http.StatusText(httpResponse.StatusCode)) 123 } 124 return nil, fmt.Errorf("%s", respBytes) 125 } 126 127 // If requested, print raw json response. 128 if cfg.PrintJSON { 129 fmt.Println(string(respBytes)) 130 } 131 132 // Unmarshal the response. 133 var resp *msgjson.Message 134 if err := json.Unmarshal(respBytes, &resp); err != nil { 135 return nil, err 136 } 137 138 return resp, nil 139 }