From 5b270b2be5558634e1dd8355617dcc1e861d9d1d Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Fri, 10 Jun 2022 14:50:10 +0200 Subject: [PATCH] feat: add .tar.gz gateway support License: MIT Signed-off-by: Henrique Dias --- core/corehttp/gateway_handler.go | 10 ++++++++-- core/corehttp/gateway_handler_tar.go | 23 ++++++++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index 3b41570eef8..903e17d1155 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -432,7 +432,11 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request return case "application/x-tar": logger.Debugw("serving tar file", "path", contentPath) - i.serveTAR(r.Context(), w, r, resolvedPath, contentPath, begin, logger) + i.serveTAR(r.Context(), w, r, resolvedPath, contentPath, begin, logger, false) + return + case "application/x-tar+gzip": + logger.Debugw("serving tar file", "path", contentPath) + i.serveTAR(r.Context(), w, r, resolvedPath, contentPath, begin, logger, true) return default: // catch-all for unsuported application/vnd.* err := fmt.Errorf("unsupported format %q", responseFormat) @@ -878,7 +882,7 @@ func getEtag(r *http.Request, cid cid.Cid) string { // Etag: "cid.foo" (gives us nice compression together with Content-Disposition in block (raw) and car responses) suffix = `.` + f + suffix // Since different TAR implementations may produce different byte-for-byte responses, we define a weak Etag. - if responseFormat == "application/x-tar" { + if strings.HasPrefix(responseFormat, "application/x-tar") { prefix = "W/" + prefix } } @@ -897,6 +901,8 @@ func customResponseFormat(r *http.Request) (mediaType string, params map[string] return "application/vnd.ipld.car", nil, nil case "tar": return "application/x-tar", nil, nil + case "tar.gz": + return "application/x-tar+gzip", nil, nil } } // Browsers and other user agents will send Accept header with generic types like: diff --git a/core/corehttp/gateway_handler_tar.go b/core/corehttp/gateway_handler_tar.go index bbf60cefeda..0abbe5136c8 100644 --- a/core/corehttp/gateway_handler_tar.go +++ b/core/corehttp/gateway_handler_tar.go @@ -1,8 +1,10 @@ package corehttp import ( + "compress/gzip" "context" "html" + "io" "net/http" "time" @@ -16,7 +18,7 @@ import ( var unixEpochTime = time.Unix(0, 0) -func (i *gatewayHandler) serveTAR(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, begin time.Time, logger *zap.SugaredLogger) { +func (i *gatewayHandler) serveTAR(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, begin time.Time, logger *zap.SugaredLogger, compressed bool) { ctx, span := tracing.Span(ctx, "Gateway", "ServeTAR", trace.WithAttributes(attribute.String("path", resolvedPath.String()))) defer span.End() @@ -38,6 +40,9 @@ func (i *gatewayHandler) serveTAR(ctx context.Context, w http.ResponseWriter, r name = urlFilename } else { name = resolvedPath.Cid().String() + ".tar" + if compressed { + name += ".gz" + } } setContentDispositionHeader(w, name, "attachment") @@ -49,8 +54,19 @@ func (i *gatewayHandler) serveTAR(ctx context.Context, w http.ResponseWriter, r } defer file.Close() + // Define the output writer, maybe build a Gzip writer + var dstw io.Writer + if compressed { + gzipw := gzip.NewWriter(w) + defer gzipw.Close() + + dstw = gzipw + } else { + dstw = w + } + // Construct the TAR writer - tarw, err := files.NewTarWriter(w) + tarw, err := files.NewTarWriter(dstw) if err != nil { webError(w, "could not build tar writer", err, http.StatusInternalServerError) return @@ -64,7 +80,8 @@ func (i *gatewayHandler) serveTAR(ctx context.Context, w http.ResponseWriter, r w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat)) } - w.Header().Set("Content-Type", "application/x-tar") + responseFormat, _, _ := customResponseFormat(r) + w.Header().Set("Content-Type", responseFormat) if err := tarw.WriteFile(file, name); err != nil { // We return error as a trailer, however it is not something browsers can access