Skip to content

Commit

Permalink
feat: --compression-level for gzip responses
Browse files Browse the repository at this point in the history
  • Loading branch information
rvagg committed Oct 5, 2023
1 parent 25a2b0a commit 8b5af30
Show file tree
Hide file tree
Showing 8 changed files with 371 additions and 118 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Full argument list:
* `--log-file` - path to file to append HTTP request and error logs to. Defaults to `stdout`.
* `--max-response-duration` - maximum duration to spend responding to a request. Defaults to `5m`.
* `--max-response-bytes` - maximum size of a response from IPNI. Defaults to `100MiB`.
* `--compression-level` - compression level to use for HTTP response data where the client accepts it. `0`-`9`, `0` is no compression, `9` is maximum compression. Defaults to `0` (none).
* `--verbose` - enable verbose logging. Defaults to `false`. Same as using `GOLOG_LOG_LEVEL=debug` as an environment variable. `GOLOG_LOG_LEVEL` can be used for more fine-grained control of log output.
* `--help` - show help.

Expand Down
10 changes: 10 additions & 0 deletions cmd/frisbii/flags.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"compress/gzip"
"errors"
"net/url"
"path/filepath"
Expand Down Expand Up @@ -55,6 +56,11 @@ var Flags = []cli.Flag{
Usage: "maximum number of bytes to send in a response (use 0 for no limit)",
Value: "100MiB",
},
&cli.IntFlag{
Name: "compression-level",
Usage: "compression level to use for responses, 0-9, 0 is no compression, 9 is maximum compression",
Value: gzip.NoCompression,
},
&cli.BoolFlag{
Name: "verbose",
Usage: "enable verbose debug logging to stderr, same as setting GOLOG_LOG_LEVEL=DEBUG",
Expand All @@ -78,6 +84,7 @@ type Config struct {
LogFile string
MaxResponseDuration time.Duration
MaxResponseBytes int64
CompressionLevel int
Verbose bool
}

Expand Down Expand Up @@ -124,6 +131,8 @@ func ToConfig(c *cli.Context) (Config, error) {
}
}

compressionLevel := c.Int("compression-level")

return Config{
Cars: carPaths,
Listen: listen,
Expand All @@ -134,6 +143,7 @@ func ToConfig(c *cli.Context) (Config, error) {
LogFile: logFile,
MaxResponseDuration: maxResponseDuration,
MaxResponseBytes: int64(maxResponseBytes),
CompressionLevel: compressionLevel,
Verbose: verbose,
}, nil
}
1 change: 1 addition & 0 deletions cmd/frisbii/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ func action(c *cli.Context) error {
config.Listen,
frisbii.WithMaxResponseDuration(config.MaxResponseDuration),
frisbii.WithMaxResponseBytes(config.MaxResponseBytes),
frisbii.WithCompressionLevel(config.CompressionLevel),
)
if err != nil {
return err
Expand Down
9 changes: 6 additions & 3 deletions frisbii.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ func NewFrisbiiServer(
if err != nil {
return nil, err
}
if logWriter == nil {
logWriter = io.Discard
}
return &FrisbiiServer{
ctx: ctx,
logWriter: logWriter,
Expand All @@ -65,14 +68,14 @@ func (fs *FrisbiiServer) Addr() net.Addr {

func (fs *FrisbiiServer) Serve() error {
fs.mux = http.NewServeMux()

fs.mux.Handle("/ipfs/", NewHttpIpfs(fs.ctx, fs.lsys, fs.httpOptions...))
var ipfsHandler http.Handler = NewHttpIpfs(fs.ctx, fs.lsys, fs.httpOptions...)
fs.mux.Handle("/ipfs/", ipfsHandler)
fs.mux.Handle("/", http.NotFoundHandler())
server := &http.Server{
Addr: fs.Addr().String(),
BaseContext: func(listener net.Listener) context.Context { return fs.ctx },
Handler: NewLogMiddleware(fs.mux, fs.logWriter),
}
fs.mux.Handle("/", http.NotFoundHandler())
logger.Debugf("Serve() server on %s", fs.Addr().String())
return server.Serve(fs.listener)
}
Expand Down
170 changes: 170 additions & 0 deletions frisbii_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package frisbii_test

import (
"compress/gzip"
"context"
"io"
"math/rand"
"net/http"
"testing"
"time"

"github.com/ipfs/go-cid"
unixfstestutil "github.com/ipfs/go-unixfsnode/testutil"
"github.com/ipld/frisbii"
"github.com/ipld/go-car/v2"
unixfsgen "github.com/ipld/go-fixtureplate/generator"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
"github.com/ipld/go-ipld-prime/storage/memstore"
"github.com/ipld/go-trustless-utils/testutil"
"github.com/stretchr/testify/require"
)

func TestFrisbiiServer(t *testing.T) {
testCases := []struct {
name string
acceptGzip bool
noClientCompression bool
serverCompressionLevel int
expectGzip bool
}{
{
name: "default",
},
{
name: "no client compression (no server gzip)",
noClientCompression: true,
serverCompressionLevel: gzip.NoCompression,
expectGzip: false,
},
{
name: "no client compression (with server gzip)",
noClientCompression: true,
serverCompressionLevel: gzip.DefaultCompression,
expectGzip: false,
},
{
name: "gzip (with server 1)",
acceptGzip: true,
serverCompressionLevel: gzip.BestSpeed,
expectGzip: true,
},
{
name: "gzip (with server 9)",
acceptGzip: true,
serverCompressionLevel: gzip.BestCompression,
expectGzip: true,
},
{
name: "gzip (no server gzip)",
acceptGzip: true,
serverCompressionLevel: gzip.NoCompression,
expectGzip: false,
},
{
name: "gzip transparent (no server gzip)",
serverCompressionLevel: gzip.NoCompression,
expectGzip: false,
},
{
name: "gzip transparent (with server gzip)",
serverCompressionLevel: gzip.DefaultCompression,
expectGzip: true,
},
}

rndSeed := time.Now().UTC().UnixNano()
t.Logf("random seed: %d", rndSeed)
var rndReader io.Reader = rand.New(rand.NewSource(rndSeed))

store := &testutil.CorrectedMemStore{ParentStore: &memstore.Store{Bag: make(map[string][]byte)}}
lsys := cidlink.DefaultLinkSystem()
lsys.SetReadStorage(store)
lsys.SetWriteStorage(store)
lsys.TrustedStorage = true

entity, err := unixfsgen.Parse("file:1MiB")
require.NoError(t, err)
t.Logf("Generating: %s", entity.Describe(""))
rootEnt, err := entity.Generate(lsys, rndReader)
require.NoError(t, err)

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
req := require.New(t)
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()

opts := []frisbii.HttpOption{}
if tc.serverCompressionLevel != gzip.NoCompression {
opts = append(opts, frisbii.WithCompressionLevel(tc.serverCompressionLevel))
}
server, err := frisbii.NewFrisbiiServer(ctx, nil, lsys, "localhost:0", opts...)
req.NoError(err)
go func() {
req.NoError(server.Serve())
}()
addr := server.Addr()

request, err := http.NewRequest("GET", "http://"+addr.String()+"/ipfs/"+rootEnt.Root.String(), nil)
request.Header.Set("Accept", "application/vnd.ipld.car")
if tc.acceptGzip {
request.Header.Set("Accept-Encoding", "gzip")
}
req.NoError(err)
request = request.WithContext(ctx)
client := &http.Client{Transport: &http.Transport{DisableCompression: tc.noClientCompression}}
response, err := client.Do(request)
req.NoError(err)
if response.StatusCode != http.StatusOK {
body, _ := io.ReadAll(response.Body)
req.Failf("wrong response code not received", "expected %d, got %d; body: [%s]", http.StatusOK, response.StatusCode, string(body))
}

req.Equal("application/vnd.ipld.car;version=1;order=dfs;dups=y", response.Header.Get("Content-Type"))
req.Equal("Accept, Accept-Encoding", response.Header.Get("Vary"))

rdr := response.Body
if tc.expectGzip {
if tc.noClientCompression || tc.acceptGzip { // in either of these cases we expect to handle it ourselves
req.Equal("gzip", response.Header.Get("Content-Encoding"))
rdr, err = gzip.NewReader(response.Body)
req.NoError(err)
} // else should be handled by the go client
req.Regexp(`\.car\.\w{13}\.gz"$`, response.Header.Get("Etag"))
} else {
req.Regexp(`\.car\.\w{13}"$`, response.Header.Get("Etag"))
}
cr, err := car.NewBlockReader(rdr)
req.NoError(err)
req.Equal(cr.Version, uint64(1))
req.Equal(cr.Roots, []cid.Cid{rootEnt.Root})

wantCids := toCids(rootEnt)
gotCids := make([]cid.Cid, 0)
for {
blk, err := cr.Next()
if err != nil {
req.ErrorIs(err, io.EOF)
break
}
req.NoError(err)
gotCids = append(gotCids, blk.Cid())
}
req.ElementsMatch(wantCids, gotCids)
})
}
}

func toCids(e unixfstestutil.DirEntry) []cid.Cid {
cids := make([]cid.Cid, 0)
var r func(e unixfstestutil.DirEntry)
r = func(e unixfstestutil.DirEntry) {
cids = append(cids, e.SelfCids...)
for _, c := range e.Children {
r(c)
}
}
r(e)
return cids
}
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/ipld/frisbii
go 1.20

require (
github.com/NYTimes/gziphandler v1.1.1
github.com/dustin/go-humanize v1.0.1
github.com/ipfs/go-block-format v0.2.0
github.com/ipfs/go-cid v0.4.1
Expand All @@ -12,9 +13,10 @@ require (
github.com/ipfs/go-unixfsnode v1.9.0
github.com/ipld/go-car/v2 v2.13.1
github.com/ipld/go-codec-dagpb v1.6.0
github.com/ipld/go-fixtureplate v0.0.2
github.com/ipld/go-ipld-prime v0.21.0
github.com/ipld/go-trustless-utils v0.3.1
github.com/ipld/ipld/specs v0.0.0-20230907004443-0e4ff95ff474
github.com/ipld/ipld/specs v0.0.0-20230927010225-ef4dbd703269
github.com/ipni/go-libipni v0.5.2
github.com/ipni/index-provider v0.14.2
github.com/ipni/storetheindex v0.8.1
Expand Down
9 changes: 7 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
Expand Down Expand Up @@ -282,15 +284,17 @@ github.com/ipld/go-car/v2 v2.13.1 h1:KnlrKvEPEzr5IZHKTXLAEub+tPrzeAFQVRlSQvuxBO4
github.com/ipld/go-car/v2 v2.13.1/go.mod h1:QkdjjFNGit2GIkpQ953KBwowuoukoM75nP/JI1iDJdo=
github.com/ipld/go-codec-dagpb v1.6.0 h1:9nYazfyu9B1p3NAgfVdpRco3Fs2nFC72DqVsMj6rOcc=
github.com/ipld/go-codec-dagpb v1.6.0/go.mod h1:ANzFhfP2uMJxRBr8CE+WQWs5UsNa0pYtmKZ+agnUw9s=
github.com/ipld/go-fixtureplate v0.0.2 h1:q7dSdIXeyY5v4AAFvB6M1nPWuRzTeYcgpbeqGygSLu8=
github.com/ipld/go-fixtureplate v0.0.2/go.mod h1:z7AnsVOetEl6sQ7j5iMyqVGrS9BgGGE4u69TtsLhJGU=
github.com/ipld/go-ipld-adl-hamt v0.0.0-20220616142416-9004dbd839e0 h1:QAI/Ridj0+foHD6epbxmB4ugxz9B4vmNdYSmQLGa05E=
github.com/ipld/go-ipld-adl-hamt v0.0.0-20220616142416-9004dbd839e0/go.mod h1:odxGcpiQZLzP5+yGu84Ljo8y3EzCvNAQKEodHNsHLXA=
github.com/ipld/go-ipld-prime v0.21.0 h1:n4JmcpOlPDIxBcY037SVfpd1G+Sj1nKZah0m6QH9C2E=
github.com/ipld/go-ipld-prime v0.21.0/go.mod h1:3RLqy//ERg/y5oShXXdx5YIp50cFGOanyMctpPjsvxQ=
github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20230102063945-1a409dc236dd h1:gMlw/MhNr2Wtp5RwGdsW23cs+yCuj9k2ON7i9MiJlRo=
github.com/ipld/go-trustless-utils v0.3.1 h1:i7lPoo7HbThj93wJO1aow3UKdL6AV/jeTLXzTEBZDsE=
github.com/ipld/go-trustless-utils v0.3.1/go.mod h1:SQR5abLVb2YcZiy9QEsBhNyIPj6ubWISJnrpwNBdmpA=
github.com/ipld/ipld/specs v0.0.0-20230907004443-0e4ff95ff474 h1:CiDtcUFyzRwhKyiS4Gn+fzdraMoNVtPHd+wVIekExPc=
github.com/ipld/ipld/specs v0.0.0-20230907004443-0e4ff95ff474/go.mod h1:WcT0DfRe+e2QFY0kcbsOnuT6jL5Q0JNZ83I5DHIdStg=
github.com/ipld/ipld/specs v0.0.0-20230927010225-ef4dbd703269 h1:MXBxKsw8geRJitw8f1dr3EfwbEV0WXVbEH7e/o3p+NI=
github.com/ipld/ipld/specs v0.0.0-20230927010225-ef4dbd703269/go.mod h1:WcT0DfRe+e2QFY0kcbsOnuT6jL5Q0JNZ83I5DHIdStg=
github.com/ipni/go-libipni v0.5.2 h1:9vaYOnR4dskd8p88NOboqI6yVqBwYPNCQ/zOaRSr59I=
github.com/ipni/go-libipni v0.5.2/go.mod h1:UnrhEqjVI2Z2HXlaieOBONJmtW557nZkYpB4IIsMD+s=
github.com/ipni/index-provider v0.14.2 h1:daA3IFnI2n2x/mL0K91SQHNLq6Vvfp5q4uFX9G4glvE=
Expand Down Expand Up @@ -532,6 +536,7 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
Expand Down
Loading

0 comments on commit 8b5af30

Please sign in to comment.