fup: factor out metadata retrieval into a separate file
We'll need this for cleanup operations as well. This should likely be factored out again into an entirely separate package that deals with storage access.
This commit is contained in:
parent
7bac0aee74
commit
4c4bd46aa8
3 changed files with 117 additions and 37 deletions
42
web/fup/fuphttp/errors.go
Normal file
42
web/fup/fuphttp/errors.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package fuphttp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gocloud.dev/gcerrors"
|
||||
)
|
||||
|
||||
type fupError struct {
|
||||
Code gcerrors.ErrorCode
|
||||
msg string
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *fupError) Error() string {
|
||||
var msg string
|
||||
if e.msg == "" {
|
||||
msg = fmt.Sprintf("code=%v", e.Code)
|
||||
} else {
|
||||
msg = fmt.Sprintf("%s (code=%v)", e.msg, e.Code)
|
||||
}
|
||||
if e.err != nil {
|
||||
msg = fmt.Sprintf("%s: %s", msg, e.err)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func (e *fupError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
func errorCode(err error) gcerrors.ErrorCode {
|
||||
if err == nil {
|
||||
return gcerrors.OK
|
||||
}
|
||||
var e *fupError
|
||||
if errors.As(err, &e) {
|
||||
return e.Code
|
||||
}
|
||||
return gcerrors.Code(err)
|
||||
}
|
|
@ -11,7 +11,6 @@ import (
|
|||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/google/safehtml"
|
||||
|
@ -90,52 +89,25 @@ func (a *Application) rawDownload(rw http.ResponseWriter, r *http.Request) {
|
|||
vars := mux.Vars(r)
|
||||
filename := vars["filename"]
|
||||
|
||||
attrs, err := a.storageBackend.Attributes(ctx, filename)
|
||||
switch gcerrors.Code(err) {
|
||||
meta, err := metadata(ctx, a.storageBackend, filename)
|
||||
switch errorCode(err) {
|
||||
case gcerrors.NotFound:
|
||||
a.notFound(rw, r)
|
||||
return
|
||||
default:
|
||||
log.Printf("rawDownload(%q) Attributes: %v", filename, err)
|
||||
log.Printf("rawDownload(%q) metadata: %v", filename, err)
|
||||
a.internalError(rw, r)
|
||||
return
|
||||
case gcerrors.OK:
|
||||
// OK
|
||||
}
|
||||
|
||||
var exp time.Time
|
||||
if expStr, ok := attrs.Metadata["expires-at"]; ok {
|
||||
// Check for expiry.
|
||||
expSec, err := strconv.ParseInt(expStr, 10, 64)
|
||||
if err != nil {
|
||||
log.Printf("parsing expiration %q for %q: %v", expStr, filename, err)
|
||||
a.internalError(rw, r)
|
||||
return
|
||||
}
|
||||
|
||||
exp = time.Unix(expSec, 0)
|
||||
if exp.Before(time.Now()) {
|
||||
// This file has expired. Delete it from the backing storage.
|
||||
err := a.storageBackend.Delete(ctx, filename)
|
||||
switch gcerrors.Code(err) {
|
||||
case gcerrors.NotFound:
|
||||
// Something probably deleted it before we could get there.
|
||||
case gcerrors.OK:
|
||||
// This was fine.
|
||||
default:
|
||||
log.Printf("deleting expired file %q: %v", filename, err)
|
||||
}
|
||||
a.notFound(rw, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Do things with attributes?
|
||||
if a.redirectToBlobstore {
|
||||
redirectExpiry := a.redirectExpiry
|
||||
if !exp.IsZero() {
|
||||
if !meta.ExpiresAt.IsZero() {
|
||||
// Cap the redirect lifetime to the remaining lifetime of the object.
|
||||
remainingLifetime := exp.Sub(time.Now())
|
||||
remainingLifetime := meta.ExpiresAt.Sub(time.Now())
|
||||
if remainingLifetime < redirectExpiry {
|
||||
redirectExpiry = remainingLifetime
|
||||
}
|
||||
|
@ -143,7 +115,7 @@ func (a *Application) rawDownload(rw http.ResponseWriter, r *http.Request) {
|
|||
u, err := a.storageBackend.SignedURL(ctx, filename, &blob.SignedURLOptions{
|
||||
Expiry: redirectExpiry,
|
||||
})
|
||||
switch gcerrors.Code(err) {
|
||||
switch errorCode(err) {
|
||||
case gcerrors.NotFound:
|
||||
// This is unlikely to get returned.
|
||||
a.notFound(rw, r)
|
||||
|
@ -159,13 +131,13 @@ func (a *Application) rawDownload(rw http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
// Perform direct serving.
|
||||
a.rawDownloadDirect(ctx, rw, r, filename, attrs)
|
||||
a.rawDownloadDirect(ctx, rw, r, filename, meta)
|
||||
}
|
||||
|
||||
func (a *Application) rawDownloadDirect(ctx context.Context, rw http.ResponseWriter, r *http.Request, filename string, attrs *blob.Attributes) {
|
||||
func (a *Application) rawDownloadDirect(ctx context.Context, rw http.ResponseWriter, r *http.Request, filename string, meta *Metadata) {
|
||||
// TODO(lukegb): Range header and conditionals?.
|
||||
rdr, err := a.storageBackend.NewReader(ctx, filename, nil)
|
||||
switch gcerrors.Code(err) {
|
||||
switch errorCode(err) {
|
||||
case gcerrors.NotFound:
|
||||
a.notFound(rw, r)
|
||||
return
|
||||
|
@ -177,6 +149,7 @@ func (a *Application) rawDownloadDirect(ctx context.Context, rw http.ResponseWri
|
|||
}
|
||||
defer rdr.Close()
|
||||
|
||||
attrs := meta.Attributes
|
||||
if v := attrs.CacheControl; v != "" {
|
||||
rw.Header().Set("Cache-Control", v)
|
||||
}
|
||||
|
|
65
web/fup/fuphttp/metadata.go
Normal file
65
web/fup/fuphttp/metadata.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package fuphttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"gocloud.dev/blob"
|
||||
"gocloud.dev/gcerrors"
|
||||
)
|
||||
|
||||
type Metadata struct {
|
||||
// ExpiresAt is the expiry time of the object.
|
||||
// May be the zero time, if this object does not expire.
|
||||
// Stored in expires-at.
|
||||
ExpiresAt time.Time
|
||||
|
||||
// Attributes is the underlying gocloud Attributes.
|
||||
Attributes *blob.Attributes
|
||||
}
|
||||
|
||||
// metadata retrieves the Metadata for the object.
|
||||
// Note: if the object is expired, it will delete it.
|
||||
func metadata(ctx context.Context, bucket *blob.Bucket, filename string) (*Metadata, error) {
|
||||
attrs, err := bucket.Attributes(ctx, filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Attributes(%q): %w", filename, err)
|
||||
}
|
||||
|
||||
m := Metadata{
|
||||
Attributes: attrs,
|
||||
}
|
||||
|
||||
var exp time.Time
|
||||
if expStr, ok := attrs.Metadata["expires-at"]; ok {
|
||||
// Check for expiry.
|
||||
expSec, err := strconv.ParseInt(expStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing expiration %q for %q: %v", expStr, filename, err)
|
||||
}
|
||||
|
||||
exp = time.Unix(expSec, 0)
|
||||
if exp.Before(time.Now()) {
|
||||
// This file has expired. Delete it from the backing storage.
|
||||
err := bucket.Delete(ctx, filename)
|
||||
switch errorCode(err) {
|
||||
case gcerrors.NotFound:
|
||||
// Something probably deleted it before we could get there.
|
||||
case gcerrors.OK:
|
||||
// This was fine.
|
||||
default:
|
||||
log.Printf("deleting expired file %q: %v", filename, err)
|
||||
}
|
||||
return nil, &fupError{
|
||||
Code: gcerrors.NotFound,
|
||||
}
|
||||
}
|
||||
|
||||
m.ExpiresAt = exp
|
||||
}
|
||||
|
||||
return &m, nil
|
||||
}
|
Loading…
Reference in a new issue