// vi:ts=4:sts=4:sw=4:noet:tw=72 package main import ( "flag" "fmt" "log" "net/http" "strconv" "strings" "github.com/davidbyttow/govips/pkg/vips" ) var ( imgServer = flag.String("s", "https://images.example.com/", "URL prefix") listenAddr = flag.String("l", "0.0.0.0:8080", "Listen address") sizesFilter = flag.String("f", "", "Allowed sizes") ) func init() { flag.Parse() } func resizeHandler(w http.ResponseWriter, r *http.Request) { // Get the query parameters from the request URL path := r.URL.EscapedPath() parm := strings.Split(path, "/") if len(parm) < 4 { writeError(w, "", "", "error in arguments", http.StatusBadRequest) return } method := parm[1] args := parm[2] imgPath := strings.Join(parm[3:], "/") // Start fetching the image from the given url resp, err := http.Get(*imgServer + imgPath) if err != nil { writeError(w, imgPath, method, "failed to fetch image", http.StatusNotFound) return } defer resp.Body.Close() switch method { case "resize": allowed := strings.Split(*sizesFilter, ",") forbidden := true if allowed[0] == "*" { forbidden = false } else { for _, v := range allowed { if args == v { forbidden = false break } } } if forbidden { writeError(w, imgPath, method, "size is not allowed", http.StatusForbidden) } size := strings.Split(args, "x") if len(size) < 2 { writeError(w, imgPath, method, "url, width, and height are required", http.StatusBadRequest) return } width := size[0] height := size[1] // Validate that all three required fields are present if imgPath == "" || width == "" || height == "" { writeError(w, imgPath, method, "url, width, and height are required", http.StatusBadRequest) return } // Convert width and height to integers iw, errW := strconv.Atoi(width) ih, errH := strconv.Atoi(height) if errW != nil || errH != nil { writeError(w, imgPath, method, "width and height must be integers", http.StatusBadRequest) return } // Ensure that a valid response was given if resp.StatusCode/100 != 2 { writeError(w, imgPath, method, fmt.Sprintf("failed to fetch image (%v)", resp.StatusCode), http.StatusBadRequest) return } // govips returns the output of the image as a []byte object. We don't need // it since we are directly piping it to the ResponseWriter _, _, err = vips.NewTransform(). Load(resp.Body). ResizeStrategy(vips.ResizeStrategyAuto). Resize(iw, ih). Output(w). Apply() if err != nil { writeError(w, imgPath, method, "failed to resize", http.StatusInternalServerError) return } case "thumbnail": default: writeError(w, imgPath, method, "unknown method", http.StatusBadRequest) return } } func writeError(w http.ResponseWriter, imgPath, method, err string, status int) { w.WriteHeader(status) w.Write([]byte(fmt.Sprintf("Error [%s %s]: %s", method, imgPath, err))) } func main() { // Start vips with the default configuration vips.Startup(nil) defer vips.Shutdown() http.HandleFunc("/", resizeHandler) log.Fatal(http.ListenAndServe(*listenAddr, nil)) }