New release, minor features, bimg upgrade and several fixes (#311)

* feat: add fly.io

* feat(readme): fly image width

* feat(readme): add html image + badge

* fix(docs): try space non-URL encoding

* Update README.md

* Update README.md

* Update README.md

* feat(docs): add fly.io info and links

* feat(#305, #309, #308, #305. #284)

* fix(ci): golinter

* fix(Dockerfile): use root folder for go test / ci linter

* fix(ci): allow to fail  go 1.14 build, still not sure why

* fix(ci): bad yaml format

* fix(ci): use travis-compatible regex

* fix(ci): lets skip go1.14 for now

* fix(mod): update dependencies
This commit is contained in:
Tom 2020-06-07 18:38:14 +02:00 committed by GitHub
parent b2a142adb8
commit 384bb00b15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 108 additions and 123 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

4
go.mod
View File

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

8
go.sum
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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