mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-21 18:37:45 +08:00
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
277 lines
5.7 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|