diff --git a/deployment/serverless-image-handler.template b/deployment/serverless-image-handler.template index 3e655f49e..948d2b40b 100644 --- a/deployment/serverless-image-handler.template +++ b/deployment/serverless-image-handler.template @@ -1,13 +1,19 @@ { "AWSTemplateFormatVersion": "2010-09-09", "Description": "(SO0023) - Serverless Image Handler: This template deploys and configures a serverless architecture that is optimized for dynamic image manipulation and delivery at low latency and cost. Leverages SharpJS for image processing. Template version 4.", - "Parameters": { + "Parameters": { + "AutoWebP" : { + "Description" : "Would you like to enable automatic WebP based on accept headers? Select 'Yes' if so.", + "Default" : "No", + "Type" : "String", + "AllowedValues" : [ "Yes", "No" ] + }, "CorsEnabled" : { "Description" : "Would you like to enable Cross-Origin Resource Sharing (CORS) for the image handler API? Select 'Yes' if so.", "Default" : "No", "Type" : "String", "AllowedValues" : [ "Yes", "No" ] - }, + }, "CorsOrigin" : { "Description" : "If you selected 'Yes' above, please specify an origin value here. A wildcard (*) value will support any origin. We recommend specifying an origin (i.e. https://example.domain) to restrict cross-site access to your API.", "Default" : "*", @@ -224,7 +230,10 @@ "MemorySize": 1024, "Timeout": 30, "Environment" : { - "Variables" : { + "Variables" : { + "AUTO_WEBP" : { + "Ref" : "AutoWebP" + }, "CORS_ENABLED" : { "Ref" : "CorsEnabled" }, @@ -675,4 +684,4 @@ "Value" : { "Ref" : "LogRetentionPeriod" } } } -} \ No newline at end of file +} diff --git a/source/image-handler/image-request.js b/source/image-handler/image-request.js index 75b7da7e7..c3e0f116e 100644 --- a/source/image-handler/image-request.js +++ b/source/image-handler/image-request.js @@ -26,7 +26,11 @@ class ImageRequest { this.bucket = this.parseImageBucket(event, this.requestType); this.key = this.parseImageKey(event, this.requestType); this.edits = this.parseImageEdits(event, this.requestType); - this.originalImage = await this.getOriginalImage(this.bucket, this.key) + this.originalImage = await this.getOriginalImage(this.bucket, this.key); + const outputFormat = this.getOutputFormat(event); + if(outputFormat) { + this.outputFormat = outputFormat; + } return Promise.resolve(this); } catch (err) { return Promise.reject(err); @@ -232,7 +236,19 @@ class ImageRequest { return buckets; } } + + /** + * Return the output format depending on the accepts headers + * @param {Object} event - The request body. + */ + getOutputFormat(event) { + const autoWebP = process.env.AUTO_WEBP; + if (autoWebP && event.headers.Accept && event.headers.Accept.includes("image/webp")) { + return "webp"; + } + return null; + } } // Exports -module.exports = ImageRequest; \ No newline at end of file +module.exports = ImageRequest; diff --git a/source/image-handler/test/test-image-request.js b/source/image-handler/test/test-image-request.js index 122da5552..bd081eed3 100644 --- a/source/image-handler/test/test-image-request.js +++ b/source/image-handler/test/test-image-request.js @@ -644,4 +644,79 @@ describe('getAllowedSourceBuckets()', function() { }); }); }); -}) \ No newline at end of file +}); + +// ---------------------------------------------------------------------------- +// getOutputFormat() +// ---------------------------------------------------------------------------- +describe('getOutputFormat()', function () { + describe('001/AcceptsHeaderIncludesWebP', function () { + it(`Should pass if it returns "webp" for an accepts header which includes webp`, function () { + // Arrange + process.env = { + AUTO_WEBP: true + }; + const event = { + headers: { + Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" + } + }; + // Act + const imageRequest = new ImageRequest(); + var result = imageRequest.getOutputFormat(event); + // Assert + assert.deepEqual(result, "webp"); + }); + }); + describe('002/AcceptsHeaderDoesNotIncludeWebP', function () { + it(`Should pass if it returns null for an accepts header which does not include webp`, function () { + // Arrange + process.env = { + AUTO_WEBP: true + }; + const event = { + headers: { + Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" + } + }; + // Act + const imageRequest = new ImageRequest(); + var result = imageRequest.getOutputFormat(event); + // Assert + assert.deepEqual(result, null); + }); + }); + describe('003/AutoWebPDisabled', function () { + it(`Should pass if it returns null when AUTO_WEBP is disabled with accepts header including webp`, function () { + // Arrange + process.env = { + AUTO_WEBP: false + }; + const event = { + headers: { + Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" + } + }; + // Act + const imageRequest = new ImageRequest(); + var result = imageRequest.getOutputFormat(event); + // Assert + assert.deepEqual(result, null); + }); + }); + describe('004/AutoWebPUnset', function () { + it(`Should pass if it returns null when AUTO_WEBP is not set with accepts header including webp`, function () { + // Arrange + const event = { + headers: { + Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" + } + }; + // Act + const imageRequest = new ImageRequest(); + var result = imageRequest.getOutputFormat(event); + // Assert + assert.deepEqual(result, null); + }); + }); +});