From baa096c4f4e6824189792c379aebb0d408aef4ec Mon Sep 17 00:00:00 2001 From: ltdk Date: Sun, 8 Sep 2024 23:31:23 -0400 Subject: [PATCH] Implement static serving of compressed files --- server/upstream/upstream.go | 93 ++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go index d9c131e..f02e4c0 100644 --- a/server/upstream/upstream.go +++ b/server/upstream/upstream.go @@ -1,10 +1,13 @@ package upstream import ( + "cmp" "errors" "fmt" "io" "net/http" + "slices" + "strconv" "strings" "time" @@ -19,6 +22,8 @@ import ( const ( headerLastModified = "Last-Modified" headerIfModifiedSince = "If-Modified-Since" + headerAcceptEncoding = "Accept-Encoding" + headerContentEncoding = "Content-Encoding" rawMime = "text/plain; charset=utf-8" ) @@ -52,6 +57,73 @@ type Options struct { ServeRaw bool } +// allowed encodings +var allowedEncodings = map[string]string{ + "gzip": ".gz", + "br": ".br", + "zstd": ".zstd", + "identity": "", +} + +// parses Accept-Encoding header into a list of acceptable encodings +func AcceptEncodings(header string) []string { + log.Trace().Msgf("got accept-encoding: %s", header) + encodings := []string{} + globQuality := 0.0 + qualities := make(map[string]float64) + + for _, encoding := range strings.Split(header, ",") { + splits := strings.SplitN(encoding, ";q=", 2) + name := splits[0] + quality := 1.0 + + if len(splits) > 1 { + var err error + quality, err = strconv.ParseFloat(splits[1], 64) + if err != nil || quality < 0 { + continue + } + } + + name = strings.TrimSpace(name) + + if name == "*" { + globQuality = quality + } else { + _, allowed := allowedEncodings[name] + if allowed { + qualities[name] = quality + if quality > 0 { + encodings = append(encodings, name) + } + } + } + } + + if globQuality > 0 { + for encoding := range allowedEncodings { + _, exists := qualities[encoding] + if !exists { + encodings = append(encodings, encoding) + qualities[encoding] = globQuality + } + } + } else { + _, exists := qualities["identity"] + if !exists { + encodings = append(encodings, "identity") + qualities["identity"] = -1 + } + } + + slices.SortStableFunc(encodings, func(x, y string) int { + // sort in reverse order; big quality comes first + return cmp.Compare(qualities[y], qualities[x]) + }) + log.Trace().Msgf("decided encoding order: %#v", encodings) + return encodings +} + // Upstream requests a file from the Gitea API at GiteaRoot and writes it to the request context. func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redirectsCache cache.ICache) bool { log := log.With().Strs("upstream", []string{o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath}).Logger() @@ -97,7 +169,26 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redi log.Debug().Msg("Preparing") - reader, header, statusCode, err := giteaClient.ServeRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath) + // pick first non-404 response for encoding + var reader io.ReadCloser + var header http.Header + var statusCode int + var err error + for _, encoding := range AcceptEncodings(ctx.Req.Header.Get(headerAcceptEncoding)) { + log.Trace().Msgf("try %s encoding", encoding) + + // add extension for encoding + path := o.TargetPath + allowedEncodings[encoding] + reader, header, statusCode, err = giteaClient.ServeRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, path) + if statusCode == 404 { + continue + } + log.Debug().Msgf("using %s encoding", encoding) + if encoding != "identity" { + header.Set(headerContentEncoding, encoding) + } + } + if reader != nil { defer reader.Close() }