From e38850f7baa04ed15117c87560dc0c136e3c49e9 Mon Sep 17 00:00:00 2001 From: Sagid Magomedov Date: Sat, 1 Apr 2023 20:15:44 +0300 Subject: [PATCH] Update s3-resizer 4.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Technically, the interface is still compatible with version 3.x (`100x100_max/my-image.jpg` - the path is still the same), but this update brings major changes including using new import/export syntax, so the decision was to jump to a new version. Here is what's new: - Update the Sharp library: [v0.23.3](https://www.npmjs.com/package/sharp/v/0.23.3) => [v0.32.0](https://www.npmjs.com/package/sharp/v/0.32.0) - Update AWS SDK: [aws-sdk v2.36.0](https://www.npmjs.com/package/aws-sdk/v/2.36.0) => [@aws-sdk/client-s3 v3.304.0](https://www.npmjs.com/package/@aws-sdk/client-s3/v/3.304.0). The former api contained the whole sdk with 99% unused code whereas the new one contains functions specific to S3. - Buffers were replaced by streams. So now it's almost impossible to go out-of-memory if an image is too large (though it is still highly recommended to keep memory big +512mb, you can do that in Configuration->Edit) - CommonJS => ES Module. And hence `require/exports` => `import/export`. They promise to be faster as well. - Reduce zip size containing compiled node_modules: 30mb => 10mb. - If `?path=` is not set, return explaining message and `statusCode: 400`. Previously it was `Internal Server Error` with `statusCode: 500`. 🎉 Compiled and tested for NodeJS 18.x 🎉 ```bash npm i --arch=x64 --platform=linux npm i --arch=x64 --platform=linux --only=prod ``` Use the latter only if you know what you are doing! It does not contain awk libraries in node_modules, so the latest version of the sdk will be used. You can configure auto update of built-in sdk in lambda _Runtime settings -> Edit runtime management configuration -> Update runtime version_. --- index.js | 82 +++++++++++++++++++++++++++++----------------------- package.json | 10 ++++--- 2 files changed, 52 insertions(+), 40 deletions(-) diff --git a/index.js b/index.js index ef7d22a..594020d 100755 --- a/index.js +++ b/index.js @@ -1,9 +1,7 @@ -'use strict' +import {S3Client, GetObjectCommand} from "@aws-sdk/client-s3"; +import {Upload} from '@aws-sdk/lib-storage'; +import Sharp from 'sharp'; - -const AWS = require('aws-sdk'); -const S3 = new AWS.S3({signatureVersion: 'v4'}); -const Sharp = require('sharp'); const PathPattern = /(.*\/)?(.*)\/(.*)/; // parameters @@ -12,10 +10,18 @@ const WHITELIST = process.env.WHITELIST ? Object.freeze(process.env.WHITELIST.split(' ')) : null; +const s3Client = new S3Client(); + +export const handler = async (event) => { + const path = event.queryStringParameters?.path; + if (!path) { + return errorResponse('Parameter "?path=" is empty or missing'); + } -exports.handler = async (event) => { - const path = event.queryStringParameters.path; const parts = PathPattern.exec(path); + if (!parts || !parts[2] || !parts[3]) { + return errorResponse('Parameter "?path=" should look like: path/150x150_max/image.jpg'); + } const dir = parts[1] || ''; const resizeOption = parts[2]; // e.g. "150x150_max" const sizeAndAction = resizeOption.split('_'); @@ -26,27 +32,22 @@ exports.handler = async (event) => { // Whitelist validation. if (WHITELIST && !WHITELIST.includes(resizeOption)) { - return { - statusCode: 400, - body: `WHITELIST is set but does not contain the size parameter "${resizeOption}"`, - headers: {"Content-Type": "text/plain"} - }; + return errorResponse(`WHITELIST is set but does not contain the size parameter "${resizeOption}"`, 403); } // Action validation. if (action && action !== 'max' && action !== 'min') { - return { - statusCode: 400, - body: `Unknown func parameter "${action}"\n` + - 'For query ".../150x150_func", "_func" must be either empty, "_min" or "_max"', - headers: {"Content-Type": "text/plain"} - }; + return errorResponse( + `Unknown func parameter "${action}"\n` + + 'For query ".../150x150_func", "_func" must be either empty, "_min" or "_max"' + ); } try { - const data = await S3 - .getObject({Bucket: BUCKET, Key: dir + filename}) - .promise(); + const originImage = await s3Client.send(new GetObjectCommand({ + Bucket: BUCKET, + Key: dir + filename, + })); const width = sizes[0] === 'AUTO' ? null : parseInt(sizes[0]); const height = sizes[1] === 'AUTO' ? null : parseInt(sizes[1]); @@ -62,28 +63,37 @@ exports.handler = async (event) => { fit = 'cover'; break; } - const result = await Sharp(data.Body, {failOnError: false}) + const sharp = Sharp({failOn: 'none'}) .resize(width, height, {withoutEnlargement: true, fit}) - .rotate() - .toBuffer(); + .rotate(); - await S3.putObject({ - Body: result, - Bucket: BUCKET, - ContentType: data.ContentType, - Key: path, - CacheControl: 'public, max-age=86400' - }).promise(); + // This does not work: await s3Client.send(new PutObjectCommand({params}) + // Solution: https://github.com/aws/aws-sdk-js-v3/issues/1920#issuecomment-761298908 + const upload = new Upload({ + client: s3Client, + params: { + Bucket: BUCKET, + Key: path, + Body: originImage.Body.pipe(sharp), + ContentType: originImage.ContentType, + CacheControl: 'public, max-age=86400' + }, + }); + await upload.done(); return { statusCode: 301, headers: {"Location" : `${URL}/${path}`} }; } catch (e) { - return { - statusCode: e.statusCode || 400, - body: 'Exception: ' + e.message, - headers: {"Content-Type": "text/plain"} - }; + return errorResponse('Exception: ' + e.message, e.statusCode || 400); + } +} + +function errorResponse(body, statusCode = 400) { + return { + statusCode, + body, + headers: {"Content-Type": "text/plain"} } } diff --git a/package.json b/package.json index 078a137..374869c 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,12 @@ { "name": "s3-resizer", - "version": "3.0.1", + "version": "4.0.0", "dependencies": { - "sharp": "^0.23.3" + "sharp": "^0.32.0" }, "devDependencies": { - "aws-sdk": "^2.36.0" - } + "@aws-sdk/client-s3": "^3.304.0", + "@aws-sdk/lib-storage": "^3.304.0" + }, + "type": "module" }