kubo/client/httpapi/api_test.go
Ross Jones 7d8262307c fix: use https URI when multiaddress specifies tls (#177)
Currently any clients created through `NewApiWithClient` will make a
HTTP request to the api, even if the multiaddress specifies TLS or (the
deprecated multiaddr option) https.

This commit addresses this by having NewApiWithClient iterate the
available protocols for the multiaddress, specifying the URL proto as
https if it finds TLS or HTTPS is specified. The default continues to be
http for those multiaddresses that do not specify these options.

Should resolve #176

This commit was moved from ipfs/go-ipfs-http-client@7e1de1f7cc
2023-05-15 18:18:51 +02:00

277 lines
5.7 KiB
Go

package httpapi
import (
"context"
"net/http"
"net/http/httptest"
"os"
"runtime"
"strconv"
"strings"
"sync"
"testing"
"time"
iface "github.com/ipfs/boxo/coreiface"
"github.com/ipfs/boxo/coreiface/path"
"github.com/ipfs/boxo/coreiface/tests"
local "github.com/ipfs/iptb-plugins/local"
"github.com/ipfs/iptb/testbed"
testbedi "github.com/ipfs/iptb/testbed/interfaces"
ma "github.com/multiformats/go-multiaddr"
)
const parallelSpeculativeNodes = 15 // 15 seems to work best
func init() {
_, err := testbed.RegisterPlugin(testbed.IptbPlugin{
From: "<builtin>",
NewNode: local.NewNode,
GetAttrList: local.GetAttrList,
GetAttrDesc: local.GetAttrDesc,
PluginName: local.PluginName,
BuiltIn: true,
}, false)
if err != nil {
panic(err)
}
}
type NodeProvider struct {
simple <-chan func(context.Context) ([]iface.CoreAPI, error)
}
func newNodeProvider(ctx context.Context) *NodeProvider {
simpleNodes := make(chan func(context.Context) ([]iface.CoreAPI, error), parallelSpeculativeNodes)
np := &NodeProvider{
simple: simpleNodes,
}
// start basic nodes speculatively in parallel
for i := 0; i < parallelSpeculativeNodes; i++ {
go func() {
for {
ctx, cancel := context.WithCancel(ctx)
snd, err := np.makeAPISwarm(ctx, false, 1)
res := func(ctx context.Context) ([]iface.CoreAPI, error) {
if err != nil {
return nil, err
}
go func() {
<-ctx.Done()
cancel()
}()
return snd, nil
}
select {
case simpleNodes <- res:
case <-ctx.Done():
return
}
}
}()
}
return np
}
func (np *NodeProvider) MakeAPISwarm(ctx context.Context, fullIdentity bool, n int) ([]iface.CoreAPI, error) {
if !fullIdentity && n == 1 {
return (<-np.simple)(ctx)
}
return np.makeAPISwarm(ctx, fullIdentity, n)
}
func (NodeProvider) makeAPISwarm(ctx context.Context, fullIdentity bool, n int) ([]iface.CoreAPI, error) {
dir, err := os.MkdirTemp("", "httpapi-tb-")
if err != nil {
return nil, err
}
tb := testbed.NewTestbed(dir)
specs, err := testbed.BuildSpecs(tb.Dir(), n, "localipfs", nil)
if err != nil {
return nil, err
}
if err := testbed.WriteNodeSpecs(tb.Dir(), specs); err != nil {
return nil, err
}
nodes, err := tb.Nodes()
if err != nil {
return nil, err
}
apis := make([]iface.CoreAPI, n)
wg := sync.WaitGroup{}
zero := sync.WaitGroup{}
wg.Add(len(nodes))
zero.Add(1)
errs := make(chan error, len(nodes))
for i, nd := range nodes {
go func(i int, nd testbedi.Core) {
defer wg.Done()
if _, err := nd.Init(ctx, "--empty-repo"); err != nil {
errs <- err
return
}
if _, err := nd.RunCmd(ctx, nil, "ipfs", "config", "--json", "Experimental.FilestoreEnabled", "true"); err != nil {
errs <- err
return
}
if _, err := nd.Start(ctx, true, "--enable-pubsub-experiment", "--offline="+strconv.FormatBool(n == 1)); err != nil {
errs <- err
return
}
if i > 0 {
zero.Wait()
if err := nd.Connect(ctx, nodes[0]); err != nil {
errs <- err
return
}
} else {
zero.Done()
}
addr, err := nd.APIAddr()
if err != nil {
errs <- err
return
}
maddr, err := ma.NewMultiaddr(addr)
if err != nil {
errs <- err
return
}
c := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DisableKeepAlives: true,
DisableCompression: true,
},
}
apis[i], err = NewApiWithClient(maddr, c)
if err != nil {
errs <- err
return
}
// empty node is pinned even with --empty-repo, we don't want that
emptyNode := path.New("/ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn")
if err := apis[i].Pin().Rm(ctx, emptyNode); err != nil {
errs <- err
return
}
}(i, nd)
}
wg.Wait()
go func() {
<-ctx.Done()
defer os.Remove(dir)
defer func() {
for _, nd := range nodes {
_ = nd.Stop(context.Background())
}
}()
}()
select {
case err = <-errs:
default:
}
return apis, err
}
func TestHttpApi(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("skipping due to #142")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
tests.TestApi(newNodeProvider(ctx))(t)
}
func Test_NewURLApiWithClient_With_Headers(t *testing.T) {
var (
headerToTest = "Test-Header"
expectedHeaderValue = "thisisaheadertest"
)
ts := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
val := r.Header.Get(headerToTest)
if val != expectedHeaderValue {
w.WriteHeader(400)
return
}
http.ServeContent(w, r, "", time.Now(), strings.NewReader("test"))
}),
)
defer ts.Close()
api, err := NewURLApiWithClient(ts.URL, &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DisableKeepAlives: true,
},
})
if err != nil {
t.Fatal(err)
}
api.Headers.Set(headerToTest, expectedHeaderValue)
if err := api.Pin().Rm(context.Background(), path.New("/ipfs/QmS4ustL54uo8FzR9455qaxZwuMiUhyvMcX9Ba8nUH4uVv")); err != nil {
t.Fatal(err)
}
}
func Test_NewURLApiWithClient_HTTP_Variant(t *testing.T) {
testcases := []struct {
address string
expected string
}{
{address: "/ip4/127.0.0.1/tcp/80", expected: "http://127.0.0.1:80"},
{address: "/ip4/127.0.0.1/tcp/443/tls", expected: "https://127.0.0.1:443"},
{address: "/ip4/127.0.0.1/tcp/443/https", expected: "https://127.0.0.1:443"},
{address: "/ip4/127.0.0.1/tcp/443/tls/http", expected: "https://127.0.0.1:443"},
}
for _, tc := range testcases {
address, err := ma.NewMultiaddr(tc.address)
if err != nil {
t.Fatal(err)
}
api, err := NewApiWithClient(address, &http.Client{})
if err != nil {
t.Fatal(err)
}
if api.url != tc.expected {
t.Errorf("Expected = %s; got %s", tc.expected, api.url)
}
}
}