AwsGatewayで地図データ(PNG)を配信するAPIを作成し、S3に保存されたpngファイルを画像データとしてApiGatewayを使って配信したい。
これは MIERUNE AdventCalendar 2022 13日目の記事です。
昨日は @chizutodesign さんによる MapTilerの地図アイコンをデザインした話 でした。
APIのURLは以下を想定。ApiGateway+Lambdaで構築する。
(汎用例)https://hogehogeapi/raster/<バケット名>?key=<S3オブジェクトパス>.png
(具体例)https://hogehogeapi/raster/s3backetname?key=/10/20/30.png
実装方針
S3バケット名をパスパラメータに、オブジェクトパスをkey(クエリパラメータ)としてLambda関数に渡し、Lambda関数はS3よりオブジェクトを取得してBASE64にエンコードしApiGatewayに返却、ApiGatewayはBASE64をデコードしてクライアントへ返却する。
Lambdaに使用する言語はPythonで構築、以下のようなLambda関数を用意する。 (raster.py)
BASE64にエンコード後にApiGatewayでバイナリに戻して上げる点が注意点となる。
# raster.py
import base64
from pathlib import Path
import boto3
s3 = boto3.resource("s3")
def get_raster(event, context):
try:
bucket = s3.Bucket(event["bucket"])
path = event["path"]
if Path(path).suffix == ".png":
response = bucket.Object(path).get()
data = response["Body"].read()
return base64.b64encode(data).decode("utf-8")
else:
return "Invalid path"
except Exception as e:
print(f"Exception..{e}")
return "Invalid Request"
AWSコンソールからのApiGatewayの設定
APIGatewayにおいてLambdaプロキシ統合の使用を選択すると、文字列(主にJSONなど)を返却するようになるため、バイナリで返却したい場合以下の設定をApiGatewayで行う必要がある。
-
統合レスポンスの設定を行いたいため、統合リクエストのLambdaプロキシ統合の使用のチェックを外す。
上記の設定によりLambdaにわたすリクエストのマッピングは自動で設定されないので、マッピングテンプレートを設定する。
-
統合レスポンスの設定。
コンテンツの処理の設定を行うとApiGatewayはLambda関数から渡されたレスポンス(BASE64)を統合レスポンスでバイナリ化してメソッドレスポンスとしてクライアント側へ返却する。
上記の設定を行うことにより、https://hogehogeapi/raster/<バケット名>?key=.pngでこのAPIを呼び出すと、S3に保存されている /<バケット名>/< S3オブジェクトパス>.png がクライアント側にコンテンツタイプ
image/png として呼び出され、PNG画像が表示される。
CDKでの実装例
AWSコンソール画面からの設定では毎回同じことを繰り返さなければならないのでCDKで作成しておく。
以下がその実装例。
肝となる点は以下
const lambdaIntegration = new LambdaIntegration(
.
.
.
proxy: false,
passthroughBehavior: PassthroughBehavior.WHEN_NO_MATCH,
requestTemplates: { 'application/json': requestTemplates },
integrationResponses: [
{
statusCode: '200',
contentHandling: ContentHandling.CONVERT_TO_BINARY,
responseParameters: { 'method.response.header.Content-Type': "'image/png'" }
},
]
全てのコード
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { ContentHandling, LambdaIntegration, PassthroughBehavior, RestApi } from 'aws-cdk-lib/aws-apigateway';
export class hogehogeapi extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const requestTemplates = `
{
"bucket": "$input.params(\'bucket\')",
"key": "$input.params(\'key\')",
}`;
const lambdaIntegration = new LambdaIntegration(
new lambda.Function(
this,
`get_raster`,
{
functionName: "funcName",
runtime: lambda.Runtime.PYTHON_3_8,
code: lambda.Code.fromAsset('./handler'),
memorySize: 128,
handler: "handlerName"
}),
{
proxy: false,
passthroughBehavior: PassthroughBehavior.WHEN_NO_MATCH,
requestTemplates: { 'application/json': requestTemplates },
integrationResponses: [
{
statusCode: '200',
contentHandling: ContentHandling.CONVERT_TO_BINARY,
responseParameters: { 'method.response.header.Content-Type': "'image/png'" }
},
]
}
);
// APIGateway関連の設定
const api = new RestApi(this, `api`, {
restApiName: `hogehogeapi`,
description: `hogehogeapi backend Service`,
binaryMediaTypes: ['image/png'],
deployOptions: {
stageName: 'dev'
},
});
const CorsOptions = {
statusCode: 200,
allowOrigins: ['*'],
allowMethods: ['OPTIONS', 'GET']
};
const resource = api.root.addResource('raster')
const resourcePath = resource.addResource('{bucket}');
resourcePath.addMethod('GET', lambdaIntegration, {
requestParameters: {
'method.request.querystring.key': false,
},
methodResponses: [{
statusCode: '200',
responseParameters: {
'method.response.header.Content-Type': true
},
responseModels: {
'image/png': { modelId: 'Empty' }
}
}]
});
resourcePath.addCorsPreflight(CorsOptions);
}
}
明日は @dayjournal さんによる QGISのプロセッシングを色々とためしてみたです!お楽しみにー