mirror of
https://github.com/ipfs/kubo.git
synced 2026-03-06 08:47:52 +08:00
Initial structure, path stuff
This commit was moved from ipfs/go-ipfs-http-client@93943f7f56
This commit is contained in:
parent
202081523f
commit
6d85aff407
127
client/httpapi/api.go
Normal file
127
client/httpapi/api.go
Normal file
@ -0,0 +1,127 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"io/ioutil"
|
||||
gohttp "net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/ipfs/go-ipfs/core/coreapi/interface"
|
||||
homedir "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"
|
||||
)
|
||||
|
||||
var ErrNotImplemented = errors.New("not implemented")
|
||||
|
||||
type HttpApi struct {
|
||||
url string
|
||||
httpcli *gohttp.Client
|
||||
}
|
||||
|
||||
func NewLocalApi() iface.CoreAPI {
|
||||
baseDir := os.Getenv(EnvDir)
|
||||
if baseDir == "" {
|
||||
baseDir = DefaultPathRoot
|
||||
}
|
||||
|
||||
baseDir, err := homedir.Expand(baseDir)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
apiFile := path.Join(baseDir, DefaultApiFile)
|
||||
|
||||
if _, err := os.Stat(apiFile); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
api, err := ioutil.ReadFile(apiFile)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return NewApi(strings.TrimSpace(string(api)))
|
||||
}
|
||||
|
||||
func NewApi(url string) *HttpApi {
|
||||
c := &gohttp.Client{
|
||||
Transport: &gohttp.Transport{
|
||||
Proxy: gohttp.ProxyFromEnvironment,
|
||||
DisableKeepAlives: true,
|
||||
},
|
||||
}
|
||||
|
||||
return NewApiWithClient(url, c)
|
||||
}
|
||||
|
||||
func NewApiWithClient(url string, c *gohttp.Client) *HttpApi {
|
||||
if a, err := ma.NewMultiaddr(url); err == nil {
|
||||
_, host, err := manet.DialArgs(a)
|
||||
if err == nil {
|
||||
url = host
|
||||
}
|
||||
}
|
||||
|
||||
return &HttpApi{
|
||||
url: url,
|
||||
httpcli: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (api *HttpApi) request(command string, args ...string) *RequestBuilder {
|
||||
return &RequestBuilder{
|
||||
command: command,
|
||||
args: args,
|
||||
shell: api,
|
||||
}
|
||||
}
|
||||
|
||||
func (api *HttpApi) Unixfs() iface.UnixfsAPI {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *HttpApi) Block() iface.BlockAPI {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *HttpApi) Dag() iface.DagAPI {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *HttpApi) Name() iface.NameAPI {
|
||||
return (*NameAPI)(api)
|
||||
}
|
||||
|
||||
func (api *HttpApi) Key() iface.KeyAPI {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *HttpApi) Pin() iface.PinAPI {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *HttpApi) Object() iface.ObjectAPI {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *HttpApi) Dht() iface.DhtAPI {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *HttpApi) Swarm() iface.SwarmAPI {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *HttpApi) PubSub() iface.PubSubAPI {
|
||||
return nil
|
||||
}
|
||||
35
client/httpapi/name.go
Normal file
35
client/httpapi/name.go
Normal file
@ -0,0 +1,35 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/ipfs/go-ipfs/core/coreapi/interface"
|
||||
"github.com/ipfs/go-ipfs/core/coreapi/interface/options"
|
||||
)
|
||||
|
||||
type NameAPI HttpApi
|
||||
|
||||
func (api *NameAPI) Publish(ctx context.Context, p iface.Path, opts ...options.NamePublishOption) (iface.IpnsEntry, error) {
|
||||
return nil, ErrNotImplemented
|
||||
}
|
||||
|
||||
func (api *NameAPI) Search(ctx context.Context, name string, opts ...options.NameResolveOption) (<-chan iface.IpnsResult, error) {
|
||||
return nil, ErrNotImplemented
|
||||
}
|
||||
|
||||
func (api *NameAPI) Resolve(ctx context.Context, name string, opts ...options.NameResolveOption) (iface.Path, error) {
|
||||
// TODO: options!
|
||||
|
||||
req := api.core().request("name/resolve")
|
||||
req.Arguments(name)
|
||||
|
||||
var out struct{ Path string }
|
||||
if err := req.Exec(ctx, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return iface.ParsePath(out.Path)
|
||||
}
|
||||
|
||||
func (api *NameAPI) core() *HttpApi {
|
||||
return (*HttpApi)(api)
|
||||
}
|
||||
48
client/httpapi/path.go
Normal file
48
client/httpapi/path.go
Normal file
@ -0,0 +1,48 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ipfs/go-ipfs/core/coreapi/interface"
|
||||
|
||||
cid "gx/ipfs/QmR8BauakNcBa3RbE4nbQu76PDiJgoQgz8AJdhJuiU4TAw/go-cid"
|
||||
ipfspath "gx/ipfs/QmRG3XuGwT7GYuAqgWDJBKTzdaHMwAnc1x7J2KHEXNHxzG/go-path"
|
||||
ipld "gx/ipfs/QmcKKBwfz6FyQdHR2jsXrrF6XeSBXYL86anmWNewpFpoF5/go-ipld-format"
|
||||
)
|
||||
|
||||
func (api *HttpApi) ResolvePath(ctx context.Context, path iface.Path) (iface.ResolvedPath, error) {
|
||||
var out struct {
|
||||
Cid cid.Cid
|
||||
RemPath string
|
||||
}
|
||||
|
||||
//TODO: this is hacky, fixing https://github.com/ipfs/go-ipfs/issues/5703 would help
|
||||
|
||||
var err error
|
||||
if path.Namespace() == "ipns" {
|
||||
if path, err = api.Name().Resolve(ctx, path.String()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := api.request("dag/resolve", path.String()).Exec(ctx, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO:
|
||||
ipath, err := ipfspath.FromSegments("/" +path.Namespace() + "/", out.Cid.String(), out.RemPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
root, err := cid.Parse(ipfspath.Path(path.String()).Segments()[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return iface.NewResolvedPath(ipath, out.Cid, root, out.RemPath), nil
|
||||
}
|
||||
|
||||
func (api *HttpApi) ResolveNode(context.Context, iface.Path) (ipld.Node, error) {
|
||||
return nil, ErrNotImplemented
|
||||
}
|
||||
34
client/httpapi/request.go
Normal file
34
client/httpapi/request.go
Normal file
@ -0,0 +1,34 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
ApiBase string
|
||||
Command string
|
||||
Args []string
|
||||
Opts map[string]string
|
||||
Body io.Reader
|
||||
Headers map[string]string
|
||||
}
|
||||
|
||||
func NewRequest(ctx context.Context, url, command string, args ...string) *Request {
|
||||
if !strings.HasPrefix(url, "http") {
|
||||
url = "http://" + url
|
||||
}
|
||||
|
||||
opts := map[string]string{
|
||||
"encoding": "json",
|
||||
"stream-channels": "true",
|
||||
}
|
||||
return &Request{
|
||||
ApiBase: url + "/api/v0",
|
||||
Command: command,
|
||||
Args: args,
|
||||
Opts: opts,
|
||||
Headers: make(map[string]string),
|
||||
}
|
||||
}
|
||||
100
client/httpapi/requestbuilder.go
Normal file
100
client/httpapi/requestbuilder.go
Normal file
@ -0,0 +1,100 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RequestBuilder is an IPFS commands request builder.
|
||||
type RequestBuilder struct {
|
||||
command string
|
||||
args []string
|
||||
opts map[string]string
|
||||
headers map[string]string
|
||||
body io.Reader
|
||||
|
||||
shell *HttpApi
|
||||
}
|
||||
|
||||
// Arguments adds the arguments to the args.
|
||||
func (r *RequestBuilder) Arguments(args ...string) *RequestBuilder {
|
||||
r.args = append(r.args, args...)
|
||||
return r
|
||||
}
|
||||
|
||||
// BodyString sets the request body to the given string.
|
||||
func (r *RequestBuilder) BodyString(body string) *RequestBuilder {
|
||||
return r.Body(strings.NewReader(body))
|
||||
}
|
||||
|
||||
// BodyBytes sets the request body to the given buffer.
|
||||
func (r *RequestBuilder) BodyBytes(body []byte) *RequestBuilder {
|
||||
return r.Body(bytes.NewReader(body))
|
||||
}
|
||||
|
||||
// Body sets the request body to the given reader.
|
||||
func (r *RequestBuilder) Body(body io.Reader) *RequestBuilder {
|
||||
r.body = body
|
||||
return r
|
||||
}
|
||||
|
||||
// Option sets the given option.
|
||||
func (r *RequestBuilder) Option(key string, value interface{}) *RequestBuilder {
|
||||
var s string
|
||||
switch v := value.(type) {
|
||||
case bool:
|
||||
s = strconv.FormatBool(v)
|
||||
case string:
|
||||
s = v
|
||||
case []byte:
|
||||
s = string(v)
|
||||
default:
|
||||
// slow case.
|
||||
s = fmt.Sprint(value)
|
||||
}
|
||||
if r.opts == nil {
|
||||
r.opts = make(map[string]string, 1)
|
||||
}
|
||||
r.opts[key] = s
|
||||
return r
|
||||
}
|
||||
|
||||
// Header sets the given header.
|
||||
func (r *RequestBuilder) Header(name, value string) *RequestBuilder {
|
||||
if r.headers == nil {
|
||||
r.headers = make(map[string]string, 1)
|
||||
}
|
||||
r.headers[name] = value
|
||||
return r
|
||||
}
|
||||
|
||||
// Send sends the request and return the response.
|
||||
func (r *RequestBuilder) Send(ctx context.Context) (*Response, error) {
|
||||
req := NewRequest(ctx, r.shell.url, r.command, r.args...)
|
||||
req.Opts = r.opts
|
||||
req.Headers = r.headers
|
||||
req.Body = r.body
|
||||
return req.Send(r.shell.httpcli)
|
||||
}
|
||||
|
||||
// Exec sends the request a request and decodes the response.
|
||||
func (r *RequestBuilder) Exec(ctx context.Context, res interface{}) error {
|
||||
httpRes, err := r.Send(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if res == nil {
|
||||
httpRes.Close()
|
||||
if httpRes.Error != nil {
|
||||
return httpRes.Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return httpRes.Decode(res)
|
||||
}
|
||||
132
client/httpapi/response.go
Normal file
132
client/httpapi/response.go
Normal file
@ -0,0 +1,132 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
files "github.com/ipfs/go-ipfs-files"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Output io.ReadCloser
|
||||
Error *Error
|
||||
}
|
||||
|
||||
func (r *Response) Close() error {
|
||||
if r.Output != nil {
|
||||
// always drain output (response body)
|
||||
ioutil.ReadAll(r.Output)
|
||||
return r.Output.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Response) Decode(dec interface{}) error {
|
||||
defer r.Close()
|
||||
if r.Error != nil {
|
||||
return r.Error
|
||||
}
|
||||
|
||||
return json.NewDecoder(r.Output).Decode(dec)
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
Command string
|
||||
Message string
|
||||
Code int
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
var out string
|
||||
if e.Command != "" {
|
||||
out = e.Command + ": "
|
||||
}
|
||||
if e.Code != 0 {
|
||||
out = fmt.Sprintf("%s%d: ", out, e.Code)
|
||||
}
|
||||
return out + e.Message
|
||||
}
|
||||
|
||||
func (r *Request) Send(c *http.Client) (*Response, error) {
|
||||
url := r.getURL()
|
||||
req, err := http.NewRequest("POST", url, r.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add any headers that were supplied via the RequestBuilder.
|
||||
for k, v := range r.Headers {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
|
||||
if fr, ok := r.Body.(*files.MultiFileReader); ok {
|
||||
req.Header.Set("Content-Type", "multipart/form-data; boundary="+fr.Boundary())
|
||||
req.Header.Set("Content-Disposition", "form-data: name=\"files\"")
|
||||
}
|
||||
|
||||
resp, err := c.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
parts := strings.Split(contentType, ";")
|
||||
contentType = parts[0]
|
||||
|
||||
nresp := new(Response)
|
||||
|
||||
nresp.Output = resp.Body
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
e := &Error{
|
||||
Command: r.Command,
|
||||
}
|
||||
switch {
|
||||
case resp.StatusCode == http.StatusNotFound:
|
||||
e.Message = "command not found"
|
||||
case contentType == "text/plain":
|
||||
out, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ipfs-shell: warning! response (%d) read error: %s\n", resp.StatusCode, err)
|
||||
}
|
||||
e.Message = string(out)
|
||||
case contentType == "application/json":
|
||||
if err = json.NewDecoder(resp.Body).Decode(e); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ipfs-shell: warning! response (%d) unmarshall error: %s\n", resp.StatusCode, err)
|
||||
}
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "ipfs-shell: warning! unhandled response (%d) encoding: %s", resp.StatusCode, contentType)
|
||||
out, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ipfs-shell: response (%d) read error: %s\n", resp.StatusCode, err)
|
||||
}
|
||||
e.Message = fmt.Sprintf("unknown ipfs-shell error encoding: %q - %q", contentType, out)
|
||||
}
|
||||
nresp.Error = e
|
||||
nresp.Output = nil
|
||||
|
||||
// drain body and close
|
||||
ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
}
|
||||
|
||||
return nresp, nil
|
||||
}
|
||||
|
||||
func (r *Request) getURL() string {
|
||||
|
||||
values := make(url.Values)
|
||||
for _, arg := range r.Args {
|
||||
values.Add("arg", arg)
|
||||
}
|
||||
for k, v := range r.Opts {
|
||||
values.Add(k, v)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s?%s", r.ApiBase, r.Command, values.Encode())
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user