Add /v2/assets/ handler, with resizing
This commit is contained in:
parent
f9d1f664f0
commit
0fdece68c0
@ -27,6 +27,7 @@ type Params struct {
|
|||||||
PowManager pow.Manager
|
PowManager pow.Manager
|
||||||
|
|
||||||
PostStore post.Store
|
PostStore post.Store
|
||||||
|
PostAssetStore post.AssetStore
|
||||||
|
|
||||||
MailingList mailinglist.MailingList
|
MailingList mailinglist.MailingList
|
||||||
|
|
||||||
@ -192,6 +193,8 @@ func (a *api) handler() http.Handler {
|
|||||||
mux.Handle("/v2/posts/", a.renderPostHandler())
|
mux.Handle("/v2/posts/", a.renderPostHandler())
|
||||||
mux.Handle("/v2/", a.renderIndexHandler())
|
mux.Handle("/v2/", a.renderIndexHandler())
|
||||||
|
|
||||||
|
mux.Handle("/v2/assets/", a.servePostAssetHandler())
|
||||||
|
|
||||||
var globalHandler http.Handler = mux
|
var globalHandler http.Handler = mux
|
||||||
globalHandler = setLoggerMiddleware(a.params.Logger, globalHandler)
|
globalHandler = setLoggerMiddleware(a.params.Logger, globalHandler)
|
||||||
|
|
||||||
|
113
srv/src/api/assets.go
Normal file
113
srv/src/api/assets.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/jpeg"
|
||||||
|
"image/png"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mediocregopher/blog.mediocregopher.com/srv/api/apiutil"
|
||||||
|
"github.com/mediocregopher/blog.mediocregopher.com/srv/post"
|
||||||
|
"golang.org/x/image/draw"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resizeImage(out io.Writer, in io.Reader, maxWidth float64) error {
|
||||||
|
|
||||||
|
img, format, err := image.Decode(in)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("decoding image: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
imgRect := img.Bounds()
|
||||||
|
imgW, imgH := float64(imgRect.Dx()), float64(imgRect.Dy())
|
||||||
|
|
||||||
|
if imgW > maxWidth {
|
||||||
|
|
||||||
|
newH := imgH * maxWidth / imgW
|
||||||
|
newImg := image.NewRGBA(image.Rect(0, 0, int(maxWidth), int(newH)))
|
||||||
|
|
||||||
|
// Resize
|
||||||
|
draw.BiLinear.Scale(
|
||||||
|
newImg, newImg.Bounds(), img, img.Bounds(), draw.Over, nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
img = newImg
|
||||||
|
}
|
||||||
|
|
||||||
|
switch format {
|
||||||
|
case "jpeg":
|
||||||
|
return jpeg.Encode(out, img, nil)
|
||||||
|
case "png":
|
||||||
|
return png.Encode(out, img)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown image format %q", format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *api) servePostAssetHandler() http.Handler {
|
||||||
|
|
||||||
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
id := filepath.Base(r.URL.Path)
|
||||||
|
|
||||||
|
maxWidth, err := apiutil.StrToInt(r.FormValue("w"), 0)
|
||||||
|
if err != nil {
|
||||||
|
apiutil.BadRequest(rw, r, fmt.Errorf("invalid w parameter: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
err = a.params.PostAssetStore.Get(id, buf)
|
||||||
|
|
||||||
|
if errors.Is(err, post.ErrAssetNotFound) {
|
||||||
|
http.Error(rw, "Asset not found", 404)
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
apiutil.InternalServerError(
|
||||||
|
rw, r, fmt.Errorf("fetching asset with id %q: %w", id, err),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxWidth == 0 {
|
||||||
|
|
||||||
|
if _, err := io.Copy(rw, buf); err != nil {
|
||||||
|
apiutil.InternalServerError(
|
||||||
|
rw, r,
|
||||||
|
fmt.Errorf(
|
||||||
|
"copying asset with id %q to response writer: %w",
|
||||||
|
id, err,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(id), ".")); ext {
|
||||||
|
case "jpg", "jpeg", "png":
|
||||||
|
|
||||||
|
if err := resizeImage(rw, buf, float64(maxWidth)); err != nil {
|
||||||
|
apiutil.InternalServerError(
|
||||||
|
rw, r,
|
||||||
|
fmt.Errorf(
|
||||||
|
"resizing image with id %q to size %d: %w",
|
||||||
|
id, maxWidth, err,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
apiutil.BadRequest(rw, r, fmt.Errorf("cannot resize file with extension %q", ext))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
@ -120,10 +120,12 @@ func main() {
|
|||||||
defer postSQLDB.Close()
|
defer postSQLDB.Close()
|
||||||
|
|
||||||
postStore := post.NewStore(postSQLDB)
|
postStore := post.NewStore(postSQLDB)
|
||||||
|
postAssetStore := post.NewAssetStore(postSQLDB)
|
||||||
|
|
||||||
apiParams.Logger = logger.WithNamespace("api")
|
apiParams.Logger = logger.WithNamespace("api")
|
||||||
apiParams.PowManager = powMgr
|
apiParams.PowManager = powMgr
|
||||||
apiParams.PostStore = postStore
|
apiParams.PostStore = postStore
|
||||||
|
apiParams.PostAssetStore = postAssetStore
|
||||||
apiParams.MailingList = ml
|
apiParams.MailingList = ml
|
||||||
apiParams.GlobalRoom = chatGlobalRoom
|
apiParams.GlobalRoom = chatGlobalRoom
|
||||||
apiParams.UserIDCalculator = chatUserIDCalc
|
apiParams.UserIDCalculator = chatUserIDCalc
|
||||||
|
@ -17,6 +17,7 @@ require (
|
|||||||
github.com/tilinna/clock v1.1.0
|
github.com/tilinna/clock v1.1.0
|
||||||
github.com/ziutek/mymysql v1.5.4 // indirect
|
github.com/ziutek/mymysql v1.5.4 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
||||||
|
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||||
)
|
)
|
||||||
|
@ -184,6 +184,8 @@ golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPh
|
|||||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 h1:LRtI4W37N+KFebI/qV0OFiLUv4GLOWeEW5hn/KEJvxE=
|
||||||
|
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
@ -218,6 +220,7 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+
|
|||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
Loading…
Reference in New Issue
Block a user