diff --git a/.travis.yml b/.travis.yml index feb3f90..de0cbd7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,21 +3,20 @@ language: go services: - docker -dist: trusty +dist: focal go: - - 1.12.x - - 1.11.x + - "1.12" + - "1.13" + # - "1.14" env: global: - GOLANG_VERSION="${TRAVIS_GO_VERSION}" - IMAGINARY_VERSION="${TRAVIS_TAG:-dev}" matrix: - - LIBVIPS=8.7.3 - - LIBVIPS=8.7.4 - - LIBVIPS=8.8.0 - - LIBVIPS=8.8.1 + - LIBVIPS=8.8.4 + - LIBVIPS=8.9.2 before_install: - docker pull h2non/imaginary:latest || true @@ -28,14 +27,14 @@ install: script: - docker build --pull --cache-from h2non/imaginary:latest --build-arg GOLANG_VERSION="${GOLANG_VERSION%.x}" --build-arg LIBVIPS_VERSION="${LIBVIPS}" --build-arg IMAGINARY_VERSION="${IMAGINARY_VERSION#v}" --tag h2non/imaginary:${IMAGINARY_VERSION} . -# before_deploy: -# - docker login -u "$REGISTRY_USER" -p "$REGISTRY_PASS" +before_deploy: + - docker login -u "$DOCKER_LOGIN" -p "$DOCKER_PWD" -# deploy: -# provider: script -# script: -# - docker tag h2non/imaginary:${IMAGINARY_VERSION} latest -# - docker push h2non/imaginary:${IMAGINARY_VERSION} -# - docker push h2non/imaginary:latest -# on: -# condition: "${TRAVIS_TAG} =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$" +deploy: + provider: script + script: + - docker tag h2non/imaginary:${IMAGINARY_VERSION} h2non/imaginary:latest + - docker push h2non/imaginary:${IMAGINARY_VERSION} + - docker push h2non/imaginary:latest + on: + condition: "${TRAVIS_TAG} =~ ^v([0-9]+).([0-9]+).([0-9]+)$ AND env(GOLANG_VERSION) = 1.13" diff --git a/Dockerfile b/Dockerfile index 6d63734..d516c3c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -ARG GOLANG_VERSION=1.13.7 +ARG GOLANG_VERSION=1.14 FROM golang:${GOLANG_VERSION} as builder ARG IMAGINARY_VERSION=dev -ARG LIBVIPS_VERSION=8.9.1 +ARG LIBVIPS_VERSION=8.9.2 ARG GOLANGCILINT_VERSION=1.23.3 # Installs libvips + required libraries @@ -50,8 +50,8 @@ RUN go mod download COPY . . # Run quality control -RUN go test -test.v -test.race -test.covermode=atomic ./... -RUN golangci-lint run ./... +RUN go test -test.v -test.race -test.covermode=atomic . +RUN golangci-lint run . # Compile imaginary RUN go build -a \ diff --git a/README.md b/README.md index 9edaf4f..6c35563 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# imaginary [![Build Status](https://travis-ci.org/h2non/imaginary.png)](https://travis-ci.org/h2non/imaginary) [![Docker](https://img.shields.io/badge/docker-h2non/imaginary-blue.svg)](https://hub.docker.com/r/h2non/imaginary/) [![Docker Registry](https://img.shields.io/docker/pulls/h2non/imaginary.svg)](https://hub.docker.com/r/h2non/imaginary/) [![Go Report Card](http://goreportcard.com/badge/h2non/imaginary)](https://goreportcard.com/report/h2non/imaginary) [![Fly.io](https://img.shields.io/badge/deploy-fly.io-blue.svg)](https://fly.io/launch/github/h2non/imaginary) +# imaginary [![Build Status](https://travis-ci.org/h2non/imaginary.svg)](https://travis-ci.org/h2non/imaginary) [![Docker](https://img.shields.io/badge/docker-h2non/imaginary-blue.svg)](https://hub.docker.com/r/h2non/imaginary/) [![Docker Registry](https://img.shields.io/docker/pulls/h2non/imaginary.svg)](https://hub.docker.com/r/h2non/imaginary/) [![Go Report Card](http://goreportcard.com/badge/h2non/imaginary)](https://goreportcard.com/report/h2non/imaginary) [![Fly.io](https://img.shields.io/badge/deploy-fly.io-blue.svg)](https://fly.io/launch/github/h2non/imaginary) **[Fast](#benchmarks) HTTP [microservice](http://microservices.io/patterns/microservices.html)** written in Go **for high-level image processing** backed by [bimg](https://github.com/h2non/bimg) and [libvips](https://github.com/jcupitt/libvips). `imaginary` can be used as private or public HTTP service for massive image processing with first-class support for [Docker](#docker) & [Fly.io](#flyio). It's almost dependency-free and only uses [`net/http`](http://golang.org/pkg/net/http/) native package without additional abstractions for better [performance](#performance). @@ -83,7 +83,7 @@ go get -u github.com/h2non/imaginary Also, be sure you have the latest version of `bimg`: ```bash -go get -u gopkg.in/h2non/bimg.v1 +go get -u github.com/h2non/bimg ``` ### libvips @@ -553,7 +553,7 @@ Image measures are always in pixels, unless otherwise indicated. - **url** `string` - Fetch the image from a remote HTTP server. In order to use this you must pass the `-enable-url-source` flag. - **colorspace** `string` - Use a custom color space for the output image. Allowed values are: `srgb` or `bw` (black&white) - **field** `string` - Custom image form field name if using `multipart/form`. Defaults to: `file` -- **extend** `string` - Extend represents the image extend mode used when the edges of an image are extended. Allowed values are: `black`, `copy`, `mirror`, `white` and `background`. If `background` value is specified, you can define the desired extend RGB color via `background` param, such as `?extend=background&background=250,20,10`. For more info, see [libvips docs](http://www.vips.ecs.soton.ac.uk/supported/8.4/doc/html/libvips/libvips-conversion.html#VIPS-EXTEND-BACKGROUND:CAPS). +- **extend** `string` - Extend represents the image extend mode used when the edges of an image are extended. Defaults to `copy`. Allowed values are: `black`, `copy`, `mirror`, `white`, `lastpixel` and `background`. If `background` value is specified, you can define the desired extend RGB color via `background` param, such as `?extend=background&background=250,20,10`. For more info, see [libvips docs](http://www.vips.ecs.soton.ac.uk/supported/8.4/doc/html/libvips/libvips-conversion.html#VIPS-EXTEND-BACKGROUND:CAPS). - **background** `string` - Background RGB decimal base color to use when flattening transparent PNGs. Example: `255,200,150` - **sigma** `float` - Size of the gaussian mask to use when blurring an image. Example: `15.0` - **minampl** `float` - Minimum amplitude of the gaussian filter to use when blurring an image. Default: Example: `0.5` diff --git a/controllers.go b/controllers.go index df21faa..084f957 100644 --- a/controllers.go +++ b/controllers.go @@ -44,7 +44,11 @@ func imageController(o ServerOptions, operation Operation) func(http.ResponseWri buf, err := imageSource.GetImage(req) if err != nil { - ErrorReply(req, w, NewError(err.Error(), BadRequest), o) + if xerr, ok := err.(Error); ok { + ErrorReply(req, w, xerr, o) + } else { + ErrorReply(req, w, NewError(err.Error(), http.StatusBadRequest), o) + } return } @@ -100,7 +104,7 @@ func imageHandler(w http.ResponseWriter, r *http.Request, buf []byte, operation opts, err := buildParamsFromQuery(r.URL.Query()) if err != nil { - ErrorReply(r, w, NewError("Error while processing parameters, "+err.Error(), BadRequest), o) + ErrorReply(r, w, NewError("Error while processing parameters, "+err.Error(), http.StatusBadRequest), o) return } @@ -115,7 +119,7 @@ func imageHandler(w http.ResponseWriter, r *http.Request, buf []byte, operation image, err := operation.Run(buf, opts) if err != nil { - ErrorReply(r, w, NewError("Error while processing the image: "+err.Error(), BadRequest), o) + ErrorReply(r, w, NewError("Error while processing the image: "+err.Error(), http.StatusBadRequest), o) return } diff --git a/error.go b/error.go index f3d029a..d866e54 100644 --- a/error.go +++ b/error.go @@ -9,38 +9,26 @@ import ( "github.com/h2non/bimg" ) -const ( - _ uint8 = iota - BadRequest - NotAllowed - Unsupported - Unauthorized - InternalError - NotFound - NotImplemented - Forbidden - NotAcceptable -) - var ( - ErrNotFound = NewError("not found", NotFound) - ErrInvalidAPIKey = NewError("invalid or missing API key", Unauthorized) - ErrMethodNotAllowed = NewError("method not allowed", NotAllowed) - ErrUnsupportedMedia = NewError("unsupported media type", Unsupported) - ErrOutputFormat = NewError("unsupported output image format", BadRequest) - ErrEmptyBody = NewError("empty image", BadRequest) - ErrMissingParamFile = NewError("missing required param: file", BadRequest) - ErrInvalidFilePath = NewError("invalid file path", BadRequest) - ErrInvalidImageURL = NewError("invalid image URL", BadRequest) - ErrMissingImageSource = NewError("cannot process the image due to missing or invalid params", BadRequest) - ErrNotImplemented = NewError("not implemented endpoint", NotImplemented) - ErrInvalidURLSignature = NewError("invalid URL signature", BadRequest) - ErrURLSignatureMismatch = NewError("URL signature mismatch", Forbidden) + ErrNotFound = NewError("Not found", http.StatusNotFound) + ErrInvalidAPIKey = NewError("Invalid or missing API key", http.StatusUnauthorized) + ErrMethodNotAllowed = NewError("HTTP method not allowed. Try with a POST or GET method (-enable-url-source flag must be defined)", http.StatusMethodNotAllowed) + ErrGetMethodNotAllowed = NewError("GET method not allowed. Make sure remote URL source is enabled by using the flag: -enable-url-source", http.StatusMethodNotAllowed) + ErrUnsupportedMedia = NewError("Unsupported media type", http.StatusNotAcceptable) + ErrOutputFormat = NewError("Unsupported output image format", http.StatusBadRequest) + ErrEmptyBody = NewError("Empty or unreadable image", http.StatusBadRequest) + ErrMissingParamFile = NewError("Missing required param: file", http.StatusBadRequest) + ErrInvalidFilePath = NewError("Invalid file path", http.StatusBadRequest) + ErrInvalidImageURL = NewError("Unvalid image URL", http.StatusBadRequest) + ErrMissingImageSource = NewError("Cannot process the image due to missing or invalid params", http.StatusBadRequest) + ErrNotImplemented = NewError("Not implemented endpoint", http.StatusNotImplemented) + ErrInvalidURLSignature = NewError("Invalid URL signature", http.StatusBadRequest) + ErrURLSignatureMismatch = NewError("URL signature mismatch", http.StatusForbidden) ) type Error struct { Message string `json:"message,omitempty"` - Code uint8 `json:"code"` + Code int `json:"status"` } func (e Error) JSON() []byte { @@ -53,34 +41,21 @@ func (e Error) Error() string { } func (e Error) HTTPCode() int { - var codes = map[uint8]int{ - BadRequest: http.StatusBadRequest, - NotAllowed: http.StatusMethodNotAllowed, - Unsupported: http.StatusUnsupportedMediaType, - InternalError: http.StatusInternalServerError, - Unauthorized: http.StatusUnauthorized, - NotFound: http.StatusNotFound, - NotImplemented: http.StatusNotImplemented, - Forbidden: http.StatusForbidden, - NotAcceptable: http.StatusNotAcceptable, + if e.Code >= 400 && e.Code <= 511 { + return e.Code } - - if v, ok := codes[e.Code]; ok { - return v - } - return http.StatusServiceUnavailable } -func NewError(err string, code uint8) Error { +func NewError(err string, code int) Error { err = strings.Replace(err, "\n", "", -1) - return Error{err, code} + return Error{Message: err, Code: code} } -func sendErrorResponse(w http.ResponseWriter, httpStatusCode int, imaginaryErrorCode uint8, err error) { +func sendErrorResponse(w http.ResponseWriter, httpStatusCode int, err error) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(httpStatusCode) - _, _ = w.Write([]byte(fmt.Sprintf("{\"error\":\"%s\", \"code\": %d}", err.Error(), imaginaryErrorCode))) + _, _ = w.Write([]byte(fmt.Sprintf("{\"error\":\"%s\", \"status\": %d}", err.Error(), httpStatusCode))) } func replyWithPlaceholder(req *http.Request, w http.ResponseWriter, errCaller Error, o ServerOptions) error { @@ -94,20 +69,20 @@ func replyWithPlaceholder(req *http.Request, w http.ResponseWriter, errCaller Er bimgOptions.Width, err = parseInt(req.URL.Query().Get("width")) if err != nil { - sendErrorResponse(w, http.StatusBadRequest, BadRequest, err) + sendErrorResponse(w, http.StatusBadRequest, err) return err } bimgOptions.Height, err = parseInt(req.URL.Query().Get("height")) if err != nil { - sendErrorResponse(w, http.StatusBadRequest, BadRequest, err) + sendErrorResponse(w, http.StatusBadRequest, err) return err } // Resize placeholder to expected output buf, err := bimg.Resize(o.PlaceholderImage, bimgOptions) if err != nil { - sendErrorResponse(w, http.StatusBadRequest, BadRequest, err) + sendErrorResponse(w, http.StatusBadRequest, err) return err } diff --git a/error_test.go b/error_test.go index e89e37f..8830295 100644 --- a/error_test.go +++ b/error_test.go @@ -2,23 +2,23 @@ package main import "testing" -func TestError(t *testing.T) { - err := NewError("oops!\n\n", 1) +func TestDefaultError(t *testing.T) { + err := NewError("oops!\n\n", 503) if err.Error() != "oops!" { t.Fatal("Invalid error message") } - if err.Code != 1 { + if err.Code != 503 { t.Fatal("Invalid error code") } code := err.HTTPCode() - if code != 400 { + if code != 503 { t.Fatalf("Invalid HTTP error status: %d", code) } json := string(err.JSON()) - if json != "{\"message\":\"oops!\",\"code\":1}" { + if json != "{\"message\":\"oops!\",\"status\":503}" { t.Fatalf("Invalid JSON output: %s", json) } } diff --git a/go.mod b/go.mod index 5e73a46..8bb9acf 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/garyburd/redigo v1.6.0 // indirect github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad // indirect github.com/rs/cors v0.0.0-20170727213201-7af7a1e09ba3 - github.com/h2non/bimg v1.0.20-0.20200405220655-daafbf6d972d - github.com/h2non/filetype v1.0.12 + github.com/h2non/bimg v1.1.0 + github.com/h2non/filetype v1.1.0 gopkg.in/throttled/throttled.v2 v2.0.3 ) diff --git a/go.sum b/go.sum index f02cdcb..1a085f4 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,10 @@ -github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc= github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/h2non/bimg v1.1.0/go.mod h1:R3+UiYwkK4rQl6KVFTOFJHitgLbZXBZNFh2cv3AEbp8= +github.com/h2non/filetype v1.1.0 h1:Or/gjocJrJRNK/Cri/TDEKFjAR+cfG6eK65NGYB6gBA= +github.com/h2non/filetype v1.1.0/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad h1:eMxs9EL0PvIGS9TTtxg4R+JxuPGav82J8rA+GFnY7po= github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/rs/cors v0.0.0-20170727213201-7af7a1e09ba3 h1:86ukAHRTa2CXdBnWJHcjjPPGTyLGEF488OFRsbBAuFs= github.com/rs/cors v0.0.0-20170727213201-7af7a1e09ba3/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/h2non/bimg v1.0.20-0.20200405220655-daafbf6d972d h1:AhGp7Xiew8DAYGJ1MojrE11chxHVOv4NvE/y5i2IvBI= -github.com/h2non/bimg v1.0.20-0.20200405220655-daafbf6d972d/go.mod h1:R3+UiYwkK4rQl6KVFTOFJHitgLbZXBZNFh2cv3AEbp8= -github.com/h2non/filetype v1.0.12 h1:yHCsIe0y2cvbDARtJhGBTD2ecvqMSTvlIcph9En/Zao= -github.com/h2non/filetype v1.0.12/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= gopkg.in/throttled/throttled.v2 v2.0.3 h1:PGm7nfjjexecEyI2knw1akeLcrjzqxuYSU9a04R8rfU= gopkg.in/throttled/throttled.v2 v2.0.3/go.mod h1:L4cTNZO77XKEXtn8HNFRCMNGZPtRRKAhyuJBSvK/T90= diff --git a/image.go b/image.go index b4d40d3..e70d249 100644 --- a/image.go +++ b/image.go @@ -65,7 +65,7 @@ func Info(buf []byte, o ImageOptions) (Image, error) { meta, err := bimg.Metadata(buf) if err != nil { - return image, NewError("Cannot retrieve image metadata: %s"+err.Error(), BadRequest) + return image, NewError("Cannot retrieve image metadata: %s"+err.Error(), http.StatusBadRequest) } info := ImageInfo{ @@ -87,7 +87,7 @@ func Info(buf []byte, o ImageOptions) (Image, error) { func Resize(buf []byte, o ImageOptions) (Image, error) { if o.Width == 0 && o.Height == 0 { - return Image{}, NewError("Missing required param: height or width", BadRequest) + return Image{}, NewError("Missing required param: height or width", http.StatusBadRequest) } opts := BimgOptions(o) @@ -102,7 +102,7 @@ func Resize(buf []byte, o ImageOptions) (Image, error) { func Fit(buf []byte, o ImageOptions) (Image, error) { if o.Width == 0 || o.Height == 0 { - return Image{}, NewError("Missing required params: height, width", BadRequest) + return Image{}, NewError("Missing required params: height, width", http.StatusBadRequest) } metadata, err := bimg.Metadata(buf) @@ -113,7 +113,7 @@ func Fit(buf []byte, o ImageOptions) (Image, error) { dims := metadata.Size if dims.Width == 0 || dims.Height == 0 { - return Image{}, NewError("Width or height of requested image is zero", NotAcceptable) + return Image{}, NewError("Width or height of requested image is zero", http.StatusNotAcceptable) } // metadata.Orientation @@ -165,7 +165,7 @@ func calculateDestinationFitDimension(imageWidth, imageHeight, fitWidth, fitHeig func Enlarge(buf []byte, o ImageOptions) (Image, error) { if o.Width == 0 || o.Height == 0 { - return Image{}, NewError("Missing required params: height, width", BadRequest) + return Image{}, NewError("Missing required params: height, width", http.StatusBadRequest) } opts := BimgOptions(o) @@ -179,7 +179,7 @@ func Enlarge(buf []byte, o ImageOptions) (Image, error) { func Extract(buf []byte, o ImageOptions) (Image, error) { if o.AreaWidth == 0 || o.AreaHeight == 0 { - return Image{}, NewError("Missing required params: areawidth or areaheight", BadRequest) + return Image{}, NewError("Missing required params: areawidth or areaheight", http.StatusBadRequest) } opts := BimgOptions(o) @@ -193,7 +193,7 @@ func Extract(buf []byte, o ImageOptions) (Image, error) { func Crop(buf []byte, o ImageOptions) (Image, error) { if o.Width == 0 && o.Height == 0 { - return Image{}, NewError("Missing required param: height or width", BadRequest) + return Image{}, NewError("Missing required param: height or width", http.StatusBadRequest) } opts := BimgOptions(o) @@ -203,7 +203,7 @@ func Crop(buf []byte, o ImageOptions) (Image, error) { func SmartCrop(buf []byte, o ImageOptions) (Image, error) { if o.Width == 0 && o.Height == 0 { - return Image{}, NewError("Missing required param: height or width", BadRequest) + return Image{}, NewError("Missing required param: height or width", http.StatusBadRequest) } opts := BimgOptions(o) @@ -214,7 +214,7 @@ func SmartCrop(buf []byte, o ImageOptions) (Image, error) { func Rotate(buf []byte, o ImageOptions) (Image, error) { if o.Rotate == 0 { - return Image{}, NewError("Missing required param: rotate", BadRequest) + return Image{}, NewError("Missing required param: rotate", http.StatusBadRequest) } opts := BimgOptions(o) @@ -235,7 +235,7 @@ func Flop(buf []byte, o ImageOptions) (Image, error) { func Thumbnail(buf []byte, o ImageOptions) (Image, error) { if o.Width == 0 && o.Height == 0 { - return Image{}, NewError("Missing required params: width or height", BadRequest) + return Image{}, NewError("Missing required params: width or height", http.StatusBadRequest) } return Process(buf, BimgOptions(o)) @@ -243,14 +243,14 @@ func Thumbnail(buf []byte, o ImageOptions) (Image, error) { func Zoom(buf []byte, o ImageOptions) (Image, error) { if o.Factor == 0 { - return Image{}, NewError("Missing required param: factor", BadRequest) + return Image{}, NewError("Missing required param: factor", http.StatusBadRequest) } opts := BimgOptions(o) if o.Top > 0 || o.Left > 0 { if o.AreaWidth == 0 && o.AreaHeight == 0 { - return Image{}, NewError("Missing required params: areawidth, areaheight", BadRequest) + return Image{}, NewError("Missing required params: areawidth, areaheight", http.StatusBadRequest) } opts.Top = o.Top @@ -269,10 +269,10 @@ func Zoom(buf []byte, o ImageOptions) (Image, error) { func Convert(buf []byte, o ImageOptions) (Image, error) { if o.Type == "" { - return Image{}, NewError("Missing required param: type", BadRequest) + return Image{}, NewError("Missing required param: type", http.StatusBadRequest) } if ImageType(o.Type) == bimg.UNKNOWN { - return Image{}, NewError("Invalid image type: "+o.Type, BadRequest) + return Image{}, NewError("Invalid image type: "+o.Type, http.StatusBadRequest) } opts := BimgOptions(o) @@ -281,7 +281,7 @@ func Convert(buf []byte, o ImageOptions) (Image, error) { func Watermark(buf []byte, o ImageOptions) (Image, error) { if o.Text == "" { - return Image{}, NewError("Missing required param: text", BadRequest) + return Image{}, NewError("Missing required param: text", http.StatusBadRequest) } opts := BimgOptions(o) @@ -302,11 +302,11 @@ func Watermark(buf []byte, o ImageOptions) (Image, error) { func WatermarkImage(buf []byte, o ImageOptions) (Image, error) { if o.Image == "" { - return Image{}, NewError("Missing required param: image", BadRequest) + return Image{}, NewError("Missing required param: image", http.StatusBadRequest) } response, err := http.Get(o.Image) if err != nil { - return Image{}, NewError(fmt.Sprintf("Unable to retrieve watermark image. %s", o.Image), BadRequest) + return Image{}, NewError(fmt.Sprintf("Unable to retrieve watermark image. %s", o.Image), http.StatusBadRequest) } defer func() { _ = response.Body.Close() @@ -322,7 +322,7 @@ func WatermarkImage(buf []byte, o ImageOptions) (Image, error) { errMessage = fmt.Sprintf("%s. %s", errMessage, err.Error()) } - return Image{}, NewError(errMessage, BadRequest) + return Image{}, NewError(errMessage, http.StatusBadRequest) } opts := BimgOptions(o) @@ -336,7 +336,7 @@ func WatermarkImage(buf []byte, o ImageOptions) (Image, error) { func GaussianBlur(buf []byte, o ImageOptions) (Image, error) { if o.Sigma == 0 && o.MinAmpl == 0 { - return Image{}, NewError("Missing required param: sigma or minampl", BadRequest) + return Image{}, NewError("Missing required param: sigma or minampl", http.StatusBadRequest) } opts := BimgOptions(o) return Process(buf, opts) @@ -344,10 +344,10 @@ func GaussianBlur(buf []byte, o ImageOptions) (Image, error) { func Pipeline(buf []byte, o ImageOptions) (Image, error) { if len(o.Operations) == 0 { - return Image{}, NewError("Missing or invalid pipeline operations JSON", BadRequest) + return Image{}, NewError("Missing or invalid pipeline operations JSON", http.StatusBadRequest) } if len(o.Operations) > 10 { - return Image{}, NewError("Maximum allowed pipeline operations exceeded", BadRequest) + return Image{}, NewError("Maximum allowed pipeline operations exceeded", http.StatusBadRequest) } // Validate and built operations @@ -355,7 +355,7 @@ func Pipeline(buf []byte, o ImageOptions) (Image, error) { // Validate supported operation name var exists bool if operation.Operation, exists = OperationsMap[operation.Name]; !exists { - return Image{}, NewError(fmt.Sprintf("Unsupported operation name: %s", operation.Name), BadRequest) + return Image{}, NewError(fmt.Sprintf("Unsupported operation name: %s", operation.Name), http.StatusBadRequest) } // Parse and construct operation options diff --git a/imaginary.go b/imaginary.go index eac4da6..0b3e9e0 100644 --- a/imaginary.go +++ b/imaginary.go @@ -114,7 +114,7 @@ type URLSignature struct { func main() { flag.Usage = func() { - _, _ = fmt.Fprint(os.Stderr, fmt.Sprintf(usage, Version, runtime.NumCPU())) + _, _ = fmt.Fprint(os.Stderr, usage, Version, runtime.NumCPU()) } flag.Parse() diff --git a/middleware.go b/middleware.go index 2ab0c14..11a15db 100644 --- a/middleware.go +++ b/middleware.go @@ -9,8 +9,8 @@ import ( "strings" "time" - "github.com/rs/cors" "github.com/h2non/bimg" + "github.com/rs/cors" "gopkg.in/throttled/throttled.v2" "gopkg.in/throttled/throttled.v2/store/memstore" ) @@ -105,7 +105,7 @@ func validateImage(next http.Handler, o ServerOptions) http.Handler { } if r.Method == http.MethodGet && o.Mount == "" && !o.EnableURLSource { - ErrorReply(r, w, ErrMethodNotAllowed, o) + ErrorReply(r, w, ErrGetMethodNotAllowed, o) return } diff --git a/params.go b/params.go index cf1cf89..f4c59fd 100644 --- a/params.go +++ b/params.go @@ -344,9 +344,11 @@ func coerceInterlace(io *ImageOptions, param interface{}) (err error) { } func buildParamsFromOperation(op PipelineOperation) (ImageOptions, error) { - var options ImageOptions + // Apply defaults + options.Extend = bimg.ExtendCopy + for key, value := range op.Params { fn, ok := paramTypeCoercions[key] if !ok { @@ -366,6 +368,9 @@ func buildParamsFromOperation(op PipelineOperation) (ImageOptions, error) { func buildParamsFromQuery(query url.Values) (ImageOptions, error) { var options ImageOptions + // Apply defaults + options.Extend = bimg.ExtendCopy + // Extract only known parameters for key := range query { fn, ok := paramTypeCoercions[key] @@ -448,8 +453,8 @@ func parseExtendMode(val string) bimg.Extend { if val == "white" { return bimg.ExtendWhite } - if val == "copy" { - return bimg.ExtendCopy + if val == "black" { + return bimg.ExtendBlack } if val == "mirror" { return bimg.ExtendMirror @@ -457,7 +462,10 @@ func parseExtendMode(val string) bimg.Extend { if val == "background" { return bimg.ExtendBackground } - return bimg.ExtendBlack + if val == "lastpixel" { + return bimg.ExtendLast + } + return bimg.ExtendCopy } func parseGravity(val string) bimg.Gravity { diff --git a/params_test.go b/params_test.go index 49a1542..e73d41f 100644 --- a/params_test.go +++ b/params_test.go @@ -141,10 +141,11 @@ func TestParseExtend(t *testing.T) { {"black", bimg.ExtendBlack}, {"copy", bimg.ExtendCopy}, {"mirror", bimg.ExtendMirror}, + {"lastpixel", bimg.ExtendLast}, {"background", bimg.ExtendBackground}, {" BACKGROUND ", bimg.ExtendBackground}, - {"invalid", bimg.ExtendBlack}, - {"", bimg.ExtendBlack}, + {"invalid", bimg.ExtendCopy}, + {"", bimg.ExtendCopy}, } for _, extend := range cases { diff --git a/source_http.go b/source_http.go index 863bc8f..5bfeeaa 100644 --- a/source_http.go +++ b/source_http.go @@ -41,11 +41,11 @@ func (s *HTTPImageSource) fetchImage(url *url.URL, ireq *http.Request) ([]byte, req := newHTTPRequest(s, ireq, http.MethodHead, url) res, err := http.DefaultClient.Do(req) if err != nil { - return nil, fmt.Errorf("error fetching image http headers: %v", err) + return nil, fmt.Errorf("error fetching remote http image headers: %v", err) } _ = res.Body.Close() if res.StatusCode < 200 && res.StatusCode > 206 { - return nil, fmt.Errorf("error fetching image http headers: (status=%d) (url=%s)", res.StatusCode, req.URL.String()) + return nil, NewError(fmt.Sprintf("error fetching remote http image headers: (status=%d) (url=%s)", res.StatusCode, req.URL.String()), res.StatusCode) } contentLength, _ := strconv.Atoi(res.Header.Get("Content-Length")) @@ -58,11 +58,11 @@ func (s *HTTPImageSource) fetchImage(url *url.URL, ireq *http.Request) ([]byte, req := newHTTPRequest(s, ireq, http.MethodGet, url) res, err := http.DefaultClient.Do(req) if err != nil { - return nil, fmt.Errorf("error downloading image: %v", err) + return nil, fmt.Errorf("error fetching remote http image: %v", err) } defer res.Body.Close() if res.StatusCode != 200 { - return nil, fmt.Errorf("error downloading image: (status=%d) (url=%s)", res.StatusCode, req.URL.String()) + return nil, NewError(fmt.Sprintf("error fetching remote http image: (status=%d) (url=%s)", res.StatusCode, req.URL.String()), res.StatusCode) } // Read the body