feat: make corehttp a reusable component
Ian Davis committed
Jun 30, 2022 at 10:12 UTC
f4d87419ce702ac127943d43a1844e55b6fdab52
2 files changed
+77
-48
core/corehttp/gateway.go
+68
-39
@@ -1,18 +1,20 @@
1
package corehttp
2
3
import (
4
+ "context"
5
"fmt"
6
"net"
7
"net/http"
8
"sort"
9
10
+ coreiface "github.com/ipfs/interface-go-ipfs-core"
11
+ options "github.com/ipfs/interface-go-ipfs-core/options"
12
+ path "github.com/ipfs/interface-go-ipfs-core/path"
13
version "github.com/ipfs/kubo"
14
core "github.com/ipfs/kubo/core"
15
coreapi "github.com/ipfs/kubo/core/coreapi"
12
- "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
13
-
14
- options "github.com/ipfs/interface-go-ipfs-core/options"
16
id "github.com/libp2p/go-libp2p/p2p/protocol/identify"
17
+ "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
18
)
19
20
type GatewayConfig struct {
@@ -22,6 +24,21 @@ type GatewayConfig struct {
24
FastDirIndexThreshold int
25
}
26
27
+// NodeAPI defines the minimal set of API services required by a gateway handler
28
+type NodeAPI interface {
29
+ // Unixfs returns an implementation of Unixfs API
30
+ Unixfs() coreiface.UnixfsAPI
31
+
32
+ // Block returns an implementation of Block API
33
+ Block() coreiface.BlockAPI
34
+
35
+ // Dag returns an implementation of Dag API
36
+ Dag() coreiface.APIDagService
37
+
38
+ // ResolvePath resolves the path using Unixfs resolver
39
+ ResolvePath(context.Context, path.Path) (path.Resolved, error)
40
+}
41
+
42
// A helper function to clean up a set of headers:
43
// 1. Canonicalizes.
44
// 2. Deduplicates.
@@ -59,49 +76,22 @@ func GatewayOption(writable bool, paths ...string) ServeOption {
76
headers[http.CanonicalHeaderKey(h)] = v
77
}
78
62
- // Hard-coded headers.
63
- const ACAHeadersName = "Access-Control-Allow-Headers"
64
- const ACEHeadersName = "Access-Control-Expose-Headers"
65
- const ACAOriginName = "Access-Control-Allow-Origin"
66
- const ACAMethodsName = "Access-Control-Allow-Methods"
67
-
68
- if _, ok := headers[ACAOriginName]; !ok {
69
- // Default to *all*
70
- headers[ACAOriginName] = []string{"*"}
79
+ acheaders := AccessControlHeaders()
80
+ for k, v := range acheaders {
81
+ headers[k] = v
82
}
72
- if _, ok := headers[ACAMethodsName]; !ok {
73
- // Default to GET
74
- headers[ACAMethodsName] = []string{http.MethodGet}
83
+
84
+ offlineApi, err := api.WithOptions(options.Api.Offline(true))
85
+ if err != nil {
86
+ return nil, err
87
}
88
77
- headers[ACAHeadersName] = cleanHeaderSet(
78
- append([]string{
79
- "Content-Type",
80
- "User-Agent",
81
- "Range",
82
- "X-Requested-With",
83
- }, headers[ACAHeadersName]...))
84
-
85
- headers[ACEHeadersName] = cleanHeaderSet(
86
- append([]string{
87
- "Content-Length",
88
- "Content-Range",
89
- "X-Chunked-Output",
90
- "X-Stream-Output",
91
- "X-Ipfs-Path",
92
- "X-Ipfs-Roots",
93
- }, headers[ACEHeadersName]...))
94
-
95
- var gateway http.Handler
96
- gateway, err = newGatewayHandler(GatewayConfig{
89
+ gateway := NewGatewayHandler(GatewayConfig{
90
Headers: headers,
91
Writable: writable,
92
PathPrefixes: cfg.Gateway.PathPrefixes,
93
FastDirIndexThreshold: int(cfg.Gateway.FastDirIndexThreshold.WithDefault(100)),
101
- }, api)
102
- if err != nil {
103
- return nil, err
104
- }
94
+ }, api, offlineApi)
95
96
gateway = otelhttp.NewHandler(gateway, "Gateway.Request")
97
@@ -112,6 +102,45 @@ func GatewayOption(writable bool, paths ...string) ServeOption {
102
}
103
}
104
105
+func AccessControlHeaders() map[string][]string {
106
+ headers := make(map[string][]string)
107
+
108
+ // Hard-coded headers.
109
+ const ACAHeadersName = "Access-Control-Allow-Headers"
110
+ const ACEHeadersName = "Access-Control-Expose-Headers"
111
+ const ACAOriginName = "Access-Control-Allow-Origin"
112
+ const ACAMethodsName = "Access-Control-Allow-Methods"
113
+
114
+ if _, ok := headers[ACAOriginName]; !ok {
115
+ // Default to *all*
116
+ headers[ACAOriginName] = []string{"*"}
117
+ }
118
+ if _, ok := headers[ACAMethodsName]; !ok {
119
+ // Default to GET
120
+ headers[ACAMethodsName] = []string{http.MethodGet}
121
+ }
122
+
123
+ headers[ACAHeadersName] = cleanHeaderSet(
124
+ append([]string{
125
+ "Content-Type",
126
+ "User-Agent",
127
+ "Range",
128
+ "X-Requested-With",
129
+ }, headers[ACAHeadersName]...))
130
+
131
+ headers[ACEHeadersName] = cleanHeaderSet(
132
+ append([]string{
133
+ "Content-Length",
134
+ "Content-Range",
135
+ "X-Chunked-Output",
136
+ "X-Stream-Output",
137
+ "X-Ipfs-Path",
138
+ "X-Ipfs-Roots",
139
+ }, headers[ACEHeadersName]...))
140
+
141
+ return headers
142
+}
143
+
144
func VersionOption() ServeOption {
145
return func(_ *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
146
mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {
core/corehttp/gateway_handler.go
+9
-9
@@ -24,7 +24,6 @@ import (
24
path "github.com/ipfs/go-path"
25
"github.com/ipfs/go-path/resolver"
26
coreiface "github.com/ipfs/interface-go-ipfs-core"
27
- options "github.com/ipfs/interface-go-ipfs-core/options"
27
ipath "github.com/ipfs/interface-go-ipfs-core/path"
28
routing "github.com/libp2p/go-libp2p-core/routing"
29
prometheus "github.com/prometheus/client_golang/prometheus"
@@ -68,8 +67,8 @@ type redirectTemplateData struct {
67
// (it serves requests like GET /ipfs/QmVRzPKPzNtSrEzBFm2UZfxmPAgnaLke4DMcerbsGGSaFe/link)
68
type gatewayHandler struct {
69
config GatewayConfig
71
- api coreiface.CoreAPI
72
- offlineApi coreiface.CoreAPI
70
+ api NodeAPI
71
+ offlineApi NodeAPI
72
73
// generic metrics
74
firstContentBlockGetMetric *prometheus.HistogramVec
@@ -213,11 +212,12 @@ func newGatewayHistogramMetric(name string, help string) *prometheus.HistogramVe
212
return histogramMetric
213
}
214
216
-func newGatewayHandler(c GatewayConfig, api coreiface.CoreAPI) (*gatewayHandler, error) {
217
- offlineApi, err := api.WithOptions(options.Api.Offline(true))
218
- if err != nil {
219
- return nil, err
220
- }
215
+// NewGatewayHandler returns an http.Handler that can act as a gateway to IPFS content
216
+func NewGatewayHandler(c GatewayConfig, api NodeAPI, offlineApi NodeAPI) http.Handler {
217
+ return newGatewayHandler(c, api, offlineApi)
218
+}
219
+
220
+func newGatewayHandler(c GatewayConfig, api NodeAPI, offlineApi NodeAPI) *gatewayHandler {
221
i := &gatewayHandler{
222
config: c,
223
api: api,
@@ -262,7 +262,7 @@ func newGatewayHandler(c GatewayConfig, api coreiface.CoreAPI) (*gatewayHandler,
262
"The time to receive the first UnixFS node on a GET from the gateway.",
263
),
264
}
265
- return i, nil
265
+ return i
266
}
267
268
func parseIpfsPath(p string) (cid.Cid, string, error) {