feat(#94): support placeholder image

This commit is contained in:
Tomas Aparicio 2016-10-02 00:42:56 +01:00
parent 43bc548f8f
commit 6ff8b157e4
9 changed files with 194 additions and 89 deletions

View File

@ -3,6 +3,7 @@
- feat(#95): use `libvips@8.4.1`.
- fix(#75): use `bimg@1.0.5´, which provides extract area fix.
- feat(api): supports `extend` and `embed` query params. See HTTP API params docs for more details.
- feat(#94): add placeholder image support in case of error.
## 0.1.27 / 27-09-2016

View File

@ -58,6 +58,7 @@ To get started, take a look the [installation](#installation) steps, [usage](#us
- Custom output color space (RGB, black/white...)
- Format conversion (with additional quality/compression settings)
- Info (image size, format, orientation, alpha...)
- Reply with default or custom placeholder image in case of error.
## Prerequisites
@ -237,11 +238,14 @@ Usage:
imaginary -p 80
imaginary -cors -gzip
imaginary -concurrency 10
imaginary -path-prefix /api/v1
imaginary -enable-url-source
imaginary -enable-url-source -allowed-origins http://localhost,http://server.com
imaginary -enable-url-source -enable-auth-forwarding
imaginary -enable-url-source -authorization "Basic AwDJdL2DbwrD=="
imaginary -h | -help
imaginary -enable-placeholder
imaginery -enable-url-source -placeholder ./placeholder.jpg
imaginary -h | -help
imaginary -v | -version
Options:
@ -249,6 +253,7 @@ Options:
-p <port> bind port [default: 8088]
-h, -help output help
-v, -version output version
-path-prefix <value> Url path prefix to listen to [default: "/"]
-cors Enable CORS support [default: false]
-gzip Enable gzip compression [default: false]
-key <key> Define API key for authorization
@ -257,14 +262,16 @@ Options:
-http-read-timeout <num> HTTP read timeout in seconds [default: 30]
-http-write-timeout <num> HTTP write timeout in seconds [default: 30]
-enable-url-source Restrict remote image source processing to certain origins (separated by commas)
-enable-placeholder Enable image response placeholder to be used in case of error [default: false]
-enable-auth-forwarding Forwards X-Forward-Authorization or Authorization header to the image source server. -enable-url-source flag must be defined. Tip: secure your server from public access to prevent attack vectors
-allowed-origins <urls> TLS certificate file path
-certfile <path> TLS certificate file path
-keyfile <path> TLS private key file path
-authorization <value> Defines a constant Authorization header value passed to all the image source servers. -enable-url-source flag must be defined. This overwrites authorization headers forwarding behavior via X-Forward-Authorization
-concurreny <num> Throttle concurrency limit per second [default: disabled]
-placeholder <path> Image path to image custom placeholder to be used in case of error. Recommended minimum image size is: 1200x1200
-concurreny <num> Throttle concurrency limit per second [default: disabled]
-burst <num> Throttle burst max cache size [default: 100]
-mrelease <num> OS memory release inverval in seconds [default: 30]
-mrelease <num> OS memory release interval in seconds [default: 30]
-cpus <num> Number of used cpu cores.
(default for current machine is 8 cores)
```
@ -374,6 +381,21 @@ Here an example response error when the payload is empty:
See all the predefined supported errors [here](https://github.com/h2non/imaginary/blob/master/error.go#L19-L28).
#### Placeholder
If `-enable-placeholder` or `-placeholder <image path>` flags are passed to `imaginary`, a placeholder image will be used in case of error or invalid request input.
If `-enable-placeholder` is passed, the default `imaginary` placeholder image will be used, however you can customized it via `-placeholder` flag, loading a custom compatible image from the file system.
Since `imaginary` has been partially designed to be used as public HTTP service, including web pages, in certain scenarios the response MIME type must be respected,
so the server will always reply with a placeholder image in case of error, such as image processing error, read error, payload error, request invalid request or any other.
You can customize the placeholder image passing the `-placeholder <image path>` flag when starting `imaginary`.
In this scenarios, the error message details will be exposed in the `Error` response header field as JSON for further inspection from API clients.
In some edge cases the placeholder image resizing might fail, so a 400 Bad Request will be used as response status and the `Content-Type` will be `application/json` with the proper message info. Note that this scenario won't be common.
### Form data
If you're pushing images to `imaginary` as `multipart/form-data` (you can do it as well as `image/*`), you must define at least one input field called `file` with the raw image data in order to be processed properly by imaginary.

View File

@ -12,7 +12,7 @@ import (
func indexController(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
ErrorReply(w, ErrNotFound)
ErrorReply(r, w, ErrNotFound, ServerOptions{})
return
}
@ -32,26 +32,26 @@ func imageController(o ServerOptions, operation Operation) func(http.ResponseWri
return func(w http.ResponseWriter, req *http.Request) {
var imageSource = MatchSource(req)
if imageSource == nil {
ErrorReply(w, ErrMissingImageSource)
ErrorReply(req, w, ErrMissingImageSource, o)
return
}
buf, err := imageSource.GetImage(req)
if err != nil {
ErrorReply(w, NewError(err.Error(), BadRequest))
ErrorReply(req, w, NewError(err.Error(), BadRequest), o)
return
}
if len(buf) == 0 {
ErrorReply(w, ErrEmptyBody)
ErrorReply(req, w, ErrEmptyBody, o)
return
}
imageHandler(w, req, buf, operation)
imageHandler(w, req, buf, operation, o)
}
}
func imageHandler(w http.ResponseWriter, r *http.Request, buf []byte, Operation Operation) {
func imageHandler(w http.ResponseWriter, r *http.Request, buf []byte, Operation Operation, o ServerOptions) {
// Infer the body MIME type via mimesniff algorithm
mimeType := http.DetectContentType(buf)
@ -72,19 +72,19 @@ func imageHandler(w http.ResponseWriter, r *http.Request, buf []byte, Operation
// Finally check if image MIME type is supported
if IsImageMimeTypeSupported(mimeType) == false {
ErrorReply(w, ErrUnsupportedMedia)
ErrorReply(r, w, ErrUnsupportedMedia, o)
return
}
opts := readParams(r.URL.Query())
if opts.Type != "" && ImageType(opts.Type) == 0 {
ErrorReply(w, ErrOutputFormat)
ErrorReply(r, w, ErrOutputFormat, o)
return
}
image, err := Operation.Run(buf, opts)
if err != nil {
ErrorReply(w, NewError("Error while processing the image: "+err.Error(), BadRequest))
ErrorReply(r, w, NewError("Error while processing the image: "+err.Error(), BadRequest), o)
return
}

View File

@ -2,8 +2,11 @@ package main
import (
"encoding/json"
"fmt"
"net/http"
"strings"
bimg "gopkg.in/h2non/bimg.v1"
)
const (
@ -70,7 +73,43 @@ func NewError(err string, code uint8) Error {
return Error{err, code}
}
func ErrorReply(w http.ResponseWriter, err Error) error {
func replyWithPlaceholder(req *http.Request, w http.ResponseWriter, err Error, o ServerOptions) error {
image := o.PlaceholderImage
// Resize placeholder to expected output
buf, _err := bimg.Resize(o.PlaceholderImage, bimg.Options{
Force: true,
Crop: true,
Enlarge: true,
Width: parseInt(req.URL.Query().Get("width")),
Height: parseInt(req.URL.Query().Get("height")),
Type: ImageType(req.URL.Query().Get("type")),
})
if _err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(fmt.Sprintf("{\"error\":\"%s\", \"code\": %d}", _err.Error(), BadRequest)))
return _err
}
// Use final response body image
image = buf
// Placeholder image response
w.Header().Set("Content-Type", GetImageMimeType(bimg.DetermineImageType(image)))
w.Header().Set("Error", string(err.JSON()))
w.WriteHeader(err.HTTPCode())
w.Write(image)
return err
}
func ErrorReply(req *http.Request, w http.ResponseWriter, err Error, o ServerOptions) error {
// Reply with placeholder if required
if o.EnablePlaceholder || o.Placeholder != "" {
return replyWithPlaceholder(req, w, err, o)
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(err.HTTPCode())
w.Write(err.JSON())

View File

@ -3,6 +3,7 @@ package main
import (
"flag"
"fmt"
"io/ioutil"
"net/url"
"os"
"runtime"
@ -11,36 +12,40 @@ import (
"strings"
"time"
bimg "gopkg.in/h2non/bimg.v1"
. "github.com/tj/go-debug"
)
var debug = Debug("imaginary")
var (
aAddr = flag.String("a", "", "bind address")
aPort = flag.Int("p", 8088, "port to listen")
aVers = flag.Bool("v", false, "Show version")
aVersl = flag.Bool("version", false, "Show version")
aHelp = flag.Bool("h", false, "Show help")
aHelpl = flag.Bool("help", false, "Show help")
aPathPrefix = flag.String("path-prefix", "/", "Url path prefix to listen to")
aCors = flag.Bool("cors", false, "Enable CORS support")
aGzip = flag.Bool("gzip", false, "Enable gzip compression")
aAuthForwarding = flag.Bool("enable-auth-forwarding", false, "Forwards X-Forward-Authorization or Authorization header to the image source server. -enable-url-source flag must be defined. Tip: secure your server from public access to prevent attack vectors")
aEnableURLSource = flag.Bool("enable-url-source", false, "Enable remote HTTP URL image source processing")
aAlloweOrigins = flag.String("allowed-origins", "", "Restrict remote image source processing to certain origins (separated by commas)")
aKey = flag.String("key", "", "Define API key for authorization")
aMount = flag.String("mount", "", "Mount server local directory")
aCertFile = flag.String("certfile", "", "TLS certificate file path")
aKeyFile = flag.String("keyfile", "", "TLS private key file path")
aAuthorization = flag.String("authorization", "", "Defines a constant Authorization header value passed to all the image source servers. -enable-url-source flag must be defined. This overwrites authorization headers forwarding behavior via X-Forward-Authorization")
aHttpCacheTtl = flag.Int("http-cache-ttl", -1, "The TTL in seconds")
aReadTimeout = flag.Int("http-read-timeout", 60, "HTTP read timeout in seconds")
aWriteTimeout = flag.Int("http-write-timeout", 60, "HTTP write timeout in seconds")
aConcurrency = flag.Int("concurrency", 0, "Throttle concurrency limit per second")
aBurst = flag.Int("burst", 100, "Throttle burst max cache size")
aMRelease = flag.Int("mrelease", 30, "OS memory release inverval in seconds")
aCpus = flag.Int("cpus", runtime.GOMAXPROCS(-1), "Number of cpu cores to use")
aAddr = flag.String("a", "", "bind address")
aPort = flag.Int("p", 8088, "port to listen")
aVers = flag.Bool("v", false, "Show version")
aVersl = flag.Bool("version", false, "Show version")
aHelp = flag.Bool("h", false, "Show help")
aHelpl = flag.Bool("help", false, "Show help")
aPathPrefix = flag.String("path-prefix", "/", "Url path prefix to listen to")
aCors = flag.Bool("cors", false, "Enable CORS support")
aGzip = flag.Bool("gzip", false, "Enable gzip compression")
aAuthForwarding = flag.Bool("enable-auth-forwarding", false, "Forwards X-Forward-Authorization or Authorization header to the image source server. -enable-url-source flag must be defined. Tip: secure your server from public access to prevent attack vectors")
aEnableURLSource = flag.Bool("enable-url-source", false, "Enable remote HTTP URL image source processing")
aEnablePlaceholder = flag.Bool("enable-placeholder", false, "Enable image response placeholder to be used in case of error")
aAlloweOrigins = flag.String("allowed-origins", "", "Restrict remote image source processing to certain origins (separated by commas)")
aKey = flag.String("key", "", "Define API key for authorization")
aMount = flag.String("mount", "", "Mount server local directory")
aCertFile = flag.String("certfile", "", "TLS certificate file path")
aKeyFile = flag.String("keyfile", "", "TLS private key file path")
aAuthorization = flag.String("authorization", "", "Defines a constant Authorization header value passed to all the image source servers. -enable-url-source flag must be defined. This overwrites authorization headers forwarding behavior via X-Forward-Authorization")
aPlaceholder = flag.String("placeholder", "", "Image path to image custom placeholder to be used in case of error. Recommended minimum image size is: 1200x1200")
aHttpCacheTtl = flag.Int("http-cache-ttl", -1, "The TTL in seconds")
aReadTimeout = flag.Int("http-read-timeout", 60, "HTTP read timeout in seconds")
aWriteTimeout = flag.Int("http-write-timeout", 60, "HTTP write timeout in seconds")
aConcurrency = flag.Int("concurrency", 0, "Throttle concurrency limit per second")
aBurst = flag.Int("burst", 100, "Throttle burst max cache size")
aMRelease = flag.Int("mrelease", 30, "OS memory release interval in seconds")
aCpus = flag.Int("cpus", runtime.GOMAXPROCS(-1), "Number of cpu cores to use")
)
const usage = `imaginary %s
@ -54,7 +59,9 @@ Usage:
imaginary -enable-url-source -allowed-origins http://localhost,http://server.com
imaginary -enable-url-source -enable-auth-forwarding
imaginary -enable-url-source -authorization "Basic AwDJdL2DbwrD=="
imaginary -h | -help
imaginary -enable-placeholder
imaginery -enable-url-source -placeholder ./placeholder.jpg
imaginary -h | -help
imaginary -v | -version
Options:
@ -71,14 +78,16 @@ Options:
-http-read-timeout <num> HTTP read timeout in seconds [default: 30]
-http-write-timeout <num> HTTP write timeout in seconds [default: 30]
-enable-url-source Restrict remote image source processing to certain origins (separated by commas)
-enable-placeholder Enable image response placeholder to be used in case of error [default: false]
-enable-auth-forwarding Forwards X-Forward-Authorization or Authorization header to the image source server. -enable-url-source flag must be defined. Tip: secure your server from public access to prevent attack vectors
-allowed-origins <urls> TLS certificate file path
-certfile <path> TLS certificate file path
-keyfile <path> TLS private key file path
-authorization <value> Defines a constant Authorization header value passed to all the image source servers. -enable-url-source flag must be defined. This overwrites authorization headers forwarding behavior via X-Forward-Authorization
-concurreny <num> Throttle concurrency limit per second [default: disabled]
-placeholder <path> Image path to image custom placeholder to be used in case of error. Recommended minimum image size is: 1200x1200
-concurreny <num> Throttle concurrency limit per second [default: disabled]
-burst <num> Throttle burst max cache size [default: 100]
-mrelease <num> OS memory release inverval in seconds [default: 30]
-mrelease <num> OS memory release interval in seconds [default: 30]
-cpus <num> Number of used cpu cores.
(default for current machine is %d cores)
`
@ -101,24 +110,26 @@ func main() {
port := getPort(*aPort)
opts := ServerOptions{
Port: port,
Address: *aAddr,
Gzip: *aGzip,
CORS: *aCors,
AuthForwarding: *aAuthForwarding,
EnableURLSource: *aEnableURLSource,
PathPrefix: *aPathPrefix,
ApiKey: *aKey,
Concurrency: *aConcurrency,
Burst: *aBurst,
Mount: *aMount,
CertFile: *aCertFile,
KeyFile: *aKeyFile,
HttpCacheTtl: *aHttpCacheTtl,
HttpReadTimeout: *aReadTimeout,
HttpWriteTimeout: *aWriteTimeout,
Authorization: *aAuthorization,
AlloweOrigins: parseOrigins(*aAlloweOrigins),
Port: port,
Address: *aAddr,
Gzip: *aGzip,
CORS: *aCors,
AuthForwarding: *aAuthForwarding,
EnableURLSource: *aEnableURLSource,
EnablePlaceholder: *aEnablePlaceholder,
PathPrefix: *aPathPrefix,
ApiKey: *aKey,
Concurrency: *aConcurrency,
Burst: *aBurst,
Mount: *aMount,
CertFile: *aCertFile,
KeyFile: *aKeyFile,
Placeholder: *aPlaceholder,
HttpCacheTtl: *aHttpCacheTtl,
HttpReadTimeout: *aReadTimeout,
HttpWriteTimeout: *aWriteTimeout,
Authorization: *aAuthorization,
AlloweOrigins: parseOrigins(*aAlloweOrigins),
}
// Create a memory release goroutine
@ -136,6 +147,24 @@ func main() {
checkHttpCacheTtl(*aHttpCacheTtl)
}
// Read placeholder image, if required
if *aPlaceholder != "" {
buf, err := ioutil.ReadFile(*aPlaceholder)
if err != nil {
exitWithError("cannot start the server: %s", err)
}
imageType := bimg.DetermineImageType(buf)
if !bimg.IsImageTypeSupportedByVips(imageType) {
exitWithError("Placeholder image type is not supported. Only JPEG, PNG or WEBP are supported")
}
opts.PlaceholderImage = buf
} else if *aEnablePlaceholder {
// Expose default placeholder
opts.PlaceholderImage = placeholder
}
debug("imaginary server listening on port :%d/%s", opts.Port, strings.TrimPrefix(opts.PathPrefix, "/"))
// Load image source providers
@ -144,7 +173,7 @@ func main() {
// Start the server
err := Server(opts)
if err != nil {
exitWithError("cannot start the server: %s\n", err)
exitWithError("cannot start the server: %s", err)
}
}
@ -171,10 +200,10 @@ func showVersion() {
func checkMountDirectory(path string) {
src, err := os.Stat(path)
if err != nil {
exitWithError("error while mounting directory: %s\n", err)
exitWithError("error while mounting directory: %s", err)
}
if src.IsDir() == false {
exitWithError("mount path is not a directory: %s\n", path)
exitWithError("mount path is not a directory: %s", path)
}
if path == "/" {
exitWithError("cannot mount root directory for security reasons")
@ -217,6 +246,6 @@ func memoryRelease(interval int) {
}
func exitWithError(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, format, args)
fmt.Fprintf(os.Stderr, format+"\n", args)
os.Exit(1)
}

View File

@ -26,13 +26,13 @@ func Middleware(fn func(http.ResponseWriter, *http.Request), o ServerOptions) ht
next = cors.Default().Handler(next)
}
if o.ApiKey != "" {
next = authorizeClient(next, o.ApiKey)
next = authorizeClient(next, o)
}
if o.HttpCacheTtl >= 0 {
next = setCacheHeaders(next, o.HttpCacheTtl)
}
return validate(defaultHeaders(next))
return validate(defaultHeaders(next), o)
}
func ImageMiddleware(o ServerOptions) func(Operation) http.Handler {
@ -67,10 +67,10 @@ func throttle(next http.Handler, o ServerOptions) http.Handler {
return httpRateLimiter.RateLimit(next)
}
func validate(next http.Handler) http.Handler {
func validate(next http.Handler, o ServerOptions) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" && r.Method != "POST" {
ErrorReply(w, ErrMethodNotAllowed)
ErrorReply(r, w, ErrMethodNotAllowed, o)
return
}
@ -87,7 +87,7 @@ func validateImage(next http.Handler, o ServerOptions) http.Handler {
}
if r.Method == "GET" && o.Mount == "" && o.EnableURLSource == false {
ErrorReply(w, ErrMethodNotAllowed)
ErrorReply(r, w, ErrMethodNotAllowed, o)
return
}
@ -95,15 +95,15 @@ func validateImage(next http.Handler, o ServerOptions) http.Handler {
})
}
func authorizeClient(next http.Handler, validKey string) http.Handler {
func authorizeClient(next http.Handler, o ServerOptions) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
key := r.Header.Get("API-Key")
if key == "" {
key = r.URL.Query().Get("key")
}
if key != validKey {
ErrorReply(w, ErrInvalidApiKey)
if key != o.ApiKey {
ErrorReply(r, w, ErrInvalidApiKey, o)
return
}

11
placeholder.go Normal file
View File

@ -0,0 +1,11 @@
package main
import (
"encoding/base64"
"io/ioutil"
"strings"
)
const placeholderData = `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`
var placeholder, _ = ioutil.ReadAll(base64.NewDecoder(base64.StdEncoding, strings.NewReader(placeholderData)))

View File

@ -10,24 +10,27 @@ import (
)
type ServerOptions struct {
Port int
Burst int
Concurrency int
HttpCacheTtl int
HttpReadTimeout int
HttpWriteTimeout int
CORS bool
Gzip bool
EnableURLSource bool
AuthForwarding bool
Address string
PathPrefix string
ApiKey string
Mount string
CertFile string
KeyFile string
Authorization string
AlloweOrigins []*url.URL
Port int
Burst int
Concurrency int
HttpCacheTtl int
HttpReadTimeout int
HttpWriteTimeout int
CORS bool
Gzip bool
AuthForwarding bool
EnableURLSource bool
EnablePlaceholder bool
Address string
PathPrefix string
ApiKey string
Mount string
CertFile string
KeyFile string
Authorization string
Placeholder string
PlaceholderImage []byte
AlloweOrigins []*url.URL
}
func Server(o ServerOptions) error {

View File

@ -303,7 +303,7 @@ func TestMountInvalidPath(t *testing.T) {
func controller(op Operation) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
buf, _ := ioutil.ReadAll(r.Body)
imageHandler(w, r, buf, op)
imageHandler(w, r, buf, op, ServerOptions{})
}
}