Documentation
CDN

Anchor link Installation

The easiest way to install CDN is using DADI CLI. CLI is a command line application that can be used to create and maintain installations of DADI products. Follow the simple instructions below, or see more detailed documentation for DADI CLI.

Anchor link Install DADI CLI

$ npm install @dadi/cli -g

Anchor link Create new CDN installation

There are two ways to create a new CDN with the CLI: either manually create a new directory for CDN or let CLI handle that for you. DADI CLI accepts an argument for project-name which it uses to create a directory for the installation.

Manual directory creation

$ mkdir my-cdn
$ cd my-cdn
$ dadi cdn new

Automatic directory creation

$ dadi cdn new my-cdn
$ cd my-cdn

DADI CLI will install the latest version of CDN and copy a set of files to your chosen directory so you can launch CDN almost immediately.

Installing DADI CDN directly from NPM

All DADI platform microservices are also available from NPM. To add CDN to an existing project as a dependency:

$ cd my-existing-node-app
$ npm install --save @dadi/cdn

Anchor link Forever (optional)

As with most Node.js applications, to run the app in the background you will need to install install Forever and Forever-service:

$ [sudo] npm install forever -g
$ [sudo] npm install -g forever-service

Install DADI CDN as a service and ensure it loads on boot:

$ [sudo] forever-service install -s main.js -e NODE_ENV=production cdn --start

Note the environment variable NODE_ENV=production must be set to target the required config version.

You can then interact with the service using the following command:

$ [sudo] service cdn start
$ [sudo] service cdn stop
$ [sudo] service cdn status 
$ [sudo] service cdn restart

Anchor link Configuration

API reads a series of configuration parameters to define its behaviour and to adapt to each environment it runs on. These parameters are defined in JSON files placed inside the config/ directory, named as config.{ENVIRONMENT}.json, where {ENVIRONMENT} is the value of the NODE_ENV environment variable. In practice, this allows you to have different configuration parameters for when API is running in development, production and any staging, QA or anything in between, as per the requirements of your development workflow. For more advanced users this can also load based on the hostname i.e., it will also look for config." + req.headers.host + ".json.

Some configuration parameters also have corresponding environment variables, which will override whatever value is set in the configuration file.

The following table shows a list of all the available configuration parameters.

Path Description Environment variable Default Format Domain override
server.host The IP address the application will run on N/A 0.0.0.0 ipaddress No
server.port The port number the application will bind to PORT 8080 port No
server.redirectPort Port to redirect http connections to https from REDIRECT_PORT port No
server.name Server name N/A DADI (CDN) String No
server.protocol The protocol the web application will use PROTOCOL http String No
server.sslPassphrase The passphrase of the SSL private key SSL_PRIVATE_KEY_PASSPHRASE String No
server.sslPrivateKeyPath The filename of the SSL private key SSL_PRIVATE_KEY_PATH String No
server.sslCertificatePath The filename of the SSL certificate SSL_CERTIFICATE_PATH String No
server.sslIntermediateCertificatePath The filename of an SSL intermediate certificate, if any SSL_INTERMEDIATE_CERTIFICATE_PATH String No
server.sslIntermediateCertificatePaths The filenames of SSL intermediate certificates, overrides sslIntermediateCertificate (singular) SSL_INTERMEDIATE_CERTIFICATE_PATHS Array No
logging.enabled If true, logging is enabled using the following settings. N/A Boolean No
logging.level Sets the logging level. N/A info debug or info or warn or error or trace No
logging.path The absolute or relative path to the directory for log files. N/A ./log String No
logging.filename The name to use for the log file, without extension. N/A cdn String No
logging.extension The extension to use for the log file. N/A log String No
logging.accessLog.enabled If true, HTTP access logging is enabled. The log file name is similar to the setting used for normal logging, with the addition of 'access'. For example cdn.access.log. N/A true Boolean No
notFound.statusCode If set, overrides the status code in the case of a 404 N/A 404 Number Yes
notFound.images.enabled If true, returns a default image when request returns a 404 N/A Boolean Yes
notFound.images.path The path to the default image N/A ./images/missing.png String Yes
images.directory.enabled If true, image files will be loaded from the filesystem N/A Boolean Yes
images.directory.path The path to the image directory N/A ./images String No
images.s3.enabled If true, image files may be requested from Amazon S3 Buckets or Digital Ocean Spaces N/A Boolean No
images.s3.accessKey The access key used to connect to Amazon or Digital Ocean services for image files AWS_S3_IMAGES_ACCESS_KEY String No
images.s3.secretKey The secret used to connect to Amazon or Digital Ocean services for image files AWS_S3_IMAGES_SECRET_KEY String No
images.s3.bucketName The Amazon S3 Bucket or Digital Ocean Space that contains the image files AWS_S3_IMAGES_BUCKET_NAME String No
images.s3.region The Amazon S3 or Digital Ocean region the Bucket/Space is served from AWS_S3_IMAGES_REGION String No
images.s3.endpoint The endpoint used to access Digital Ocean Spaces. Not required for Amazon S3. AWS_S3_IMAGES_ENDPOINT String No
images.remote.enabled If true, image files will be requested from a remote host N/A Boolean Yes
images.remote.path The remote host to request images from, for example http://media.example.com N/A String Yes
images.remote.allowFullURL If true, images can be loaded from any remote URL N/A true Boolean Yes
assets.directory.enabled If true, asset files will be loaded from the filesystem N/A Boolean Yes
assets.directory.path The remote host to request images from, for example http://media.example.com N/A ./public String No
assets.s3.enabled If true, asset files may be requested from Amazon S3 Buckets or Digital Ocean Spaces N/A Boolean No
assets.s3.accessKey The access key used to connect to Amazon or Digital Ocean services for asset files AWS_S3_ASSETS_ACCESS_KEY String No
assets.s3.secretKey The secret used to connect to Amazon or Digital Ocean services for asset files AWS_S3_ASSETS_SECRET_KEY String No
assets.s3.bucketName The Amazon S3 Bucket or Digital Ocean Space that contains the asset files AWS_S3_ASSETS_BUCKET_NAME String No
assets.s3.region The Amazon S3 or Digital Ocean region the Bucket/Space is served from AWS_S3_ASSETS_REGION String No
assets.s3.endpoint The endpoint used to access Digital Ocean Spaces. Not required for Amazon S3. AWS_S3_ASSETS_ENDPOINT String No
assets.remote.enabled If true, asset files will be requested from a remote host N/A Boolean Yes
assets.remote.path The remote host to request assets from, for example http://media.example.com N/A String No
caching.expireAt Cron-style pattern specifying when the cache should be expired N/A String Yes
caching.ttl Amount of time, in seconds, after which cached items should expire N/A 3600 Number Yes
caching.directory.enabled If true, cache files will be saved to the filesystem CACHE_ENABLE_DIRECTORY true Boolean No
caching.directory.path The relative path to the cache directory N/A ./cache/ String No
caching.redis.enabled If true, cache files will be saved to the specified Redis server CACHE_ENABLE_REDIS Boolean No
caching.redis.host The Redis server host REDIS_HOST String No
caching.redis.port The port for the Redis server REDIS_PORT 6379 port No
caching.redis.password REDIS_PASSWORD String No
status.enabled If true, status endpoint is enabled. N/A true Boolean No
status.requireAuthentication If true, status endpoint requires authentication. N/A true Boolean No
status.standalone If true, status endpoint will run on an standalone address/port. N/A Boolean No
status.port Accept connections on the specified port. A value of zero will assign a random port. STATUS_PORT 8003 Number No
status.routes An array of routes to test. Each route object must contain properties route and expectedResponseTime. Note, expectedResponseTime is seconds. N/A [object Object] Array No
security.maxWidth The maximum width, in pixels, for an output image N/A 2048 Number No
security.maxHeight The maximum height, in pixels, for an output image N/A 1024 Number No
auth.tokenUrl Endpoint for requesting bearer tokens N/A /token String No
auth.clientId Client ID used to access protected endpoints AUTH_TOKEN_ID 1235488 String Yes
auth.secret Client secret used to access protected endpoints AUTH_TOKEN_SECRET asd544see68e52 String Yes
auth.tokenTtl Lifetime of bearer tokens (in seconds) AUTH_TOKEN_TTL 1800 Number Yes
auth.privateKey Private key for signing JSON Web Tokens AUTH_KEY YOU-MUST-CHANGE-ME-NOW! String Yes
cloudfront.enabled Enable Amazon CloudFront N/A Boolean No
cloudfront.accessKey CloudFront access key CLOUDFRONT_ACCESS_KEY String No
cloudfront.secretKey CloudFront secret key CLOUDFRONT_SECRET_KEY String No
cloudfront.distribution Name of the CloudFront distribution to use CLOUDFRONT_DISTRIBUTION String No
cluster If true, CDN runs in cluster mode, starting a worker for each CPU core N/A true Boolean No
paths.plugins Path to plugins directory N/A workspace/plugins String Yes
paths.recipes Path to recipes directory N/A workspace/recipes String Yes
paths.routes Path to routes directory N/A workspace/routes String Yes
headers.useGzipCompression If true, uses gzip compression and adds a 'Content-Encoding:gzip' header to the response. N/A true Boolean Yes
headers.cacheControl A set of cache control headers based on specified mimetypes or paths N/A [object Object] Object Yes
robots The path to a robots.txt file N/A String No
env The applicaton environment. NODE_ENV development String No
geolocation.enabled Enable geolocation N/A Boolean No
geolocation.method Method to use for geolocation N/A maxmind maxmind or remote No
geolocation.maxmind.countryDbPath Path to Maxmind country database N/A ./vendor/maxmind-country.mmdb String No
geolocation.remote.url Remote URL to be used for geolocation. {key}, {secret} and {ip} will be replaced by the API key, secret and IP to locate, respectively N/A String No
geolocation.remote.key Key to be used with remote geolocation service GEOLOCATION_REMOTE_KEY String No
geolocation.remote.secret Secret to be used with remote geolocation service GEOLOCATION_REMOTE_SECRET String No
geolocation.remote.countryPath Path to the country code within the response object N/A location.country.isoCode String No
network.url Remote URL to be used for network test service. {key}, {secret} and {ip} will be replaced by the API key, secret and IP to locate, respectively N/A String No
network.key Key to be used with network test service NETWORK_REMOTE_KEY String No
network.secret Secret to be used with network test service NETWORK_REMOTE_SECRET String No
network.path Path to the network type within the response object N/A speed.connectionType String No
engines.sharp.kernel The kernel to use for image reduction N/A lanczos3 nearest or cubic or lanczos2 or lanczos3 No
engines.sharp.interpolator The interpolator to use for image enlargement N/A bicubic nearest or bilinear or vertexSplitQuadraticBasisSpline or bicubic or locallyBoundedBicubic or nohalo No
engines.sharp.centreSampling Whether to use *magick centre sampling convention instead of corner sampling N/A Boolean No
experimental.jsTranspiling Whether to enable experimental support for on-demand JavaScript transpiling JSTRANSPILING Boolean Yes
multiDomain.directory Path to domains directory N/A domains String No
multiDomain.enabled Enable multi-domain configuration for this CDN instance N/A Boolean No
http.followRedirects The number of redirects to follow when retrieving assets via HTTP requests N/A 10 Number Yes

Anchor link Example

A very basic config.development.json file looks like this:

{
  "server": {
    "host": "localhost",
    "port": 3001
  },
  "images": {
    "directory": {
      "enabled": true,
      "path": "./path-to-images"
    }
  }
}

Anchor link Sources

CDN is capable of pulling files from a variety of sources:

Anchor link Local filesystem

The configuration file contains two sections for configuring a Local Filesystem source. One section is for images and the other is for assets. Specifying image and asset settings separately allows you to use different filesystem locations for each one.

Configuration for a Local Filesystem image source

"images": {
  "directory": {
    "enabled": true,
    "path": "relative/path/to/your/images"
  }
}

Configuration for a Local Filesystem asset source

"assets": {
  "directory": {
    "enabled": true,
    "path": "/Users/absolute/path/to/your/assets"
  }
}

With the configuration above, accessing https://your-cdn.somedomain.tech/test.jpg would load an image from {CDN-PATH}/relative/path/to/your/images/test.jpg, where {CDN-PATH} is the path to your CDN installation, whilst https://your-cdn.somedomain.tech/main.js would load the file from /Users/absolute/path/to/your/assets/main.js.

Anchor link Remote server

The remote server source connects CDN to any publicly available URL where you are hosting your assets and images.

The configuration file contains two sections for configuring a remote server source. One section is for images and the other is for assets. This makes it possible to store your images and assets in different locations on a remote server, or even use different servers for each.

Configuration for a remote server image source

"images": {
  "remote": {
    "enabled": true,
    "path": "https://server1.somedomain.tech/images"
  }
}

Configuration for a remote server asset source

"assets": {
  "remote": {
    "enabled": true,
    "path": "https://server2.somedomain.tech/assets"
  }
}

With the configuration above, accessing https://your-cdn.somedomain.tech/test.jpg would load an image from https://server1.somedomain.tech/images/test.jpg, whilst https://your-cdn.somedomain.tech/main.js would load the file from https://server2.somedomain.tech/assets/main.js.

Anchor link allowFullURL

When the allowFullURL is set to true (defaults to false), users can specify a full URL for a file rather than just a relative path that will be appended to the base URI defined in the path property.

Configuration for a remote server image source allowing full URLs

"images": {
  "remote": {
    "enabled": true,
    "path": "https://server1.somedomain.tech/images",
    "allowFullURL": true
  }
}

With the configuration above, accessing https://your-cdn.somedomain.tech/test.jpg would load an image from https://server1.somedomain.tech/images/test.jpg, whilst https://your-cdn.somedomain.tech/https://some-other.domain.com/test.jpg would load the file from https://some-other.domain.com/test.jpg.

When allowFullURL is disabled and a request specifying a full URL is made, the following error message is delivered:

{
  "statusCode": 403,
  "message": "Loading images from a full remote URL is not supported by this instance of DADI CDN"
}

Anchor link Amazon S3 and Digital Ocean Spaces

CDN can be configured to serve files from S3-compatible services, such as Amazon S3 and Digital Ocean Spaces. Most of the configuration options are the same, however Digital Ocean requires an additional configuration property for endpoint.

Security Note

If using AWS S3, we strongly recommend creating an Amazon IAM account specifically for accessing your S3 buckets.

Anchor link Using the configuration file

The configuration file contains two sections for configuring an S3 source. One section is for images and the other is for assets. Specifying image and asset settings separately allows you to use different credentials for each one.

Security Note: We strongly recommend that S3 credentials are not stored in your configuration file if that file could be viewed by the public (for example, committed to a public GitHub repository). A better solution is to use Environment Variables when configuring an S3 source.

Configuration for an S3 image source

"images": {
  "s3": {
    "enabled": true,
    "accessKey": "your-access-key",
    "secretKey": "your-secret",
    "bucketName": "your-bucket",
    "region": "your-region"
  }
}

Configuration for an S3 asset source

"assets": {
  "s3": {
    "enabled": true,
    "accessKey": "your-access-key",
    "secretKey": "your-secret",
    "bucketName": "your-bucket",
    "region": "your-region"
  }
}

Setting the enabled property is essential only when you're using the legacy Path URL scheme. In that case, only one of the source types can be configured for use at any one time, by setting it's enabled property to true.

A full S3 image source configuration, using the Path URL scheme

"images": {
  "s3": {
    "enabled": true,
    "accessKey": "your-access-key",
    "secretKey": "your-secret",
    "bucketName": "your-bucket",
    "region": "your-region"
  },
  "remote": {
    "enabled": false
  },
  "directory": {
    "enabled": false
  }
}
Anchor link Connecting to a Digital Ocean Space

Digital Ocean Spaces uses an S3-compatible API and as such the above configuration section is almost all that's needed - just add access key and secret credentials from your Digital Ocean account. One extra property is required to tell CDN where to find your images: endpoint. When configuring a Space in your Digital Ocean account you are given an endpoint with the format <datacenter-region>.digitaloceanspaces.com, for example ams3.digitaloceanspaces.com. Add this to CDN's S3 configuration block:

"images": {
  "s3": {
    "enabled": true,
    "accessKey": "your-access-key",
    "secretKey": "your-secret",
    "bucketName": "name-of-your-space",
    "region": "ams3",
    "endpoint": "ams3.digitaloceanspaces.com"
  }
}

Remember that if using the Querystring URL Scheme, you can use all configured sources at the same time, regardless of the value of the enabled property.

Anchor link Using environment variables

One easy way to set environment variables is to specify them on the command line when you launch CDN:

$ AWS_S3_IMAGES_ACCESS_KEY=your-access-key AWS_S3_IMAGES_SECRET_KEY=your-secret node main.js

See the documentation for your operating system for details on setting environment variables. A useful guide for Linux users can be found here https://www.digitalocean.com/community/tutorials/how-to-read-and-set-environmental-and-shell-variables-on-a-linux-vps.

Anchor link Serving files

CDN is capable of serving any type of file – images, CSS, JavaScript, PDFs or anything else. Requests for files have the following format:

Format

https://{cdn-domain}/{path}

Example

https://cdn.somedomain.tech/cars/aston-martin.jpg

Legacy URL format

There is an alternative URL syntax introduced in version 1.0 that is now deprecated and therefore omitted from this documentation page. You can see more details in the documentation pages for version 1.x. If you're still using it, you're encouraged to upgrade your implementation as support for this format will be removed in a future version of CDN.

Anchor link Locating Files

The path segment in the URL is used by CDN to locate the file to be served. The following sections described how the various source types use that parameter to determine the location of files.

Anchor link Amazon S3 source

When CDN is connected to an Amazon S3 source, the path in the URL is used to locate the file within the S3 bucket you specified in the configuration file.

Depending on the region in your configuration, CDN might construct a URL similar to one the following:

The following table shows how CDN will try to load files given a request URL and a set of configuration parameters.

URL Configuration (images.s3) S3 URL
https://cdn.somedomain.tech/cars/aston-martin.jpg {"region":"eu-west-1", "bucketName":"my-images1"} http://my-images1.s3-eu-west-1.amazonaws.com/cars/aston-martin.jpg
https://cdn.somedomain.tech/cars/aston-martin.jpg {"region":"us-east-1", "bucketName":"my-images2"} http://my-images2.s3-us-east-1.amazonaws.com/cars/aston-martin.jpg

Anchor link Remote server source

When CDN is connected to a remote server source, the value of path extracted from the URL is appended to the value of remote.path specified in the sources block of configuration file, resulting in a full remote URL. Optionally, if remote.allowFullURL is true and the path segment of the URL contains a full URL (i.e. starting with http:// or https://), that is used instead as the final URL, ignoring the base path defined in remote.path.

The following table shows how CDN will try to load files given a request URL and a set of configuration parameters.

URL Configuration (images.remote) Remote URL
https://cdn.somedomain.tech/cars/aston-martin.jpg {"path":"https://source1.somedomain.tech/images"} https://source1.somedomain.tech/images/cars/aston-martin.jpg
https://cdn.somedomain.tech/https://source2.somedomain.tech/cars/aston-martin.jpg {"allowFullURL": true, "path":"https://source1.somedomain.tech/images"} https://source2.somedomain.tech/cars/aston-martin.jpg

Anchor link Local filesystem source

When you connect CDN to a local filesystem source, the path in the URL is appended to the value of the remote.path configuration parameter in order to form the full path from where CDN will load the file. If remote.path is not a full path, one will be calculated by using the directory where CDN is installed as base.

The following table shows how CDN will try to load files given a request URL and a set of configuration parameters, assuming that CDN is installed on /apps/cdn.

URL Configuration (images.directory) Full path
https://cdn.somedomain.tech/cars/aston-martin.jpg {"path":"/Users/johndoe/images"} /Users/johndoe/images/cars/aston-martin.jpg
https://cdn.somedomain.tech/cars/aston-martin.jpg {"path":"images"} /apps/cdn/images/cars/aston-martin.jpg

Anchor link Dynamic Sources

Adding a source parameter to the request URL allows you to specify which connected source CDN serves your asset or image from. This feature allows you to store files in multiple places and serve them all from a single CDN installation.

When you add a source parameter, CDN reads the standard configuration block for that source, regardless of whether the enabled property is set to true.

For each of the below source types, refer to the Locating Files section above to understand how CDN interprets the path to your files.

Anchor link Specifying an Amazon S3 source in the URL

Format

https://{cdn-domain}/s3/{bucket-name}/{path}

Example

https://cdn.somedomain.tech/s3/images/cars/aston-martin.jpg?width=600&height=400

Anchor link Specifying a Remote Server source in the URL

Format

https://{cdn-domain}/http/{path}

Example

https://cdn.somedomain.tech/http/cars/aston-martin.jpg?width=600&height=400

Anchor link Specifying a Local Filesystem source in the URL

Format

https://{cdn-domain}/disk/{path}

Example

https://cdn.somedomain.tech/disk/cars/aston-martin.jpg?width=600&height=400

Anchor link Transforming images

CDN includes a full-featured image processing engine that allows various types of image conversion and manipulation to be made in real-time, based on a set of optional URL parameters supplied in the request.

Format

https://{cdn-domain}/{path}{?parameters}

Example

https://cdn.somedomain.tech/cars/aston-martin.jpg?width=600&height=400&format=png

The URL above loads a JPEG from /cars/aston-martin.jpg and specifies that the result should be 600x400 pixels in size and be converted to a PNG.

Anchor link Parameters

Anchor link blur

The blur parameter adds blur to an image, using any value above zero.

Examples

https://cdn.somedomain.tech/samples/dog.jpeg?blur=5

Original JPG Original image Blur 1 Blur amount = 1 Blur 5 Blur amount = 5
Blur 10 Blur amount = 10 Blur 20 Blur amount = 20 Blur 20 Blur amount = 100

Anchor link filter

The filter parameter allows you to specify the interpolation method to use when resizing images. When reducing the size of an image (or downsampling), some image data is simply discarded. However when increasing image dimensions, the image is expanded and gaps must be "filled in". Each interpolation filter uses a different algorithm for determining how to fill the gaps.

Example

https://cdn.somedomain.tech/samples/dog.jpeg?width=600&height=400&resize=aspectfill&filter=linear

The following filters are available:

Filter Description
nearest-neighbor The simplest approach to interpolation. Rather than calculating an average value by some weighting criteria or generating an intermediate value based on complicated rules, this method simply determines the "nearest" neighbouring pixel, and assumes the intensity value of it.
linear Considers the closest two pixels and takes a weighted average to arrive at its final interpolated value. Results in a much smoother image than nearest-neighbor.
cubic Images resampled with cubic interpolation are smoother and have fewer interpolation artifacts, but processing is slower than with linear or nearest-neighbor.
lanczos Tends to reduce aliasing artifacts and preserve sharp edges. It has been considered the "best compromise" among several simple filters for this purpose.

Anchor link flip

The flip parameter flips images horizontally, vertically or both. A horizontal flip can also be referred to as "mirroring".

Horizontal flip: ?flip=x Vertical flip: ?flip=y Horizontal and vertical flip: ?flip=xy
Dog flipped on the X axis Dog flipped on the Y axis Dog flipped on both axes

Anchor link format

Use the format parameter to specify the required output image format.

DADI CDN can currently convert the following:

From To
GIF JPG
PNG
JPG PNG
PNG JPG

Original JPG Image

Original JPG

JPG to PNG

https://cdn.somedomain.tech/samples/dog.jpeg?format=png

PNG

Anchor link from GIF

GIF to JPG

https://cdn.somedomain.tech/samples/giphy.gif?format=jpg

JPG

GIF to PNG

https://cdn.somedomain.tech/samples/giphy.gif?format=png

PNG

Anchor link from PNG

Original PNG Image

Original PNG

PNG to JPG

https://cdn.somedomain.tech/samples/mountain.png?w=400&resize=aspectfit&format=jpg

JPG

Anchor link gravity

Used to position the crop area. Available options (case sensitive):

Original image

https://cdn.somedomain.tech/samples/med.jpeg

g=west g=center g=east

Anchor link height

The height parameter is used to specify the required height of the output image, in pixels.

If only height is specified, the width dimension will be set to the width of the original image. If you'd like to ensure the output image retains the aspect ratio of the original image, please ensure resize=aspectfit is specified.

If both width and height are omitted, the original image’s dimensions are used.

Security Note:

The maximum output image size can be specified in the configuration file.

The security setting allows you to set a maximum width and height for generated images. This prevents the potential for a DOS attack based on the repeated generation of large images which could push your platform offline by exhausting CPU and/or available memory.

You should set this to the maximum size required for images in your application.

"security": {
 "maxWidth": 2048,
 "maxHeight": 1024
}

Example

https://cdn.somedomain.tech/samples/canoe.jpeg?h=400&resize=aspectfit

h=400 h=400&resize=aspectfit
Height 400 Height 400, Aspect Fit

Anchor link quality

The quality parameter applies compression to an image, reducing it's file size.

The best results for quality and file size can be found around 40-60, where we've found generated images to be visually indistinguishable from the source image.

Examples

The original image and all quality variations below are 2048 × 1024 pixels.

Original image, 4.7MB

https://cdn.somedomain.tech/samples/vegetables.jpg

Quality 100 Quality = 100, 1.3MB Quality 75 Quality = 75, 180kB
Quality 50 Quality = 50, 119kB Quality 25 Quality = 25, 82kB

Anchor link ratio

Use the ratio parameter in combination with width (w) or height (h) to crop the image to the specified aspect ratio. Resize styles are respected.

https://cdn.somedomain.tech/samples/canoe.jpeg?h=400&ratio=16-9

Anchor link rotate

The rotate parameter rotates the image according to the value specified in degrees. The image will be zoomed so that it covers the entire area after rotation.

Anchor link saturate

The saturate parameter increases or reduces an image's colour saturation and can be used to convert it to black and white.

To desaturate (convert to black and white), use 0 or any number below 0.

https://cdn.somedomain.tech/samples/beach.jpeg?saturate=0
Saturate amount = 0 (B&W) Saturate amount = 1
Saturate 0 Saturate 1

Anchor link sharpen

The sharpen parameter adds sharpness to an image.

Example

https://cdn.somedomain.tech/samples/beach.jpeg?sharpen=1
Default amount = 0 Sharpen amount = 1 Sharpen amount = 5
Sharpen 0 Sharpen 1 Sharpen 5

Anchor link width

The width parameter is used to specify the required width of the output image, in pixels.

If only width is specified, the height dimension will be set to the height of the original image. If you'd like to ensure the output image retains the aspect ratio of the original image, please ensure resize=aspectfit is specified.

If both width and height are omitted, the original image’s dimensions are used.

Security Note:

The maximum output image size can be specified in the configuration file.

The security setting allows you to set a maximum width and height for generated images. This prevents the potential for a DOS attack based on the repeated generation of large images which could push your platform offline by exhausting CPU and/or available memory.

You should set this to the maximum size required for images in your application.

"security": {
 "maxWidth": 2048,
 "maxHeight": 1024
}

Example

https://cdn.somedomain.tech/samples/canoe.jpeg?w=400&resize=aspectfit

w=400 w=400&resize=aspectfit
Width 400 Width 400, Aspect Fit

Anchor link Resizing

Images can be easily resized using DADI CDN. Use the resize parameter with width and height to resize images, or specify crop along with crop coordinates for full control over the portion of the original image that is retained.

There are several ways to resize an image, the simplest of which is to specify the dimensions of the output images. Use width and height parameters to specify the final dimensions of the output image.

Anchor link Using the width parameter

The width parameter (alias: w) specifies the width of the required output image in pixels. If only width is specified, the height dimension will be calculated automatically so that the original aspect ratio of the image is maintained given the new width. If both width and height are omitted, the original image’s dimensions are used.

Security Note:

The maximum output image size can be specified in the configuration file.

The security setting allows you to set a maximum width and height for generated images. This prevents the potential for a DOS attack based on the repeated generation of large images which could push your platform offline by exhausting CPU and/or available memory.

You should set this to the maximum size required for images in your application.

"security": {
 "maxWidth": 2048,
 "maxHeight": 1024
}

Example

https://cdn.somedomain.tech/samples/canoe.jpeg?w=400

Anchor link Using the height parameter

The height parameter (alias: h) specifies the height of the required output image in pixels. If only height is specified, the width dimension will be calculated automatically so that the original aspect ratio of the image is maintained given the new height. If both width and height are omitted, the original image’s dimensions are used.

Security Note:

The maximum output image size can be specified in the configuration file.

The security setting allows you to set a maximum width and height for generated images. This prevents the potential for a DOS attack based on the repeated generation of large images which could push your platform offline by exhausting CPU and/or available memory.

You should set this to the maximum size required for images in your application.

"security": {
 "maxWidth": 2048,
 "maxHeight": 1024
}

Example

https://cdn.somedomain.tech/samples/canoe.jpeg?h=400

Anchor link Specifying aspect ratio

Images can be resized to a specified aspect ratio by providing a width or height in combination with the ratio parameter. CDN will respect any resizeStyle specified.

https://cdn.somedomain.tech/samples/canoe.jpeg?h=400&ratio=16-9

Height of 400, aspect ratio of 16-9

Anchor link Specifying a resize style

The resizeStyle (alias: resize) parameter allows you to specify how CDN should fit your image into the specified dimensions.

Anchor link aspectfill

Keeps the aspect ratio of the original image and generates an output image of the specified width and height. The output image may be cropped, however by specifying the gravity parameter you can tell CDN which part of the image should be retained.

The output image is 400 x 300 pixels.

Width of 400, height of 300, resize style of aspectfill

Anchor link aspectfit

Keeps the aspect ratio of the original image and generates an output image with the maximum dimensions that fit inside the specified width and height.

The output image is 400 x 267 pixels.

Width of 400, height of 300, resize style of aspectfit

Anchor link crop

When crop is used as the resize style then an additional parameter must be used to specify the coordinates of the crop rectangle. There are two ways to pass the crop rectangle coordinates:

Anchor link entropy

Used in combination with width and height parameters, entropy crops the image using a technique that determines the most important areas. Areas of higher contrast are considered more important, and images are often cropped to remove large areas of static colour.

https://cdn.somedomain.tech/samples/med.jpeg?w=250&h=300&resize=entropy

Original image Entropy crop
Anchor link fill

Generates an output image with the exact specified width and height dimensions, ignoring the aspect ratio of the original image. The output image may appear squashed or stretched.

The output image is 400 x 300 pixels.

Width of 400, height of 300, resize style of fill

Anchor link gravity

Used to position the crop area. See Parameters / gravity for details and examples.

Anchor link Cropping

For more control over the output image than available when using the standard resize style options aspectFit, aspectFill, fit and fill, you can specify three different ways to crop an image.

Anchor link Top-left crop

By specifying only the top left corner of the crop rectangle, CDN works out the full crop rectangle size by using the specified width and height parameters. To obtain an image that is 300 wide and 400 high, but cropped from 100 pixels from the top edge:

https://cdn.somedomain.tech/samples/med.jpeg?resize=crop&crop=0,10&width=300&height=400

Anchor link Crop rectangle

It is also possible to supply both the top left and the bottom right coordinates of a crop rectangle (e.g. resize=crop&crop=0,0,100,100). This allows you to selectively crop an exact area out of an image.

Let's take a look at some examples. We'll use our 400x400 pixel image, which has squares marked at x, y (size): 0,0 (25 px), 25,25 (25px), 50,50 (50px), 100,100 (100px), 200,200 (200px):

We can crop the second and third boxes by supplying a top left of 50,50, and a bottom right of 200,200, giving us a 150x150 pixel image:

https://cdn.somedomain.tech/samples/measure.png?resize=crop&crop=50,50,200,200

We're also able to change the size of the image we get back. Let's change it to 100 pixels wide. In this case, the height will be inferred from the width. The same goes if we only supply a height.

https://cdn.somedomain.tech/samples/measure.png?resize=crop&crop=50,50,200,200&width=100

If you really want to get creative, you can supply a cropping rectangle along with both a width and height, which will allow you to completely resize an image after cropping. Let's take the above image and squash it.

https://cdn.somedomain.tech/samples/measure.png?resize=crop&crop=50,50,200,200&width=400&height=100

Another way of resizing our crops, if we don't want to be as specific, is to provide the devicePixelRatio ratio.

https://cdn.somedomain.tech/samples/measure.png?resize=crop&crop=50,50,200,200&devicePixelRatio=2

Read more about dealing with pixel ratios.

Anchor link Entropy

Using resize=entropy crops the image using a technique that determines the most important areas.

Read more about entropy.

Anchor link Example use case

For this example, we're going to imagine you have a magazine-style website with a list of articles on the homepage and an article page. We'll start with the following image, which your editor wants to use as the main article image.

Original image, 5616 × 3744 px, 4MB

https://cdn.somedomain.tech/samples/beach.jpeg

This is a large image, and it's not going to fit easily into the two spaces we have available for it. Unfortunately, no one in the department knows how to use Adobe Photoshop to make appropriately sized images. Fortunately, CDN can handle this task for you.

Anchor link Resizing and cropping

On the article page, let’s assume your main image spot is a 500×300 pixel container. It's an odd size, but illustrates this concept well. To fit the base image into that container, we’ll need to change the dimensions and crop some data from the top and bottom.

To adjust the image we need to specify the new width and height, as well as tell CDN how we want to crop the image.

https://cdn.somedomain.tech/samples/beach.jpeg?w=500&h=300&resize=entropy

Resized image, 500 × 300 px, 98kB

Resized image, 500 × 300 px, 98kB

Now, on the homepage, let’s assume each feature article has an image container that is 200×200 pixels. With the main subject of the image so close to the right, we'll once again need to tell CDN how we want the image cropped.

https://cdn.somedomain.tech/samples/beach.jpeg?w=200&h=200&resize=entropy

Resized image, 200 × 200 px, 29kB

Resized image, 200 × 200 px, 29kB

If we don't specify entropy as the resize parameter, CDN defaults to using aspectfit and our image would look a little different, with the main subject almost excluded from the image.

https://cdn.somedomain.tech/samples/beach.jpeg?w=200&h=200

Aspectfit, 200 × 200 px, 29kB

Anchor link Transforming CSS

CDN is capable of processing CSS in real-time based on a set of optional URL parameters supplied in the request.

Format

https://{cdn-domain}/{path}{?parameters}

Example

https://cdn.somedomain.tech/styles.css?compress=1

Anchor link Parameters

Anchor link compress

Returns a compressed version of a style sheet by removing all unnecessary or redundant data without affecting how the resource is processed by the browser. It removes code comments, spaces and line breaks.

Example (input)

/* This comment will go away */
p {
  color: blue;
  width: 1.2em;
}

/* This one will go too */
h1 {
  font-weight: bold;
  line-height: 1.3;
}

Example (output)

p{color:#00f;width:1.2em}h1{font-weight:700;line-height:1.3}

Anchor link Transforming JavaScript

CDN is capable of processing JavaScript in real-time based on a set of optional URL parameters supplied in the request.

Format

https://{cdn-domain}/{path}{?parameters}

Example

https://cdn.somedomain.tech/script.js?compress=1&transform=1

Anchor link Parameters

Anchor link compress

Returns a compressed (minified) version of the JavaScript file by removing all unnecessary or redundant data without affecting how the resource is processed by the browser.

Example (input)

let numbers = [1,2,3]

numbers.forEach(number => {
  console.log(`Hi, I'm number ${number}`)
})

Example (output)

let numbers=[1,2,3];numbers.forEach(a=>{console.log(`Hi, I'm number ${a}`)});

Anchor link transform

Experimental

This feature is experimental and it should be considered unstable. To enable it, you must set the experimental.jsTranspiling configuration property or the JSTRANSPILING environment variable to true.

Analyses the ECMAScript features used in the JavaScript file and makes any necessary translations (i.e. transpiling) so that the script is fully compatible with the requesting browser, using the User-Agent header to determine the capabilities of the client.

It's a common practice to transpile JavaScript bundles at build time, creating an ES5-compatible script that will be delivered to all clients – even to the ones that fully support the original, modern and often times faster ES6 features. CDN offers an alternative approach to this, whereby transpiling scripts on the edge means that only the features that are not supported by the client will be modified, leaving the rest of the code untouched.

Example (input)

let numbers = [1,2,3]

numbers.forEach(number => {
  console.log(`Hi, I'm number ${number}`)
})

Example (output on IE8)

"use strict";

var numbers = [1, 2, 3];

numbers.forEach(function (number) {
  console.log("Hi, I'm number " + number);
});

Example (output on Chrome 66)

let numbers = [1,2,3]

numbers.forEach(number => {
  console.log(`Hi, I'm number ${number}`)
})

Anchor link Serving audio and video files

Starting with Version 3.0.3, CDN has support for serving "seekable" content such as audio and video. Audio and video files are served from the configured "assets" location.

The following snippets serve as a guide for testing video in operation. Configure your CDN instance using the below settings:

Anchor link Configure CDN

config/config.development.json

"images": {
  "directory": {
    "enabled": true,
    "path": "./images"
  },
  "s3": {
    "enabled": false
  },
  "remote": {
    "enabled": false
  }
},
"assets": {
  "directory": {
    "enabled": true,
    "path": "./assets"
  },
  "s3": {
    "enabled": false
  },
  "remote": {
    "enabled": false
  }
}

Anchor link Add files to be served

Add an image called "poster.jpg" to the "images" directory, and a video called "video.mp4" to the "assets" directory:

your-cdn-installation/
├── config
│   └── config.development.json
├── images
│   └── poster.jpg
├── assets
│   └── video.mp4
└── index.js

Anchor link Create an HTML page to serve video content

The following example uses video.js to load and play the video content. The example assumes your CDN is running on http://localhost:3000, so make adjustments to the HTML where necessary:

index.html

<head>
  <link href="https://vjs.zencdn.net/7.0.3/video-js.css" rel="stylesheet">
</head>

<body>
  <video id="my-video" class="video-js" controls preload="auto" width="640" height="264"
  poster="http://localhost:3000/poster.jpg" data-setup="{}">
    <source src="http://localhost:3000/video.mp4" type='video/mp4'>
    <p class="vjs-no-js">
      To view this video please enable JavaScript, and consider upgrading to a web browser that
      <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a>
    </p>
  </video>

  <script src="https://vjs.zencdn.net/7.0.3/video.js"></script>
</body>

Anchor link Multiple domains

CDN offers support for a multi-domain configuration, which means that a single instance of CDN can have different configuration files and workspace directories for different domains.

In terms of directory structure, CDN still expects a global config/ directory at root level with a configuration file per environment. A domains directory (configurable via the multiDomain.directory property) holds domain-specific files, with a config/ and workspace/ directories to specify configuration overrides and workspace files, respectively. When multi-domain is enabled, the global workspace/ directory is ignored.

When a domain-specific configuration file specifies the value for a configuration property, it overrides whatever value was set in the global config if that value is part of a list of properties that can be overridden at domain level. Check the Domain override column in the Configuration table to see which properties are part of that list.

your-cdn/
  |_ config/ # Global config
    |_ config.development.json
    |_ config.production.json
  |_ domains/
    |_ somedomain.tech/
       |_ config/ # Domain-specific config
          |_ config.development.json
          |_ config.production.json
       |_ workspace/
          |_ plugins/
             |_ plugin-one.js
          |_ recipes/
             |_ recipe-one.json
             |_ recipe-two.json
          |_ routes/
             |_ route-one.json
    |_ someotherdomain.tech/
       |_ config/ # Domain-specific config
          |_ config.development.json
          |_ config.production.json
       |_ workspace/
          |_ plugins/
             |_ plugin-one.js
          |_ recipes/
             |_ recipe-one.json
             |_ recipe-two.json
          |_ routes/
             |_ route-one.json 

Anchor link Enable multi-domain

To enable support for multiple domains, you must:

  1. Set multiDomain.enabled to true in your configuration file
  2. Define the path for the domains directory in multiDomain.directory (or use the default, domains)
  3. Create a sub-directory under the directory defined in 2 for each domain you want to support
    • Example: domains/somedomain.tech
  4. Under the directory created in 3, create a config/ sub-directort with a configuration file per environment
    • Example: domains/somedomain.tech/config/config.development.json
  5. Define the domain-specific configuration using any of the properties marked with Domain override: Yes in Configuration

Anchor link How it works

When a request comes through, CDN will inspect the Host header to determine what is the target domain. If the domain is configured (i.e. there is a sub-directory for that domain in multiDomain.directory), the request will be served. CDN will use a combination of the configuration properties defined at domain level and globally.

If the domain is not configured, CDN will return a 404.

Anchor link Pre-signed URLs

A pre-signed URL allows you to give one-off access to private S3 objects, useful for when users may not have direct access to the file. Pre-signing generates a valid URL signed with your S3 credentials that any user can access.

Anchor link Generating a pre-signed URL

To generate a simple pre-signed URL that allows any user to view the contents of a private object in a bucket you own, you can use the following call to getSignedUrl:

const AWS = require('aws-sdk')

AWS.config.update({
  accessKeyId: '<your-access-key-id>',
  secretAccessKey: '<your-secret-access-key>'
})

let params = {
  Bucket: '<your-bucket-name>',
  Key: '<your-filename>'
}

let s3 = new AWS.S3()

s3.getSignedUrl('getObject', params, (err, url) => {
  console.log('The URL is', url)
})

The above should result in a response similar to this:

The URL is https://your-bucket-name.s3.amazonaws.com/your-filename?AWSAccessKeyId=your-access-key-id&Expires=1490052681&Signature=VzHKnHucNgKPG7lDbnzW6blQuGQ%3D

Anchor link Controlling expiry time

The default Expires time is 15 minutes. This can be modified by passing a value in the params that are passed to the getSignedUrl method. The following example will cause the signed URL to expire after 60 seconds.

let params = {
  Bucket: 'myBucket',
  Key: 'myKey',
  Expires: 60 // expire time, in seconds
}

Anchor link Accessing via CDN

https://cdn.somedomain.tech/https://your-bucket-name.s3.amazonaws.com/your-filename?AWSAccessKeyId=your-access-key-id&Expires=1490052681&Signature=VzHKnHucNgKPG7lDbnzW6blQuGQ%3D

Anchor link Adding image manipulation parameters

When requesting images from external URLs it is possible they have an existing querystring. To add CDN image manipulation parameters to an external URL, they must be added to the existing querystring.

The following example uses the above URL, adding width and height parameters.

Notice that we've added the parameters as part of the existing querystring by including an ampersand before the CDN querystring begins:

...W6blQuGQ%3D&?width=300&height=300

https://cdn.somedomain.tech/https://your-bucket-name.s3.amazonaws.com/your-filename?AWSAccessKeyId=your-access-key-id&Expires=1490052681&Signature=VzHKnHucNgKPG7lDbnzW6blQuGQ%3D&?width=300&height=300

Anchor link Expired URLs

If a pre-signed URL has already expired at the time of the request, a HTTP 403 Forbidden error will be returned:

{
  "statusCode": "403",
  "message": "Forbidden: https://cdn.somedomain.tech/https://your-bucket-name.s3.amazonaws.com/your-filename?AWSAccessKeyId=your-access-key-id&Expires=1490052681&Signature=VzHKnHucNgKPG7lDbnzW6blQuGQ%3D"
}

Anchor link Delivery recipes

A Delivery Recipe is a predefined set of image manipulation parameters stored in a JSON file and applied to images at the time of request.

Let's use the image from our magazine example:

https://cdn.somedomain.tech/thumbnail/samples/beach.jpeg

https://cdn.somedomain.tech/samples/beach.jpeg?width=100&height=100&resizeStyle=entropy

Thumbnail image, 100 × 100 px, 9kB

Recipes are defined in JSON files held in the /workspace/recipes folder.

Anchor link Example recipe

{
  "recipe": "thumbnail",
  "settings": {
    "format": "jpg",
    "quality": "80",
    "width": "100",
    "height": "100",
    "resizeStyle": "entropy"
  }
}

Anchor link Using a recipe

Making use of a recipe is simple: call your image via the recipe name defined in the recipe JSON.

For example:

https://cdn.somedomain.tech/thumbnail/image-filename.png

Anchor link Delivery routes

Delivery Routes allow you to let CDN choose the appropriate recipe based on device, network, location or language.

Routes allow CDN to make a decision about which Delivery Recipe to use for the current request, based on a set of configurable conditions.

Conditions can include the type of device being used, the network type, user location and language.

Anchor link Creating a route

A route is defined in JSON format and added to a directory in your CDN installation. The default location for route files is workspace/routes, but this is configurable.

You can create route files in a text editor and manually copy them to the routes folder, or you can send a POST request to CDN with the route content and have CDN create it for you.

Anchor link POSTing to CDN

Send a POST request to the routes endpoint with the request body containing the route content.

An example using cURL

curl -i -H "Content-Type: application/json" -X POST "https://cdn.somedomain.tech/api/routes" -d '{
  "route": "example-route",
  "branches": [
    {
      "recipe": "thumbnail"
    }
  ]
}'

Response Codes

Status Code Description Response
200 Route saved successfully { success: true }
400 No request body sent { success: false, errors: ['Bad Request'] }
400 Route validation failed { success: false, errors: validationErrors }
400 A route with the same name already exists { success: false, errors: ['Route already exists'] }
400 An error occurred when saving { success: false, errors: ['Error when saving route'] }

Anchor link Route basics

A route must contain a name property, as well as an array of branches which contain the conditions that must be true for CDN to select the route.

At a minimum, a route must take the following form. The branches array below contains a single branch with no conditions, representing the default recipe to use.

{
  "route": "example-route",
  "branches": [
    {
      "recipe": "thumbnail"
    }
  ]
}

Anchor link Branches

Each branch within the branches array should contain two properties, recipe and condition.

{
  "route": "example-route",
  "branches": [
    {
      "recipe": "thumbnail-120",
      "condition": {
        "device": "desktop"
      }
    },
    {
      "recipe": "thumbnail-50"
    }
  ]
}

Anchor link Branch evaluation

Branches are evaluated in the order they appear in the route. If a branch condition is not met, the branch is skipped and the next one evaluated.

The default case (where none of the conditions are met) is handled by a branch with a recipe but no condition, which is matched immediately. This branch must be last in the array, otherwise it may be used when you don't intend it to be.

Anchor link Conditions

Anchor link Device

The device condition matches the user's device type, based on the user-agent header sent in the request.

Possible values:

The device condition can test against a single device type:

"condition": {
  "device": "mobile"
}

...or multiple device types:

"condition": {
  "device": ["tablet", "smarttv"]
}
Anchor link Default value

If a device type is specified that doesn't match one of the possible values above, CDN uses desktop in its place.

Anchor link Location

The location condition uses the IP address from the request and performs a GeoLocation lookup to obtain the user's approximate location.

CDN can perform the GeoLocation lookup using the Maxmind GeoIP database (bundled with the application), or by making a request to any remote address (such as the DADI GeoLocation API).

Anchor link Configuring CDN to use the Maxmind GeoIP database

To use the Maxmind GeoIP database CDN's main configuration file should contain the following block:

"geolocation": {
  "enabled": true,
  "method": "maxmind",
  "countryDbPath": "vendor/maxmind-country.mmdb"
}

If no value is provided for countryDbPath it defaults to the one shown in the above example.

Anchor link Configuring CDN for remote lookup

To use a remote lookup service, the geolocation block in CDN's main configuration file should specify a remote URI.

The URI format uses placeholders (shown below with curly braces) to indicate where CDN should insert the parameters required for the lookup.

Placeholders

{key} and {secret} can be set either in the configuration file or as environment variables. Set the environment variables GEOLOCATION_REMOTE_KEY and GEOLOCATION_REMOTE_SECRET to enable CDN to read these values from the environment.

"geolocation": {
  "enabled": true,
  "method": "remote",
  "url": "https://api.example.com/location/?key={key}&secret={secret}&ip={ip}",
  "key": "1234567",
  "secret": "1q2w3e4r"
}

Response format

By default CDN expects a response from the remote service in the format used by the DADI GeoLocation service, where the path for the country code within the response is location.country.isoCode.

If the response format for the service you use differs (and it probably does), you can tell CDN where to find the country code in the response by adding a path property to the geolocation configuration block.

"geolocation": {
  "enabled": true,
  "method": "remote",
  "url": "https://api.other-service.com/location/?key={key}&secret={secret}&ip={ip}",
  "key": "1234567",
  "secret": "1q2w3e4r",
  "path": "results.address.country"
}

Anchor link Language

The language condition is based on the Accept-Language headers sent in the request. Values are ISO 639-1 language codes.

The following condition will be met if the request contains the header Accept-Language: en

"condition": {
  "language": "en"
}

Language detection has support for quality values, which represent an estimate of the user's preference for multiple languages. By default only the main language (quality = 1) is used, but this can be changed by adding an optional languageMinQuality property to the condition, which adjusts the threshold.

"condition": {
  "language": ["en", "pt"],
  "languageMinQuality": 0.5
}

Anchor link Network

Specifying the network condition in a route performs a remote lookup on a network connectivity API to determine the type of connection being used.

Note: This condition tests for a connection type (e.g. cable or mobile) and not connection speed.

The condition can be specified as a single connection type:

"condition": {
  "network": "cable"
}

...or as an array of multiple connection types:

"condition": {
  "network": ["cable", "dsl"]
}
Anchor link Configuration

The configuration block required for network connectivity lookups is similar to that used for GeoLocation.

"network": {
  "url": "https://api.example.com/connectivity/?key={key}&secret={secret}&ip={ip}",
  "key": "1234567",
  "secret": "1q2w3e4r"
}

The URI format uses placeholders (shown below with curly braces) to indicate where CDN should insert the parameters required for the lookup.

Placeholders

{key} and {secret} can be set either in the configuration file or as environment variables. Set the environment variables NETWORK_REMOTE_KEY and NETWORK_REMOTE_SECRET to enable CDN to read these values from the environment.

Response format

By default CDN expects a response from the remote service in the format used by the DADI Network Connectivity service, where the path for the connection type within the response is speed.connectionType.

If the response format for the service you use differs (and it probably does), you can tell CDN where to find the connection type in the response by adding a path property to the network configuration block.

"network": {
  "url": "https://api.other-service.com/connectivity/?key={key}&secret={secret}&ip={ip}",
  "key": "1234567",
  "secret": "1q2w3e4r",
  "path": "results.connection.type"
}

Anchor link Caching

Caching is automatically enabled for routes. Depending on what's defined in the config, it uses Redis or the local filesystem.

Example route

{
  "route": "sample-route",
  "branches": [
    {
      "recipe": "thumbnail",
      "condition": {
        "device": "desktop",
        "language": "en",
        "country": ["GB", "US"],
        "network": "cable"
      }
    },
    {
      "recipe": "thumbnail-lo-res",
      "condition": {
        "device": ["mobile", "tablet"],
        "language": ["en", "pt"],
        "country": "GB",
        "network": ["cable", "dsl"]
      }
    },
    {
      "recipe": "default-recipe"
    }
  ]
}

Anchor link Retrieving a colour palette

Each image stored in CDN can be returned as a json object with information on how the image was generated. Within that object there are primaryColor and palette nodes which contains information about the colour of the image.

Request URL:

https://cdn.somedomain.tech/samples/canoe.jpeg?format=json

Response JSON:

{
  "primaryColor": "#1888a2",
  "palette": {
    "rgb": [
      [173, 220, 233],
      [23, 109, 129],
      [244, 240, 230],
      [116, 87, 68],
      [179, 150, 133]
    ],
    "hex": [
      "#addce9",
      "#176d81",
      "#f4f0e6",
      "#745744",
      "#b39685"
    ]
  }
 }
Input image Generated palette
input image generated palette

Anchor link Dealing with pixel ratios

When dealing with multiple device pixel ratios, you can 'multiply' the outputted size of the image by adding the parameter devicePixelRatio to your query. devicePixelRatio can be anywhere from 1 to 3. Supplying this parameter will inform the CDN to multiply the output dimensions by that factor.

For example a 100px x 100px image with the variable devicePixelRatio=2 will return an image of 200px x 200px in size. You can then scale down the image in your front-end output e.g.,

<img src="https://cdn.somedomain.tech/samples/dog.jpeg?w=100&height=100&devicePixelRatio=2" width="100">

But why not just use width=200&height=200, right? Well devicePixelRatio becomes more powerful when cropping, for example. You could crop an image to take out a 150px x 150px chunk:

<img src="https://cdn.somedomain.tech/samples/measure.png?resize=crop&crop=50,50,200,200" width="300">

150x150

and then enlarge it to 300px x 300px:

<img src="https://cdn.somedomain.tech/samples/measure.png?resize=crop&crop=50,50,200,200&devicePixelRatio=2" width="300">

300x300

It is also possible to set the width, height, or both width & height explicitly, if we wanted an image of 600x600 for a HDPI display, etc:

<img src="https://cdn.somedomain.tech/samples/measure.png?resize=crop&crop=50,50,200,200&width=600" width="300">

600x600

Read more about Cropping Images.

Anchor link Plugins

Plugins are modular pieces of user-defined logic with the power to extend the functionality of the core application, changing the normal course of an image request with custom behaviour.

They are defined as JavaScript files that sit in the directory defined in the paths.plugins configuration parameters (defaults to workspace/plugins). There are three types of plugins triggered at different points in the lifecycle of a request (pre-processing, post-processing and controller), which are defined by the functions exported by the plugin file.

Diagram showing the flow of data in the three types of plugins

Anchor link Pre-processing plugins

Pre-processing plugins are executed before any image processing begins. All they can do is change the set of manipulation options, which come from either the query parameters in the URL, from the settings block of a recipe, or both.

In reality, a pre-processing plugin is similar to a recipe, as they both allow a group of manipulation options to be applied to an image, but whilst a recipe defines its parameters as a static JSON object, a plugin applies them dynamically using arbitrary code. As an example, this allows a parameter to be applied only if certain conditions are met.

Anchor link Reference

Pre-processing plugins must be loaded from a recipe, by adding the name of the plugin to a plugins array. They must export a pre function that receives a single object parameter with the following properties.

Anchor link Receives
Property Type Description Example
options Object The parameters used to process the image, obtained from the URL and/or any associated recipes { blur: 5, width: 500, format: 'png' }
url String The URL of the request '/my-recipe/my-image.jpg?width=500&blur=5&format=png'
Anchor link Returns

The return value of the pre function is ignored, but the plugin may change the options object, which will change the image manipulation parameters.

Anchor link Example

Let’s create a simple pre-processing plugin: when a request is made in hours of darkness, say between 7pm and 7am, we want to bring the saturation down to make the output image black and white.

Because pre-processing plugins are loaded from recipes, the first step is to create one.

workspace/recipes/daylight.json

{
  "recipe": "daylight",
  "plugins": ["night-mode"]
}

When a recipe contains a plugins array, the corresponding plugins will be executed sequentially, from left to right. In our example, we’re loading a single plugin, but you can use as many as you want. Now, we need to create the night-mode plugin we’ve referenced.

workspace/plugins/night-mode.js

module.exports.pre = ({options, url}) => {
  let hour = new Date().getHours()

  // Are we between 7pm and 7am?
  if ((hour < 7) || (hour >= 19)) {
    options.saturate = -100
  }
}

After reloading the application, requesting an image with the path /daylight/image.jpg will load the recipe and the plugin, so you should see the saturation going down depending on the time of the day.

Anchor link Post-processing plugins

The execution of post-processing plugins takes place after CDN's image processor has finished applying all the manipulation parameters. At this point, plugins have the ability to apply additional transformations to the image before it’s delivered to the user.

Anchor link Reference

Pre-processing plugins must be loaded from a recipe, by adding the name of the plugin to a plugins array. They must export a pre function that receives a single object parameter with the following properties.

Anchor link Receives
Property Type Description Example
assetStore Function A function that fetches an asset or image from whatever storage handler is configured. Receives a String specifying the asset type (asset or image) as well as a filename. Returns a prototype with a get() method, which provides the asset as a Promise with a Stream assetStore('image', 'test.jpg').get()
cache.get Function A function that retrieves an item from cache. Receives a cache key (String) and returns a Stream if the item exists in cache, or null if not cache.get('/my-image.jpg')
cache.set Function A function that stores an item in cache. Receives a cache key (String) and a value (Stream or String). Returns a Promise that resolves once the item has been cached cache.set('/my-image.jpg', myImageStream)
imageInfo Object The dimensions and type of the original image, as obtained from reading the file. Contains width, height and type properties. {width: 400, height: 600, type: 'jpg'}
jsonData Object Any JSON data obtained from the image. It will be used as the response if format is set to json { primaryColor: '#1888a2', palette: { hex: ['#addce9', '#176d81'] } }
options Object The parameters used to process the image, obtained from the URL and/or any associated recipes { blur: 5, width: 500, format: 'png' }
processor Sharp The instance of the Sharp engine used to process the image processor.greyscale()
sharp Sharp A reference to the Sharp module sharp(data).toFormat('jpg')
stream Stream A Stream returned by a post-processing plugin executed before this one. See Returns below for more details. N/A
url String The URL of the request '/my-recipe/my-image.jpg?width=500&blur=5&format=png'
Anchor link Returns

If a post-processing plugin returns undefined, the original instance of the image processing engine (i.e. the value of processor) will be used to form the response. If a post-processing plugin returns a value, which must be a Stream, that will be used to form the response, and the original instance of the processing image will be discarded.

If multiple post-processing plugins are configured, the return value of one will be passed to the next via the stream named parameter, therefore it is considered a good practice for a post-processing plugin to check the value of the stream parameter before deciding what source to use as a starting point, as another plugin might have done some work upstream.

Anchor link Example

This article describes an image filter called duotone, where two colors (one for the highlights, one for the shadows) replace all the others in an image. Let’s see how we can build that effect as a post-processing plugin in CDN.

Because pre-processing plugins are loaded from recipes, the first step is to create one.

workspace/recipes/duotone.json

{
  "recipe": "duotone",
  "plugins": ["duotone-plugin"]
}

Now, let’s create the plugin script.

workspace/plugins/duotone-plugin.js

module.exports.post = ({jsonData, options, processor, sharp, stream, url}) => {
  const parsedUrl = urlParser.parse(url, true)
  const colourHighlight = parsedUrl.query.highlight || '#f00e2e'
  const colourShadow = parsedUrl.query.shadow || '#192550'
  const duotoneGradient = createDuotoneGradient(
    hexToRgb(colourHighlight),
    hexToRgb(colourShadow)
  )
  return processor
    .raw()
    .toBuffer({resolveWithObject: true})
    .then(({data, info}) => {
      for (let i = 0; i < data.length; i = i + info.channels) {
        const r = data[i + 0]
        const g = data[i + 1]
        const b = data[i + 2]
        const avg = Math.round(0.2126 * r + 0.7152 * g + 0.0722 * b)
        data[i + 0] = duotoneGradient[avg][0]
        data[i + 1] = duotoneGradient[avg][1]
        data[i + 2] = duotoneGradient[avg][2]
      }
      return sharp(data, {
        raw: info,
      }).toFormat(options.format).toBuffer()
    })
    .then(buffer => {
      let bufferStream = new PassThrough()
      bufferStream.end(buffer)
      return bufferStream
    })
}

Note: additional functions used by the code above were omitted for simplification purposes. You can find the full source code for the duotone plugin here.

Anchor link Controller plugins

Pre and post-processing plugins can be seen as optional detours in a well-defined road: CDN receives a path for an image it needs to serve and it will follow a series of steps to process and serve it, which you may wish to intercept somewhere along the way.

Controller plugins are different, in that there isn’t necessarily a traditional image path and CDN won't do anything automatically for you. You get a URL and you're expected to provide a response that is to be delivered to the user — what happens in between is entirely up to you. The name comes from the “C” in an MVC architecture, since you're put in charge of handling a request and preparing a response.

Anchor link Reference

Unlike pre and post-processing plugins, controller plugins are not loaded from a recipe. If a plugin is named foobar.js, a /foobar endpoint is created and the responsibility to handle requests made to it is given to the controller plugin.

Controller plugins must export a function in the main export, which will receive a single object parameter with the following properties.

Anchor link Receives
Property Type Description Example
assetStore Function A function that fetches an asset or image from whatever storage handler is configured. Receives a String specifying the asset type (asset or image) as well as a filename. Returns a prototype with a get() method, which provides the asset as a Promise with a Stream assetStore('image', 'test.jpg').get()
cache.get Function A function that retrieves an item from cache. Receives a cache key (String) and returns a Stream if the item exists in cache, or null if not cache.get('/my-image.jpg')
cache.set Function A function that stores an item in cache. Receives a cache key (String) and a value (Stream or String). Returns a Promise that resolves once the item has been cached cache.set('/my-image.jpg', myImageStream)
req http.IncomingMessage The instance of http.IncomingMessage that originated the request console.log(req.url)
setHeader Function A function that allows the plugin to set a response header setHeader('X-Cache', 'HIT')
Anchor link Returns

Controller plugins must return a Stream, containing an image buffer or a JSON object, depending on the type of payload they wish to send.

Anchor link Example

Let’s build a controller plugin that receives the name of a movie, does a lookup for that name on OMDb and returns its poster image.

workspace/plugins/movie-poster.js

module.exports = ({assetStore, cache, req, setHeader}) => {
  const parsedUrl = urlParser.parse(req.url, true)
  const searchTerm = parsedUrl.path.replace('/movie-poster/', '')
  const cacheKey = req.url
  // Let's see if this request is in the cache
  return cache.get(cacheKey).then(cached => {
    if (cached) return cached
    return new Promise((resolve, reject) => {
      request({
        url: `http://www.omdbapi.com/?t=${searchTerm}&apikey=${API_KEY}`,
        json: true
      }, (error, response) => {
        if (error || (response.statusCode !== 200)) {
          return reject(new Error('Could not retrieve data for movie'))
        }
        if (typeof response.body.Poster !== 'string') {
          return reject(new Error('Could not find poster for movie'))
        }
        let cacheStream = new PassThrough()
        let outputStream = new PassThrough()
        http.get(response.body.Poster, res => {
          setHeader('content-type', res.headers['content-type'])
          res.pipe(cacheStream)
          res.pipe(outputStream)
          res.on('end', () => {
            // Add the result to cache
            cache.set(cacheStream, cacheKey)
          })
          resolve(outputStream)
        }).on('error', reject)
      })
    })
  })
}

To use the plugin, you’d hit /movie-poster/{NAME-OF-THE-MOVIE} (e.g. /movie-poster/inception).

Anchor link Caching

Caching improves performance in CDN by storing images that have been modified by image manipulation parameters either on the local filesystem or in a Redis server. THhe main configuration file can be used to configure global caching settings:

Path Description Environment variable Default Format
caching.ttl The number of seconds until a cached item is considered stale N/A 3600 Number
caching.directory.enabled If true, cache files will be saved to the filesystem CACHE_ENABLE_DIRECTORY true Boolean
caching.directory.path The relative path to the cache directory N/A "./cache/" String
caching.redis.enabled If true, cache files will be saved to the specified Redis server CACHE_ENABLE_REDIS Boolean
caching.redis.host The Redis server host REDIS_HOST String
caching.redis.port The port for the Redis server REDIS_PORT 6379 port
caching.redis.password REDIS_PASSWORD String
"caching": {
  "ttl": 3600,
  "directory": {
    "enabled": true,
    "path": "./cache/"
  },
  "redis": {
    "enabled": false,
    "host": "127.0.0.1",
    "port": 6379
  }
}

Anchor link Connecting to Redis

In the event that CDN can't connect to a configured Redis server, caching will fallback to using the local filesystem as the caching layer. When the Redis server is again reachable, CDN will need a restart to reconnect.

Anchor link Caching by MIME type

Browser cache settings can be varied by MIME type. Use the headers.cacheControl section of the configuration file to configure the cache for different MIME types:

"headers": {
  "cacheControl": {
    "default": "public, max-age=3600",
    "paths": [],
    "mimetypes": [
      { "image/jpeg": "public, max-age=86400" },
      { "text/css": "public, max-age=86400" },
      { "text/javascript": "public, max-age=86400" },
      { "application/javascript": "public, max-age=86400" }
    ]
  }
}

Anchor link Server status

A "status" endpoint can be enabled at /api/status for performance monitoring. Add a status configuration section to the main configuration file using the settings below.

Path Description Environment variable Default Format
status.enabled If true, status endpoint is enabled. N/A true Boolean
status.requireAuthentication If true, status endpoint requires authentication. N/A true Boolean
status.standalone If true, status endpoint will run on an standalone address/port. N/A Boolean
status.port Accept connections on the specified port. A value of zero will assign a random port. STATUS_PORT 8003 Number
status.routes An array of routes to test. Each route object must contain properties route and expectedResponseTime. Note, expectedResponseTime is seconds. N/A [{"route":"/test.jpg?format=png&quality=50&width=800&height=600","expectedResponseTime":0.025}] Array

To use the status endpoint an access token must first be obtained by sending a POST request to the /token endpoint with the body of the request containing the credentials from the configuration file's "auth" section:

"auth": {
  "tokenUrl": "/token",
  "clientId": "your-client-id",
  "secret": "your-secret"
}
POST /token HTTP/1.1
Content-Type: application/json
Host: cdn.somedomain.tech
Connection: close
Content-Length: 65

{
  "clientId": "your-client-id",
  "secret": "your-secret"
}

With a request like the above, you should expect a response containing an access token, as below:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Content-Length: 95

{
  "accessToken": "4172bbf1-0890-41c7-b0db-477095a288b6",
  "tokenType": "Bearer",
  "expiresIn": 3600
}

With an access token obtained, send a POST request to /api/status using the access token as the value for the Authorization header:

POST /api/status HTTP/1.1
Content-Type: application/json
Authorization: Bearer 4172bbf1-0890-41c7-b0db-477095a288b6
Host: cdn.somedomain.tech
Connection: close

Anchor link Robots.txt

CDN can be configured to respond to the route /robots.txt. To do so, specify the path to a robots.txt file in the main configuration file:

"robots": "path/to/robots.txt"