feat(#94): support placeholder image
This commit is contained in:
parent
43bc548f8f
commit
6ff8b157e4
|
@ -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
|
||||
|
||||
|
|
28
README.md
28
README.md
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
41
error.go
41
error.go
|
@ -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())
|
||||
|
|
127
imaginary.go
127
imaginary.go
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)))
|
39
server.go
39
server.go
|
@ -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 {
|
||||
|
|
|
@ -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{})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue