func (im *Image) LoadImage() (err error) { m := metrics.GetOrRegisterTimer("fn.image.LoadBlob", nil) // TODO: update metric name defer m.UpdateSince(time.Now()) // TODO: throttle the number of images we load at a given time.. // this should be configurable... defer func() { if r := recover(); r != nil { var ok bool err, ok = r.(error) if !ok { err = fmt.Errorf("imgry: %v", r) } } }() var formatHint string if im.SrcFileExtension() == "ico" { formatHint = "ico" } ng := imagick.Engine{} im.img, err = ng.LoadBlob(im.Data, formatHint) if err != nil { if err == imagick.ErrEngineFailure { lg.Errorf("**** ENGINE FAILURE on %s", im.SrcUrl) } return err } im.sync() return nil }
func (b *Bucket) AddImagesFromUrls(urls []string) ([]*Image, error) { responses, err := app.HttpFetcher.GetAll(urls) if err != nil { return nil, err } // TODO: do not release the image here, instead, return it // and get BucketFetchItem to set c.Env["image"] = image // and let the BucketGetItem release it instead.. // Build images from fetched remote sources images := make([]*Image, len(responses)) for i, r := range responses { images[i] = NewImageFromSrcUrl(r.URL.String()) defer images[i].Release() if r.Status == 200 && len(r.Data) > 0 { images[i].Data = r.Data if err = images[i].LoadImage(); err != nil { lg.Errorf("LoadBlob data for %s returned error: %s", r.URL.String(), err) } } } return images, b.AddImages(images) }
func BucketGetItem(ctx context.Context, w http.ResponseWriter, r *http.Request) { // TODO: bucket binding should happen in a middleware... refactor all handlers // that use it.. bucket, err := NewBucket(chi.URLParams(ctx)["bucket"]) if err != nil { lg.Errorf("Failed to create bucket for %s cause: %s", r.URL, err) respond.ImageError(w, 422, err) return } sizing, err := imgry.NewSizingFromQuery(r.URL.RawQuery) if err != nil { lg.Errorf("Failed to create sizing for %s cause: %s", r.URL, err) respond.ImageError(w, 422, err) return } im, err := bucket.GetImageSize(ctx, chi.URLParams(ctx)["key"], sizing) if err != nil { lg.Errorf("Failed to get image for %s cause: %s", r.URL, err) respond.ImageError(w, 422, err) return } w.Header().Set("Content-Type", im.MimeType()) w.Header().Set("X-Meta-Width", fmt.Sprintf("%d", im.Width)) w.Header().Set("X-Meta-Height", fmt.Sprintf("%d", im.Height)) w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", app.Config.CacheMaxAge)) // If requested, only return the image details instead of the data if r.URL.Query().Get("info") != "" { // TODO: eventually, once the ruby stack is updated, we should // return an ImageInfo packet here instead.. respond.JSON(w, http.StatusOK, im) return } respond.Data(w, 200, im.Data) }
func BucketFetchItem(ctx context.Context, w http.ResponseWriter, r *http.Request) { bucket, err := NewBucket(chi.URLParams(ctx)["bucket"]) if err != nil { respond.ImageError(w, 422, err) return } fetchUrl := r.URL.Query().Get("url") if fetchUrl == "" { respond.ImageError(w, 422, ErrInvalidURL) return } u, err := urlx.Parse(fetchUrl) if err != nil { respond.ImageError(w, 422, ErrInvalidURL) return } fetchUrl = u.String() imKey := sha1Hash(fetchUrl) // transform to what is expected.. chi.URLParams(ctx)["key"] = imKey // First check if we have the original.. a bit of extra overhead, but its okay _, err = bucket.DbFindImage(ctx, imKey, nil) if err != nil && err != ErrImageNotFound { respond.ImageError(w, 422, err) return } // Fetch the image on-demand and add to bucket if we dont have it if err == ErrImageNotFound { // TODO: add image sizing throttler here.... _, err := bucket.AddImagesFromUrls(ctx, []string{fetchUrl}) if err != nil { lg.Errorf("Fetching failed for %s because %s", fetchUrl, err) respond.ImageError(w, 422, err) return } } BucketGetItem(ctx, w, r) }
func CreateJob(ctx context.Context, w http.ResponseWriter, r *http.Request) { // Low, high and urgent priorities only (high is default). priority := r.URL.Query().Get("priority") switch priority { case "low", "high", "urgent": // NOP. case "": priority = "high" default: http.Error(w, "unknown priority \""+priority+"\"", 422) return } // Decode request data. var req *api.ScriptsRequest err := json.NewDecoder(r.Body).Decode(&req) if err != nil { http.Error(w, "parse request body: "+err.Error(), 422) return } req.Script = chi.URLParams(ctx)["filename"] // Make sure ASYNC callback is valid URL. if req.CallbackURL != "" { req.CallbackURL, err = urlx.NormalizeString(req.CallbackURL) if err != nil { http.Error(w, "parse request body: "+err.Error(), 422) return } } // Enqueue the request. data, err := json.Marshal(req) if err != nil { http.Error(w, err.Error(), 500) return } lg.Debugf("Handler:\tEnqueue \"%v\" request", priority) job, err := Qmd.Enqueue(string(data), priority) if err != nil { http.Error(w, err.Error(), 500) return } // Async. if req.CallbackURL != "" { resp, _ := Qmd.GetAsyncResponse(req, job.ID) w.Write(resp) lg.Debugf("Handler:\tResponded with job %s ASYNC result", job.ID) go func() { //TODO: Retry callback if it failed? err := Qmd.PostResponseCallback(req, job.ID) if err != nil { lg.Errorf("can't post callback to %v", err) } }() return } // Sync. lg.Debugf("Handler:\tWaiting for job %s", job.ID) resp, _ := Qmd.GetResponse(job.ID) w.Write(resp) lg.Debugf("Handler:\tResponded with job %s result", job.ID) // // Kill the job, if client closes the connection before // // it receives the data. // done := make(chan struct{}) // defer close(done) // connClosed := w.(http.CloseNotifier).CloseNotify() // go func() { // select { // case <-connClosed: // job.Kill() // case <-done: // } // }() }