mirror of
https://github.com/ipfs/kubo.git
synced 2026-03-13 09:53:17 +08:00
refactor(gateway): simplify landing page implementation
- pass headers into LandingPageOption instead of reading config internally, the caller in daemon.go already has cfg in scope - revert getGatewayConfig to original 3-return signature, only HostnameOption needs RootRedirect so it reads it directly from n.Repo.Config() instead of threading it through all callers - remove Flush/Hijack from landingResponseWriter, the wrapper only intercepts "/" on loopback so streaming and websockets never apply, Unwrap is sufficient for http.ResponseController delegation - use net.ParseIP + IsLoopback() for loopback detection instead of a hardcoded switch, covers addresses like 127.0.0.2 and ::ffff:127.0.0.1 - clone header slices in serveLandingPage with slices.Clone to avoid sharing backing arrays with the config map
This commit is contained in:
@@ -881,7 +881,7 @@ func serveHTTPApi(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, error
|
||||
if len(cfg.Gateway.RootRedirect) > 0 {
|
||||
opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect))
|
||||
} else {
|
||||
opts = append(opts, corehttp.LandingPageOption())
|
||||
opts = append(opts, corehttp.LandingPageOption(cfg.Gateway.HTTPHeaders))
|
||||
}
|
||||
|
||||
node, err := cctx.ConstructNode()
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
|
||||
func GatewayOption(paths ...string) ServeOption {
|
||||
return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
|
||||
config, headers, _, err := getGatewayConfig(n)
|
||||
config, headers, err := getGatewayConfig(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -56,7 +56,12 @@ func GatewayOption(paths ...string) ServeOption {
|
||||
// will show a landing page instead.
|
||||
func HostnameOption() ServeOption {
|
||||
return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
|
||||
cfg, headers, rootRedirect, err := getGatewayConfig(n)
|
||||
gwCfg, headers, err := getGatewayConfig(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nodeCfg, err := n.Repo.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -69,13 +74,13 @@ func HostnameOption() ServeOption {
|
||||
childMux := http.NewServeMux()
|
||||
|
||||
var handler http.Handler
|
||||
handler = gateway.NewHostnameHandler(cfg, backend, childMux)
|
||||
handler = gateway.NewHostnameHandler(gwCfg, backend, childMux)
|
||||
handler = gateway.NewHeaders(headers).ApplyCors().Wrap(handler)
|
||||
|
||||
// When RootRedirect is not configured, wrap with landing page fallback.
|
||||
// This intercepts 404 responses for "/" on loopback addresses (like localhost)
|
||||
// and serves a kubo-specific landing page instead.
|
||||
if rootRedirect == "" {
|
||||
if nodeCfg.Gateway.RootRedirect == "" {
|
||||
handler = withLandingPageFallback(handler, headers)
|
||||
}
|
||||
|
||||
@@ -272,11 +277,10 @@ var defaultKnownGateways = map[string]*gateway.PublicGateway{
|
||||
"localhost": subdomainGatewaySpec,
|
||||
}
|
||||
|
||||
// getGatewayConfig returns gateway configuration, HTTP headers, and root redirect URL.
|
||||
func getGatewayConfig(n *core.IpfsNode) (gateway.Config, map[string][]string, string, error) {
|
||||
func getGatewayConfig(n *core.IpfsNode) (gateway.Config, map[string][]string, error) {
|
||||
cfg, err := n.Repo.Config()
|
||||
if err != nil {
|
||||
return gateway.Config{}, nil, "", err
|
||||
return gateway.Config{}, nil, err
|
||||
}
|
||||
|
||||
// Initialize gateway configuration, with empty PublicGateways, handled after.
|
||||
@@ -316,5 +320,5 @@ func getGatewayConfig(n *core.IpfsNode) (gateway.Config, map[string][]string, st
|
||||
}
|
||||
}
|
||||
|
||||
return gwCfg, cfg.Gateway.HTTPHeaders, cfg.Gateway.RootRedirect, nil
|
||||
return gwCfg, cfg.Gateway.HTTPHeaders, nil
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ func TestDeserializedResponsesInheritance(t *testing.T) {
|
||||
n, err := core.NewNode(context.Background(), &core.BuildCfg{Repo: r})
|
||||
assert.NoError(t, err)
|
||||
|
||||
gwCfg, _, _, err := getGatewayConfig(n)
|
||||
gwCfg, _, err := getGatewayConfig(n)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Contains(t, gwCfg.PublicGateways, "example.com")
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package corehttp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
_ "embed"
|
||||
"net"
|
||||
"net/http"
|
||||
"slices"
|
||||
|
||||
core "github.com/ipfs/kubo/core"
|
||||
"github.com/ipfs/kubo/core"
|
||||
)
|
||||
|
||||
//go:embed assets/landing.html
|
||||
@@ -17,13 +17,8 @@ var landingPageHTML []byte
|
||||
// This helps third-party gateway operators by clearly indicating that the
|
||||
// gateway software is working but needs configuration, and provides guidance
|
||||
// for abuse reporting.
|
||||
func LandingPageOption() ServeOption {
|
||||
return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
|
||||
cfg, err := n.Repo.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
headers := cfg.Gateway.HTTPHeaders
|
||||
func LandingPageOption(headers map[string][]string) ServeOption {
|
||||
return func(_ *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
|
||||
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/" {
|
||||
http.NotFound(w, r)
|
||||
@@ -38,7 +33,7 @@ func LandingPageOption() ServeOption {
|
||||
// serveLandingPage writes the landing page HTML with appropriate headers.
|
||||
func serveLandingPage(w http.ResponseWriter, headers map[string][]string) {
|
||||
for k, v := range headers {
|
||||
w.Header()[http.CanonicalHeaderKey(k)] = v
|
||||
w.Header()[http.CanonicalHeaderKey(k)] = slices.Clone(v)
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
_, _ = w.Write(landingPageHTML)
|
||||
@@ -72,12 +67,11 @@ func withLandingPageFallback(next http.Handler, headers map[string][]string) htt
|
||||
if h, _, err := net.SplitHostPort(r.Host); err == nil {
|
||||
host = h
|
||||
}
|
||||
switch host {
|
||||
case "localhost", "127.0.0.1", "::1", "[::1]":
|
||||
// Continue to intercept
|
||||
default:
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
if host != "localhost" {
|
||||
if ip := net.ParseIP(host); ip == nil || !ip.IsLoopback() {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap ResponseWriter to intercept 404 responses
|
||||
@@ -93,6 +87,7 @@ func withLandingPageFallback(next http.Handler, headers map[string][]string) htt
|
||||
|
||||
// landingResponseWriter wraps http.ResponseWriter to intercept 404 responses.
|
||||
// It suppresses the 404 status and body so we can serve a landing page instead.
|
||||
// Unwrap allows http.ResponseController to reach the underlying writer.
|
||||
type landingResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
wroteHeader bool
|
||||
@@ -121,21 +116,6 @@ func (w *landingResponseWriter) Write(b []byte) (int, error) {
|
||||
return w.ResponseWriter.Write(b)
|
||||
}
|
||||
|
||||
// Flush implements http.Flusher for streaming responses.
|
||||
func (w *landingResponseWriter) Flush() {
|
||||
if f, ok := w.ResponseWriter.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// Hijack implements http.Hijacker for websocket support.
|
||||
func (w *landingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if h, ok := w.ResponseWriter.(http.Hijacker); ok {
|
||||
return h.Hijack()
|
||||
}
|
||||
return nil, nil, http.ErrNotSupported
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying ResponseWriter for http.ResponseController.
|
||||
func (w *landingResponseWriter) Unwrap() http.ResponseWriter {
|
||||
return w.ResponseWriter
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
func RoutingOption() ServeOption {
|
||||
return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
|
||||
_, headers, _, err := getGatewayConfig(n)
|
||||
_, headers, err := getGatewayConfig(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user