You've already forked imgproxy
mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-12-03 23:19:17 +02:00
Better expected errors handling
This commit is contained in:
28
download.go
28
download.go
@@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
@@ -25,11 +24,13 @@ var (
|
|||||||
imageTypeCtxKey = ctxKey("imageType")
|
imageTypeCtxKey = ctxKey("imageType")
|
||||||
imageDataCtxKey = ctxKey("imageData")
|
imageDataCtxKey = ctxKey("imageData")
|
||||||
|
|
||||||
errSourceDimensionsTooBig = errors.New("Source image dimensions are too big")
|
errSourceDimensionsTooBig = newError(422, "Source image dimensions are too big", "Invalid source image")
|
||||||
errSourceResolutionTooBig = errors.New("Source image resolution are too big")
|
errSourceResolutionTooBig = newError(422, "Source image resolution are too big", "Invalid source image")
|
||||||
errSourceImageTypeNotSupported = errors.New("Source image type not supported")
|
errSourceImageTypeNotSupported = newError(422, "Source image type not supported", "Invalid source image")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const msgSourceImageIsUnreachable = "Source image is unreachable"
|
||||||
|
|
||||||
var downloadBufPool = sync.Pool{
|
var downloadBufPool = sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() interface{} {
|
||||||
return new(bytes.Buffer)
|
return new(bytes.Buffer)
|
||||||
@@ -78,7 +79,7 @@ func checkDimensions(width, height int) error {
|
|||||||
func checkTypeAndDimensions(r io.Reader) (imageType, error) {
|
func checkTypeAndDimensions(r io.Reader) (imageType, error) {
|
||||||
imgconf, imgtypeStr, err := image.DecodeConfig(r)
|
imgconf, imgtypeStr, err := image.DecodeConfig(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return imageTypeUnknown, err
|
return imageTypeUnknown, errSourceImageTypeNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
imgtype, imgtypeOk := imageTypes[imgtypeStr]
|
imgtype, imgtypeOk := imageTypes[imgtypeStr]
|
||||||
@@ -105,12 +106,14 @@ func readAndCheckImage(ctx context.Context, res *http.Response) (context.Context
|
|||||||
return ctx, cancel, err
|
return ctx, cancel, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = buf.ReadFrom(res.Body); err == nil {
|
if _, err = buf.ReadFrom(res.Body); err != nil {
|
||||||
ctx = context.WithValue(ctx, imageTypeCtxKey, imgtype)
|
return ctx, cancel, newError(404, err.Error(), msgSourceImageIsUnreachable)
|
||||||
ctx = context.WithValue(ctx, imageDataCtxKey, buf)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx, cancel, err
|
ctx = context.WithValue(ctx, imageTypeCtxKey, imgtype)
|
||||||
|
ctx = context.WithValue(ctx, imageDataCtxKey, buf)
|
||||||
|
|
||||||
|
return ctx, cancel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadImage(ctx context.Context) (context.Context, context.CancelFunc, error) {
|
func downloadImage(ctx context.Context) (context.Context, context.CancelFunc, error) {
|
||||||
@@ -127,20 +130,21 @@ func downloadImage(ctx context.Context) (context.Context, context.CancelFunc, er
|
|||||||
|
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx, func() {}, err
|
return ctx, func() {}, newError(404, err.Error(), msgSourceImageIsUnreachable)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("User-Agent", conf.UserAgent)
|
req.Header.Set("User-Agent", conf.UserAgent)
|
||||||
|
|
||||||
res, err := downloadClient.Do(req)
|
res, err := downloadClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx, func() {}, err
|
return ctx, func() {}, newError(404, err.Error(), msgSourceImageIsUnreachable)
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
if res.StatusCode != 200 {
|
if res.StatusCode != 200 {
|
||||||
body, _ := ioutil.ReadAll(res.Body)
|
body, _ := ioutil.ReadAll(res.Body)
|
||||||
return ctx, func() {}, fmt.Errorf("Can't download image; Status: %d; %s", res.StatusCode, string(body))
|
msg := fmt.Sprintf("Can't download image; Status: %d; %s", res.StatusCode, string(body))
|
||||||
|
return ctx, func() {}, newError(404, msg, msgSourceImageIsUnreachable)
|
||||||
}
|
}
|
||||||
|
|
||||||
return readAndCheckImage(ctx, res)
|
return readAndCheckImage(ctx, res)
|
||||||
|
|||||||
10
errors.go
10
errors.go
@@ -13,17 +13,17 @@ type imgproxyError struct {
|
|||||||
PublicMessage string
|
PublicMessage string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e imgproxyError) Error() string {
|
func (e *imgproxyError) Error() string {
|
||||||
return e.Message
|
return e.Message
|
||||||
}
|
}
|
||||||
|
|
||||||
func newError(status int, msg string, pub string) imgproxyError {
|
func newError(status int, msg string, pub string) *imgproxyError {
|
||||||
return imgproxyError{status, msg, pub}
|
return &imgproxyError{status, msg, pub}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newUnexpectedError(err error, skip int) imgproxyError {
|
func newUnexpectedError(err error, skip int) *imgproxyError {
|
||||||
msg := fmt.Sprintf("Unexpected error: %s\n%s", err, stacktrace(skip+1))
|
msg := fmt.Sprintf("Unexpected error: %s\n%s", err, stacktrace(skip+1))
|
||||||
return imgproxyError{500, msg, "Internal error"}
|
return &imgproxyError{500, msg, "Internal error"}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stacktrace(skip int) string {
|
func stacktrace(skip int) string {
|
||||||
|
|||||||
2
etag.go
2
etag.go
@@ -9,8 +9,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errNotModified = newError(304, "Not modified", "Not modified")
|
|
||||||
|
|
||||||
type eTagCalc struct {
|
type eTagCalc struct {
|
||||||
hash hash.Hash
|
hash hash.Hash
|
||||||
enc *json.Encoder
|
enc *json.Encoder
|
||||||
|
|||||||
@@ -141,13 +141,16 @@ const (
|
|||||||
processingOptionsCtxKey = ctxKey("processingOptions")
|
processingOptionsCtxKey = ctxKey("processingOptions")
|
||||||
urlTokenPlain = "plain"
|
urlTokenPlain = "plain"
|
||||||
maxClientHintDPR = 8
|
maxClientHintDPR = 8
|
||||||
|
|
||||||
|
msgForbidden = "Forbidden"
|
||||||
|
msgInvalidURL = "Invalid URL"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errInvalidImageURL = errors.New("Invalid image url")
|
errInvalidImageURL = errors.New("Invalid image url")
|
||||||
errInvalidURLEncoding = errors.New("Invalid url encoding")
|
errInvalidURLEncoding = errors.New("Invalid url encoding")
|
||||||
errInvalidPath = errors.New("Invalid path")
|
|
||||||
errResultingImageFormatIsNotSupported = errors.New("Resulting image format is not supported")
|
errResultingImageFormatIsNotSupported = errors.New("Resulting image format is not supported")
|
||||||
|
errInvalidPath = newError(404, "Invalid path", msgInvalidURL)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (it imageType) String() string {
|
func (it imageType) String() string {
|
||||||
@@ -836,7 +839,7 @@ func parsePath(ctx context.Context, r *http.Request) (context.Context, error) {
|
|||||||
|
|
||||||
if !conf.AllowInsecure {
|
if !conf.AllowInsecure {
|
||||||
if err := validatePath(parts[0], strings.TrimPrefix(path, fmt.Sprintf("/%s", parts[0]))); err != nil {
|
if err := validatePath(parts[0], strings.TrimPrefix(path, fmt.Sprintf("/%s", parts[0]))); err != nil {
|
||||||
return ctx, err
|
return ctx, newError(403, err.Error(), msgForbidden)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -858,13 +861,13 @@ func parsePath(ctx context.Context, r *http.Request) (context.Context, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx, err
|
return ctx, newError(404, err.Error(), msgInvalidURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, imageURLCtxKey, imageURL)
|
ctx = context.WithValue(ctx, imageURLCtxKey, imageURL)
|
||||||
ctx = context.WithValue(ctx, processingOptionsCtxKey, po)
|
ctx = context.WithValue(ctx, processingOptionsCtxKey, po)
|
||||||
|
|
||||||
return ctx, err
|
return ctx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getImageURL(ctx context.Context) string {
|
func getImageURL(ctx context.Context) string {
|
||||||
|
|||||||
20
server.go
20
server.go
@@ -135,7 +135,7 @@ func respondWithImage(ctx context.Context, reqID string, r *http.Request, rw htt
|
|||||||
logResponse(200, fmt.Sprintf("[%s] Processed in %s: %s; %+v", reqID, getTimerSince(ctx), getImageURL(ctx), po))
|
logResponse(200, fmt.Sprintf("[%s] Processed in %s: %s; %+v", reqID, getTimerSince(ctx), getImageURL(ctx), po))
|
||||||
}
|
}
|
||||||
|
|
||||||
func respondWithError(reqID string, rw http.ResponseWriter, err imgproxyError) {
|
func respondWithError(reqID string, rw http.ResponseWriter, err *imgproxyError) {
|
||||||
logResponse(err.StatusCode, fmt.Sprintf("[%s] %s", reqID, err.Message))
|
logResponse(err.StatusCode, fmt.Sprintf("[%s] %s", reqID, err.Message))
|
||||||
|
|
||||||
rw.WriteHeader(err.StatusCode)
|
rw.WriteHeader(err.StatusCode)
|
||||||
@@ -147,6 +147,11 @@ func respondWithOptions(reqID string, rw http.ResponseWriter) {
|
|||||||
rw.WriteHeader(200)
|
rw.WriteHeader(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func respondWithNotModified(reqID string, rw http.ResponseWriter) {
|
||||||
|
logResponse(200, fmt.Sprintf("[%s] Not modified", reqID))
|
||||||
|
rw.WriteHeader(304)
|
||||||
|
}
|
||||||
|
|
||||||
func prepareAuthHeaderMust() []byte {
|
func prepareAuthHeaderMust() []byte {
|
||||||
if len(authHeaderMust) == 0 {
|
if len(authHeaderMust) == 0 {
|
||||||
authHeaderMust = []byte(fmt.Sprintf("Bearer %s", conf.Secret))
|
authHeaderMust = []byte(fmt.Sprintf("Bearer %s", conf.Secret))
|
||||||
@@ -180,11 +185,9 @@ func (h *httpHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
if rerr := recover(); rerr != nil {
|
if rerr := recover(); rerr != nil {
|
||||||
if err, ok := rerr.(error); ok {
|
if err, ok := rerr.(error); ok {
|
||||||
if err != errNotModified {
|
|
||||||
reportError(err, r)
|
reportError(err, r)
|
||||||
}
|
|
||||||
|
|
||||||
if ierr, ok := err.(imgproxyError); ok {
|
if ierr, ok := err.(*imgproxyError); ok {
|
||||||
respondWithError(reqID, rw, ierr)
|
respondWithError(reqID, rw, ierr)
|
||||||
} else {
|
} else {
|
||||||
respondWithError(reqID, rw, newUnexpectedError(err, 4))
|
respondWithError(reqID, rw, newUnexpectedError(err, 4))
|
||||||
@@ -239,7 +242,7 @@ func (h *httpHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
ctx, err := parsePath(ctx, r)
|
ctx, err := parsePath(ctx, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(newError(404, err.Error(), "Invalid image url"))
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, downloadcancel, err := downloadImage(ctx)
|
ctx, downloadcancel, err := downloadImage(ctx)
|
||||||
@@ -251,7 +254,7 @@ func (h *httpHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||||||
if prometheusEnabled {
|
if prometheusEnabled {
|
||||||
incrementPrometheusErrorsTotal("download")
|
incrementPrometheusErrorsTotal("download")
|
||||||
}
|
}
|
||||||
panic(newError(404, err.Error(), "Image is unreachable"))
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
checkTimeout(ctx)
|
checkTimeout(ctx)
|
||||||
@@ -261,7 +264,8 @@ func (h *httpHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||||||
rw.Header().Set("ETag", eTag)
|
rw.Header().Set("ETag", eTag)
|
||||||
|
|
||||||
if eTag == r.Header.Get("If-None-Match") {
|
if eTag == r.Header.Get("If-None-Match") {
|
||||||
panic(errNotModified)
|
respondWithNotModified(reqID, rw)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,7 +279,7 @@ func (h *httpHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||||||
if prometheusEnabled {
|
if prometheusEnabled {
|
||||||
incrementPrometheusErrorsTotal("processing")
|
incrementPrometheusErrorsTotal("processing")
|
||||||
}
|
}
|
||||||
panic(newError(500, err.Error(), "Error occurred while processing image"))
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
checkTimeout(ctx)
|
checkTimeout(ctx)
|
||||||
|
|||||||
Reference in New Issue
Block a user