Example #1
0
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
}
Example #2
0
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)
}
Example #3
0
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)
}
Example #4
0
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)
}
Example #5
0
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:
	// 		}
	// 	}()
}