ipfs-cluster/api/rest/client/client.go
Hector Sanjuan 1b8967aa46 Merge branch 'master' into feat/sharding-v1
License: MIT
Signed-off-by: Hector Sanjuan <code@hector.link>
2018-08-14 11:40:03 +02:00

243 lines
5.7 KiB
Go

package client
import (
"context"
"fmt"
"net/http"
"time"
shell "github.com/ipfs/go-ipfs-api"
logging "github.com/ipfs/go-log"
host "github.com/libp2p/go-libp2p-host"
ma "github.com/multiformats/go-multiaddr"
madns "github.com/multiformats/go-multiaddr-dns"
manet "github.com/multiformats/go-multiaddr-net"
)
// Configuration defaults
var (
DefaultTimeout = 0
DefaultAPIAddr = "/ip4/127.0.0.1/tcp/9094"
DefaultLogLevel = "info"
DefaultProxyPort = 9095
ResolveTimeout = 30 * time.Second
DefaultPort = 9094
)
var loggingFacility = "apiclient"
var logger = logging.Logger(loggingFacility)
// Config allows to configure the parameters to connect
// to the ipfs-cluster REST API.
type Config struct {
// Enable SSL support. Only valid without PeerAddr.
SSL bool
// Skip certificate verification (insecure)
NoVerifyCert bool
// Username and password for basic authentication
Username string
Password string
// The ipfs-cluster REST API endpoint in multiaddress form
// (takes precedence over host:port). It this address contains
// an /ipfs/, /p2p/ or /dnsaddr, the API will be contacted
// through a libp2p tunnel, thus getting encryption for
// free. Using the libp2p tunnel will ignore any configurations.
APIAddr ma.Multiaddr
// PeerAddr is deprecated. It's aliased to APIAddr
PeerAddr ma.Multiaddr
// REST API endpoint host and port. Only valid without
// APIAddr and PeerAddr
Host string
Port string
// If PeerAddr is provided, and the peer uses private networks
// (pnet), then we need to provide the key. If the peer is the
// cluster peer, this corresponds to the cluster secret.
ProtectorKey []byte
// ProxyAddr is used to obtain a go-ipfs-api Shell instance pointing
// to the ipfs proxy endpoint of ipfs-cluster. If empty, the location
// will be guessed from one of APIAddr/Host,
// and the port used will be ipfs-cluster's proxy default port (9095)
ProxyAddr ma.Multiaddr
// Define timeout for network operations
Timeout time.Duration
// Specifies if we attempt to re-use connections to the same
// hosts.
DisableKeepAlives bool
// LogLevel defines the verbosity of the logging facility
LogLevel string
}
// Client provides methods to interact with the ipfs-cluster API. Use
// NewClient() to create one.
type Client struct {
ctx context.Context
cancel func()
config *Config
transport *http.Transport
net string
hostname string
client *http.Client
p2p host.Host
}
// NewClient initializes a client given a Config.
func NewClient(cfg *Config) (*Client, error) {
ctx := context.Background()
client := &Client{
ctx: ctx,
config: cfg,
}
if paddr := client.config.PeerAddr; paddr != nil {
client.config.APIAddr = paddr
}
if client.config.Port == "" {
client.config.Port = fmt.Sprintf("%d", DefaultPort)
}
err := client.setupAPIAddr()
if err != nil {
return nil, err
}
err = client.resolveAPIAddr()
if err != nil {
return nil, err
}
err = client.setupHTTPClient()
if err != nil {
return nil, err
}
err = client.setupHostname()
if err != nil {
return nil, err
}
err = client.setupProxy()
if err != nil {
return nil, err
}
if lvl := cfg.LogLevel; lvl != "" {
logging.SetLogLevel(loggingFacility, lvl)
} else {
logging.SetLogLevel(loggingFacility, DefaultLogLevel)
}
return client, nil
}
func (c *Client) setupAPIAddr() error {
var addr ma.Multiaddr
var err error
if c.config.APIAddr == nil {
if c.config.Host == "" { //default
addr, err = ma.NewMultiaddr(DefaultAPIAddr)
} else {
addrStr := fmt.Sprintf("/dns4/%s/tcp/%s", c.config.Host, c.config.Port)
addr, err = ma.NewMultiaddr(addrStr)
}
c.config.APIAddr = addr
return err
}
return nil
}
func (c *Client) resolveAPIAddr() error {
resolveCtx, cancel := context.WithTimeout(c.ctx, ResolveTimeout)
defer cancel()
resolved, err := madns.Resolve(resolveCtx, c.config.APIAddr)
if err != nil {
return err
}
if len(resolved) == 0 {
return fmt.Errorf("resolving %s returned 0 results", c.config.APIAddr)
}
c.config.APIAddr = resolved[0]
return nil
}
func (c *Client) setupHTTPClient() error {
var err error
switch {
case IsPeerAddress(c.config.APIAddr):
err = c.enableLibp2p()
case c.config.SSL:
err = c.enableTLS()
default:
c.defaultTransport()
}
if err != nil {
return err
}
c.client = &http.Client{
Transport: c.transport,
Timeout: c.config.Timeout,
}
return nil
}
func (c *Client) setupHostname() error {
// Extract host:port form APIAddr or use Host:Port.
// For libp2p, hostname is set in enableLibp2p()
if !IsPeerAddress(c.config.APIAddr) {
_, hostname, err := manet.DialArgs(c.config.APIAddr)
if err != nil {
return err
}
c.hostname = hostname
}
return nil
}
func (c *Client) setupProxy() error {
if c.config.ProxyAddr != nil {
return nil
}
// Guess location from APIAddr
port, err := ma.NewMultiaddr(fmt.Sprintf("/tcp/%d", DefaultProxyPort))
if err != nil {
return err
}
c.config.ProxyAddr = ma.Split(c.config.APIAddr)[0].Encapsulate(port)
return nil
}
// IPFS returns an instance of go-ipfs-api's Shell, pointing to the
// configured ProxyAddr (or to the default ipfs-cluster's IPFS proxy port).
// It re-uses this Client's HTTP client, thus will be constrained by
// the same configurations affecting it (timeouts...).
func (c *Client) IPFS() *shell.Shell {
return shell.NewShellWithClient(c.config.ProxyAddr.String(), c.client)
}
// IsPeerAddress detects if the given multiaddress identifies a libp2p peer,
// either because it has the /p2p/ protocol or because it uses /dnsaddr/
func IsPeerAddress(addr ma.Multiaddr) bool {
if addr == nil {
return false
}
pid, err := addr.ValueForProtocol(ma.P_IPFS)
dnsaddr, err2 := addr.ValueForProtocol(madns.DnsaddrProtocol.Code)
return (pid != "" && err == nil) || (dnsaddr != "" && err2 == nil)
}