Skip to content

Commit 413c421

Browse files
committed
feat!: NewHttpIpfs constructor takes options; add tests
1 parent bfca501 commit 413c421

File tree

6 files changed

+217
-48
lines changed

6 files changed

+217
-48
lines changed

cmd/frisbii/main.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,14 @@ func action(c *cli.Context) error {
101101
unixfsnode.AddUnixFSReificationToLinkSystem(&lsys)
102102
lsys.SetReadStorage(multicar)
103103

104-
server, err := frisbii.NewFrisbiiServer(ctx, logWriter, lsys, config.MaxResponseDuration, config.MaxResponseBytes, config.Listen)
104+
server, err := frisbii.NewFrisbiiServer(
105+
ctx,
106+
logWriter,
107+
lsys,
108+
config.Listen,
109+
frisbii.WithMaxResponseDuration(config.MaxResponseDuration),
110+
frisbii.WithMaxResponseBytes(config.MaxResponseBytes),
111+
)
105112
if err != nil {
106113
return err
107114
}

frisbii.go

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"io"
77
"net"
88
"net/http"
9-
"time"
109

1110
"github.com/ipfs/go-cid"
1211
"github.com/ipfs/go-log/v2"
@@ -25,11 +24,10 @@ var advMetadata = metadata.Default.New(metadata.IpfsGatewayHttp{})
2524
// HTTP server to serve data according to the Trustless Gateway spec and it
2625
// also provides a mechanism to announce the server to the indexer service.
2726
type FrisbiiServer struct {
28-
ctx context.Context
29-
lsys linking.LinkSystem
30-
logWriter io.Writer
31-
maxResponseDuration time.Duration
32-
maxResponseBytes int64
27+
ctx context.Context
28+
lsys linking.LinkSystem
29+
logWriter io.Writer
30+
httpOptions []HttpOption
3331

3432
listener net.Listener
3533
mux *http.ServeMux
@@ -45,22 +43,19 @@ func NewFrisbiiServer(
4543
ctx context.Context,
4644
logWriter io.Writer,
4745
lsys linking.LinkSystem,
48-
maxResponseDuration time.Duration,
49-
maxResponseBytes int64,
5046
address string,
47+
httpOptions ...HttpOption,
5148
) (*FrisbiiServer, error) {
5249
listener, err := net.Listen("tcp", address)
5350
if err != nil {
5451
return nil, err
5552
}
5653
return &FrisbiiServer{
57-
ctx: ctx,
58-
logWriter: logWriter,
59-
lsys: lsys,
60-
maxResponseDuration: maxResponseDuration,
61-
maxResponseBytes: maxResponseBytes,
62-
63-
listener: listener,
54+
ctx: ctx,
55+
logWriter: logWriter,
56+
lsys: lsys,
57+
httpOptions: httpOptions,
58+
listener: listener,
6459
}, nil
6560
}
6661

@@ -71,7 +66,7 @@ func (fs *FrisbiiServer) Addr() net.Addr {
7166
func (fs *FrisbiiServer) Serve() error {
7267
fs.mux = http.NewServeMux()
7368

74-
fs.mux.Handle("/ipfs/", NewHttpIpfs(fs.ctx, fs.logWriter, fs.lsys, fs.maxResponseDuration, fs.maxResponseBytes))
69+
fs.mux.Handle("/ipfs/", NewHttpIpfs(fs.ctx, fs.lsys, fs.httpOptions...))
7570
server := &http.Server{
7671
Addr: fs.Addr().String(),
7772
BaseContext: func(listener net.Listener) context.Context { return fs.ctx },

go.mod

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ require (
1010
github.com/ipfs/go-ipld-format v0.6.0
1111
github.com/ipfs/go-log/v2 v2.5.1
1212
github.com/ipfs/go-unixfsnode v1.8.0
13-
github.com/ipld/go-car/v2 v2.11.0
13+
github.com/ipld/go-car/v2 v2.12.0
1414
github.com/ipld/go-ipld-prime v0.21.0
1515
github.com/ipld/go-trustless-utils v0.0.0
16+
github.com/ipld/ipld/specs v0.0.0-20230826120441-91918996e8eb
1617
github.com/ipni/go-libipni v0.3.4
1718
github.com/ipni/index-provider v0.13.5
1819
github.com/libp2p/go-libp2p v0.30.0
@@ -70,7 +71,7 @@ require (
7071
github.com/ipfs/go-ipfs-chunker v0.0.5 // indirect
7172
github.com/ipfs/go-ipfs-pq v0.0.3 // indirect
7273
github.com/ipfs/go-ipfs-util v0.0.3 // indirect
73-
github.com/ipfs/go-ipld-cbor v0.0.6 // indirect
74+
github.com/ipfs/go-ipld-cbor v0.1.0 // indirect
7475
github.com/ipfs/go-log v1.0.5 // indirect
7576
github.com/ipfs/go-peertaskqueue v0.8.1 // indirect
7677
github.com/ipld/go-codec-dagpb v1.6.0 // indirect
@@ -130,8 +131,9 @@ require (
130131
github.com/russross/blackfriday/v2 v2.1.0 // indirect
131132
github.com/spaolacci/murmur3 v1.1.0 // indirect
132133
github.com/twmb/murmur3 v1.1.6 // indirect
134+
github.com/warpfork/go-testmark v0.12.1 // indirect
133135
github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect
134-
github.com/whyrusleeping/cbor-gen v0.0.0-20230418232409-daab9ece03a0 // indirect
136+
github.com/whyrusleeping/cbor-gen v0.0.0-20230818171029-f91ae536ca25 // indirect
135137
github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f // indirect
136138
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
137139
go.opentelemetry.io/otel v1.16.0 // indirect

go.sum

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,8 @@ github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyB
246246
github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0=
247247
github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs=
248248
github.com/ipfs/go-ipld-cbor v0.0.3/go.mod h1:wTBtrQZA3SoFKMVkp6cn6HMRteIB1VsmHA0AQFOn7Nc=
249-
github.com/ipfs/go-ipld-cbor v0.0.6 h1:pYuWHyvSpIsOOLw4Jy7NbBkCyzLDcl64Bf/LZW7eBQ0=
250-
github.com/ipfs/go-ipld-cbor v0.0.6/go.mod h1:ssdxxaLJPXH7OjF5V4NSjBbcfh+evoR4ukuru0oPXMA=
249+
github.com/ipfs/go-ipld-cbor v0.1.0 h1:dx0nS0kILVivGhfWuB6dUpMa/LAwElHPw1yOGYopoYs=
250+
github.com/ipfs/go-ipld-cbor v0.1.0/go.mod h1:U2aYlmVrJr2wsUBU67K4KgepApSZddGRDWBYR0H4sCk=
251251
github.com/ipfs/go-ipld-format v0.0.1/go.mod h1:kyJtbkDALmFHv3QR6et67i35QzO3S0dCDnkOJhcZkms=
252252
github.com/ipfs/go-ipld-format v0.0.2/go.mod h1:4B6+FM2u9OJ9zCV+kSbgFAZlOrv1Hqbf0INGQgiKf9k=
253253
github.com/ipfs/go-ipld-format v0.6.0 h1:VEJlA2kQ3LqFSIm5Vu6eIlSxD/Ze90xtc4Meten1F5U=
@@ -271,8 +271,8 @@ github.com/ipfs/go-unixfs v0.4.4 h1:D/dLBOJgny5ZLIur2vIXVQVW0EyDHdOMBDEhgHrt6rY=
271271
github.com/ipfs/go-unixfsnode v1.8.0 h1:yCkakzuE365glu+YkgzZt6p38CSVEBPgngL9ZkfnyQU=
272272
github.com/ipfs/go-unixfsnode v1.8.0/go.mod h1:HxRu9HYHOjK6HUqFBAi++7DVoWAHn0o4v/nZ/VA+0g8=
273273
github.com/ipfs/go-verifcid v0.0.2 h1:XPnUv0XmdH+ZIhLGKg6U2vaPaRDXb9urMyNVCE7uvTs=
274-
github.com/ipld/go-car/v2 v2.11.0 h1:lkAPwbbTFqbdfawgm+bfmFc8PjGC7D12VcaLXPCLNfM=
275-
github.com/ipld/go-car/v2 v2.11.0/go.mod h1:aDszqev0zjtU8l96g4lwXHaU9bzArj56Y7eEN0q/xqA=
274+
github.com/ipld/go-car/v2 v2.12.0 h1:4wpZwCEK2Th7lrVhkAio7fnxZb6COrSHxSz9xCR6FOo=
275+
github.com/ipld/go-car/v2 v2.12.0/go.mod h1:QkdjjFNGit2GIkpQ953KBwowuoukoM75nP/JI1iDJdo=
276276
github.com/ipld/go-codec-dagpb v1.6.0 h1:9nYazfyu9B1p3NAgfVdpRco3Fs2nFC72DqVsMj6rOcc=
277277
github.com/ipld/go-codec-dagpb v1.6.0/go.mod h1:ANzFhfP2uMJxRBr8CE+WQWs5UsNa0pYtmKZ+agnUw9s=
278278
github.com/ipld/go-ipld-adl-hamt v0.0.0-20220616142416-9004dbd839e0 h1:QAI/Ridj0+foHD6epbxmB4ugxz9B4vmNdYSmQLGa05E=
@@ -283,6 +283,7 @@ github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20230102063945-1a409dc236
283283
github.com/ipld/go-trustless-utils v0.0.0 h1:X+hY7lWZSd6kdBGfmGtEIjCUeqETaeahMKjont7+OGo=
284284
github.com/ipld/go-trustless-utils v0.0.0/go.mod h1:rqvDGdgk2acsKiGyV5mzCZZRK8JtnEZqvUBxLuYxR6A=
285285
github.com/ipld/ipld/specs v0.0.0-20230826120441-91918996e8eb h1:5ARxkQ9NqZq33RM7i/Eq3bvBj2RBxx2xE63hqfa+9KY=
286+
github.com/ipld/ipld/specs v0.0.0-20230826120441-91918996e8eb/go.mod h1:AfGlAr20WOjV5PyCowEnGY3pAm5x5i+o0R8IUeir6cs=
286287
github.com/ipni/go-libipni v0.3.4 h1:ZYgCE2TOZt/QJJcBZb+R63FaBLlA2suZGP2IH1fKv4A=
287288
github.com/ipni/go-libipni v0.3.4/go.mod h1:6EIUhN83pd1i6q7SCSCIuuUC3XgR7D/gjKkEnVyIQWE=
288289
github.com/ipni/index-provider v0.13.5 h1:pNOO795k4mR0bwm9npkapSWJld7fYP/8//DMJZi1w/M=
@@ -410,7 +411,6 @@ github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI1
410411
github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U=
411412
github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
412413
github.com/multiformats/go-multihash v0.0.9/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
413-
github.com/multiformats/go-multihash v0.0.10/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
414414
github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc=
415415
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
416416
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
@@ -534,7 +534,9 @@ github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
534534
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
535535
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
536536
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
537+
github.com/warpfork/go-fsx v0.3.0/go.mod h1:oTACCMj+Zle+vgVa5SAhGAh7WksYpLgGUCKEAVc+xPg=
537538
github.com/warpfork/go-testmark v0.12.1 h1:rMgCpJfwy1sJ50x0M0NgyphxYYPMOODIJHhsXyEHU0s=
539+
github.com/warpfork/go-testmark v0.12.1/go.mod h1:kHwy7wfvGSPh1rQJYKayD4AbtNaeyZdcGi9tNJTaa5Y=
538540
github.com/warpfork/go-wish v0.0.0-20180510122957-5ad1f5abf436/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
539541
github.com/warpfork/go-wish v0.0.0-20190328234359-8b3e70f8e830/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
540542
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ=
@@ -545,8 +547,8 @@ github.com/whyrusleeping/cbor-gen v0.0.0-20191216205031-b047b6acb3c0/go.mod h1:x
545547
github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI=
546548
github.com/whyrusleeping/cbor-gen v0.0.0-20200710004633-5379fc63235d/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ=
547549
github.com/whyrusleeping/cbor-gen v0.0.0-20200826160007-0b9f6c5fb163/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ=
548-
github.com/whyrusleeping/cbor-gen v0.0.0-20230418232409-daab9ece03a0 h1:XYEgH2nJgsrcrj32p+SAbx6T3s/6QknOXezXtz7kzbg=
549-
github.com/whyrusleeping/cbor-gen v0.0.0-20230418232409-daab9ece03a0/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ=
550+
github.com/whyrusleeping/cbor-gen v0.0.0-20230818171029-f91ae536ca25 h1:yVYDLoN2gmB3OdBXFW8e1UwgVbmCvNlnAKhvHPaNARI=
551+
github.com/whyrusleeping/cbor-gen v0.0.0-20230818171029-f91ae536ca25/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ=
550552
github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E=
551553
github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8=
552554
github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM=

httpipfs.go

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,49 +25,77 @@ type ErrorLogger interface {
2525
// HttpIpfs is an http.Handler that serves IPLD data via HTTP according to the
2626
// Trustless Gateway specification.
2727
type HttpIpfs struct {
28-
ctx context.Context
29-
logWriter io.Writer
30-
lsys linking.LinkSystem
31-
maxResponseDuration time.Duration
32-
maxResponseBytes int64
28+
ctx context.Context
29+
lsys linking.LinkSystem
30+
cfg *httpOptions
31+
}
32+
33+
type httpOptions struct {
34+
MaxResponseDuration time.Duration
35+
MaxResponseBytes int64
36+
}
37+
38+
type HttpOption func(*httpOptions)
39+
40+
// WithMaxResponseDuration sets the maximum duration for a response to be
41+
// streamed before the connection is closed. This allows a server to limit the
42+
// amount of time a client can hold a connection open; and also restricts the
43+
// ability to serve very large DAGs.
44+
//
45+
// A value of 0 will disable the limitation. This is the default.
46+
func WithMaxResponseDuration(d time.Duration) HttpOption {
47+
return func(o *httpOptions) {
48+
o.MaxResponseDuration = d
49+
}
50+
}
51+
52+
// WithMaxResponseBytes sets the maximum number of bytes that will be streamed
53+
// before the connection is closed. This allows a server to limit the amount of
54+
// data a client can request; and also restricts the ability to serve very large
55+
// DAGs.
56+
//
57+
// A value of 0 will disable the limitation. This is the default.
58+
func WithMaxResponseBytes(b int64) HttpOption {
59+
return func(o *httpOptions) {
60+
o.MaxResponseBytes = b
61+
}
3362
}
3463

3564
func NewHttpIpfs(
3665
ctx context.Context,
37-
logWriter io.Writer,
3866
lsys linking.LinkSystem,
39-
maxResponseDuration time.Duration,
40-
maxResponseBytes int64,
67+
opts ...HttpOption,
4168
) *HttpIpfs {
69+
cfg := &httpOptions{}
70+
for _, opt := range opts {
71+
opt(cfg)
72+
}
4273

4374
return &HttpIpfs{
44-
ctx: ctx,
45-
logWriter: logWriter,
46-
lsys: lsys,
47-
maxResponseDuration: maxResponseDuration,
48-
maxResponseBytes: maxResponseBytes,
75+
ctx: ctx,
76+
lsys: lsys,
77+
cfg: cfg,
4978
}
5079
}
5180

5281
func (hi *HttpIpfs) ServeHTTP(res http.ResponseWriter, req *http.Request) {
5382
ctx := hi.ctx
54-
if hi.maxResponseDuration > 0 {
83+
if hi.cfg.MaxResponseDuration > 0 {
5584
var cancel context.CancelFunc
56-
ctx, cancel = context.WithTimeout(ctx, hi.maxResponseDuration)
85+
ctx, cancel = context.WithTimeout(ctx, hi.cfg.MaxResponseDuration)
5786
defer cancel()
5887
}
5988

6089
logError := func(status int, err error) {
90+
res.WriteHeader(status)
91+
res.Write([]byte(err.Error()))
6192
if lrw, ok := res.(ErrorLogger); ok {
6293
lrw.LogError(status, err)
6394
} else {
64-
logger.Debug("Error handling request from [%s] for [%s] status=%d, msg=%s", req.RemoteAddr, req.URL, status, err.Error())
95+
logger.Debugf("Error handling request from [%s] for [%s] status=%d, msg=%s", req.RemoteAddr, req.URL, status, err.Error())
6596
}
6697
}
6798

68-
path := datamodel.ParsePath(req.URL.Path)
69-
_, path = path.Shift() // remove /ipfs
70-
7199
// filter out everything but GET requests
72100
switch req.Method {
73101
case http.MethodGet:
@@ -78,6 +106,9 @@ func (hi *HttpIpfs) ServeHTTP(res http.ResponseWriter, req *http.Request) {
78106
return
79107
}
80108

109+
path := datamodel.ParsePath(req.URL.Path)
110+
_, path = path.Shift() // remove /ipfs
111+
81112
// check if CID path param is missing
82113
if path.Len() == 0 {
83114
// not a valid path to hit
@@ -102,7 +133,7 @@ func (hi *HttpIpfs) ServeHTTP(res http.ResponseWriter, req *http.Request) {
102133
cidSeg, path = path.Shift()
103134
rootCid, err := cid.Parse(cidSeg.String())
104135
if err != nil {
105-
logError(http.StatusInternalServerError, errors.New("failed to parse CID path parameter"))
136+
logError(http.StatusBadRequest, errors.New("failed to parse CID path parameter"))
106137
return
107138
}
108139

@@ -131,7 +162,7 @@ func (hi *HttpIpfs) ServeHTTP(res http.ResponseWriter, req *http.Request) {
131162
}
132163

133164
bytesWrittenCh := make(chan struct{})
134-
writer := newIpfsResponseWriter(res, hi.maxResponseBytes, func() {
165+
writer := newIpfsResponseWriter(res, hi.cfg.MaxResponseBytes, func() {
135166
// called once we start writing blocks into the CAR (on the first Put())
136167
res.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", fileName))
137168
res.Header().Set("Cache-Control", trustlesshttp.ResponseCacheControlHeader)

0 commit comments

Comments
 (0)