gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 23034ec145662b9edcd98df3a570e968389e3fa8
parent e55382acd6152e4375e2de77ce6f00ee797481b2
Author: tobi <31960611+tsmethurst@users.noreply.github.com>
Date:   Sat, 19 Feb 2022 11:44:56 +0100

[feature] Stream files via reader (#404)

* serve files via reader rather than byte slice

* close readcloser when we're done with it

* cast reader to readcloser
Diffstat:
Minternal/api/client/fileserver/servefile.go | 13+++++++++++--
Minternal/api/model/content.go | 6++++--
Minternal/processing/media/getfile.go | 9++++++---
3 files changed, 21 insertions(+), 7 deletions(-)

diff --git a/internal/api/client/fileserver/servefile.go b/internal/api/client/fileserver/servefile.go @@ -19,7 +19,7 @@ package fileserver import ( - "bytes" + "io" "net/http" "github.com/gin-gonic/gin" @@ -91,6 +91,15 @@ func (m *FileServer) ServeFile(c *gin.Context) { return } + defer func() { + // if the content is a ReadCloser, close it when we're done + if closer, ok := content.Content.(io.ReadCloser); ok { + if err := closer.Close(); err != nil { + l.Errorf("error closing readcloser: %s", err) + } + } + }() + // TODO: if the requester only accepts text/html we should try to serve them *something*. // This is mostly needed because when sharing a link to a gts-hosted file on something like mastodon, the masto servers will // attempt to look up the content to provide a preview of the link, and they ask for text/html. @@ -100,5 +109,5 @@ func (m *FileServer) ServeFile(c *gin.Context) { return } - c.DataFromReader(http.StatusOK, content.ContentLength, format, bytes.NewReader(content.Content), nil) + c.DataFromReader(http.StatusOK, content.ContentLength, format, content.Content, nil) } diff --git a/internal/api/model/content.go b/internal/api/model/content.go @@ -18,14 +18,16 @@ package model +import "io" + // Content wraps everything needed to serve a blob of content (some kind of media) through the API. type Content struct { // MIME content type ContentType string // ContentLength in bytes ContentLength int64 - // Actual content blob - Content []byte + // Actual content + Content io.Reader } // GetContentRequestForm describes a piece of content desired by the caller of the fileserver API. diff --git a/internal/processing/media/getfile.go b/internal/processing/media/getfile.go @@ -83,9 +83,11 @@ func (p *processor) GetFile(ctx context.Context, account *gtsmodel.Account, form switch mediaSize { case media.SizeOriginal: content.ContentType = e.ImageContentType + content.ContentLength = int64(e.ImageFileSize) storagePath = e.ImagePath case media.SizeStatic: content.ContentType = e.ImageStaticContentType + content.ContentLength = int64(e.ImageStaticFileSize) storagePath = e.ImageStaticPath default: return nil, gtserror.NewErrorNotFound(fmt.Errorf("media size %s not recognized for emoji", mediaSize)) @@ -101,21 +103,22 @@ func (p *processor) GetFile(ctx context.Context, account *gtsmodel.Account, form switch mediaSize { case media.SizeOriginal: content.ContentType = a.File.ContentType + content.ContentLength = int64(a.File.FileSize) storagePath = a.File.Path case media.SizeSmall: content.ContentType = a.Thumbnail.ContentType + content.ContentLength = int64(a.Thumbnail.FileSize) storagePath = a.Thumbnail.Path default: return nil, gtserror.NewErrorNotFound(fmt.Errorf("media size %s not recognized for attachment", mediaSize)) } } - bytes, err := p.storage.Get(storagePath) + reader, err := p.storage.GetStream(storagePath) if err != nil { return nil, gtserror.NewErrorNotFound(fmt.Errorf("error retrieving from storage: %s", err)) } - content.ContentLength = int64(len(bytes)) - content.Content = bytes + content.Content = reader return content, nil }