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  }