// SPDX-FileCopyrightText: 2021 Luke Granger-Brown // // SPDX-License-Identifier: Apache-2.0 package fuphttp import ( "context" "fmt" "io" "log" "net/http" "time" "github.com/gorilla/mux" "gocloud.dev/blob" "gocloud.dev/gcerrors" ) func (a *Application) rawDownload(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() vars := mux.Vars(r) filename := vars["filename"] meta, err := metadata(ctx, a.storageBackend, filename) switch errorCode(err) { case gcerrors.NotFound: a.notFound(rw, r) return default: log.Printf("rawDownload(%q) metadata: %v", filename, err) a.internalError(rw, r) return case gcerrors.OK: // OK } // Do things with attributes? if a.redirectToBlobstore { redirectExpiry := a.redirectExpiry if !meta.ExpiresAt.IsZero() { // Cap the redirect lifetime to the remaining lifetime of the object. remainingLifetime := meta.ExpiresAt.Sub(time.Now()) if remainingLifetime < redirectExpiry { redirectExpiry = remainingLifetime } } u, err := a.storageBackend.SignedURL(ctx, filename, &blob.SignedURLOptions{ Expiry: redirectExpiry, }) switch errorCode(err) { case gcerrors.NotFound: // This is unlikely to get returned. a.notFound(rw, r) return case gcerrors.OK: http.Redirect(rw, r, u, http.StatusFound) return case gcerrors.Unimplemented: // Fall back to serving directly. default: log.Printf("rawDownload(%q) SignedURL (continuing with fallback): %v", filename, err) } } // Perform direct serving. a.rawDownloadDirect(ctx, rw, r, filename, meta) } 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 errorCode(err) { case gcerrors.NotFound: a.notFound(rw, r) return default: a.internalError(rw, r) return case gcerrors.OK: // OK } defer rdr.Close() attrs := meta.Attributes if v := attrs.CacheControl; v != "" { rw.Header().Set("Cache-Control", v) } if v := attrs.ContentDisposition; v != "" { rw.Header().Set("Content-Disposition", v) } if v := attrs.ContentEncoding; v != "" { rw.Header().Set("Content-Encoding", v) } if v := attrs.ContentLanguage; v != "" { rw.Header().Set("Content-Language", v) } if v := attrs.ContentType; v != "" { rw.Header().Set("Content-Type", v) } if v := attrs.ETag; v != "" { rw.Header().Set("ETag", v) } // If we're serving from local disk, use http.ServeContent, using this somewhat opaque method. var ior io.Reader if rdr.As(&ior) { if iors, ok := ior.(io.ReadSeeker); ok { http.ServeContent(rw, r, filename, attrs.ModTime, iors) return } } if v := attrs.ModTime; !v.IsZero() { rw.Header().Set("Last-Modified", v.UTC().Format(http.TimeFormat)) } if v := attrs.Size; v != 0 { rw.Header().Set("Content-Length", fmt.Sprintf("%d", v)) } if _, err := io.Copy(rw, rdr); err != nil { log.Printf("rawDownloadDirect(%q) copy: %v", filename, err) } }