Skip to content

Commit

Permalink
client/rpc: migrate go-ipfs-http-client
Browse files Browse the repository at this point in the history
  • Loading branch information
Jorropo committed May 30, 2023
2 parents c10b804 + 7d82623 commit 86d8e75
Show file tree
Hide file tree
Showing 31 changed files with 3,195 additions and 51 deletions.
33 changes: 0 additions & 33 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,39 +127,6 @@ jobs:
working-directory: go-ipfs-api
- run: cmd/ipfs/ipfs shutdown
if: always()
go-ipfs-http-client:
needs: [interop-prep]
runs-on: ubuntu-latest
timeout-minutes: 5
env:
TEST_DOCKER: 0
TEST_FUSE: 0
TEST_VERBOSE: 1
TRAVIS: 1
GIT_PAGER: cat
IPFS_CHECK_RCMGR_DEFAULTS: 1
defaults:
run:
shell: bash
steps:
- uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/download-artifact@v3
with:
name: kubo
path: cmd/ipfs
- run: chmod +x cmd/ipfs/ipfs
- uses: actions/checkout@v3
with:
repository: ipfs/go-ipfs-http-client
path: go-ipfs-http-client
- uses: protocol/cache-go-action@v1
with:
name: ${{ github.job }}
- run: echo '${{ github.workspace }}/cmd/ipfs' >> $GITHUB_PATH
- run: go test -count=1 -v ./...
working-directory: go-ipfs-http-client
ipfs-webui:
needs: [interop-prep]
runs-on: ${{ fromJSON(github.repository == 'ipfs/kubo' && '["self-hosted", "linux", "x64", "2xlarge"]' || '"ubuntu-latest"') }}
Expand Down
3 changes: 3 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ linters:

linters-settings:
stylecheck:
checks:
- all
- '-ST1003'
dot-import-whitelist:
- github.com/ipfs/kubo/test/cli/testutils
44 changes: 44 additions & 0 deletions client/rpc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# `httpapi`

> IPFS CoreAPI implementation using HTTP API
This packages implements [`coreiface.CoreAPI`](https://pkg.go.dev/github.com/ipfs/boxo/coreiface#CoreAPI) over the HTTP API.

## Documentation

https://pkg.go.dev/github.com/ipfs/kubo/client/rpc

### Example

Pin file on your local IPFS node based on its CID:

```go
package main

import (
"context"
"fmt"

ipfsClient "github.com/ipfs/kubo/client/rpc"
path "github.com/ipfs/boxo/coreiface/path"
)

func main() {
// "Connect" to local node
node, err := ipfsClient.NewLocalApi()
if err != nil {
fmt.Printf(err)
return
}
// Pin a given file by its CID
ctx := context.Background()
cid := "bafkreidtuosuw37f5xmn65b3ksdiikajy7pwjjslzj2lxxz2vc4wdy3zku"
p := path.New(cid)
err = node.Pin().Add(ctx, p)
if err != nil {
fmt.Printf(err)
return
}
return
}
```
215 changes: 215 additions & 0 deletions client/rpc/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package httpapi

import (
"errors"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"

iface "github.com/ipfs/boxo/coreiface"
caopts "github.com/ipfs/boxo/coreiface/options"
"github.com/mitchellh/go-homedir"
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)
}

// 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 := homedir.Expand(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) {
c := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DisableKeepAlives: true,
},
}

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) {
api := &HttpApi{
url: url,
httpcli: *c,
Headers: make(map[string][]string),
applyGlobal: func(*requestBuilder) {},
}

// 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 := *api
subApi.applyGlobal = func(req *requestBuilder) {
if options.Offline {
req.Option("offline", options.Offline)
}
}

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) Dht() iface.DhtAPI {
return (*DhtAPI)(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)
}
Loading

0 comments on commit 86d8e75

Please sign in to comment.