Last active
May 30, 2022 19:25
-
-
Save altrr2/30ca0635ff4dac03c00ad2c21d0288eb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Resize an image on the fly in Yandex Cloud from STORAGE_BUCKET to IMAGES_BUCKET | |
// https://aws.amazon.com/blogs/compute/resize-images-on-the-fly-with-amazon-s3-aws-lambda-and-amazon-api-gateway/ | |
// on YC set up the following rules for the images bucket (as a website hosting): | |
// Condition | |
// Response code 404 | |
// Key prefix resize | |
// Redirect | |
// Protocol HTTPS | |
// Domain name your_domain_name.tld | |
// Response code 307 | |
// Replace key Prefix only | |
// New key prefix resize?key=resize | |
// APIGW setup: | |
// /resize/{proxy+}: | |
// get: | |
// x-yc-apigateway-integration: | |
// type: cloud_functions | |
// function_id: your_function_id | |
// tag: $latest | |
// service_account_id: your_function_sa_id | |
// parameters: | |
// - explode: true | |
// in: path | |
// name: proxy | |
// required: true | |
// schema: | |
// type: string | |
// style: simple | |
// responses: | |
// '301': | |
// description: 301 redirect | |
import { | |
S3Client, GetObjectCommand, PutObjectCommand, DeleteObjectsCommand, ListObjectsCommand, | |
} from '@aws-sdk/client-s3'; | |
import sharp from 'sharp'; | |
import { Readable } from 'stream'; | |
const storageBucket = process.env.STORAGE_BUCKET as string; | |
const imagesBucket = process.env.IMAGES_BUCKET as string; | |
const imagesUrl = process.env.IMAGES_URL as string; | |
let s3Client: S3Client; | |
const allowedDimensions = new Set<string>(); | |
if (allowedDimensions.entries.length === 0 && process.env.RESIZE_ALLOWED_DIMENSIONS) { | |
const dimensions = (process.env.RESIZE_ALLOWED_DIMENSIONS).split(/\s*\/\s*/); | |
dimensions.forEach((dimension) => allowedDimensions.add(dimension)); | |
} | |
export const handler: AWSLambda.Handler = async function(event, _context) { | |
const key = (event.queryStringParameters?.key ?? '') as string; | |
const match = key.match(/((\d+)x(\d+))\/(.*)/); | |
if (match == null || match.length !== 5) { | |
return { | |
statusCode: '403', | |
headers: {}, | |
body: '', | |
}; | |
} | |
const dimensions = match[1]; | |
const width = parseInt(match[2], 10); | |
const height = parseInt(match[3], 10); | |
const originalKey = match[4]; | |
const notFound = { | |
statusCode: '404', | |
headers: {}, | |
body: 'Object not found. Key: ' + originalKey, | |
}; | |
if (allowedDimensions.size > 0 && !allowedDimensions.has(dimensions)) { | |
return notFound; | |
} | |
const client = getS3Client(); | |
if (dimensions === '0x0') { | |
if (originalKey.includes('.')) { | |
const deleteCommand = new DeleteObjectsCommand({ | |
Bucket: imagesBucket, | |
Delete: { Objects: [] }, | |
}); | |
for (const d of allowedDimensions.values()) { | |
if (d !== '0x0') { | |
deleteCommand.input.Delete?.Objects?.push({ Key: key.replace('0x0', d) }); | |
} | |
} | |
if ((deleteCommand.input.Delete?.Objects?.length ?? 0) !== 0) { | |
try { | |
await client.send(deleteCommand); | |
} catch (err) { | |
console.log(err); | |
} | |
} | |
} else { | |
const promises: Promise<unknown>[] = []; | |
for (const d of allowedDimensions.values()) { | |
if (d !== '0x0') { | |
promises.push(deleteFolder(client, key.replace('0x0', d))); | |
} | |
} | |
await Promise.all(promises); | |
} | |
return notFound; | |
} | |
try { | |
const getCommand = new GetObjectCommand({ | |
Bucket: storageBucket, | |
Key: originalKey, | |
}); | |
const data = await client.send(getCommand); | |
if (data.Body == null || !(data.Body instanceof Readable)) { | |
return notFound; | |
} | |
const pipeline = sharp(); | |
const stream = pipeline.rotate().resize(width, height).toFormat('png'); | |
data.Body.pipe(pipeline); | |
const putCommand = new PutObjectCommand({ | |
Body: stream, | |
Bucket: imagesBucket, | |
ContentType: 'image/png', | |
Key: key, | |
}); | |
await client.send(putCommand); | |
return { | |
statusCode: '301', | |
headers: { 'Location': `${imagesUrl}/${key}` }, | |
body: '', | |
}; | |
} catch (err) { | |
if (!(err instanceof Error && err.name === 'NoSuchKey')) { | |
console.log(err); | |
} | |
return notFound; | |
} | |
}; | |
async function deleteFolder(client: S3Client, prefix: string) { | |
// 1000 objects limit? | |
const listCommand = new ListObjectsCommand({ | |
Bucket: imagesBucket, | |
Prefix: prefix, | |
}); | |
const deleteCommand = new DeleteObjectsCommand({ | |
Bucket: imagesBucket, | |
Delete: { Objects: [] }, | |
}); | |
const listed = await client.send(listCommand); | |
if (listed.Contents != null && listed.Contents.length !== 0) { | |
listed.Contents.forEach(({ Key }) => { | |
deleteCommand.input.Delete?.Objects?.push({ Key }); | |
}); | |
} | |
if ((deleteCommand.input.Delete?.Objects?.length ?? 0) !== 0) { | |
try { | |
await client.send(deleteCommand); | |
} catch (err) { | |
console.log(err); | |
} | |
} | |
} | |
function getS3Client() { | |
if (s3Client == null) { | |
const config = { | |
endpoint: process.env.S3_ENDPOINT as string, | |
region: process.env.S3_REGION_NAME as string, | |
credentials: { | |
accessKeyId: process.env.S3_ACCESS_KEY_ID as string, | |
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY as string, | |
}, | |
signatureVersion: 'v4', | |
}; | |
s3Client = new S3Client(config); | |
} | |
return s3Client; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment