lbcd/cmd/btcctl/httpclient.go
Dave Collins 5a800b9580 Rewrite btcctl to use the new features of btcjson.
This commit contains what is essentially a complete rewrite of the btcctl
utility to make use of the new features provided by the latest version
btcjson and improve several things along the way.  The following
summarizes the changes:

- The supported commands and handling now come directly from btcjson, so
  it is no longer necessary to manually add new commands.  Once a command
  has been registered with btcjson, it will automatically become usable by
  btcctl complete with full error handling (once it is re-compiled of
  course)
- Rather than dumping the entire list of commands on every error, the user
  now must specifically request the list of command via the -l option
- The list of commands is now categorized by chain and wallet and
  alphabetized
- The help flag now only shows the help options instead of also dumping
  all of the commands
- The error display on valid commands with invalid parameters has been
  greatly improved to show the specific parameter number, reason, and
  error code
- When a valid command is specified with invalid parameter, only the usage
  for that specific command is shown now
- It is now possible to use a SOCKS5 proxy for connection
- The output of commands has been improved in the following ways:
  - Strings on commands such as getbestblockhash no longer have quotes
    wrapped around them
  - Fields that are integers no longer show in scientific notation when
    they are large (timestamps for example)

This closes #305 as a side effect.
2015-02-25 16:03:31 -06:00

128 lines
3.4 KiB
Go

package main
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/go-socks/socks"
)
// newHTTPClient returns a new HTTP client that is configured according to the
// proxy and TLS settings in the associated connection configuration.
func newHTTPClient(cfg *config) (*http.Client, error) {
// Configure proxy if needed.
var dial func(network, addr string) (net.Conn, error)
if cfg.Proxy != "" {
proxy := &socks.Proxy{
Addr: cfg.Proxy,
Username: cfg.ProxyUser,
Password: cfg.ProxyPass,
}
dial = func(network, addr string) (net.Conn, error) {
c, err := proxy.Dial(network, addr)
if err != nil {
return nil, err
}
return c, nil
}
}
// Configure TLS if needed.
var tlsConfig *tls.Config
if !cfg.NoTLS && cfg.RPCCert != "" {
pem, err := ioutil.ReadFile(cfg.RPCCert)
if err != nil {
return nil, err
}
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(pem)
tlsConfig = &tls.Config{
RootCAs: pool,
InsecureSkipVerify: cfg.TLSSkipVerify,
}
}
// Create and return the new HTTP client potentially configured with a
// proxy and TLS.
client := http.Client{
Transport: &http.Transport{
Dial: dial,
TLSClientConfig: tlsConfig,
},
}
return &client, nil
}
// sendPostRequest sends the marshalled JSON-RPC command using HTTP-POST mode
// to the server described in the passed config struct. It also attempts to
// unmarshal the response as a JSON-RPC response and returns either the result
// field or the error field depending on whether or not there is an error.
func sendPostRequest(marshalledJSON []byte, cfg *config) ([]byte, error) {
// Generate a request to the configured RPC server.
protocol := "http"
if !cfg.NoTLS {
protocol = "https"
}
url := protocol + "://" + cfg.RPCServer
bodyReader := bytes.NewReader(marshalledJSON)
httpRequest, err := http.NewRequest("POST", url, bodyReader)
if err != nil {
return nil, err
}
httpRequest.Close = true
httpRequest.Header.Set("Content-Type", "application/json")
// Configure basic access authorization.
httpRequest.SetBasicAuth(cfg.RPCUser, cfg.RPCPassword)
// Create the new HTTP client that is configured according to the user-
// specified options and submit the request.
httpClient, err := newHTTPClient(cfg)
if err != nil {
return nil, err
}
httpResponse, err := httpClient.Do(httpRequest)
if err != nil {
return nil, err
}
// Read the raw bytes and close the response.
respBytes, err := ioutil.ReadAll(httpResponse.Body)
httpResponse.Body.Close()
if err != nil {
err = fmt.Errorf("error reading json reply: %v", err)
return nil, err
}
// Handle unsuccessful HTTP responses
if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 {
// Generate a standard error to return if the server body is
// empty. This should not happen very often, but it's better
// than showing nothing in case the target server has a poor
// implementation.
if len(respBytes) == 0 {
return nil, fmt.Errorf("%d %s", httpResponse.StatusCode,
http.StatusText(httpResponse.StatusCode))
}
return nil, fmt.Errorf("%s", respBytes)
}
// Unmarshal the response.
var resp btcjson.Response
if err := json.Unmarshal(respBytes, &resp); err != nil {
return nil, err
}
if resp.Error != nil {
return nil, resp.Error
}
return resp.Result, nil
}