Response image stream with Sharp/Jimp


#1

What I want to do is the following:

Read some image that I have in the storage and edit it ( crop/resize … whatever ) with Sharp or Jimp, but instead of writing that edited file to the storage, I want just to create it as a buffer, cache it and response it as a buffer stream.

How can I respond images like that as a stream ? Any help ?


#2

Can you explain your use case? That type of approach (serving dynamically resized images) doesn’t scale well for several reasons.


#3

@benallfree The idea is to have a route like this:

YOUR_DOMAIN/images/image.jpg?width=100&height200

Something like this. So in this case I would generate a stream of that image with this specific size and cache it for other use ( not for forever ).

UPDATE: I found out how it has to be:

  1. Set up your headers for images : response.header('Content-Type', 'image/gif');

  2. Set buffer, and on buffer done, respond with response.send:

     var image = sharp(Config.get('path.images') + filename)
             
    self.setSize(size, image)
     
    image.toBuffer(function(err, data,info){
        response.send(data) 
    })
    

@benallfree Why do you that it doesn’t scale well ?


#4

I’ve used this “dynamic” approach before and have moved away from it in favor of pre-generated image sizes. Several problems that are hard to solve:

Performance

  1. If you get hit with a bunch of identical requests at once (same image, same size), they will all be a cache miss and cause major CPU spikes unless you have some kind of queuing system.
  2. Getting hit with too many different requests at once (different images or sizes) can also overwhelm the system. Again, queuing would solve it
  3. Even with queuing, too many requests too close together will result in timeouts while images are being processed

Security

  1. You must carefully check the request parameters to make sure appropriate sizes are requested
  2. Someone could potentially mess up your cache by sending requests for every possible image size within bounds

The security could be solved by fixed/named sizes: YOUR_DOMAIN/images/image.jpg?size=medium, but that will not solve the performance issues. In my opinion there are no reasonable ways to solve the performance issues because resizing images on demand is a fundamentally flawed approach. It means keeping connections open and waiting for a potentially long running request, which is generally the opposite of what HTTP is supposed to do.

The solution I’ve favored in recent years (and the one I’m building in Adonis) is one that pre-generates sizes when the image initially comes into the system. That is a better place to queue them for processing and size generation, and that solution can scale because it causes you to address the case of the image not being immediately available (such as returning a placeholder).


#5

@benallfree . the talk about spikes and cache is a bit over my skills but i have used Intervention Image in PHP/Laravel and i used it only to upon image upload to resize the image into two predefined sizes.

Many years ago when i used Joomla i had an image component that allowed me to do some image manipulation after upload, as rotate crop and stuff but i realized that my customers didn’t use those features at all so when moving to Laravel i skipped it.

Now i have a customer that can’t learn how to hold the iPhone while taking pictures so every picture is laying down and i need the function rotate at least.

Will your package/plugin have any of these functions?


#6

I am using the same approach in my applications, the images of various sizes are generated dynamically, but I do cache them on disk, and as soon as Nginx is in front of Adonis in my case, all the static assets, including images are served by it.
For this to work, I would recommend not to use query params in url but params in filename, for examle: image@300x300.jpg, so you easily parse that filename as the first request arrives, then resize the image and save it on the disk in public, afterwards Nginx will serve it for you.
This approach adds a bit more complexity but gains way more performance.
Forgot to mention, that there is a spetial config for Nginx, that should be used in that case: try_files
You can read about it here: https://www.nginx.com/resources/admin-guide/serving-static-content/