package main import ( "encoding/json" "fmt" "mime" "net/http" "path" "strconv" "strings" "github.com/h2non/bimg" "github.com/h2non/filetype" ) func indexController(o ServerOptions) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != path.Join(o.PathPrefix, "/") { ErrorReply(r, w, ErrNotFound, ServerOptions{}) return } body, _ := json.Marshal(Versions{ Version, bimg.Version, bimg.VipsVersion, }) w.Header().Set("Content-Type", "application/json") _, _ = w.Write(body) } } func healthController(w http.ResponseWriter, r *http.Request) { health := GetHealthStats() body, _ := json.Marshal(health) w.Header().Set("Content-Type", "application/json") _, _ = w.Write(body) } func imageController(o ServerOptions, operation Operation) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, req *http.Request) { var imageSource = MatchSource(req) if imageSource == nil { ErrorReply(req, w, ErrMissingImageSource, o) return } buf, err := imageSource.GetImage(req) if err != nil { if xerr, ok := err.(Error); ok { ErrorReply(req, w, xerr, o) } else { ErrorReply(req, w, NewError(err.Error(), http.StatusBadRequest), o) } return } if len(buf) == 0 { ErrorReply(req, w, ErrEmptyBody, o) return } imageHandler(w, req, buf, operation, o) } } func determineAcceptMimeType(accept string) string { for _, v := range strings.Split(accept, ",") { mediaType, _, _ := mime.ParseMediaType(v) switch mediaType { case "image/webp": return "webp" case "image/png": return "png" case "image/jpeg": return "jpeg" } } return "" } func imageHandler(w http.ResponseWriter, r *http.Request, buf []byte, operation Operation, o ServerOptions) { // Infer the body MIME type via mime sniff algorithm mimeType := http.DetectContentType(buf) // If cannot infer the type, infer it via magic numbers if mimeType == "application/octet-stream" { kind, err := filetype.Get(buf) if err == nil && kind.MIME.Value != "" { mimeType = kind.MIME.Value } } // Infer text/plain responses as potential SVG image if strings.Contains(mimeType, "text/plain") && len(buf) > 8 { if bimg.IsSVGImage(buf) { mimeType = "image/svg+xml" } } // Finally check if image MIME type is supported if !IsImageMimeTypeSupported(mimeType) { ErrorReply(r, w, ErrUnsupportedMedia, o) return } opts, err := buildParamsFromQuery(r.URL.Query()) if err != nil { ErrorReply(r, w, NewError("Error while processing parameters, "+err.Error(), http.StatusBadRequest), o) return } vary := "" if opts.Type == "auto" { opts.Type = determineAcceptMimeType(r.Header.Get("Accept")) vary = "Accept" // Ensure caches behave correctly for negotiated content } else if opts.Type != "" && ImageType(opts.Type) == 0 { ErrorReply(r, w, ErrOutputFormat, o) return } sizeInfo, err := bimg.Size(buf) if err != nil { ErrorReply(r, w, NewError("Error while processing the image: "+err.Error(), http.StatusBadRequest), o) return } // https://en.wikipedia.org/wiki/Image_resolution#Pixel_count imgResolution := float64(sizeInfo.Width) * float64(sizeInfo.Height) if (imgResolution / 1000000) > o.MaxAllowedPixels { ErrorReply(r, w, ErrResolutionTooBig, o) return } image, err := operation.Run(buf, opts) if err != nil { // Ensure the Vary header is set when an error occurs if vary != "" { w.Header().Set("Vary", vary) } ErrorReply(r, w, NewError("Error while processing the image: "+err.Error(), http.StatusBadRequest), o) return } // Expose Content-Length response header w.Header().Set("Content-Length", strconv.Itoa(len(image.Body))) w.Header().Set("Content-Type", image.Mime) if image.Mime != "application/json" && o.ReturnSize { meta, err := bimg.Metadata(image.Body) if err == nil { w.Header().Set("Image-Width", strconv.Itoa(meta.Size.Width)) w.Header().Set("Image-Height", strconv.Itoa(meta.Size.Height)) } } if vary != "" { w.Header().Set("Vary", vary) } _, _ = w.Write(image.Body) } func formController(o ServerOptions) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { operations := []struct { name string method string args string }{ {"Resize", "resize", "width=300&height=200&type=jpeg"}, {"Force resize", "resize", "width=300&height=200&force=true"}, {"Crop", "crop", "width=300&quality=95"}, {"SmartCrop", "crop", "width=300&height=260&quality=95&gravity=smart"}, {"Extract", "extract", "top=100&left=100&areawidth=300&areaheight=150"}, {"Enlarge", "enlarge", "width=1440&height=900&quality=95"}, {"Rotate", "rotate", "rotate=180"}, {"AutoRotate", "autorotate", "quality=90"}, {"Flip", "flip", ""}, {"Flop", "flop", ""}, {"Thumbnail", "thumbnail", "width=100"}, {"Zoom", "zoom", "factor=2&areawidth=300&top=80&left=80"}, {"Color space (black&white)", "resize", "width=400&height=300&colorspace=bw"}, {"Add watermark", "watermark", "textwidth=100&text=Hello&font=sans%2012&opacity=0.5&color=255,200,50"}, {"Convert format", "convert", "type=png"}, {"Image metadata", "info", ""}, {"Gaussian blur", "blur", "sigma=15.0&minampl=0.2"}, {"Pipeline (image reduction via multiple transformations)", "pipeline", "operations=%5B%7B%22operation%22:%20%22crop%22,%20%22params%22:%20%7B%22width%22:%20300,%20%22height%22:%20260%7D%7D,%20%7B%22operation%22:%20%22convert%22,%20%22params%22:%20%7B%22type%22:%20%22webp%22%7D%7D%5D"}, } html := "" for _, form := range operations { html += fmt.Sprintf(`

%s

`, path.Join(o.PathPrefix, form.name), path.Join(o.PathPrefix, form.method), form.args) } html += "" w.Header().Set("Content-Type", "text/html") _, _ = w.Write([]byte(html)) } }