Introduction#
Github this note shows
- Create APIs for generating signed url s3
- Upload and download using the singed url
Lambda Functions#
We need to create a lambda function for generating signed url for downloading file from s3
import { Context, APIGatewayProxyResult, APIGatewayEvent } from 'aws-lambda'import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'import { getSignedUrl } from '@aws-sdk/s3-request-presigner'const s3Client = new S3Client({region: process.env.REGION})export const handler = async (event: APIGatewayEvent,context: Context): Promise<APIGatewayProxyResult> => {console.log(`Event: ${JSON.stringify(event, null, 2)}`)console.log(`Context: ${JSON.stringify(context, null, 2)}`)// parse argument from contextlet key: string = ''let url: string = ''try {key = (event.queryStringParameters as any).key as string} catch (error) {}const command = new GetObjectCommand({Bucket: process.env.BUCKET,Key: key})try {url = await getSignedUrl(s3Client as any, command as any, {expiresIn: 3600})} catch (error) {}return {statusCode: 200,headers: {'Access-Control-Allow-Origin': '*','Access-Control-Allow-Headers': 'Content-Type','Access-Control-Allow-Methods': 'OPTIONS,GET'},body: JSON.stringify({signedUrl: url})}}
Then similarly, create a lambda function for generating signed url for uplading images to s3
const command = new PutObjectCommand({Bucket: process.env.BUCKET,Key: key})try {url = await getSignedUrl(s3Client as any, command as any, {expiresIn: 3600})} catch (error) {}
API Stack#
Create an API Gateway and integrate with two lambda functions. First, create a role for lambda functions
// role for lambdaconst role = new cdk.aws_iam.Role(this, 'RoleForLamdbaNext', {assumedBy: new cdk.aws_iam.ServicePrincipal('lambda.amazonaws.com')})role.addManagedPolicy(cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'))role.addToPolicy(new cdk.aws_iam.PolicyStatement({effect: Effect.ALLOW,resources: ['*'],actions: ['s3:*']}))
Create the first lambda function in Typscript, which is for generating download signed url
const downloadSignedUrl = new NodejsFunction(this, 'GetSignedUrlDownloadFunc', {entry: path.join(__dirname, './../lambda/handler_download.ts'),handler: 'handler',bundling: {externalModules: []},environment: {REGION: props.region,BUCKET: props.bucket},role: role})
Create second lambda function, which is for generating upload signed url
const uploadSignedUrl = new NodejsFunction(this, 'GetSignedUrlUploadFunc', {entry: path.join(__dirname, './../lambda/handler_upload.ts'),handler: 'handler',bundling: {externalModules: []},environment: {REGION: props.region,BUCKET: props.bucket},role: role})
Integrate with API Gateway
const apiGw = new cdk.aws_apigateway.RestApi(this, 'ApiGatewayNextApp', {restApiName: 'ApiGatewayNextApp'})// api gateawy resourcesconst download = apiGw.root.addResource('download')// integrate lambda with api gatewaydownload.addMethod('GET',new cdk.aws_apigateway.LambdaIntegration(downloadSignedUrl))// api gateway resourcesconst upload = apiGw.root.addResource('upload')upload.addMethod('GET',new cdk.aws_apigateway.LambdaIntegration(uploadSignedUrl))
Signed URL S3#
- Download image from signed url
- Upload image from signed url
Let download an image from a signed url
const response = await axios.get(config.API_DOWNLOAD_URL, {params: {key: 'pirf/tree.jpg'}})console.log(response.data.signedUrl)
Then let upload image from a singed url using axios
const submitTest = async () => {const fileName = file ? file.name : ''const response = await axios.get(config.API_UPLOAD_URL, {params: {key: 'pirf/' + fileName}})await axios.put(response.data.signedUrl as string, file)const signedDownloadUrl = await axios.get(config.API_DOWNLOAD_URL, {params: {key: 'pirf/' + fileName}})setUrl(signedDownloadUrl.data.signedUrl)return false}
Finally let upload an image from a signed url using fetch
const submit = async () => {const fileName = file ? file.name : ''const response = await axios.get(config.API_UPLOAD_URL, {params: {key: 'pirf/' + fileName}})const upload = await fetch(response.data.signedUrl as string, {method: 'PUT',body: file})const signedDownloadUrl = await axios.get(config.API_DOWNLOAD_URL, {params: {key: 'pirf/' + fileName}})setUrl(signedDownloadUrl.data.signedUrl)return false}
S3 CORS#
[{"AllowedHeaders": ["*"],"AllowedMethods": ["GET", "PUT", "POST"],"AllowedOrigins": ["*"],"ExposeHeaders": []}]
Frontend#
download.html
<html><head><title>download</title><metaname="viewport"content="width=device-width, initial-scale=1, maximum-scale=1"/><style>:root {box-sizing: border-box;}*,::before,::after {box-sizing: inherit;}.container {max-width: 800px;margin: auto;}.container-image {position: relative;background-color: gainsboro;padding: 10px 10px;align-items: center;justify-content: center;display: flex;max-height: 600px;height: 80%;}.button-download {padding: 10px 25px;background-color: orange;cursor: pointer;outline: none;border: none;border-radius: 5px;position: absolute;right: 10px;top: 50%;transform: translateY(-50%);}.input {width: 100%;padding: 15px 10px;}</style></head><body><div class="container"><div style="position: relative"><form onkeydown="return event.key != 'Enter'; "><inputtype="text"id="key"name="key"placeholder="tree.jpeg"class="input"/></form><button class="button-download" id="download">Download</button></div><div class="container-image"><img src="" alt="image" id="image" width="90%" height="auto" hidden /></div></div></body><script>const image = document.getElementById('image')const button = document.getElementById('download')const DOWNLOAD_URL ='https://s6pw7va8rg.execute-api.us-west-2.amazonaws.com/prod/download?key='const getSignedUrl = async () => {let key = document.getElementById('key').valueif ((key === '') | (key === null)) {key = 'tree.jpeg'}console.log(DOWNLOAD_URL + 'pirf/' + key)try {const response = await fetch(DOWNLOAD_URL + 'pirf/' + key, {method: 'GET'})const json = await response.json()console.log(json.signedUrl)// set imageimage.style.display = 'block'image.src = json.signedUrl} catch (error) {console.log(error)}}button.addEventListener('click', async event => {// reset imageimage.src = ''image.style.display = 'none'// get image signed urlawait getSignedUrl()})document.getElementById('key').addEventListener('keydown', async event => {if (event.code === 'Enter') {// reset imageimage.src = ''image.style.display = 'none'// get image signed urlawait getSignedUrl()}})</script></html>
upload.html
<html><head><title>Image Prompt</title><meta name="viewport" content="width=device-width, initial-scale=1" /><script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.2.1/axios.min.js"></script><style>:root {box-sizing: border-box;}*,::before,::after {box-sizing: inherit;}body {background-color: antiquewhite;}.container {max-width: 800px;margin: auto;}.container-form {position: relative;}.input-question {width: 100%;padding: 15px 10px;}.button-submit {background-color: orange;padding: 10px 25px;border-radius: 2px;border: none;outline: none;position: absolute;top: 50%;right: 10px;transform: translateY(-50%);cursor: pointer;}.input-file {width: 100%;padding: 10px 10px;background-color: aquamarine;cursor: pointer;}.container-image {position: relative;background-color: gainsboro;padding: 10px 10px;align-items: center;justify-content: center;display: flex;max-height: 600px;/* height: 50%; */}.description-image {position: absolute;bottom: 0;left: 0;background-color: azure;padding: 10px;opacity: 0.9;}</style></head><body><div class="container"><div><div class="container-form"><button class="button-submit" id="submit" type="submit">Upload</button><input type="file" id="file" class="input-file" /></div><div class="container-image"><imgsrc=""alt="upload-image"id="upload-image"width="90%"height="auto"hidden/></div><div class="container-image"><imgsrc=""alt="download-image"id="download-image"width="90%"height="auto"hidden/></div></div></div><divstyle="position: fixed;top: 0;left: 0;min-height: 100vh;width: 100%;background-color: whitesmoke;opacity: 1;"hiddenid="modal"><divstyle="max-width: 800px;margin: auto;display: flex;min-height: 100vh;justify-content: center;align-items: center;flex-direction: column;padding: 10px 10px;"><h2>Please wait for uploading ...</h2></div></div></body><script>const modal = document.getElementById('modal')const fileInput = document.getElementById('file')const uImage = document.getElementById('upload-image')const dImage = document.getElementById('download-image')const submit = document.getElementById('submit')const DOWNLOAD_URL ='https://s6pw7va8rg.execute-api.us-west-2.amazonaws.com/prod/download?key='const UPLOAD_URL ='https://s6pw7va8rg.execute-api.us-west-2.amazonaws.com/prod/upload?key='// file to uploadelet file = null// signed urllet signedUrl = ''fileInput.addEventListener('change', event => {// get filefile = event.target.files[0]if (file) {var reader = new FileReader()reader.onload = e => {uImage.src = e.target.resultuImage.style.display = 'block'}reader.readAsDataURL(file)}})// call bedrock to analyse imageconst uploadImage = async () => {modal.style.display = 'block'// get signed url for uploadtry {const response = await fetch(UPLOAD_URL + 'pirf/' + file.name, {method: 'GET'})const json = await response.json()signedUrl = json.signedUrl} catch (error) {console.log(error)}// upload image by put requesttry {console.log(signedUrl)console.log(file)const res = await axios.put(signedUrl, file)console.log(res)} catch (error) {console.log(error)}}// download imageconst downloadImage = async () => {console.log(DOWNLOAD_URL + file.name)try {const response = await fetch(DOWNLOAD_URL + 'pirf/' + file.name, {method: 'GET'})const json = await response.json()console.log(json.signedUrl)// set imagedImage.style.display = 'block'dImage.src = json.signedUrl//modal.style.display = 'none'//} catch (error) {console.log(error)}}submit.addEventListener('click', async event => {event.preventDefault()await uploadImage()await downloadImage()})</script></html>