カテゴリー
【Lambda@Edge x ウェブサイト運用】AWS S3&CloudFrontで構築したウェブサイトでOGP対応をしてみた
※ 当ページには【広告/PR】を含む場合があります。
2021/08/05
OGPがbotから読めない?問題
Curlエラー:61(BAD_CONTENT_ENCODING)
Lambda@Edgeで選別〜ブラウザかクローラーか
S3でJSONから読み込む
og:title
og:image
og:description
og:url
og:type
fb:app_id
[
{
"pageId": "top",
"title": "ページタイトル",
"author": "著者名",
"description": "ページの説明",
"url": "リンクURL",
"imgsrc": "見出し画像のリンクURL先(http://以下)",
"mimeType": "画像のMIMEタイプ(image/jpegなど)",
"width": "画像の幅",
"height": "画像の高さ",
"fb": {
"appId": "Facebookアプリ用のID"
}
},
//...
]
pageId
Lambda@Edgeの設定
バージニア北部(us-east-1)
ogpTransformer
AWSLambdaBasicExecutionRole
AmazonS3ReadOnlyAccess
edgelambda.amazonaws.com
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"edgelambda.amazonaws.com",
"lambda.amazonaws.com"
]
},
"Action": "sts:AssumeRole"
}
]
}
edgelambda.amazonaws.com
Lambdaハンドラの準備
const AWS = require('aws-sdk');
//👇botはwhitelistへ個別に登録
const bots = [
'twitter',
'facebook',
'slack',
'line'
];
//👇ターゲットのバケットリージョンに合せる必要がある
//例)バケットが大阪リージョン(ap-northeast-3)にある場合
const s3 = new AWS.S3({
apiVersion: '2006-03-01',
region: 'ap-northeast-3'
});
exports.handler = async (event, context, callback) => {
const request = event.Records[0].cf.request;
const userAgent = request.headers['user-agent'][0].value;
let isBot = false;
//👇登録されたBotかどうかを検索
for (const bot of bots) {
const rgx = new RegExp(bot,'i');
if (rgx.test(userAgent)) {
isBot = true;
break;
}
}
//👇アクセスURIから相対ルートを抽出(空の場合にはtop画面を表示)
const uri = request.uri.replace(/^\//m, '').replace(/\/$/m, '') || 'top';
//👇ボットからの画像リソースへのアクセスはスキップ
if (/(\.jpe?g|\.png|\.gif)$/mi.test(uri)) {
isBot = false;
}
if (isBot) {
const ogpList = await getJson('[S3バケット名]', '[ogp.jsonを保存した場所(バケットキー)]');
const ogpObj = ogpList.find(elem => elem.page_id == uri);
const response = {
status: '200',
statusDescription: 'OK',
headers: {
'content-type': [{
key: 'Content-Type',
value: 'text/html'
}]
},
body: getContent(ogpObj)
};
callback(null, response);
return;
}
callback(null, request);
};
async function getJson(bucket, key) {
try {
const params = { Bucket: bucket, Key: key };
const rawdata = await s3.getObject(params).promise();
return JSON.parse(rawdata.Body.toString());
} catch (err) {
console.log(err);
const message = `【エラー】S3バケット: ${bucket} 内のリソース: ${key} は存在しません。`;
console.log(message);
throw new Error(message);
}
}
function getContent(ogpObj) {
if (ogpObj) {
return `<!doctype html><html lang="ja" prefix="og: http://ogp.me/ns#"><head>
<meta charset="utf-8" />
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<title>${ogpObj.title}</title>
<meta content="${ogpObj.author}" name="author" />
<meta content="${ogpObj.description}" name="description">
<meta property="og:site_name" content="${ogpObj.title}" />
<meta content="article" property="og:type" />
<meta content="ja_JP" property="og:locale" />
<meta content="${ogpObj.url}" property="og:url" />
<meta content="${ogpObj.title}" property="og:title" />
<meta content="${ogpObj.description}" property="og:description" />
<meta content="https://${ogpObj.imgsrc}" property="og:image" />
<meta content="https://${ogpObj.imgsrc}" property="og:image:secure_url" />
<meta content="${ogpObj.mimeType}" property="og:image:type" />
<meta content="${ogpObj.width}" property="og:image:width" />
<meta content="${ogpObj.height}" property="og:image:height" />
<meta content="画像のタイトル" property="og:image:alt" />
<meta content="${ogpObj.fb.appId}" property="fb:app_id" />
<meta content="summary_large_image" property="twitter:card" />
<meta content="${ogpObj.title}" property="twitter:title" />
<meta content="${ogpObj.description}" property="twitter:description" />
<meta content="${ogpObj.imgsrc}" property="twitter:image" />
</head><body></body></html>`;
} else {
return `<!doctype html><html lang="ja"><head>
<meta charset="utf-8" />
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<title>ウェブページ名 | ページがありません</title>
</head><body></body></html>`;
}
}
[コード]
Botにも正しく画像を配給する
//...
//👇ボットからの画像へのアクセスはスキップ
if (/(\.jpe?g|\.png|\.gif)$/mi.test(uri)) {
isBot = false;
}
//...
テスト
Records.cf.request.uri
Records.cf.request.request.headers['user-agent'].value
{
"Records": [
{
"cf": {
"config": {
"distributionId": "EXAMPLE"
},
"request": {
"uri": "/",
"method": "GET",
"clientIp": "1111:2222::3333:4444",
"headers": {
"host": [
{
"key": "Host",
"value": "d123.cf.net"
}
],
"user-agent": [
{
"key": "User-Agent",
"value": "slackbot"
}
]
}
}
}
}
]
}
uri
user-argent.value
Lambda@Edgeのデプロイ
ビュアーリクエスト
Deploying...
Lambda@Edgeを導入した結果
まとめ
参考サイト
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー