Skip to content

Commit

Permalink
Merge pull request ipfs/kubo#9070 from iand/feat-modular-gateway
Browse files Browse the repository at this point in the history
feat: make corehttp a reusable component

This commit was moved from ipfs/kubo@924ab06
  • Loading branch information
lidel authored Aug 17, 2022
2 parents bb0912a + 974dfcb commit 05c6458
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 48 deletions.
109 changes: 70 additions & 39 deletions gateway/core/corehttp/gateway.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
package corehttp

import (
"context"
"fmt"
"net"
"net/http"
"sort"

coreiface "github.com/ipfs/interface-go-ipfs-core"
options "github.com/ipfs/interface-go-ipfs-core/options"
path "github.com/ipfs/interface-go-ipfs-core/path"
version "github.com/ipfs/kubo"
core "github.com/ipfs/kubo/core"
coreapi "github.com/ipfs/kubo/core/coreapi"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"

options "github.com/ipfs/interface-go-ipfs-core/options"
id "github.com/libp2p/go-libp2p/p2p/protocol/identify"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)

type GatewayConfig struct {
Expand All @@ -22,6 +24,21 @@ type GatewayConfig struct {
FastDirIndexThreshold int
}

// NodeAPI defines the minimal set of API services required by a gateway handler
type NodeAPI interface {
// Unixfs returns an implementation of Unixfs API
Unixfs() coreiface.UnixfsAPI

// Block returns an implementation of Block API
Block() coreiface.BlockAPI

// Dag returns an implementation of Dag API
Dag() coreiface.APIDagService

// ResolvePath resolves the path using Unixfs resolver
ResolvePath(context.Context, path.Path) (path.Resolved, error)
}

// A helper function to clean up a set of headers:
// 1. Canonicalizes.
// 2. Deduplicates.
Expand Down Expand Up @@ -59,49 +76,19 @@ func GatewayOption(writable bool, paths ...string) ServeOption {
headers[http.CanonicalHeaderKey(h)] = v
}

// Hard-coded headers.
const ACAHeadersName = "Access-Control-Allow-Headers"
const ACEHeadersName = "Access-Control-Expose-Headers"
const ACAOriginName = "Access-Control-Allow-Origin"
const ACAMethodsName = "Access-Control-Allow-Methods"
AddAccessControlHeaders(headers)

if _, ok := headers[ACAOriginName]; !ok {
// Default to *all*
headers[ACAOriginName] = []string{"*"}
}
if _, ok := headers[ACAMethodsName]; !ok {
// Default to GET
headers[ACAMethodsName] = []string{http.MethodGet}
offlineApi, err := api.WithOptions(options.Api.Offline(true))
if err != nil {
return nil, err
}

headers[ACAHeadersName] = cleanHeaderSet(
append([]string{
"Content-Type",
"User-Agent",
"Range",
"X-Requested-With",
}, headers[ACAHeadersName]...))

headers[ACEHeadersName] = cleanHeaderSet(
append([]string{
"Content-Length",
"Content-Range",
"X-Chunked-Output",
"X-Stream-Output",
"X-Ipfs-Path",
"X-Ipfs-Roots",
}, headers[ACEHeadersName]...))

var gateway http.Handler
gateway, err = newGatewayHandler(GatewayConfig{
gateway := NewGatewayHandler(GatewayConfig{
Headers: headers,
Writable: writable,
PathPrefixes: cfg.Gateway.PathPrefixes,
FastDirIndexThreshold: int(cfg.Gateway.FastDirIndexThreshold.WithDefault(100)),
}, api)
if err != nil {
return nil, err
}
}, api, offlineApi)

gateway = otelhttp.NewHandler(gateway, "Gateway.Request")

Expand All @@ -112,6 +99,50 @@ func GatewayOption(writable bool, paths ...string) ServeOption {
}
}

// AddAccessControlHeaders adds default headers used for controlling
// cross-origin requests. This function adds several values to the
// Access-Control-Allow-Headers and Access-Control-Expose-Headers entries.
// If the Access-Control-Allow-Origin entry is missing a value of '*' is
// added, indicating that browsers should allow requesting code from any
// origin to access the resource.
// If the Access-Control-Allow-Methods entry is missing a value of 'GET' is
// added, indicating that browsers may use the GET method when issuing cross
// origin requests.
func AddAccessControlHeaders(headers map[string][]string) {
// Hard-coded headers.
const ACAHeadersName = "Access-Control-Allow-Headers"
const ACEHeadersName = "Access-Control-Expose-Headers"
const ACAOriginName = "Access-Control-Allow-Origin"
const ACAMethodsName = "Access-Control-Allow-Methods"

if _, ok := headers[ACAOriginName]; !ok {
// Default to *all*
headers[ACAOriginName] = []string{"*"}
}
if _, ok := headers[ACAMethodsName]; !ok {
// Default to GET
headers[ACAMethodsName] = []string{http.MethodGet}
}

headers[ACAHeadersName] = cleanHeaderSet(
append([]string{
"Content-Type",
"User-Agent",
"Range",
"X-Requested-With",
}, headers[ACAHeadersName]...))

headers[ACEHeadersName] = cleanHeaderSet(
append([]string{
"Content-Length",
"Content-Range",
"X-Chunked-Output",
"X-Stream-Output",
"X-Ipfs-Path",
"X-Ipfs-Roots",
}, headers[ACEHeadersName]...))
}

func VersionOption() ServeOption {
return func(_ *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {
Expand Down
19 changes: 10 additions & 9 deletions gateway/core/corehttp/gateway_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
path "github.com/ipfs/go-path"
"github.com/ipfs/go-path/resolver"
coreiface "github.com/ipfs/interface-go-ipfs-core"
options "github.com/ipfs/interface-go-ipfs-core/options"
ipath "github.com/ipfs/interface-go-ipfs-core/path"
routing "github.com/libp2p/go-libp2p-core/routing"
prometheus "github.com/prometheus/client_golang/prometheus"
Expand Down Expand Up @@ -69,8 +68,8 @@ type redirectTemplateData struct {
// (it serves requests like GET /ipfs/QmVRzPKPzNtSrEzBFm2UZfxmPAgnaLke4DMcerbsGGSaFe/link)
type gatewayHandler struct {
config GatewayConfig
api coreiface.CoreAPI
offlineApi coreiface.CoreAPI
api NodeAPI
offlineApi NodeAPI

// generic metrics
firstContentBlockGetMetric *prometheus.HistogramVec
Expand Down Expand Up @@ -214,11 +213,13 @@ func newGatewayHistogramMetric(name string, help string) *prometheus.HistogramVe
return histogramMetric
}

func newGatewayHandler(c GatewayConfig, api coreiface.CoreAPI) (*gatewayHandler, error) {
offlineApi, err := api.WithOptions(options.Api.Offline(true))
if err != nil {
return nil, err
}
// NewGatewayHandler returns an http.Handler that can act as a gateway to IPFS content
// offlineApi is a version of the API that should not make network requests for missing data
func NewGatewayHandler(c GatewayConfig, api NodeAPI, offlineApi NodeAPI) http.Handler {
return newGatewayHandler(c, api, offlineApi)
}

func newGatewayHandler(c GatewayConfig, api NodeAPI, offlineApi NodeAPI) *gatewayHandler {
i := &gatewayHandler{
config: c,
api: api,
Expand Down Expand Up @@ -263,7 +264,7 @@ func newGatewayHandler(c GatewayConfig, api coreiface.CoreAPI) (*gatewayHandler,
"The time to receive the first UnixFS node on a GET from the gateway.",
),
}
return i, nil
return i
}

func parseIpfsPath(p string) (cid.Cid, string, error) {
Expand Down

0 comments on commit 05c6458

Please sign in to comment.