1
0
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:
DarthSim
2018-11-20 18:53:44 +06:00
parent 4187646052
commit 6997e585ef
5 changed files with 41 additions and 32 deletions

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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

View File

@@ -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 {

View File

@@ -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)