mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-21 18:37:45 +08:00
* feat(p2p): add --foreground flag to listen and forward commands
adds `-f/--foreground` option that keeps the command running until
interrupted (SIGTERM/Ctrl+C) or closed via `ipfs p2p close`. the
listener/forwarder is automatically removed when the command exits.
useful for systemd services and scripts that need cleanup on exit.
* docs: add p2p-tunnels.md with systemd examples
- add dedicated docs/p2p-tunnels.md covering:
- why p2p tunnels (NAT traversal, no public IP needed)
- quick start with netcat
- background and foreground modes
- systemd integration with path-based activation
- security considerations and troubleshooting
- document Experimental.Libp2pStreamMounting in docs/config.md
- simplify docs/experimental-features.md, link to new doc
- add "Learn more" links to ipfs p2p listen/forward --help
- update changelog entry with doc link
- add cross-reference in misc/README.md
* chore: reference kubo#5460 for p2p config
Ref. https://github.com/ipfs/kubo/issues/5460
* fix(daemon): write api/gateway files only after HTTP server is ready
fixes race condition where $IPFS_PATH/api and $IPFS_PATH/gateway files
were written before the HTTP servers were ready to accept connections.
this caused issues for tools like systemd path units that immediately
try to connect when these files appear.
changes:
- add corehttp.ServeWithReady() that signals when server is ready
- wait for ready signal before writing address files
- use sync.WaitGroup.Go() (Go 1.25) for cleaner goroutine management
- add TestAddressFileReady to verify both api and gateway files
* fix(daemon): buffer errc channel and wait for all listeners
- buffer error channel with len(listeners) to prevent deadlock when
multiple servers write errors simultaneously
- wait for ALL listeners to be ready before writing api/gateway file,
not just the first one
Feedback-from: https://github.com/ipfs/kubo/pull/11099#pullrequestreview-3593885839
* docs(changelog): improve p2p tunnel section clarity
reframe to lead with user benefit and add example output
* docs(p2p): remove obsolete race condition caveat
the "First launch fails but restarts work" troubleshooting section
described a race where the api file was written before the daemon was
ready. this was fixed in 80b703a which ensures api/gateway files are
only written after HTTP servers are ready to accept connections.
---------
Co-authored-by: Andrew Gillis <11790789+gammazero@users.noreply.github.com>
166 lines
4.7 KiB
Go
166 lines
4.7 KiB
Go
/*
|
|
Package corehttp provides utilities for the webui, gateways, and other
|
|
high-level HTTP interfaces to IPFS.
|
|
*/
|
|
package corehttp
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"time"
|
|
|
|
logging "github.com/ipfs/go-log/v2"
|
|
core "github.com/ipfs/kubo/core"
|
|
ma "github.com/multiformats/go-multiaddr"
|
|
manet "github.com/multiformats/go-multiaddr/net"
|
|
)
|
|
|
|
var log = logging.Logger("core/server")
|
|
|
|
// shutdownTimeout is the timeout after which we'll stop waiting for hung
|
|
// commands to return on shutdown.
|
|
const shutdownTimeout = 30 * time.Second
|
|
|
|
// ServeOption registers any HTTP handlers it provides on the given mux.
|
|
// It returns the mux to expose to future options, which may be a new mux if it
|
|
// is interested in mediating requests to future options, or the same mux
|
|
// initially passed in if not.
|
|
type ServeOption func(*core.IpfsNode, net.Listener, *http.ServeMux) (*http.ServeMux, error)
|
|
|
|
// MakeHandler turns a list of ServeOptions into a http.Handler that implements
|
|
// all of the given options, in order.
|
|
func MakeHandler(n *core.IpfsNode, l net.Listener, options ...ServeOption) (http.Handler, error) {
|
|
topMux := http.NewServeMux()
|
|
mux := topMux
|
|
for _, option := range options {
|
|
var err error
|
|
mux, err = option(n, l, mux)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// ServeMux does not support requests with CONNECT method,
|
|
// so we need to handle them separately
|
|
// https://golang.org/src/net/http/request.go#L111
|
|
if r.Method == http.MethodConnect {
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
topMux.ServeHTTP(w, r)
|
|
})
|
|
return handler, nil
|
|
}
|
|
|
|
// ListenAndServe runs an HTTP server listening at |listeningMultiAddr| with
|
|
// the given serve options. The address must be provided in multiaddr format.
|
|
//
|
|
// TODO intelligently parse address strings in other formats so long as they
|
|
// unambiguously map to a valid multiaddr. e.g. for convenience, ":8080" should
|
|
// map to "/ip4/0.0.0.0/tcp/8080".
|
|
func ListenAndServe(n *core.IpfsNode, listeningMultiAddr string, options ...ServeOption) error {
|
|
addr, err := ma.NewMultiaddr(listeningMultiAddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
list, err := manet.Listen(addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// we might have listened to /tcp/0 - let's see what we are listing on
|
|
addr = list.Multiaddr()
|
|
fmt.Printf("RPC API server listening on %s\n", addr)
|
|
|
|
return Serve(n, manet.NetListener(list), options...)
|
|
}
|
|
|
|
// Serve accepts incoming HTTP connections on the listener and passes them
|
|
// to ServeOption handlers.
|
|
func Serve(node *core.IpfsNode, lis net.Listener, options ...ServeOption) error {
|
|
return ServeWithReady(node, lis, nil, options...)
|
|
}
|
|
|
|
// ServeWithReady is like Serve but signals on the ready channel when the
|
|
// server is about to accept connections. The channel is closed right before
|
|
// server.Serve() is called.
|
|
//
|
|
// This is useful for callers that need to perform actions (like writing
|
|
// address files) only after the server is guaranteed to be accepting
|
|
// connections, avoiding race conditions where clients see the file before
|
|
// the server is ready.
|
|
//
|
|
// Passing nil for ready is equivalent to calling Serve().
|
|
func ServeWithReady(node *core.IpfsNode, lis net.Listener, ready chan<- struct{}, options ...ServeOption) error {
|
|
// make sure we close this no matter what.
|
|
defer lis.Close()
|
|
|
|
handler, err := MakeHandler(node, lis, options...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
addr, err := manet.FromNetAddr(lis.Addr())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
select {
|
|
case <-node.Context().Done():
|
|
return fmt.Errorf("failed to start server, process closing")
|
|
default:
|
|
}
|
|
|
|
server := &http.Server{
|
|
Handler: handler,
|
|
}
|
|
|
|
var serverError error
|
|
serverClosed := make(chan struct{})
|
|
go func() {
|
|
if ready != nil {
|
|
close(ready)
|
|
}
|
|
serverError = server.Serve(lis)
|
|
close(serverClosed)
|
|
}()
|
|
|
|
// wait for server to exit.
|
|
select {
|
|
case <-serverClosed:
|
|
// if node being closed before server exits, close server
|
|
case <-node.Context().Done():
|
|
log.Infof("server at %s terminating...", addr)
|
|
|
|
go func() {
|
|
ticker := time.NewTicker(5 * time.Second)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
log.Infof("waiting for server at %s to terminate...", addr)
|
|
case <-serverClosed:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
// This timeout shouldn't be necessary if all of our commands
|
|
// are obeying their contexts but we should have *some* timeout.
|
|
ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
|
|
defer cancel()
|
|
err := server.Shutdown(ctx)
|
|
|
|
// Should have already closed but we still need to wait for it
|
|
// to set the error.
|
|
<-serverClosed
|
|
serverError = err
|
|
}
|
|
|
|
log.Infof("server at %s terminated", addr)
|
|
return serverError
|
|
}
|