Convert to use gorilla websockets package.
Also, since the new package exposes more connection related error information, add a new ErrInvalidEndpoint which is returned if the specified enpoint does not appear to be a valid websocket provider and only return the ErrInvalidAuth error when HTTP authorization failure status codes are detected. Closes #1.
This commit is contained in:
parent
4921282646
commit
8700eeaeb6
1 changed files with 50 additions and 46 deletions
|
@ -6,7 +6,6 @@ package btcrpcclient
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"code.google.com/p/go.net/websocket"
|
|
||||||
"container/list"
|
"container/list"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
@ -16,6 +15,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/conformal/btcjson"
|
"github.com/conformal/btcjson"
|
||||||
"github.com/conformal/go-socks"
|
"github.com/conformal/go-socks"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -30,6 +30,11 @@ var (
|
||||||
// incorrect.
|
// incorrect.
|
||||||
ErrInvalidAuth = errors.New("authentication failure")
|
ErrInvalidAuth = errors.New("authentication failure")
|
||||||
|
|
||||||
|
// ErrInvalidEndpoint is an error to describe the condition where the
|
||||||
|
// websocket handshake failed with the specified endpoint.
|
||||||
|
ErrInvalidEndpoint = errors.New("the endpoint either does not support " +
|
||||||
|
"websockets or does not exist")
|
||||||
|
|
||||||
// ErrClientDisconnect is an error to describe the condition where the
|
// ErrClientDisconnect is an error to describe the condition where the
|
||||||
// client has been disconnected from the RPC server. When the
|
// client has been disconnected from the RPC server. When the
|
||||||
// DisableAutoReconnect option is not set, any outstanding futures
|
// DisableAutoReconnect option is not set, any outstanding futures
|
||||||
|
@ -193,9 +198,9 @@ func (c *Client) removeAllRequests() {
|
||||||
// (including pass through for standard RPC commands), sends the appropriate
|
// (including pass through for standard RPC commands), sends the appropriate
|
||||||
// response. It also detects commands which are marked as long-running and
|
// response. It also detects commands which are marked as long-running and
|
||||||
// sends them off to the asyncHander for processing.
|
// sends them off to the asyncHander for processing.
|
||||||
func (c *Client) handleMessage(msg string) {
|
func (c *Client) handleMessage(msg []byte) {
|
||||||
// Attempt to unmarshal the message as a known JSON-RPC command.
|
// Attempt to unmarshal the message as a known JSON-RPC command.
|
||||||
if cmd, err := btcjson.ParseMarshaledCmd([]byte(msg)); err == nil {
|
if cmd, err := btcjson.ParseMarshaledCmd(msg); err == nil {
|
||||||
// Commands that have an ID associated with them are not
|
// Commands that have an ID associated with them are not
|
||||||
// notifications. Since this is a client, it should not
|
// notifications. Since this is a client, it should not
|
||||||
// be receiving non-notifications.
|
// be receiving non-notifications.
|
||||||
|
@ -271,8 +276,8 @@ out:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg string
|
_, msg, err := c.wsConn.ReadMessage()
|
||||||
if err := websocket.Message.Receive(c.wsConn, &msg); err != nil {
|
if err != nil {
|
||||||
// Log the error if it's not due to disconnecting.
|
// Log the error if it's not due to disconnecting.
|
||||||
if _, ok := err.(*net.OpError); !ok {
|
if _, ok := err.(*net.OpError); !ok {
|
||||||
log.Errorf("Websocket receive error from "+
|
log.Errorf("Websocket receive error from "+
|
||||||
|
@ -299,7 +304,7 @@ out:
|
||||||
// disconnected closed.
|
// disconnected closed.
|
||||||
select {
|
select {
|
||||||
case msg := <-c.sendChan:
|
case msg := <-c.sendChan:
|
||||||
err := websocket.Message.Send(c.wsConn, string(msg))
|
err := c.wsConn.WriteMessage(websocket.TextMessage, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Disconnect()
|
c.Disconnect()
|
||||||
break out
|
break out
|
||||||
|
@ -832,63 +837,62 @@ func newHttpClient(config *ConnConfig) (*http.Client, error) {
|
||||||
// dial opens a websocket connection using the passed connection configuration
|
// dial opens a websocket connection using the passed connection configuration
|
||||||
// details.
|
// details.
|
||||||
func dial(config *ConnConfig) (*websocket.Conn, error) {
|
func dial(config *ConnConfig) (*websocket.Conn, error) {
|
||||||
// Connect to websocket.
|
// Setup TLS if not disabled.
|
||||||
url := fmt.Sprintf("wss://%s/%s", config.Host, config.Endpoint)
|
var tlsConfig *tls.Config
|
||||||
wsConfig, err := websocket.NewConfig(url, "https://localhost/")
|
var scheme = "ws"
|
||||||
if err != nil {
|
if !config.DisableTLS {
|
||||||
return nil, err
|
pool := x509.NewCertPool()
|
||||||
|
pool.AppendCertsFromPEM(config.Certificates)
|
||||||
|
tlsConfig = &tls.Config{
|
||||||
|
RootCAs: pool,
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
}
|
||||||
|
scheme = "wss"
|
||||||
}
|
}
|
||||||
|
|
||||||
pool := x509.NewCertPool()
|
// Create a websocket dialer that will be used to make the connection.
|
||||||
pool.AppendCertsFromPEM(config.Certificates)
|
// It is modified by the proxy setting below as needed.
|
||||||
wsConfig.TlsConfig = &tls.Config{
|
dialer := websocket.Dialer{TLSClientConfig: tlsConfig}
|
||||||
RootCAs: pool,
|
|
||||||
MinVersion: tls.VersionTLS12,
|
|
||||||
}
|
|
||||||
|
|
||||||
// The wallet requires basic authorization, so use a custom config with
|
// Setup the proxy if one is configured.
|
||||||
// with the Authorization header set.
|
|
||||||
login := config.User + ":" + config.Pass
|
|
||||||
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login))
|
|
||||||
wsConfig.Header.Add("Authorization", auth)
|
|
||||||
|
|
||||||
// Attempt to connect to running wallet instance using a proxy if one
|
|
||||||
// is configured.
|
|
||||||
if config.Proxy != "" {
|
if config.Proxy != "" {
|
||||||
proxy := &socks.Proxy{
|
proxy := &socks.Proxy{
|
||||||
Addr: config.Proxy,
|
Addr: config.Proxy,
|
||||||
Username: config.ProxyUser,
|
Username: config.ProxyUser,
|
||||||
Password: config.ProxyPass,
|
Password: config.ProxyPass,
|
||||||
}
|
}
|
||||||
conn, err := proxy.Dial("tcp", config.Host)
|
dialer.NetDial = proxy.Dial
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConn := tls.Client(conn, wsConfig.TlsConfig)
|
|
||||||
ws, err := websocket.NewClient(wsConfig, tlsConn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ws, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// No proxy was specified, so attempt to connect to running wallet
|
// The RPC server requires basic authorization, so create a custom
|
||||||
// instance directly.
|
// request header with the Authorization header set.
|
||||||
ws, err := websocket.DialConfig(wsConfig)
|
login := config.User + ":" + config.Pass
|
||||||
|
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login))
|
||||||
|
requestHeader := make(http.Header)
|
||||||
|
requestHeader.Add("Authorization", auth)
|
||||||
|
|
||||||
|
// Dial the connection.
|
||||||
|
url := fmt.Sprintf("%s://%s/%s", scheme, config.Host, config.Endpoint)
|
||||||
|
wsConn, resp, err := dialer.Dial(url, requestHeader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// XXX(davec): This is not really accurate, but unfortunately
|
if err == websocket.ErrBadHandshake {
|
||||||
// the current websocket package does not expose the status
|
// Detect HTTP authentication error status codes.
|
||||||
// code, so it's impossible to tell for sure.
|
if resp != nil &&
|
||||||
if dialError, ok := err.(*websocket.DialError); ok {
|
(resp.StatusCode == http.StatusUnauthorized ||
|
||||||
if dialError.Err == websocket.ErrBadStatus {
|
resp.StatusCode == http.StatusForbidden) {
|
||||||
|
|
||||||
return nil, ErrInvalidAuth
|
return nil, ErrInvalidAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The connection was authenticated, but the websocket
|
||||||
|
// handshake still failed, so the endpoint is invalid
|
||||||
|
// in some way.
|
||||||
|
return nil, ErrInvalidEndpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return ws, nil
|
return wsConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// New create a new RPC client based on the provided connection configuration
|
// New create a new RPC client based on the provided connection configuration
|
||||||
|
|
Loading…
Add table
Reference in a new issue