feat: add etag for raw api (#2018)

ritik/code-1773
Abhinav Singh 2024-05-03 20:03:37 +00:00 committed by Harness
parent a692707c6c
commit a576087694
5 changed files with 72 additions and 9 deletions

View File

@ -22,6 +22,7 @@ import (
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/git"
"github.com/harness/gitness/git/sha"
"github.com/harness/gitness/types/enum"
)
@ -32,10 +33,10 @@ func (c *Controller) Raw(ctx context.Context,
repoRef string,
gitRef string,
path string,
) (io.ReadCloser, int64, error) {
) (io.ReadCloser, int64, sha.SHA, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
if err != nil {
return nil, 0, err
return nil, 0, sha.Nil, err
}
// set gitRef to default branch in case an empty reference was provided
@ -52,12 +53,12 @@ func (c *Controller) Raw(ctx context.Context,
IncludeLatestCommit: false,
})
if err != nil {
return nil, 0, fmt.Errorf("failed to read tree node: %w", err)
return nil, 0, sha.Nil, fmt.Errorf("failed to read tree node: %w", err)
}
// viewing Raw content is only supported for blob content
if treeNodeOutput.Node.Type != git.TreeNodeTypeBlob {
return nil, 0, usererror.BadRequestf(
return nil, 0, sha.Nil, usererror.BadRequestf(
"Object in '%s' at '/%s' is of type '%s'. Only objects of type %s support raw viewing.",
gitRef, path, treeNodeOutput.Node.Type, git.TreeNodeTypeBlob)
}
@ -68,8 +69,8 @@ func (c *Controller) Raw(ctx context.Context,
SizeLimit: 0, // no size limit, we stream whatever data there is
})
if err != nil {
return nil, 0, fmt.Errorf("failed to read blob: %w", err)
return nil, 0, sha.Nil, fmt.Errorf("failed to read blob: %w", err)
}
return blobReader.Content, blobReader.ContentSize, nil
return blobReader.Content, blobReader.ContentSize, blobReader.SHA, nil
}

View File

@ -41,7 +41,7 @@ func HandleRaw(repoCtrl *repo.Controller) http.HandlerFunc {
gitRef := request.GetGitRefFromQueryOrDefault(r, "")
path := request.GetOptionalRemainderFromPath(r)
dataReader, dataLength, err := repoCtrl.Raw(ctx, session, repoRef, gitRef, path)
dataReader, dataLength, sha, err := repoCtrl.Raw(ctx, session, repoRef, gitRef, path)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
@ -53,8 +53,14 @@ func HandleRaw(repoCtrl *repo.Controller) http.HandlerFunc {
}
}()
w.Header().Add("Content-Length", fmt.Sprint(dataLength))
ifNoneMatch, ok := request.GetIfNoneMatchFromHeader(r)
if ok && ifNoneMatch == sha.String() {
w.WriteHeader(http.StatusNotModified)
return
}
w.Header().Add("Content-Length", fmt.Sprint(dataLength))
w.Header().Add(request.HeaderETag, sha.String())
render.Reader(ctx, w, http.StatusOK, dataReader)
}
}

View File

@ -0,0 +1,48 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nocache
import (
"net/http"
"time"
)
// Ported from Chi's middleware, source:
// https://github.com/go-chi/chi/blob/v5.0.12/middleware/nocache.go
// Modified the middleware to retain ETags.
var epoch = time.Unix(0, 0).Format(time.RFC1123)
var noCacheHeaders = map[string]string{
"Expires": epoch,
"Cache-Control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0",
"Pragma": "no-cache",
"X-Accel-Expires": "0",
}
// NoCache is same as chi's default NoCache middleware except it doesn't remove etag headers.
func NoCache(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
// Set our NoCache headers
for k, v := range noCacheHeaders {
w.Header().Set(k, v)
}
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}

View File

@ -55,6 +55,9 @@ const (
HeaderUserAgent = "User-Agent"
HeaderAuthorization = "Authorization"
HeaderContentEncoding = "Content-Encoding"
HeaderIfNoneMatch = "If-None-Match"
HeaderETag = "ETag"
)
// GetOptionalRemainderFromPath returns the remainder ("*") from the path or an empty string if it doesn't exist.
@ -166,3 +169,7 @@ func GetDeletedBeforeOrAtFromQuery(r *http.Request) (int64, bool, error) {
func GetDeletedAtFromQuery(r *http.Request) (int64, bool, error) {
return QueryParamAsPositiveInt64(r, QueryParamDeletedAt)
}
func GetIfNoneMatchFromHeader(r *http.Request) (string, bool) {
return GetHeader(r, HeaderIfNoneMatch)
}

View File

@ -68,6 +68,7 @@ import (
middlewareauthn "github.com/harness/gitness/app/api/middleware/authn"
"github.com/harness/gitness/app/api/middleware/encode"
"github.com/harness/gitness/app/api/middleware/logging"
"github.com/harness/gitness/app/api/middleware/nocache"
middlewareprincipal "github.com/harness/gitness/app/api/middleware/principal"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/app/auth/authn"
@ -126,7 +127,7 @@ func NewAPIHandler(
r := chi.NewRouter()
// Apply common api middleware.
r.Use(middleware.NoCache)
r.Use(nocache.NoCache)
r.Use(middleware.Recoverer)
// configure logging middleware.