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 context
let 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 lambda
const 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 resources
const download = apiGw.root.addResource('download')
// integrate lambda with api gateway
download.addMethod(
'GET',
new cdk.aws_apigateway.LambdaIntegration(downloadSignedUrl)
)
// api gateway resources
const 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>
<meta
name="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'; ">
<input
type="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').value
if ((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 image
image.style.display = 'block'
image.src = json.signedUrl
} catch (error) {
console.log(error)
}
}
button.addEventListener('click', async event => {
// reset image
image.src = ''
image.style.display = 'none'
// get image signed url
await getSignedUrl()
})
document.getElementById('key').addEventListener('keydown', async event => {
if (event.code === 'Enter') {
// reset image
image.src = ''
image.style.display = 'none'
// get image signed url
await 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">
<img
src=""
alt="upload-image"
id="upload-image"
width="90%"
height="auto"
hidden
/>
</div>
<div class="container-image">
<img
src=""
alt="download-image"
id="download-image"
width="90%"
height="auto"
hidden
/>
</div>
</div>
</div>
<div
style="
position: fixed;
top: 0;
left: 0;
min-height: 100vh;
width: 100%;
background-color: whitesmoke;
opacity: 1;
"
hidden
id="modal"
>
<div
style="
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 uploade
let file = null
// signed url
let signedUrl = ''
fileInput.addEventListener('change', event => {
// get file
file = event.target.files[0]
if (file) {
var reader = new FileReader()
reader.onload = e => {
uImage.src = e.target.result
uImage.style.display = 'block'
}
reader.readAsDataURL(file)
}
})
// call bedrock to analyse image
const uploadImage = async () => {
modal.style.display = 'block'
// get signed url for upload
try {
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 request
try {
console.log(signedUrl)
console.log(file)
const res = await axios.put(signedUrl, file)
console.log(res)
} catch (error) {
console.log(error)
}
}
// download image
const 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 image
dImage.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>

Reference#