@cryptotaxi247 / kubo / commits / 6d85aff40

Initial structure, path stuff

This commit was moved from ipfs/go-ipfs-http-client@93943f7f5671948b0d2aee19c30cf689307faf13

Łukasz Magiera committed Nov 6, 2018 at 13:13 UTC 6d85aff407d3fb3eb6d2549fbac94615fd147b49
6 files changed +476
client/httpapi/api.go new
+127
@@ -0,0 +1,127 @@
1 +package httpapi
2 +
3 +import (
4 + "github.com/pkg/errors"
5 + "io/ioutil"
6 + gohttp "net/http"
7 + "os"
8 + "path"
9 + "strings"
10 +
11 + "github.com/ipfs/go-ipfs/core/coreapi/interface"
12 + homedir "github.com/mitchellh/go-homedir"
13 + ma "github.com/multiformats/go-multiaddr"
14 + manet "github.com/multiformats/go-multiaddr-net"
15 + )
16 +
17 +const (
18 + DefaultPathName = ".ipfs"
19 + DefaultPathRoot = "~/" + DefaultPathName
20 + DefaultApiFile = "api"
21 + EnvDir = "IPFS_PATH"
22 +)
23 +
24 +var ErrNotImplemented = errors.New("not implemented")
25 +
26 +type HttpApi struct {
27 + url string
28 + httpcli *gohttp.Client
29 +}
30 +
31 +func NewLocalApi() iface.CoreAPI {
32 + baseDir := os.Getenv(EnvDir)
33 + if baseDir == "" {
34 + baseDir = DefaultPathRoot
35 + }
36 +
37 + baseDir, err := homedir.Expand(baseDir)
38 + if err != nil {
39 + return nil
40 + }
41 +
42 + apiFile := path.Join(baseDir, DefaultApiFile)
43 +
44 + if _, err := os.Stat(apiFile); err != nil {
45 + return nil
46 + }
47 +
48 + api, err := ioutil.ReadFile(apiFile)
49 + if err != nil {
50 + return nil
51 + }
52 +
53 + return NewApi(strings.TrimSpace(string(api)))
54 +}
55 +
56 +func NewApi(url string) *HttpApi {
57 + c := &gohttp.Client{
58 + Transport: &gohttp.Transport{
59 + Proxy: gohttp.ProxyFromEnvironment,
60 + DisableKeepAlives: true,
61 + },
62 + }
63 +
64 + return NewApiWithClient(url, c)
65 +}
66 +
67 +func NewApiWithClient(url string, c *gohttp.Client) *HttpApi {
68 + if a, err := ma.NewMultiaddr(url); err == nil {
69 + _, host, err := manet.DialArgs(a)
70 + if err == nil {
71 + url = host
72 + }
73 + }
74 +
75 + return &HttpApi{
76 + url: url,
77 + httpcli: c,
78 + }
79 +}
80 +
81 +func (api *HttpApi) request(command string, args ...string) *RequestBuilder {
82 + return &RequestBuilder{
83 + command: command,
84 + args: args,
85 + shell: api,
86 + }
87 +}
88 +
89 +func (api *HttpApi) Unixfs() iface.UnixfsAPI {
90 + return nil
91 +}
92 +
93 +func (api *HttpApi) Block() iface.BlockAPI {
94 + return nil
95 +}
96 +
97 +func (api *HttpApi) Dag() iface.DagAPI {
98 + return nil
99 +}
100 +
101 +func (api *HttpApi) Name() iface.NameAPI {
102 + return (*NameAPI)(api)
103 +}
104 +
105 +func (api *HttpApi) Key() iface.KeyAPI {
106 + return nil
107 +}
108 +
109 +func (api *HttpApi) Pin() iface.PinAPI {
110 + return nil
111 +}
112 +
113 +func (api *HttpApi) Object() iface.ObjectAPI {
114 + return nil
115 +}
116 +
117 +func (api *HttpApi) Dht() iface.DhtAPI {
118 + return nil
119 +}
120 +
121 +func (api *HttpApi) Swarm() iface.SwarmAPI {
122 + return nil
123 +}
124 +
125 +func (api *HttpApi) PubSub() iface.PubSubAPI {
126 + return nil
127 +}
client/httpapi/name.go new
+35
@@ -0,0 +1,35 @@
1 +package httpapi
2 +
3 +import (
4 + "context"
5 + "github.com/ipfs/go-ipfs/core/coreapi/interface"
6 + "github.com/ipfs/go-ipfs/core/coreapi/interface/options"
7 +)
8 +
9 +type NameAPI HttpApi
10 +
11 +func (api *NameAPI) Publish(ctx context.Context, p iface.Path, opts ...options.NamePublishOption) (iface.IpnsEntry, error) {
12 + return nil, ErrNotImplemented
13 +}
14 +
15 +func (api *NameAPI) Search(ctx context.Context, name string, opts ...options.NameResolveOption) (<-chan iface.IpnsResult, error) {
16 + return nil, ErrNotImplemented
17 +}
18 +
19 +func (api *NameAPI) Resolve(ctx context.Context, name string, opts ...options.NameResolveOption) (iface.Path, error) {
20 + // TODO: options!
21 +
22 + req := api.core().request("name/resolve")
23 + req.Arguments(name)
24 +
25 + var out struct{ Path string }
26 + if err := req.Exec(ctx, &out); err != nil {
27 + return nil, err
28 + }
29 +
30 + return iface.ParsePath(out.Path)
31 +}
32 +
33 +func (api *NameAPI) core() *HttpApi {
34 + return (*HttpApi)(api)
35 +}
client/httpapi/path.go new
+48
@@ -0,0 +1,48 @@
1 +package httpapi
2 +
3 +import (
4 + "context"
5 +
6 + "github.com/ipfs/go-ipfs/core/coreapi/interface"
7 +
8 + cid "gx/ipfs/QmR8BauakNcBa3RbE4nbQu76PDiJgoQgz8AJdhJuiU4TAw/go-cid"
9 + ipfspath "gx/ipfs/QmRG3XuGwT7GYuAqgWDJBKTzdaHMwAnc1x7J2KHEXNHxzG/go-path"
10 + ipld "gx/ipfs/QmcKKBwfz6FyQdHR2jsXrrF6XeSBXYL86anmWNewpFpoF5/go-ipld-format"
11 +)
12 +
13 +func (api *HttpApi) ResolvePath(ctx context.Context, path iface.Path) (iface.ResolvedPath, error) {
14 + var out struct {
15 + Cid cid.Cid
16 + RemPath string
17 + }
18 +
19 + //TODO: this is hacky, fixing https://github.com/ipfs/go-ipfs/issues/5703 would help
20 +
21 + var err error
22 + if path.Namespace() == "ipns" {
23 + if path, err = api.Name().Resolve(ctx, path.String()); err != nil {
24 + return nil, err
25 + }
26 + }
27 +
28 + if err := api.request("dag/resolve", path.String()).Exec(ctx, &out); err != nil {
29 + return nil, err
30 + }
31 +
32 + // TODO:
33 + ipath, err := ipfspath.FromSegments("/" +path.Namespace() + "/", out.Cid.String(), out.RemPath)
34 + if err != nil {
35 + return nil, err
36 + }
37 +
38 + root, err := cid.Parse(ipfspath.Path(path.String()).Segments()[1])
39 + if err != nil {
40 + return nil, err
41 + }
42 +
43 + return iface.NewResolvedPath(ipath, out.Cid, root, out.RemPath), nil
44 +}
45 +
46 +func (api *HttpApi) ResolveNode(context.Context, iface.Path) (ipld.Node, error) {
47 + return nil, ErrNotImplemented
48 +}
client/httpapi/request.go new
+34
@@ -0,0 +1,34 @@
1 +package httpapi
2 +
3 +import (
4 + "context"
5 + "io"
6 + "strings"
7 +)
8 +
9 +type Request struct {
10 + ApiBase string
11 + Command string
12 + Args []string
13 + Opts map[string]string
14 + Body io.Reader
15 + Headers map[string]string
16 +}
17 +
18 +func NewRequest(ctx context.Context, url, command string, args ...string) *Request {
19 + if !strings.HasPrefix(url, "http") {
20 + url = "http://" + url
21 + }
22 +
23 + opts := map[string]string{
24 + "encoding": "json",
25 + "stream-channels": "true",
26 + }
27 + return &Request{
28 + ApiBase: url + "/api/v0",
29 + Command: command,
30 + Args: args,
31 + Opts: opts,
32 + Headers: make(map[string]string),
33 + }
34 +}
client/httpapi/requestbuilder.go new
+100
@@ -0,0 +1,100 @@
1 +package httpapi
2 +
3 +import (
4 + "bytes"
5 + "context"
6 + "fmt"
7 + "io"
8 + "strconv"
9 + "strings"
10 +)
11 +
12 +// RequestBuilder is an IPFS commands request builder.
13 +type RequestBuilder struct {
14 + command string
15 + args []string
16 + opts map[string]string
17 + headers map[string]string
18 + body io.Reader
19 +
20 + shell *HttpApi
21 +}
22 +
23 +// Arguments adds the arguments to the args.
24 +func (r *RequestBuilder) Arguments(args ...string) *RequestBuilder {
25 + r.args = append(r.args, args...)
26 + return r
27 +}
28 +
29 +// BodyString sets the request body to the given string.
30 +func (r *RequestBuilder) BodyString(body string) *RequestBuilder {
31 + return r.Body(strings.NewReader(body))
32 +}
33 +
34 +// BodyBytes sets the request body to the given buffer.
35 +func (r *RequestBuilder) BodyBytes(body []byte) *RequestBuilder {
36 + return r.Body(bytes.NewReader(body))
37 +}
38 +
39 +// Body sets the request body to the given reader.
40 +func (r *RequestBuilder) Body(body io.Reader) *RequestBuilder {
41 + r.body = body
42 + return r
43 +}
44 +
45 +// Option sets the given option.
46 +func (r *RequestBuilder) Option(key string, value interface{}) *RequestBuilder {
47 + var s string
48 + switch v := value.(type) {
49 + case bool:
50 + s = strconv.FormatBool(v)
51 + case string:
52 + s = v
53 + case []byte:
54 + s = string(v)
55 + default:
56 + // slow case.
57 + s = fmt.Sprint(value)
58 + }
59 + if r.opts == nil {
60 + r.opts = make(map[string]string, 1)
61 + }
62 + r.opts[key] = s
63 + return r
64 +}
65 +
66 +// Header sets the given header.
67 +func (r *RequestBuilder) Header(name, value string) *RequestBuilder {
68 + if r.headers == nil {
69 + r.headers = make(map[string]string, 1)
70 + }
71 + r.headers[name] = value
72 + return r
73 +}
74 +
75 +// Send sends the request and return the response.
76 +func (r *RequestBuilder) Send(ctx context.Context) (*Response, error) {
77 + req := NewRequest(ctx, r.shell.url, r.command, r.args...)
78 + req.Opts = r.opts
79 + req.Headers = r.headers
80 + req.Body = r.body
81 + return req.Send(r.shell.httpcli)
82 +}
83 +
84 +// Exec sends the request a request and decodes the response.
85 +func (r *RequestBuilder) Exec(ctx context.Context, res interface{}) error {
86 + httpRes, err := r.Send(ctx)
87 + if err != nil {
88 + return err
89 + }
90 +
91 + if res == nil {
92 + httpRes.Close()
93 + if httpRes.Error != nil {
94 + return httpRes.Error
95 + }
96 + return nil
97 + }
98 +
99 + return httpRes.Decode(res)
100 +}
client/httpapi/response.go new
+132
@@ -0,0 +1,132 @@
1 +package httpapi
2 +
3 +import (
4 + "encoding/json"
5 + "fmt"
6 + "io"
7 + "io/ioutil"
8 + "net/http"
9 + "net/url"
10 + "os"
11 + "strings"
12 +
13 + files "github.com/ipfs/go-ipfs-files"
14 +)
15 +
16 +type Response struct {
17 + Output io.ReadCloser
18 + Error *Error
19 +}
20 +
21 +func (r *Response) Close() error {
22 + if r.Output != nil {
23 + // always drain output (response body)
24 + ioutil.ReadAll(r.Output)
25 + return r.Output.Close()
26 + }
27 + return nil
28 +}
29 +
30 +func (r *Response) Decode(dec interface{}) error {
31 + defer r.Close()
32 + if r.Error != nil {
33 + return r.Error
34 + }
35 +
36 + return json.NewDecoder(r.Output).Decode(dec)
37 +}
38 +
39 +type Error struct {
40 + Command string
41 + Message string
42 + Code int
43 +}
44 +
45 +func (e *Error) Error() string {
46 + var out string
47 + if e.Command != "" {
48 + out = e.Command + ": "
49 + }
50 + if e.Code != 0 {
51 + out = fmt.Sprintf("%s%d: ", out, e.Code)
52 + }
53 + return out + e.Message
54 +}
55 +
56 +func (r *Request) Send(c *http.Client) (*Response, error) {
57 + url := r.getURL()
58 + req, err := http.NewRequest("POST", url, r.Body)
59 + if err != nil {
60 + return nil, err
61 + }
62 +
63 + // Add any headers that were supplied via the RequestBuilder.
64 + for k, v := range r.Headers {
65 + req.Header.Add(k, v)
66 + }
67 +
68 + if fr, ok := r.Body.(*files.MultiFileReader); ok {
69 + req.Header.Set("Content-Type", "multipart/form-data; boundary="+fr.Boundary())
70 + req.Header.Set("Content-Disposition", "form-data: name=\"files\"")
71 + }
72 +
73 + resp, err := c.Do(req)
74 + if err != nil {
75 + return nil, err
76 + }
77 +
78 + contentType := resp.Header.Get("Content-Type")
79 + parts := strings.Split(contentType, ";")
80 + contentType = parts[0]
81 +
82 + nresp := new(Response)
83 +
84 + nresp.Output = resp.Body
85 + if resp.StatusCode >= http.StatusBadRequest {
86 + e := &Error{
87 + Command: r.Command,
88 + }
89 + switch {
90 + case resp.StatusCode == http.StatusNotFound:
91 + e.Message = "command not found"
92 + case contentType == "text/plain":
93 + out, err := ioutil.ReadAll(resp.Body)
94 + if err != nil {
95 + fmt.Fprintf(os.Stderr, "ipfs-shell: warning! response (%d) read error: %s\n", resp.StatusCode, err)
96 + }
97 + e.Message = string(out)
98 + case contentType == "application/json":
99 + if err = json.NewDecoder(resp.Body).Decode(e); err != nil {
100 + fmt.Fprintf(os.Stderr, "ipfs-shell: warning! response (%d) unmarshall error: %s\n", resp.StatusCode, err)
101 + }
102 + default:
103 + fmt.Fprintf(os.Stderr, "ipfs-shell: warning! unhandled response (%d) encoding: %s", resp.StatusCode, contentType)
104 + out, err := ioutil.ReadAll(resp.Body)
105 + if err != nil {
106 + fmt.Fprintf(os.Stderr, "ipfs-shell: response (%d) read error: %s\n", resp.StatusCode, err)
107 + }
108 + e.Message = fmt.Sprintf("unknown ipfs-shell error encoding: %q - %q", contentType, out)
109 + }
110 + nresp.Error = e
111 + nresp.Output = nil
112 +
113 + // drain body and close
114 + ioutil.ReadAll(resp.Body)
115 + resp.Body.Close()
116 + }
117 +
118 + return nresp, nil
119 +}
120 +
121 +func (r *Request) getURL() string {
122 +
123 + values := make(url.Values)
124 + for _, arg := range r.Args {
125 + values.Add("arg", arg)
126 + }
127 + for k, v := range r.Opts {
128 + values.Add(k, v)
129 + }
130 +
131 + return fmt.Sprintf("%s/%s?%s", r.ApiBase, r.Command, values.Encode())
132 +}