From d04afa02b059d2c6a631f23a6923eb57ecb21015 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 9 Apr 2019 19:53:07 -0700 Subject: [PATCH 1/8] api: add authenticated transport, and direct connection --- api.go | 36 ++++++++++++++++++++++++++++++++++++ api_test.go | 25 +++++++++++++++++++++++-- transport.go | 21 +++++++++++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 transport.go diff --git a/api.go b/api.go index dad205f..c0eacd7 100644 --- a/api.go +++ b/api.go @@ -122,6 +122,42 @@ func NewApiWithClient(a ma.Multiaddr, c *gohttp.Client) (*HttpApi, error) { return api, nil } +// NewDirectAPIClient is used to instantiate a HttpApi client +// that connects to an endpoint which leverages additional http paths. +// +// If you need to connect to a IPFS HTTP API located at https://foo.bar/baz/api/v0 +// you should use NewDirectAPIClient. +func NewDirectAPIClient(url string) (*HttpApi, error) { + api := &HttpApi{ + url: url, + httpcli: gohttp.Client{ + Transport: &gohttp.Transport{ + Proxy: gohttp.ProxyFromEnvironment, + DisableKeepAlives: true, + }, + }, + applyGlobal: func(*RequestBuilder) {}, + } + + // We don't support redirects. + api.httpcli.CheckRedirect = func(_ *gohttp.Request, _ []*gohttp.Request) error { + return fmt.Errorf("unexpected redirect") + } + + return api, nil +} + +// WithAuthorization is used to wrap an instance of HttpApi +// with an authenticated transport, such as JWT +func (api *HttpApi) WithAuthorization(header, value string) *HttpApi { + return &HttpApi{ + url: api.url, + httpcli: gohttp.Client{ + Transport: newAuthenticatedTransport(api.httpcli.Transport, header, value), + }, + } +} + func (api *HttpApi) WithOptions(opts ...caopts.ApiOption) (iface.CoreAPI, error) { options, err := caopts.ApiOptions(opts...) if err != nil { diff --git a/api_test.go b/api_test.go index df45c15..f1b1bf2 100644 --- a/api_test.go +++ b/api_test.go @@ -9,11 +9,11 @@ import ( "sync" "testing" - "github.com/ipfs/interface-go-ipfs-core" + iface "github.com/ipfs/interface-go-ipfs-core" "github.com/ipfs/interface-go-ipfs-core/tests" local "github.com/ipfs/iptb-plugins/local" "github.com/ipfs/iptb/testbed" - "github.com/ipfs/iptb/testbed/interfaces" + testbedi "github.com/ipfs/iptb/testbed/interfaces" ma "github.com/multiformats/go-multiaddr" ) @@ -211,3 +211,24 @@ func TestHttpApi(t *testing.T) { tests.TestApi(newNodeProvider(ctx))(t) } + +func TestDirectAPI(t *testing.T) { + type args struct { + url, header, value string + } + tests := []struct { + name string + args args + }{ + {"Success", args{"http://127.0.0.1:5001/foo/bar/api/v0", "Authorization", "Bearer token"}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + api, err := NewDirectAPIClient(tt.args.url) + if err != nil { + t.Fatal(err) + } + api = api.WithAuthorization(tt.args.header, tt.args.value) + }) + } +} diff --git a/transport.go b/transport.go new file mode 100644 index 0000000..3fb0e6e --- /dev/null +++ b/transport.go @@ -0,0 +1,21 @@ +package httpapi + +import "net/http" + +type transport struct { + header, value string + httptr http.RoundTripper +} + +func newAuthenticatedTransport(tr http.RoundTripper, header, value string) *transport { + return &transport{ + header: header, + value: value, + httptr: tr, + } +} + +func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) { + req.Header.Set(t.header, t.value) + return t.httptr.RoundTrip(req) +} From 033befdef3f06a4a80fb9cf7c3afecc1a383700c Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 1 May 2019 13:13:59 -0700 Subject: [PATCH 2/8] api: expose headers via HttpApi, copy headers during request build --- api.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/api.go b/api.go index c0eacd7..712801d 100644 --- a/api.go +++ b/api.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io/ioutil" + "net/http" gohttp "net/http" "os" "path" @@ -32,9 +33,9 @@ var ErrApiNotFound = errors.New("ipfs api address could not be found") // For interface docs see // https://godoc.org/github.com/ipfs/interface-go-ipfs-core#CoreAPI type HttpApi struct { - url string - httpcli gohttp.Client - + url string + httpcli gohttp.Client + Headers http.Header applyGlobal func(*RequestBuilder) } @@ -175,10 +176,17 @@ func (api *HttpApi) WithOptions(opts ...caopts.ApiOption) (iface.CoreAPI, error) } func (api *HttpApi) Request(command string, args ...string) *RequestBuilder { + var headers 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, } } From 9c7d495b03db4d130a8fd87217093beb33e6fdb7 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 1 May 2019 13:41:15 -0700 Subject: [PATCH 3/8] api: remove WithAuthorization, DirectAPI client, instantiate header map --- api.go | 32 ++++++-------------------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/api.go b/api.go index 712801d..ffcc85f 100644 --- a/api.go +++ b/api.go @@ -112,6 +112,7 @@ func NewApiWithClient(a ma.Multiaddr, c *gohttp.Client) (*HttpApi, error) { api := &HttpApi{ url: url, httpcli: *c, + Headers: make(map[string][]string), applyGlobal: func(*RequestBuilder) {}, } @@ -123,20 +124,11 @@ func NewApiWithClient(a ma.Multiaddr, c *gohttp.Client) (*HttpApi, error) { return api, nil } -// NewDirectAPIClient is used to instantiate a HttpApi client -// that connects to an endpoint which leverages additional http paths. -// -// If you need to connect to a IPFS HTTP API located at https://foo.bar/baz/api/v0 -// you should use NewDirectAPIClient. -func NewDirectAPIClient(url string) (*HttpApi, error) { +func NewURLApiWithClient(url string, c *gohttp.Client) (*HttpApi, error) { api := &HttpApi{ - url: url, - httpcli: gohttp.Client{ - Transport: &gohttp.Transport{ - Proxy: gohttp.ProxyFromEnvironment, - DisableKeepAlives: true, - }, - }, + url: url, + httpcli: *c, + Headers: make(map[string][]string), applyGlobal: func(*RequestBuilder) {}, } @@ -144,21 +136,9 @@ func NewDirectAPIClient(url string) (*HttpApi, error) { api.httpcli.CheckRedirect = func(_ *gohttp.Request, _ []*gohttp.Request) error { return fmt.Errorf("unexpected redirect") } - return api, nil } -// WithAuthorization is used to wrap an instance of HttpApi -// with an authenticated transport, such as JWT -func (api *HttpApi) WithAuthorization(header, value string) *HttpApi { - return &HttpApi{ - url: api.url, - httpcli: gohttp.Client{ - Transport: newAuthenticatedTransport(api.httpcli.Transport, header, value), - }, - } -} - func (api *HttpApi) WithOptions(opts ...caopts.ApiOption) (iface.CoreAPI, error) { options, err := caopts.ApiOptions(opts...) if err != nil { @@ -176,7 +156,7 @@ func (api *HttpApi) WithOptions(opts ...caopts.ApiOption) (iface.CoreAPI, error) } func (api *HttpApi) Request(command string, args ...string) *RequestBuilder { - var headers map[string]string + headers := make(map[string]string) if api.Headers != nil { for k := range api.Headers { headers[k] = api.Headers.Get(k) From 2b48dd36ad170149bd4c62a4194587bf7c704f7e Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 1 May 2019 13:41:37 -0700 Subject: [PATCH 4/8] api: add wip api test --- api_test.go | 62 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/api_test.go b/api_test.go index f1b1bf2..7b519ce 100644 --- a/api_test.go +++ b/api_test.go @@ -3,11 +3,13 @@ package httpapi import ( "context" "io/ioutil" + "net/http" gohttp "net/http" "os" "strconv" "sync" "testing" + "time" iface "github.com/ipfs/interface-go-ipfs-core" "github.com/ipfs/interface-go-ipfs-core/tests" @@ -212,23 +214,51 @@ func TestHttpApi(t *testing.T) { tests.TestApi(newNodeProvider(ctx))(t) } -func TestDirectAPI(t *testing.T) { - type args struct { - url, header, value string +func Test_NewURLApiWithClient(t *testing.T) { + t.Skip() + var ( + url = "127.0.0.1:65501" + headerToTest = "Test-Header" + expectedHeaderValue = "thisisaheadertest" + ) + server, err := testHTTPServer(url, headerToTest, expectedHeaderValue) + if err != nil { + t.Fatal(err) } - tests := []struct { - name string - args args - }{ - {"Success", args{"http://127.0.0.1:5001/foo/bar/api/v0", "Authorization", "Bearer token"}}, + defer server.Close() + go func() { + server.ListenAndServe() + }() + time.Sleep(time.Second * 2) + api, err := NewURLApiWithClient(url, &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DisableKeepAlives: true, + }, + }) + if err != nil { + t.Fatal(err) } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - api, err := NewDirectAPIClient(tt.args.url) - if err != nil { - t.Fatal(err) - } - api = api.WithAuthorization(tt.args.header, tt.args.value) - }) + api.Headers.Set(headerToTest, expectedHeaderValue) + if _, err := api.Pin().Ls(context.Background()); err != nil { + t.Fatal(err) } } + +/// testHTTPServer spins up a test go http server +// used to check headers +func testHTTPServer(url, headerToTest, expectedHeaderValue string) (*http.Server, error) { + r := http.NewServeMux() + r.HandleFunc("/api/v0/pin/ls", func(w http.ResponseWriter, r *http.Request) { + val := r.Header.Get(headerToTest) + if val == expectedHeaderValue { + w.WriteHeader(400) + return + } + w.WriteHeader(200) + }) + return &http.Server{ + Handler: r, + Addr: url, + }, nil +} From aea6890f36001f15d24a4b373979bb1b9bf81483 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 1 May 2019 16:00:48 -0700 Subject: [PATCH 5/8] api: call NewURLApiWithClient from NewApiWithClient --- api.go | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/api.go b/api.go index ffcc85f..eb99230 100644 --- a/api.go +++ b/api.go @@ -109,19 +109,7 @@ func NewApiWithClient(a ma.Multiaddr, c *gohttp.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(_ *gohttp.Request, _ []*gohttp.Request) error { - return fmt.Errorf("unexpected redirect") - } - - return api, nil + return NewURLApiWithClient(url, c) } func NewURLApiWithClient(url string, c *gohttp.Client) (*HttpApi, error) { From b8b55cb89a5dd830915f83e43e8afa7a01597905 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 1 May 2019 16:13:47 -0700 Subject: [PATCH 6/8] api: cleanup header and NewURLApiWithClient test --- api_test.go | 46 +++++++++++++++------------------------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/api_test.go b/api_test.go index 52fa67d..fadd966 100644 --- a/api_test.go +++ b/api_test.go @@ -5,13 +5,14 @@ import ( "io/ioutil" "net/http" gohttp "net/http" + "net/http/httptest" "os" "strconv" "sync" "testing" "time" - "github.com/ipfs/interface-go-ipfs-core" + iface "github.com/ipfs/interface-go-ipfs-core" "github.com/ipfs/interface-go-ipfs-core/path" "github.com/ipfs/interface-go-ipfs-core/tests" @@ -212,23 +213,24 @@ func TestHttpApi(t *testing.T) { tests.TestApi(newNodeProvider(ctx))(t) } -func Test_NewURLApiWithClient(t *testing.T) { - t.Skip() +func Test_NewURLApiWithClient_With_Headers(t *testing.T) { var ( - url = "127.0.0.1:65501" headerToTest = "Test-Header" expectedHeaderValue = "thisisaheadertest" ) - server, err := testHTTPServer(url, headerToTest, expectedHeaderValue) - if err != nil { - t.Fatal(err) - } - defer server.Close() - go func() { - server.ListenAndServe() - }() + ts := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + val := r.Header.Get(headerToTest) + if val == expectedHeaderValue { + w.WriteHeader(400) + return + } + w.WriteHeader(200) + }), + ) + defer ts.Close() time.Sleep(time.Second * 2) - api, err := NewURLApiWithClient(url, &http.Client{ + api, err := NewURLApiWithClient(ts.URL, &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, DisableKeepAlives: true, @@ -242,21 +244,3 @@ func Test_NewURLApiWithClient(t *testing.T) { t.Fatal(err) } } - -/// testHTTPServer spins up a test go http server -// used to check headers -func testHTTPServer(url, headerToTest, expectedHeaderValue string) (*http.Server, error) { - r := http.NewServeMux() - r.HandleFunc("/api/v0/pin/ls", func(w http.ResponseWriter, r *http.Request) { - val := r.Header.Get(headerToTest) - if val == expectedHeaderValue { - w.WriteHeader(400) - return - } - w.WriteHeader(200) - }) - return &http.Server{ - Handler: r, - Addr: url, - }, nil -} From ea507216d821455c351440ba33775f564ea18ea8 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 1 May 2019 17:19:35 -0700 Subject: [PATCH 7/8] api: fix failing test --- api_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api_test.go b/api_test.go index fadd966..5f012b4 100644 --- a/api_test.go +++ b/api_test.go @@ -8,6 +8,7 @@ import ( "net/http/httptest" "os" "strconv" + "strings" "sync" "testing" "time" @@ -221,15 +222,14 @@ func Test_NewURLApiWithClient_With_Headers(t *testing.T) { ts := httptest.NewServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { val := r.Header.Get(headerToTest) - if val == expectedHeaderValue { + if val != expectedHeaderValue { w.WriteHeader(400) return } - w.WriteHeader(200) + http.ServeContent(w, r, "", time.Now(), strings.NewReader("test")) }), ) defer ts.Close() - time.Sleep(time.Second * 2) api, err := NewURLApiWithClient(ts.URL, &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, @@ -240,7 +240,7 @@ func Test_NewURLApiWithClient_With_Headers(t *testing.T) { t.Fatal(err) } api.Headers.Set(headerToTest, expectedHeaderValue) - if _, err := api.Pin().Ls(context.Background()); err != nil { + if err := api.Pin().Rm(context.Background(), path.New("/ipfs/QmS4ustL54uo8FzR9455qaxZwuMiUhyvMcX9Ba8nUH4uVv")); err != nil { t.Fatal(err) } } From 320130421f4727d72d68a3c6a5c0e633e1dabc38 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 1 May 2019 19:14:15 -0700 Subject: [PATCH 8/8] remove unused transport.go file --- transport.go | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 transport.go diff --git a/transport.go b/transport.go deleted file mode 100644 index 3fb0e6e..0000000 --- a/transport.go +++ /dev/null @@ -1,21 +0,0 @@ -package httpapi - -import "net/http" - -type transport struct { - header, value string - httptr http.RoundTripper -} - -func newAuthenticatedTransport(tr http.RoundTripper, header, value string) *transport { - return &transport{ - header: header, - value: value, - httptr: tr, - } -} - -func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) { - req.Header.Set(t.header, t.value) - return t.httptr.RoundTrip(req) -}