/node-image-proxy

A caching, resizing image proxy

Primary LanguageJavaScript

node-image-proxy

HitCount

node-image-proxy is a caching image proxy server written in Node.js.

  • resizing and converting remote images
  • serving aws s3 or localhost
  • caching in-memory on Redis

Table of Contents


Requirements

  • Node.js >= 8
  • Redis Server - It stores the converted URL's cache.
  • AWS S3 Optional
  • CDN Optional

Getting Started

$ npm i
$ npm start
$ curl -I http://localhost:3000/w200/https://example.com/image.jpg
HTTP/1.1 301 Moved Permanently
Location: /uploads/2019/03/14/w200/8bbaa9b8-eb59-4a95-92ac-1505369abf4b.jpg
...
# you will get your resized image url follows :
# http://localhost:3000/uploads/2019/03/14/w200/8bbaa9b8-eb59-4a95-92ac-1505369abf4b.jpg
$ pm2 start
$ curl -I http://localhost:3000/w200/https://example.com/image.jpg
HTTP/1.1 301 Moved Permanently
Location: /uploads/2019/03/14/w200/8bbaa9b8-eb59-4a95-92ac-1505369abf4b.jpg

URLStructure

nodex-image-proxy URLs are of the form http://localhost/{options}/{remote_url}.

Options

Options are available for resizing, rotation, output format, flip, flop and greyscale. Options for are specified as a comma delimited list of parameters, which can be supplied in any order. Duplicate parameters overwrite previous values.

Example

ogirinal sample image ( 1280 by 768 pixels) link

Options Meaning Image
w200 200 pixels wide, auto-scaled height
example http://localhost/w200/http://example.com/image.jpg
w200_sample
h100 100 pixels high, auto-scaled width
example http://localhost/h100/http://example.com/image.jpg
h100_sample
w200,h300 200 pixels wide and 300 pixels high image
example http://localhost/w200,h300/http://example.com/image.jpg
w200,h300_sample
wh100 100 pixels wide and 100 pixels high image
example http://localhost/wh100/http://example.com/image.jpg
wh100_sample
w100,r90 100 pixels wide, rotated 90 degrees
example http://localhost/w100,r90/http://example.com/image.jpg
w100,r90_sample
w200,png 200 pixels wide, converted to PNG format
example http://localhost/w200,png/http://example.com/image.jpg
w200,png
w200,grey 200 pixels wide, converted to 8-bit greyscale
example http://localhost/w200,grey/http://example.com/image.jpg
w200,grey
w200,flip 200 pixels wide, flip the image about the vertical Y axis
example http://localhost/w200,flip/http://example.com/image.jpg
w200,flip

The full options are:

  • wh:number - Resize image to width x height. Crop to cover both provided dimensions.
  • h:number - Resize image to height. Auto-scale the width to match the height.
  • w:number - Resize image to width. Auto-scale the height to match the width.
  • r:number - Rotate the output image by angle. it is converted to a valid positive degree rotation. For example, -450 will produce a 270deg rotation.
  • jpg, jpeg - JPEG output.
  • png - PNG output. It is always full colour at 8 or 16 bits per pixel. Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
  • webp - WEBP output.
  • flip - Flip the image about the vertical Y axis.
  • flop - Flop the image about the horizontal X axis.
  • grey gray greyscale grayscale - Convert to 8-bit greyscale

Remote URL

The URL of the original image to load is specified as the remainder of the path, without any encoding.

complex route for advanced users

To use almost features (and detail options) of sharp, use complex route and JSON Object parameter :

  • If the JSON parameter contains characters that require URI encoding, the JSON parameter need URI encoding.

For example,

  • JavaScript encodeURI
encodeURI('param={"resize":500, "flop":true, "composite":[{ "input": "http://example.comremote_input_image.png", "gravity": "centre" }], "grayscale": false}');

// output
param=%7B%22resize%22:500,%20%22flop%22:true,%20%22composite%22:%5B%7B%20%22input%22:%20%22http://example.comremote_input_image.png%22,%20%22gravity%22:%20%22centre%22%20%7D%5D,%20%22grayscale%22:%20false%7D
  • Java URLEncoder.encode
  • PHP urlencode

Using Docker

image-proxy can (and should) be used as a application inside a Docker container. Just pull the official image from Docker Hub:

# If redis is not running, run redis before running node-image-proxy.
$ docker run --name redis -d redis
$ docker pull marcjacobs1021/node-image-proxy:latest
$ docker run -p 3000:3000 --link redis:redis --name node-image-proxy -d marcjacobs1021/node-image-proxy
...
# If redis is running (REDIS_HOST 6379 port), run node-image-proxy.
$ docker pull marcjacobs1021/node-image-proxy:latest
$ docker run -p 3000:3000 -e REDIS='REDIS_HOST' --name node-image-proxy -d marcjacobs1021/node-image-proxy

How it works?

  1. Access URL by specifying option and remote URL.
  2. Verify that it is allowed host.
  3. Make sure the option & URL cached in Redis.
  4. If cached, return redirect cached URL.
  5. If not cached, download remote url's image & upload local (or aws s3) server, caching, and return redirect URL.

Configuration

Configuration can be modified in the config.js file.

Example


localhost mode

module.exports = {
  /**
   * HTTP Response Status Code
   * default : 302
   */
  'httpResponseStatusCode': 302,
  /**
   * Select mode 'local' or 's3'.
   * It means the upload target server. default 'local'
   */
  'mode': 'local',
  /**
   * node-image-proxy server's default port
   */
  'port': 3000,
  /**
   * Redis Server Information
   * default :
   *   'host': '127.0.0.1',
   *   'port': 6379,
   *   'db': 0
   */
  'redisServer': {
    'host': '127.0.0.1',
    'port': 6379,
    'db': 0
  },
  'paths':{
    /**
     * Your Domain name (or CDN host name)
     * If not use CDN or Domain, ipnut http://localhost.
     * If config.mode is s3 and empty this value (config.paths.domain_name), s3 use aws s3 public OBJECT URL
     */
    'domain_name': 'http://yourdomain-name' ,  // your Domain name
    /**
     * no image file path
     */
    'noimage': '/noimage.png',
    /**
     * prefix upload path
     */
    'upload_path': '/uploads'
  }
};

aws s3 mode

module.exports = {
  /**
   * If you use aws s3, you should input your aws configure and s3_bucket name
   * Configuring the AWS CLI : https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html
   **/
  'awsconfig': {
    'region': 'us-west-2',
    'accessKeyId': 'AKIAIOSFODNN7EXAMPLE',
    'secretAccessKey': 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
    's3_bucket': 'your_bucket_name'
  },
  /**
   * HTTP Response Status Code
   * default : 302
   */
  'httpResponseStatusCode': 302,
  /**
   * Select mode 'local' or 's3'.
   * It means the upload target server. default 'local'
   */
  'mode': 's3',
  /**
   * node-image-proxy server's default port
   */
  'port': 3000,
  /**
   * Redis Server Information
   * default :
   *   'host': '127.0.0.1',
   *   'port': 6379,
   *   'db': 0
   */
  'redisServer': {
    'host': '127.0.0.1',
    'port': 6379,
    'db': 0
  },
  'paths':{
    /**
     * Your Domain name (or CDN host name)
     * If not use CDN or Domain, ipnut http://localhost.
     * If config.mode is s3 and empty this value (config.paths.domain_name), s3 use aws s3 public OBJECT URL
     */
    'domain_name': 'http://yourdomain-name' ,  // your Domain name
    /**
     * no image file path
     */
    'noimage': '/noimage.png',
    /**
     * prefix upload path
     */
    'upload_path': '/uploads'
  }
};

Permission

Be sure to use the permission option. Security is always important. It prevents attackers of bad intentions.

module.exports = {

  ...

  /**
   * An entry for the option to grant permission.
   * Only the options allowed here are available.
   * If you do not enter anything in the each options, all are allowed by default.
   *
   */
  'permission': {
    /**
     * Input all the allowed remote URL's hostnames. If there is no data, allow all remote URL. It is weak security.
     * ex) www.example.com
     */
    allowTargetHosts: [
      // 'www.example.com'
    ],
    /**
     * If you want, only accessible for certain hosts in the HTTP referrer header
     * ex) www.example.com
     */
    allowReferrers: [
      // 'www.example.com'
    ],
    /**
     * allowed options
     */
    allowOptions: {
      // allowed wh's number
      wh: [
        100, 200, 300
      ],
      // allowed resize's height
      h: [
        100, 200, 300
      ],
      // aloowed resize's width
      w: [
        100, 200, 300
      ],
      // allowed rotate's angle
      r: [
        90, 180, 270
      ],
      // allowed output format
      format: [
        'png', 'webp', 'jpg', 'jpeg'
      ],
      // allowed ohter option
      others: [
        'flip', 'flop', 'grey', 'gray', 'greyscale', 'grayscale'
      ]
    },
    /**
     * Allowed Mime Types of Remote URL's image.
     * default :
     *  'image/jpeg',
     *  'image/png',
     *  'image/gif'
     */
    'allowMimetypes': [
      'image/jpeg',
      'image/png',
      'image/gif'
    ],
    /**
     * allowed complex options.
     */
    allowComplex: [
      'resize',
      'composite',
      'flip',
      'flop',
      'grayscale'
    ],
    /**
      * Allowed Max Content Length
      * default : 20000000 (about 20MB)
      */
    'allowMaxContentLength': 20000000,
  },

  ...
}

Benchmark

  • Test Flatform : AWS t2.micro (Amazon Linux AMI 2018.03.0.20181129 x86_64 HVM)
  • Test Option : w200,h300
  • Test Image : JPEG 4,022,495 bytes (4 MB) 3264 * 2448
#1 After the image is first converted, the URL stores the cache in redis. (Return cache of redis for same request)

Time taken for tests:   4.734 seconds / 1000 requests
Requests per second:    211.24 [#/sec] (mean)
Time per request:       4.734 [ms] (mean)

#2 Cached

Time taken for tests:   1.238 seconds / 1000 requests
Requests per second:    807.68 [#/sec] (mean)
Time per request:       1.238 [ms] (mean)

#3 Cached

Time taken for tests:   1.193 seconds / 1000 requests
Requests per second:    838.42 [#/sec] (mean)
Time per request:       1.193 [ms] (mean)
  • Test Option : w200,h300
  • Test Image : JEPG 1,029,025 bytes (1 MB) 2448 * 3264
#1 After the image is first converted, the URL stores the cache in redis. (Return cache of redis for same request)

Time taken for tests:   4.090 seconds / 1000 requests
Requests per second:    244.48 [#/sec] (mean)
Time per request:       4.090 [ms] (mean)

#2 Cached

Time taken for tests:   1.272 seconds / 1000 requests
Requests per second:    786.36 [#/sec] (mean)
Time per request:       1.272 [ms] (mean)

#3 Cached

Time taken for tests:   1.198 seconds / 1000 requests
Requests per second:    834.85 [#/sec] (mean)
Time per request:       1.198 [ms] (mean)

License

MIT