iTranslated by AI
Trying Out AWS Lambda's New HTTPS Endpoint Support
Introduction
Update
It seems the Japanese version of the article is no longer available.
On April 6, 2022 (US time), we announced the general availability of Lambda Function URLs. Lambda Function URLs is a new feature that adds an HTTPS endpoint to any Lambda function and optionally allows you to configure Cross-Origin Resource Sharing (CORS) headers.
By using this, we take care of configuring and monitoring a highly available, scalable, and secure HTTPS service, so you can focus on your core business.
Previously, we used API Gateway or Load Balancers (LB) for mapping, but now we can expose HTTPS endpoints with Lambda alone. It is a good thing that there are fewer components to manage.
You can use IAM authentication or access restrictions via CORS.
Server-side Implementation
I tried creating something that could be used as a microservice. The code is short, so I will include the entire file.
package main
import (
"bytes"
"context"
"embed"
"encoding/base64"
"encoding/json"
"fmt"
"image"
"image/jpeg"
_ "image/png"
"io/ioutil"
"log"
"net/http"
"strings"
"github.com/aws/aws-lambda-go/lambda"
pigo "github.com/esimov/pigo/core"
"github.com/nfnt/resize"
"golang.org/x/image/draw"
)
var (
maskImg image.Image
classifier *pigo.Pigo
)
//go:embed data
var fs embed.FS
func init() {
f, err := fs.Open("data/mask.png")
if err != nil {
log.Fatal("cannot open mask.png:", err)
}
defer f.Close()
maskImg, _, err = image.Decode(f)
if err != nil {
log.Fatal("cannot decode mask.png:", err)
}
f, err = fs.Open("data/facefinder")
if err != nil {
log.Fatal("cannot open facefinder:", err)
}
defer f.Close()
b, err := ioutil.ReadAll(f)
if err != nil {
log.Fatal("cannot read facefinder:", err)
}
pigo := pigo.NewPigo()
classifier, err = pigo.Unpack(b)
if err != nil {
log.Fatal("cannot unpack facefinder:", err)
}
}
type Payload struct {
Body string `json:"body"`
IsBase64Encoded bool `json:"isBase64Encoded"`
}
type Request struct {
ImgURL string `json:"img_url"`
}
func main() {
lambda.Start(func(ctx context.Context, payload Payload) (string, error) {
var req Request
if payload.IsBase64Encoded {
b, err := base64.StdEncoding.DecodeString(payload.Body)
if err != nil {
return "", fmt.Errorf("cannot decode payload: %v: %v", err, payload.Body)
}
err = json.NewDecoder(bytes.NewReader(b)).Decode(&req)
if err != nil {
return "", fmt.Errorf("cannot decode payload: %v: %v", err, payload.Body)
}
} else {
err := json.NewDecoder(strings.NewReader(payload.Body)).Decode(&req)
if err != nil {
return "", fmt.Errorf("cannot decode payload: %v: %v", err, payload.Body)
}
}
resp, err := http.Get(req.ImgURL)
if err != nil {
return "", fmt.Errorf("cannot get image: %v: %v", err, req.ImgURL)
}
defer resp.Body.Close()
img, _, err := image.Decode(resp.Body)
if err != nil {
return "", fmt.Errorf("cannot decode input image: %v: %v", err, req.ImgURL)
}
bounds := img.Bounds().Max
param := pigo.CascadeParams{
MinSize: 20,
MaxSize: 2000,
ShiftFactor: 0.1,
ScaleFactor: 1.1,
ImageParams: pigo.ImageParams{
Pixels: pigo.RgbToGrayscale(pigo.ImgToNRGBA(img)),
Rows: bounds.Y,
Cols: bounds.X,
Dim: bounds.X,
},
}
faces := classifier.RunCascade(param, 0)
faces = classifier.ClusterDetections(faces, 0.18)
canvas := image.NewRGBA(img.Bounds())
draw.Draw(canvas, img.Bounds(), img, image.Point{0, 0}, draw.Over)
for _, face := range faces {
pt := image.Point{face.Col - face.Scale/2, face.Row - face.Scale/2}
fimg := resize.Resize(uint(face.Scale), uint(face.Scale), maskImg, resize.NearestNeighbor)
log.Println(pt.X, pt.Y, face.Scale)
draw.Copy(canvas, pt, fimg, fimg.Bounds(), draw.Over, nil)
}
var buf bytes.Buffer
err = jpeg.Encode(&buf, canvas, &jpeg.Options{Quality: 100})
if err != nil {
return "", fmt.Errorf("cannot encode output image: %v", err)
}
return base64.StdEncoding.EncodeToString(buf.Bytes()), nil
})
}
The beginning part is long because I am using Go's embed feature to embed the image file. One thing to note is that the payload may or may not be base64-encoded, similar to when uploading via the AWS CLI.
type Payload struct {
Body string `json:"body"`
IsBase64Encoded bool `json:"isBase64Encoded"`
}
type Request struct {
ImgURL string `json:"img_url"`
}
var req Request
if payload.IsBase64Encoded {
b, err := base64.StdEncoding.DecodeString(payload.Body)
if err != nil {
return "", fmt.Errorf("cannot decode payload: %v: %v", err, payload.Body)
}
err = json.NewDecoder(bytes.NewReader(b)).Decode(&req)
if err != nil {
return "", fmt.Errorf("cannot decode payload: %v: %v", err, payload.Body)
}
} else {
err := json.NewDecoder(strings.NewReader(payload.Body)).Decode(&req)
if err != nil {
return "", fmt.Errorf("cannot decode payload: %v: %v", err, payload.Body)
}
}
In this way, you need to check the value of isBase64Encoded to determine if the body is base64-encoded.
After that, I use Pigo to find faces in the image, draw on it, base64-encode the result, and return it to the client.
google/ko is Convenient
As a side note, it is convenient to use google/ko to easily register this kind of Go code into Lambda.
While it was created for Google Cloud Run, it can also be used for AWS Lambda.
$ aws lambda update-function-code \
--function-name=my-function-name \
--image-uri=$(ko build ./cmd/app)
One thing to note is that the default base image specified by ko, gcr.io/distroless/base:nonroot, does not work on AWS. It is better to use public.ecr.aws/lambda/provided:al2 or alpine, which are specified by AWS. Incidentally, scratch also does not work.
In my case, I wanted to use it from Windows, so I prepared a batch file like the following:
@echo off
setlocal
set PATH=%PATH%;c:\Program Files\Amazon\AWSCLIV2
set CMD=ko build --bare .
set KO_DEFAULTBASEIMAGE=alpine
set KO_DOCKER_REPO=[Registry URL specified in Lambda]
for /f "delims=;" %%1 in ('%CMD%') do (
aws lambda update-function-code --function-name [function name] --image-uri=%%1
)
Client-side Implementation
The HTML, CSS, and JS are as follows.
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
<script src="app.js"></script>
<title>AnonymousFace</title>
</head>
<body>
<h1>AnonymousFace</h1>
<div class="content">
<p>Image URL</p>
<input type="url" id="input" value="">
<button type="submit" id="submit">Submit</button><br/>
<br/>
<img id="image"/>
<div id="response"></div>
</div>
</body>
</html>
body {margin: 6pt; text-align: center;}
#input {margin: auto; width: 500px; margin: auto}
#image {display: none; max-width: 600px}
#submit {margin-top:6pt}
async function postData(url = '', data = {}) {
const response = await fetch(url, {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json'
},
redirect: 'follow',
referrerPolicy: 'no-referrer',
body: JSON.stringify(data)
})
return response.text();
}
window.addEventListener('DOMContentLoaded', e => {
document.querySelector('#submit').addEventListener('click', e => {
const url = '[Lambda HTTPS Endpoint]';
const pred = document.querySelector('#response');
const image = document.querySelector('#image');
const input = document.querySelector('#input');
image.src = '';
image.style.display = 'none';
pred.innerHTML = '<h2>Loading...</h2>';
postData(url, { img_url: input.value }).then(data => {
pred.innerHTML = '';
image.src = 'data:image/jpeg;base64,' + data;
image.style.display = 'inline-block';
});
});
});
Lambda Configuration
In the Lambda settings, the authentication method is set to NONE to use CORS.

Since this also affects billing, it is a good idea to specify the allowed origin URLs. Also, add content-type to the allowed headers.

Execution Results
When you enter an image URL in the input area and click the submit button, a request is sent to Lambda, and an image with an Anonymous mask is returned as follows.

Conclusion
I implemented a simple web application using the HTTPS endpoint of AWS Lambda. It feels like things have become a little bit easier because I don't have to worry about services like API Gateway. While it's hard to unconditionally call it "convenient" because you can't set request limits (usage plans), it might be useful if used properly and appropriately.
Discussion
リンクが変わってる、かつ日本語じゃなくなっているっぽいです(元のリンク先を見る前に 404 になってしまったので、もともと日本語だったのかを知らないのですが・・・)。
ありがとうございます。英語の方に差し替えました。