mirror of
https://github.com/ipfs/kubo.git
synced 2025-05-17 06:57:40 +08:00

* chore: stop using go-homedir The `github.com/mitchellh/go-homedir` repo is archived, no longer needed, and no longer maintained. - `homedir.Dir` is replaced by the stdlib `os.UserHomeDir` - `homedir.Expand` is replaced by fsutil.ExpandHome` in the `github.com/ipfs/kubo/misc/fsutil` package. Additional functionality, such as `DirWritable` and `FileExists` was moved into or included in the `github.com/ipfs/kubo/misc/fsutil` package.
293 lines
6.9 KiB
Go
293 lines
6.9 KiB
Go
package rpc
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/blang/semver/v4"
|
|
"github.com/ipfs/boxo/ipld/merkledag"
|
|
"github.com/ipfs/go-cid"
|
|
legacy "github.com/ipfs/go-ipld-legacy"
|
|
ipfs "github.com/ipfs/kubo"
|
|
iface "github.com/ipfs/kubo/core/coreiface"
|
|
caopts "github.com/ipfs/kubo/core/coreiface/options"
|
|
"github.com/ipfs/kubo/misc/fsutil"
|
|
dagpb "github.com/ipld/go-codec-dagpb"
|
|
_ "github.com/ipld/go-ipld-prime/codec/dagcbor"
|
|
"github.com/ipld/go-ipld-prime/node/basicnode"
|
|
ma "github.com/multiformats/go-multiaddr"
|
|
manet "github.com/multiformats/go-multiaddr/net"
|
|
)
|
|
|
|
const (
|
|
DefaultPathName = ".ipfs"
|
|
DefaultPathRoot = "~/" + DefaultPathName
|
|
DefaultApiFile = "api"
|
|
EnvDir = "IPFS_PATH"
|
|
)
|
|
|
|
// ErrApiNotFound if we fail to find a running daemon.
|
|
var ErrApiNotFound = errors.New("ipfs api address could not be found")
|
|
|
|
// HttpApi implements github.com/ipfs/interface-go-ipfs-core/CoreAPI using
|
|
// IPFS HTTP API.
|
|
//
|
|
// For interface docs see
|
|
// https://godoc.org/github.com/ipfs/interface-go-ipfs-core#CoreAPI
|
|
type HttpApi struct {
|
|
url string
|
|
httpcli http.Client
|
|
Headers http.Header
|
|
applyGlobal func(*requestBuilder)
|
|
ipldDecoder *legacy.Decoder
|
|
versionMu sync.Mutex
|
|
version *semver.Version
|
|
}
|
|
|
|
// NewLocalApi tries to construct new HttpApi instance communicating with local
|
|
// IPFS daemon
|
|
//
|
|
// Daemon api address is pulled from the $IPFS_PATH/api file.
|
|
// If $IPFS_PATH env var is not present, it defaults to ~/.ipfs.
|
|
func NewLocalApi() (*HttpApi, error) {
|
|
baseDir := os.Getenv(EnvDir)
|
|
if baseDir == "" {
|
|
baseDir = DefaultPathRoot
|
|
}
|
|
|
|
return NewPathApi(baseDir)
|
|
}
|
|
|
|
// NewPathApi constructs new HttpApi by pulling api address from specified
|
|
// ipfspath. Api file should be located at $ipfspath/api.
|
|
func NewPathApi(ipfspath string) (*HttpApi, error) {
|
|
a, err := ApiAddr(ipfspath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
err = ErrApiNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
return NewApi(a)
|
|
}
|
|
|
|
// ApiAddr reads api file in specified ipfs path.
|
|
func ApiAddr(ipfspath string) (ma.Multiaddr, error) {
|
|
baseDir, err := fsutil.ExpandHome(ipfspath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
apiFile := filepath.Join(baseDir, DefaultApiFile)
|
|
|
|
api, err := os.ReadFile(apiFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ma.NewMultiaddr(strings.TrimSpace(string(api)))
|
|
}
|
|
|
|
// NewApi constructs HttpApi with specified endpoint.
|
|
func NewApi(a ma.Multiaddr) (*HttpApi, error) {
|
|
transport := &http.Transport{
|
|
Proxy: http.ProxyFromEnvironment,
|
|
DisableKeepAlives: true,
|
|
}
|
|
|
|
network, address, err := manet.DialArgs(a)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if network == "unix" {
|
|
transport.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
|
|
return net.Dial("unix", address)
|
|
}
|
|
c := &http.Client{
|
|
Transport: transport,
|
|
}
|
|
// This will create an API client which
|
|
// makes requests to `http://unix`.
|
|
return NewURLApiWithClient(network, c)
|
|
}
|
|
|
|
c := &http.Client{
|
|
Transport: transport,
|
|
}
|
|
|
|
return NewApiWithClient(a, c)
|
|
}
|
|
|
|
// NewApiWithClient constructs HttpApi with specified endpoint and custom http client.
|
|
func NewApiWithClient(a ma.Multiaddr, c *http.Client) (*HttpApi, error) {
|
|
_, url, err := manet.DialArgs(a)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if a, err := ma.NewMultiaddr(url); err == nil {
|
|
_, host, err := manet.DialArgs(a)
|
|
if err == nil {
|
|
url = host
|
|
}
|
|
}
|
|
|
|
proto := "http://"
|
|
|
|
// By default, DialArgs is going to provide details suitable for connecting
|
|
// a socket to, but not really suitable for making an informed choice of http
|
|
// protocol. For multiaddresses specifying tls and/or https we want to make
|
|
// a https request instead of a http request.
|
|
protocols := a.Protocols()
|
|
for _, p := range protocols {
|
|
if p.Code == ma.P_HTTPS || p.Code == ma.P_TLS {
|
|
proto = "https://"
|
|
break
|
|
}
|
|
}
|
|
|
|
return NewURLApiWithClient(proto+url, c)
|
|
}
|
|
|
|
func NewURLApiWithClient(url string, c *http.Client) (*HttpApi, error) {
|
|
decoder := legacy.NewDecoder()
|
|
// Add support for these codecs to match what is done in the merkledag library
|
|
// Note: to match prior behavior the go-ipld-prime CBOR decoder is manually included
|
|
// TODO: allow the codec registry used to be configured by the caller not through a global variable
|
|
decoder.RegisterCodec(cid.DagProtobuf, dagpb.Type.PBNode, merkledag.ProtoNodeConverter)
|
|
decoder.RegisterCodec(cid.Raw, basicnode.Prototype.Bytes, merkledag.RawNodeConverter)
|
|
|
|
api := &HttpApi{
|
|
url: url,
|
|
httpcli: *c,
|
|
Headers: make(map[string][]string),
|
|
applyGlobal: func(*requestBuilder) {},
|
|
ipldDecoder: decoder,
|
|
}
|
|
|
|
// We don't support redirects.
|
|
api.httpcli.CheckRedirect = func(_ *http.Request, _ []*http.Request) error {
|
|
return fmt.Errorf("unexpected redirect")
|
|
}
|
|
|
|
return api, nil
|
|
}
|
|
|
|
func (api *HttpApi) WithOptions(opts ...caopts.ApiOption) (iface.CoreAPI, error) {
|
|
options, err := caopts.ApiOptions(opts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
subApi := &HttpApi{
|
|
url: api.url,
|
|
httpcli: api.httpcli,
|
|
Headers: api.Headers,
|
|
applyGlobal: func(req *requestBuilder) {
|
|
if options.Offline {
|
|
req.Option("offline", options.Offline)
|
|
}
|
|
},
|
|
ipldDecoder: api.ipldDecoder,
|
|
}
|
|
|
|
return subApi, nil
|
|
}
|
|
|
|
func (api *HttpApi) Request(command string, args ...string) RequestBuilder {
|
|
headers := make(map[string]string)
|
|
if api.Headers != nil {
|
|
for k := range api.Headers {
|
|
headers[k] = api.Headers.Get(k)
|
|
}
|
|
}
|
|
return &requestBuilder{
|
|
command: command,
|
|
args: args,
|
|
shell: api,
|
|
headers: headers,
|
|
}
|
|
}
|
|
|
|
func (api *HttpApi) Unixfs() iface.UnixfsAPI {
|
|
return (*UnixfsAPI)(api)
|
|
}
|
|
|
|
func (api *HttpApi) Block() iface.BlockAPI {
|
|
return (*BlockAPI)(api)
|
|
}
|
|
|
|
func (api *HttpApi) Dag() iface.APIDagService {
|
|
return (*HttpDagServ)(api)
|
|
}
|
|
|
|
func (api *HttpApi) Name() iface.NameAPI {
|
|
return (*NameAPI)(api)
|
|
}
|
|
|
|
func (api *HttpApi) Key() iface.KeyAPI {
|
|
return (*KeyAPI)(api)
|
|
}
|
|
|
|
func (api *HttpApi) Pin() iface.PinAPI {
|
|
return (*PinAPI)(api)
|
|
}
|
|
|
|
func (api *HttpApi) Object() iface.ObjectAPI {
|
|
return (*ObjectAPI)(api)
|
|
}
|
|
|
|
func (api *HttpApi) Swarm() iface.SwarmAPI {
|
|
return (*SwarmAPI)(api)
|
|
}
|
|
|
|
func (api *HttpApi) PubSub() iface.PubSubAPI {
|
|
return (*PubsubAPI)(api)
|
|
}
|
|
|
|
func (api *HttpApi) Routing() iface.RoutingAPI {
|
|
return (*RoutingAPI)(api)
|
|
}
|
|
|
|
func (api *HttpApi) loadRemoteVersion() (*semver.Version, error) {
|
|
api.versionMu.Lock()
|
|
defer api.versionMu.Unlock()
|
|
|
|
if api.version == nil {
|
|
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second*30))
|
|
defer cancel()
|
|
|
|
resp, err := api.Request("version").Send(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp.Error != nil {
|
|
return nil, resp.Error
|
|
}
|
|
defer resp.Close()
|
|
var out ipfs.VersionInfo
|
|
dec := json.NewDecoder(resp.Output)
|
|
if err := dec.Decode(&out); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
remoteVersion, err := semver.New(out.Version)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
api.version = remoteVersion
|
|
}
|
|
|
|
return api.version, nil
|
|
}
|