mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-21 10:27:46 +08:00
fix: allow custom http provide when libp2p node is offline (#10974)
* feat: allow custom http provide when offline * refactor: improve offline HTTP provider handling and tests - fixed comment/function name mismatch - added mock server test for HTTP provide success - clarified test names for offline scenarios * test: simplify single-node provider tests use h.NewNode().Init() instead of NewNodes(1) for cleaner test setup * fix: allow SweepingProvider to work with HTTP-only routing when no DHT is available but HTTP routers are configured for providing, return NoopProvider instead of failing. this allows the daemon to start and HTTP-based providing to work through the routing system. moved HTTP provider detection to config package as HasHTTPProviderConfigured() for better code organization and reusability. this fix is important as SweepingProvider will become the new default in the future. --------- Co-authored-by: Marcin Rataj <lidel@lidel.org>
This commit is contained in:
parent
07f017f01d
commit
6fcbba4b4a
@ -214,3 +214,57 @@ func getEnvOrDefault(key string, defaultValue []string) []string {
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// HasHTTPProviderConfigured checks if the node is configured to use HTTP routers
|
||||
// for providing content announcements. This is used when determining if the node
|
||||
// can provide content even when not connected to libp2p peers.
|
||||
//
|
||||
// Note: Right now we only support delegated HTTP content providing if Routing.Type=custom
|
||||
// and Routing.Routers are configured according to:
|
||||
// https://github.com/ipfs/kubo/blob/master/docs/delegated-routing.md#configuration-file-example
|
||||
//
|
||||
// This uses the `ProvideBitswap` request type that is not documented anywhere,
|
||||
// because we hoped something like IPIP-378 (https://github.com/ipfs/specs/pull/378)
|
||||
// would get finalized and we'd switch to that. It never happened due to politics,
|
||||
// and now we are stuck with ProvideBitswap being the only API that works.
|
||||
// Some people have reverse engineered it (example:
|
||||
// https://discuss.ipfs.tech/t/only-peers-found-from-dht-seem-to-be-getting-used-as-relays-so-cant-use-http-routers/19545/9)
|
||||
// and use it, so what we do here is the bare minimum to ensure their use case works
|
||||
// using this old API until something better is available.
|
||||
func (c *Config) HasHTTPProviderConfigured() bool {
|
||||
if len(c.Routing.Routers) == 0 {
|
||||
// No "custom" routers
|
||||
return false
|
||||
}
|
||||
method, ok := c.Routing.Methods[MethodNameProvide]
|
||||
if !ok {
|
||||
// No provide method configured
|
||||
return false
|
||||
}
|
||||
return c.routerSupportsHTTPProviding(method.RouterName)
|
||||
}
|
||||
|
||||
// routerSupportsHTTPProviding checks if the supplied custom router is or
|
||||
// includes an HTTP-based router.
|
||||
func (c *Config) routerSupportsHTTPProviding(routerName string) bool {
|
||||
rp, ok := c.Routing.Routers[routerName]
|
||||
if !ok {
|
||||
// Router configured for providing doesn't exist
|
||||
return false
|
||||
}
|
||||
|
||||
switch rp.Type {
|
||||
case RouterTypeHTTP:
|
||||
return true
|
||||
case RouterTypeParallel, RouterTypeSequential:
|
||||
// Check if any child router supports HTTP
|
||||
if children, ok := rp.Parameters.(*ComposableRouterParams); ok {
|
||||
for _, childRouter := range children.Routers {
|
||||
if c.routerSupportsHTTPProviding(childRouter.RouterName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@ -170,10 +170,15 @@ var provideRefRoutingCmd = &cmds.Command{
|
||||
return errors.New("invalid configuration: Provide.Enabled is set to 'false'")
|
||||
}
|
||||
|
||||
if len(nd.PeerHost.Network().Conns()) == 0 {
|
||||
if len(nd.PeerHost.Network().Conns()) == 0 && !cfg.HasHTTPProviderConfigured() {
|
||||
// Node is depending on DHT for providing (no custom HTTP provider
|
||||
// configured) and currently has no connected peers.
|
||||
return errors.New("cannot provide, no connected peers")
|
||||
}
|
||||
|
||||
// If we reach here with no connections but HTTP provider configured,
|
||||
// we proceed with the provide operation via HTTP
|
||||
|
||||
// Needed to parse stdin args.
|
||||
// TODO: Lazy Load
|
||||
err = req.ParseBodyArgs()
|
||||
|
||||
@ -355,6 +355,12 @@ func SweepingProviderOpt(cfg *config.Config) fx.Option {
|
||||
}
|
||||
}
|
||||
if impl == nil {
|
||||
// No DHT available, check if HTTP provider is configured
|
||||
cfg, err := in.Repo.Config()
|
||||
if err == nil && cfg.HasHTTPProviderConfigured() {
|
||||
// HTTP provider is configured, return NoopProvider to allow HTTP-based providing
|
||||
return &NoopProvider{}, keyStore, nil
|
||||
}
|
||||
return &NoopProvider{}, nil, errors.New("provider: no valid DHT available for providing")
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,9 @@ package cli
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -139,6 +142,77 @@ func runProviderSuite(t *testing.T, reprovide bool, apply cfgApplier) {
|
||||
expectNoProviders(t, cid, nodes[1:]...)
|
||||
})
|
||||
|
||||
t.Run("manual provide fails when no libp2p peers and no custom HTTP router", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
h := harness.NewT(t)
|
||||
node := h.NewNode().Init()
|
||||
apply(node)
|
||||
node.SetIPFSConfig("Provide.Enabled", true)
|
||||
node.StartDaemon()
|
||||
defer node.StopDaemon()
|
||||
|
||||
cid := node.IPFSAddStr(time.Now().String())
|
||||
res := node.RunIPFS("routing", "provide", cid)
|
||||
assert.Contains(t, res.Stderr.Trimmed(), "cannot provide, no connected peers")
|
||||
assert.Equal(t, 1, res.ExitCode())
|
||||
})
|
||||
|
||||
t.Run("manual provide succeeds via custom HTTP router when no libp2p peers", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a mock HTTP server that accepts provide requests.
|
||||
// This simulates the undocumented API behavior described in
|
||||
// https://discuss.ipfs.tech/t/only-peers-found-from-dht-seem-to-be-getting-used-as-relays-so-cant-use-http-routers/19545/9
|
||||
// Note: This is NOT IPIP-378, which was not implemented.
|
||||
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Accept both PUT and POST requests to /routing/v1/providers and /routing/v1/ipns
|
||||
if (r.Method == http.MethodPut || r.Method == http.MethodPost) &&
|
||||
(strings.HasPrefix(r.URL.Path, "/routing/v1/providers") || strings.HasPrefix(r.URL.Path, "/routing/v1/ipns")) {
|
||||
// Return HTTP 200 to indicate successful publishing
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer mockServer.Close()
|
||||
|
||||
h := harness.NewT(t)
|
||||
node := h.NewNode().Init()
|
||||
apply(node)
|
||||
node.SetIPFSConfig("Provide.Enabled", true)
|
||||
// Configure a custom HTTP router for providing.
|
||||
// Using our mock server that will accept the provide requests.
|
||||
routingConf := map[string]any{
|
||||
"Type": "custom", // https://github.com/ipfs/kubo/blob/master/docs/delegated-routing.md#configuration-file-example
|
||||
"Methods": map[string]any{
|
||||
"provide": map[string]any{"RouterName": "MyCustomRouter"},
|
||||
"get-ipns": map[string]any{"RouterName": "MyCustomRouter"},
|
||||
"put-ipns": map[string]any{"RouterName": "MyCustomRouter"},
|
||||
"find-peers": map[string]any{"RouterName": "MyCustomRouter"},
|
||||
"find-providers": map[string]any{"RouterName": "MyCustomRouter"},
|
||||
},
|
||||
"Routers": map[string]any{
|
||||
"MyCustomRouter": map[string]any{
|
||||
"Type": "http",
|
||||
"Parameters": map[string]any{
|
||||
// Use the mock server URL
|
||||
"Endpoint": mockServer.URL,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
node.SetIPFSConfig("Routing", routingConf)
|
||||
node.StartDaemon()
|
||||
defer node.StopDaemon()
|
||||
|
||||
cid := node.IPFSAddStr(time.Now().String())
|
||||
// The command should successfully provide via HTTP even without libp2p peers
|
||||
res := node.RunIPFS("routing", "provide", cid)
|
||||
assert.Empty(t, res.Stderr.String(), "Should have no errors when providing via HTTP router")
|
||||
assert.Equal(t, 0, res.ExitCode(), "Should succeed with exit code 0")
|
||||
})
|
||||
|
||||
// Right now Provide and Reprovide are tied together
|
||||
t.Run("Reprovide.Interval=0 disables announcement of new CID too", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user