3
0

Cleanup attachment serving logic

Remove old workarounds in rest/handlers and replace them with
simple func(http.ResponseWriter, *http.Request) that we return
from controller functions
This commit is contained in:
Denis Arh
2019-04-27 15:25:02 +02:00
parent e9475feda0
commit ba8b77e4e1
3 changed files with 35 additions and 114 deletions
+34 -41
View File
@@ -3,15 +3,15 @@ package rest
import (
"context"
"io"
"time"
"net/http"
"net/url"
"github.com/pkg/errors"
"github.com/crusttech/crust/internal/auth"
"github.com/crusttech/crust/messaging/internal/repository"
"github.com/crusttech/crust/messaging/internal/service"
"github.com/crusttech/crust/messaging/rest/handlers"
"github.com/crusttech/crust/messaging/rest/request"
"github.com/crusttech/crust/messaging/types"
)
var _ = errors.Wrap
@@ -20,12 +20,6 @@ type (
Attachment struct {
att service.AttachmentService
}
file struct {
*types.Attachment
content io.ReadSeeker
download bool
}
)
func (Attachment) New() *Attachment {
@@ -34,12 +28,12 @@ func (Attachment) New() *Attachment {
return ctrl
}
func (ctrl *Attachment) Original(ctx context.Context, r *request.AttachmentOriginal) (interface{}, error) {
func (ctrl Attachment) Original(ctx context.Context, r *request.AttachmentOriginal) (interface{}, error) {
if err := ctrl.isAccessible(r.AttachmentID, r.UserID, r.Sign); err != nil {
return nil, err
}
return ctrl.get(ctx, r.AttachmentID, false, r.Download)
return ctrl.serve(ctx, r.AttachmentID, false, r.Download)
}
func (ctrl *Attachment) Preview(ctx context.Context, r *request.AttachmentPreview) (interface{}, error) {
@@ -47,7 +41,7 @@ func (ctrl *Attachment) Preview(ctx context.Context, r *request.AttachmentPrevie
return nil, err
}
return ctrl.get(ctx, r.AttachmentID, true, false)
return ctrl.serve(ctx, r.AttachmentID, true, false)
}
func (ctrl Attachment) isAccessible(attachmentID, userID uint64, signature string) error {
@@ -66,43 +60,42 @@ func (ctrl Attachment) isAccessible(attachmentID, userID uint64, signature strin
return nil
}
func (ctrl Attachment) get(ctx context.Context, ID uint64, preview, download bool) (handlers.Downloadable, error) {
rval := &file{download: download}
func (ctrl Attachment) serve(ctx context.Context, ID uint64, preview, download bool) (interface{}, error) {
return func(w http.ResponseWriter, req *http.Request) {
att, err := ctrl.att.With(ctx).FindByID(ID)
if err != nil {
switch {
case err == repository.ErrAttachmentNotFound:
w.WriteHeader(http.StatusNotFound)
default:
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
var fh io.ReadSeeker
if att, err := ctrl.att.With(ctx).FindByID(ID); err != nil {
return nil, err
} else {
rval.Attachment = att
if preview {
rval.content, err = ctrl.att.OpenPreview(att)
fh, err = ctrl.att.OpenPreview(att)
} else {
rval.content, err = ctrl.att.OpenOriginal(att)
fh, err = ctrl.att.OpenOriginal(att)
}
if err != nil {
return nil, err
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
return rval, nil
}
name := url.QueryEscape(att.Name)
func (f *file) Download() bool {
return f.download
}
if download {
w.Header().Add("Content-Disposition", "attachment; filename="+name)
} else {
w.Header().Add("Content-Disposition", "inline; filename="+name)
}
func (f *file) Name() string {
return f.Attachment.Name
}
func (f *file) ModTime() time.Time {
return f.Attachment.CreatedAt
}
func (f *file) Content() io.ReadSeeker {
return f.content
}
func (f *file) Valid() bool {
return f.content != nil
http.ServeContent(w, req, name, att.CreatedAt, fh)
}, nil
}
@@ -1,72 +0,0 @@
package handlers
import (
"io"
"net/http"
"net/url"
"time"
"github.com/crusttech/crust/messaging/rest/request"
)
// HTTP API interface
type AttachmentDownloadable struct {
Original func(http.ResponseWriter, *http.Request)
Preview func(http.ResponseWriter, *http.Request)
}
type Downloadable interface {
Name() string
Download() bool
ModTime() time.Time
Content() io.ReadSeeker
Valid() bool
}
func NewAttachmentDownloadable(ah AttachmentAPI) *Attachment {
serve := func(f interface{}, err error, w http.ResponseWriter, r *http.Request) {
if err != nil {
switch {
// @todo: compare concrete exported error type? Go2 .As() like check?
case err.Error() == "crust.messaging.repository.AttachmentNotFound":
w.WriteHeader(http.StatusNotFound)
default:
http.Error(w, err.Error(), http.StatusInternalServerError)
}
} else if dl, ok := f.(Downloadable); ok {
if !dl.Valid() {
w.WriteHeader(http.StatusNotFound)
} else {
if dl.Download() {
w.Header().Add("Content-Disposition", "attachment; filename="+url.QueryEscape(dl.Name()))
} else {
w.Header().Add("Content-Disposition", "inline; filename="+url.QueryEscape(dl.Name()))
}
http.ServeContent(w, r, dl.Name(), dl.ModTime(), dl.Content())
}
} else {
http.Error(w, "Got incompatible type from controller", http.StatusInternalServerError)
}
}
return &Attachment{
Original: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewAttachmentOriginal()
_ = params.Fill(r)
f, err := ah.Original(r.Context(), params)
serve(f, err, w, r)
},
Preview: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewAttachmentPreview()
_ = params.Fill(r)
f, err := ah.Preview(r.Context(), params)
serve(f, err, w, r)
},
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ func MountRoutes() func(chi.Router) {
// Initialize handlers & controllers.
return func(r chi.Router) {
r.Group(func(r chi.Router) {
handlers.NewAttachmentDownloadable(Attachment{}.New()).MountRoutes(r)
handlers.NewAttachment(Attachment{}.New()).MountRoutes(r)
})
r.Group(func(r chi.Router) {