こんにちは。katoです。
前回に引き続き、CognitoとAPI Gatewayを利用したサーバレスサイトの構築を進めていきたいと思います。
前回の記事は以下になります。
概要
前回、Cognitoを利用したログイン機能の実装をご紹介いたしましたので、今回はサイト機能の実装に移っていきたいと思います。
投稿サイトとして、Amazon TraslateとPollyを組み込んだ、翻訳コミュニティサイトとなります。
手順
Lambda
早速ですが、サイト機能の実装にあたって必要となるLambda関数を作成していきます。
今回作成するLambda関数は、以下の2つとなります。
・ログイン用
・投稿用
ログイン用Lambdaに関しましては、前回、ログイン処理のみを行う仮のものを作成しましたが、これに投稿読み込み等のサイト機能を組み込んでいきます。
login
import json import boto3 import datetime import os from collections import deque cognito = boto3.client('cognito-idp') s3 = boto3.resource('s3', region_name='us-east-1') client = s3.Bucket('bucket-name') s3_client = boto3.client('s3', region_name='us-east-1') class ExtendException(Exception): def __init__(self, status, message): self.status = status self.message = message def __str__(self): response = { "status": self.status, "message": self.message } return json.dumps(response) def lambda_handler(event, context): today = datetime.datetime.now().strftime("%Y%m%d") now = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S") print (event) jsondata = {} data = event['body'] datas = data.split("&") for num in range(len(datas)): param = datas[num].split('=') jsondata[param[0]] = param[1] print(jsondata) try: auth = cognito.initiate_auth( AuthFlow='USER_PASSWORD_AUTH', ClientId=os.environ['ClientId'], AuthParameters={ 'USERNAME': jsondata['email'], 'PASSWORD': jsondata['password'] } ) user = cognito.get_user( AccessToken=auth['AuthenticationResult']['AccessToken'] ) username = "" for user_param in user['UserAttributes']: if user_param['Name'] == 'name': username = user_param['Value'] if username == "": username = 'unknown' if 'page' in jsondata: page = jsondata['page'] mincount = (int(page) -1)*5 maxcount = (int(page))*5 else: page = 1 mincount = 0 maxcount = int(page)*5 prefix = "community/" + jsondata['src'] + "_" + jsondata['dst'] list_object = client.objects.filter(Prefix=prefix) objects = deque(list_object) objects.reverse() count = 0 src_contents1 = [] dst_contents1 = [] comments_arr = [] comment_count = [] prefix_list = [] for object in objects: if count >= maxcount: break else: object_prefix = (object.key).rsplit('/', 1) if object_prefix[1] == 'src.txt': if count >= mincount and count < maxcount: comment_html = "" comment_src= [] comment_dst = [] comment_voice_src= [] comment_voice_dst = [] comment_user = [] list_comment = client.objects.filter(Prefix=object_prefix[0]) for object in list_comment: if (object.key).rsplit('/', 1)[1] == 'src.txt': src_body = object.get()['Body'].read().decode('utf-8') src_voice_key = object_prefix[0] + "src.mp3" prefix_list.append(object_prefix[0]) elif (object.key).rsplit('/', 1)[1] == 'dst.txt': dst_body = object.get()['Body'].read().decode('utf-8') dst_voice_key = object_prefix[0] + "dst.mp3" elif (object.key).rsplit('_', 1)[1] == 'src.txt': src_comment_body = object.get()['Body'].read().decode('utf-8') comment_src.append(src_comment_body) src_comment_voice_key = (object.key).rsplit('_', 1)[0] + "_src.mp3" comment_voice_src.append(src_comment_voice_key) comment_user.append((object.key).rsplit('/', 1)[1].split('_')[1]) elif (object.key).rsplit('_', 1)[1] == 'dst.txt': dst_comment_body = object.get()['Body'].read().decode('utf-8') comment_dst.append(dst_comment_body) dst_comment_voice_key = (object.key).rsplit('_', 1)[0] + "_dst.mp3" comment_voice_dst.append(dst_comment_voice_key) else: pass src_contents1.append(src_body) dst_contents1.append(dst_body) if len(comment_src) != 0: for i in range(len(comment_src)): comment_html += "<p style=\"margin: 10px 10px 0 10px;\">" + str(i+1) + ". " + comment_user[i] + "</p><div style=\"display: flex; justify-content: center; border-bottom: solid 1px gray; margin: 10px 10px 10px 10px; \"><div style=\"width: 50%; margin: 0 0 10px 10px;\"><p>" + comment_src[i].replace('\r\n', '</br>') + "</p><audio controls src=\"https://xxxxxxxxxx.cloudfront.net/" + comment_voice_src[i] + "\"></audio></div><div style=\"width: 50%; margin: 0 0 10px 10px;\"><p>" + comment_dst[i].replace('\r\n', '</br>') + "</p><audio controls src=\"https://xxxxxxxxxxx.cloudfront.net/" + comment_voice_dst[i] + "\"></audio></div></div>" comment_count.append(str(len(comment_src))) else: comment_html = "<p style=\"margin: 10px 0 10px 10px;\"></p>" comment_count.append(str(0)) comments_arr.append(comment_html) count += 1 post_contents = [] count = 0 post_body = [] post_style = [] src_posts = [] dst_posts = [] for i in range(len(src_contents1)): count += 1 src_post = src_contents1[i].replace('\r\n', '</br>') dst_post = dst_contents1[i].replace('\r\n', '</br>') src_posts.append(src_post) dst_posts.append(dst_post) body_content = "<div style=\"background-color: white; border: double 2px black; margin: 10px 0 10px 0;\">"\ "<p style=\"margin: 5px 0 0 10px;\">" + username + "</p>"\ "<div style=\"display: flex; justify-content: center; margin: 0 0;\">"\ "<div style=\"background-color: white; width: 50%; padding: 0 10px; margin: 0 10px;\">Untranslated (" + jsondata['src'] + ")</div>"\ "<div style=\"background-color: white; width: 50%; padding: 0 10px; margin: 0 10px;\">Translated (" + jsondata['dst'] + ")</div>"\ "</div><div style=\"display: flex; justify-content: center; margin: 0 0 0 0;\">"\ "<div style=\"background-color: white; border: solid 1px black; width: 50%; padding: 0 10px; margin: 0 30px;\"><p>" + src_posts[i].replace('\r\n', '</br>') + "</p><audio controls src=\"https://xxxxxxxxxx.cloudfront.net/" + prefix_list[i] + "/src.mp3" + "\"></audio></div>"\ "<div style=\"background-color: white; border: solid 1px black; width: 50%; padding: 0 10px; margin: 0 30px;\"><p>" + dst_posts[i].replace('\r\n', '</br>') + "</p><audio controls src=\"https://xxxxxxxxxx.cloudfront.net/" + prefix_list[i] + "/dst.mp3" + "\"></audio></div></div>"\ "<div class=\"post-comment" + str(count) + "\" style=\"margin: 10px 0 10px 0;\">"\ "<label for=\"comment" + str(count) + "\">comment (" + str(comment_count[i]) + ")</label><input type=\"checkbox\" id=\"comment" + str(count) + "\">"\ "<div class=\"hidden\"><div style=\"margin: 0 30px 10px 30px; border: solid 1px black;\">"\ + comments_arr[i] \ + "<form method=\"post\" action=\"https://xxxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/login/post\"> "\ "<input type=\"hidden\" name=\"AccessToken\" value=\"" + auth['AuthenticationResult']['AccessToken'] + "\">"\ "<input type=\"hidden\" name=\"src\" value=\"" + jsondata['src'] + "\"><input type=\"hidden\" name=\"dst\" value=\"" + jsondata['dst'] + "\">"\ "<input type=\"hidden\" name=\"comment\" value=\"true\"><input type=\"hidden\" name=\"key\" value=\"" + prefix_list[i] + "\"><input type=\"hidden\" name=\"session\" value=\"" + now + "\">"\ "<div style=\"margin: 0 0 0 10px;\"><textarea name=\"post\" cols=\"50\" rows=\"5\" wrap=\"soft\"></textarea></br>"\ "<button type=\"submit\" style=\"background-color: white; border: double 1px black; padding: 10px 20px 10px 20px; margin: 0 0 10px 0; cursor :pointer;\">Submit</button></div>"\ "</form></div></div></div></div>" print (post_body) style_content = ".post-comment" + str(count) + " label {font-size: auto; padding: 0 20px 0 20px; margin: 0 30px 30px 30px; border: solid 1px black; cursor :pointer; background-color: white; }"\ ".post-comment" + str(count) + " label:hover {background: #efefef;}"\ ".post-comment" + str(count) + " input {display: none;}"\ ".post-comment" + str(count) +" .hidden {height: 0; padding: 0; overflow: hidden; opacity: 0; transition: 0.8s;}"\ ".post-comment" + str(count) + " input:checked ~ .hidden {padding: 0 30px 0 30px; height: auto; opacity: 1;}" print (post_style) post_body.append(body_content) post_style.append(style_content) return { 'body': json.dumps({ 'user': username, 'AccessToken': auth['AuthenticationResult']['AccessToken'], 'src': jsondata['src'], 'dst': jsondata['dst'], 'post_contents': post_contents, 'post_body': post_body, 'post_style': post_style, 'session': now }) } except Exception as e: print (e) raise ExtendException(401, "Unauthorized")
ログイン機能の実装自体は、前回の記事で紹介した方法と同様で、initiate_authおよびget_userを利用しております。
今回はログインformに翻訳言語の指定を追加しますので、srcおよびdstで翻訳元言語、翻訳先言語を動的に設定しております。
これを利用して、Amazon Traslateで翻訳を行っていく形となります。
サイトの作りとしては、TOPページですべての投稿を読み込むと、読み込みに時間がかかる可能性があるので、5投稿(コメント除く)ずつ読み込む形を採用しています。
各投稿には、投稿原文、翻訳投稿、コメント原文、翻訳コメント、各音声ファイル(mp3)が存在しますので、object種別に応じてパスの取得や、本文の取得を行っております。
取得したobject内容を基に、動的なHTMLコードを生成し、returnとしてAPI Gatewayに返す処理を行っております。
API Gatewayでは、このreturnの情報とマッピングテンプレートの内容から、ユーザに表示するページを生成する形となります。
本来はLambdaでHTMLを定義せずに、マッピングテンプレートのみで完結させるほうがスマートなのですが、ややこしすぎて断念しました。。。
post
次にPOST処理用のLambdaとなりますが、今回、サイトで実行する投稿やコメント、ページ遷移といったリクエストは、すべてこのLambdaで処理する形をとっております。
import json import boto3 import datetime import os import urllib.parse from contextlib import closing from collections import deque cognito = boto3.client('cognito-idp') translate = boto3.client('translate') s3 = boto3.resource('s3', region_name='us-east-1') client = s3.Bucket('bucket-name') s3_client = boto3.client('s3', region_name='us-east-1') polly = boto3.client('polly') class ExtendException(Exception): def __init__(self, status, message): self.status = status self.message = message def __str__(self): response = { "status": self.status, "message": self.message } return json.dumps(response) def lambda_handler(event, context): print (event) now = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S") today = datetime.datetime.now().strftime("%Y%m%d") jsondata = {} data = event['body'] datas = data.split("&") for num in range(len(datas)): param = datas[num].split('=') jsondata[param[0]] = param[1].replace('+',' ') print(jsondata) try: user = cognito.get_user( AccessToken=jsondata['AccessToken'] ) username = "" for user_param in user['UserAttributes']: if user_param['Name'] == 'name': username = user_param['Value'] if username == "": username = 'unknown' if 'post' not in jsondata: jsondata['post'] = "" src_lang = jsondata['src'] dst_lang = jsondata['dst'] check_content = "community/" + src_lang + "_" + dst_lang + "/" + today + "/" + username + "/" + jsondata['session'] check_session = s3_client.list_objects_v2( Bucket='xp-blog-translate-community-us', Prefix=check_content ) print (check_session) if ('Contents' in check_session): session = now elif jsondata['post'] == "": session = now else: if jsondata['src'] == 'en': src_voice = 'Joanna' elif jsondata['src'] == 'ja': src_voice = 'Mizuki' if jsondata['dst'] == 'en': dst_voice = 'Joanna' elif jsondata['dst'] == 'ja': dst_voice = 'Mizuki' #comment post if 'comment' in jsondata: src_comment = urllib.parse.unquote(jsondata['post']) print (src_comment) translate_text = translate.translate_text( Text=src_comment, SourceLanguageCode=src_lang, TargetLanguageCode=dst_lang ) print (translate_text) dst_comment = translate_text['TranslatedText'] s3_sorce_file = urllib.parse.unquote(jsondata['key']) + "/" + jsondata['session'] + "_" + username + "_comment_src.txt" s3_dst_file = urllib.parse.unquote(jsondata['key']) + "/" + jsondata['session'] + "_" + username + "_comment_dst.txt" print (s3_sorce_file) src_file = "/tmp/src.txt" dst_file = "/tmp/dst.txt" src = open(src_file, 'w') src.write(src_comment) src.close() dst = open(dst_file, 'w') dst.write(dst_comment) dst.close() with open(src_file, 'rb') as f: response = client.put_object( Key=s3_sorce_file, Body=f ) os.remove(src_file) with open(dst_file, 'rb') as f: response = client.put_object( Key=s3_dst_file, Body=f ) os.remove(dst_file) #src audio_src_key = urllib.parse.unquote(jsondata['key']) + "/" + jsondata['session'] + "_" + username + "_comment_src.mp3" create_voice_record = polly.synthesize_speech( OutputFormat='mp3', Text=src_comment, VoiceId=src_voice ) with closing(create_voice_record['AudioStream']) as stream: response = client.put_object( Key=audio_src_key, Body=stream.read() ) #dst audio_dst_key = urllib.parse.unquote(jsondata['key']) + "/" + jsondata['session'] + "_" + username + "_comment_dst.mp3" create_voice_record = polly.synthesize_speech( OutputFormat='mp3', Text=dst_comment, VoiceId=dst_voice ) with closing(create_voice_record['AudioStream']) as stream: response = client.put_object( Key=audio_dst_key, Body=stream.read() ) session = now #post else: src_post = urllib.parse.unquote(jsondata['post']) print (src_post) translate_text = translate.translate_text( Text=src_post, SourceLanguageCode=src_lang, TargetLanguageCode=dst_lang ) print (translate_text) dst_post = translate_text['TranslatedText'] s3_sorce_file = "community/" + src_lang + "_" + dst_lang + "/" + today + "/" + username + "/" + jsondata['session'] + "/src.txt" s3_dst_file = "community/" + src_lang + "_" + dst_lang + "/" + today + "/" + username + "/" + jsondata['session'] + "/dst.txt" print (s3_sorce_file) src_file = "/tmp/src.txt" dst_file = "/tmp/dst.txt" src = open(src_file, 'w') src.write(src_post) src.close() dst = open(dst_file, 'w') dst.write(dst_post) dst.close() with open(src_file, 'rb') as f: response = client.put_object( Key=s3_sorce_file, Body=f ) os.remove(src_file) with open(dst_file, 'rb') as f: response = client.put_object( Key=s3_dst_file, Body=f ) os.remove(dst_file) #src audio_src_key = "community/" + src_lang + "_" + dst_lang + "/" + today + "/" + username + "/" + jsondata['session'] + "/src.mp3" create_voice_record = polly.synthesize_speech( OutputFormat='mp3', Text=src_post, VoiceId=src_voice ) with closing(create_voice_record['AudioStream']) as stream: response = client.put_object( Key=audio_src_key, Body=stream.read() ) #dst audio_dst_key = "community/" + src_lang + "_" + dst_lang + "/" + today + "/" + username + "/" + jsondata['session'] + "/dst.mp3" create_voice_record = polly.synthesize_speech( OutputFormat='mp3', Text=dst_post, VoiceId=dst_voice ) with closing(create_voice_record['AudioStream']) as stream: response = client.put_object( Key=audio_dst_key, Body=stream.read() ) session = now if 'page' in jsondata: page = jsondata['page'] mincount = (int(page) -1)*5 maxcount = (int(page) )*5 else: page = 1 mincount = 0 maxcount = int(page)*5 prefix = "community/" + jsondata['src'] + "_" + jsondata['dst'] list_object = client.objects.filter(Prefix=prefix) objects = deque(list_object) objects.reverse() count = 0 src_contents1 = [] dst_contents1 = [] comments_arr = [] comment_count = [] prefix_list = [] for object in objects: if count >= maxcount: break else: object_prefix = (object.key).rsplit('/', 1) if object_prefix[1] == 'src.txt': if count >= mincount and count < maxcount: comment_html = "" comment_src= [] comment_dst = [] comment_voice_src= [] comment_voice_dst = [] comment_user = [] list_comment = client.objects.filter(Prefix=object_prefix[0]) for object in list_comment: if (object.key).rsplit('/', 1)[1] == 'src.txt': src_body = object.get()['Body'].read().decode('utf-8') src_voice_key = object_prefix[0] + "src.mp3" prefix_list.append(object_prefix[0]) elif (object.key).rsplit('/', 1)[1] == 'dst.txt': dst_body = object.get()['Body'].read().decode('utf-8') dst_voice_key = object_prefix[0] + "dst.mp3" elif (object.key).rsplit('_', 1)[1] == 'src.txt': src_comment_body = object.get()['Body'].read().decode('utf-8') comment_src.append(src_comment_body) src_comment_voice_key = (object.key).rsplit('_', 1)[0] + "_src.mp3" comment_voice_src.append(src_comment_voice_key) comment_user.append((object.key).rsplit('/', 1)[1].split('_')[1]) elif (object.key).rsplit('_', 1)[1] == 'dst.txt': dst_comment_body = object.get()['Body'].read().decode('utf-8') comment_dst.append(dst_comment_body) dst_comment_voice_key = (object.key).rsplit('_', 1)[0] + "_dst.mp3" comment_voice_dst.append(dst_comment_voice_key) else: pass src_contents1.append(src_body) dst_contents1.append(dst_body) if len(comment_src) != 0: for i in range(len(comment_src)): comment_html += "<p style=\"margin: 10px 10px 0 10px;\">" + str(i+1) + ". " + comment_user[i] + "</p><div style=\"display: flex; justify-content: center; border-bottom: solid 1px gray; margin: 10px 10px 10px 10px; \"><div style=\"width: 50%; margin: 0 0 10px 10px;\"><p>" + comment_src[i].replace('\r\n', '</br>') + "</p><audio controls src=\"https://d7kshlwmzx4ub.cloudfront.net/" + comment_voice_src[i] + "\"></audio></div><div style=\"width: 50%; margin: 0 0 10px 10px;\"><p>" + comment_dst[i].replace('\r\n', '</br>') + "</p><audio controls src=\"https://d7kshlwmzx4ub.cloudfront.net/" + comment_voice_dst[i] + "\"></audio></div></div>" comment_count.append(str(len(comment_src))) else: comment_html = "<p style=\"margin: 10px 0 10px 10px;\"></p>" comment_count.append(str(0)) comments_arr.append(comment_html) count += 1 if int(page) == 1 and len(src_contents1) < 5: page_div = "<div style=\"display: flex; justify-content: center;\"><p style=\"margin: 10px 10px 0 10px;\" >1</p>"\ "</div>" elif int(page) == 1: page_div = "<div style=\"display: flex; justify-content: center;\"><p style=\"margin: 10px 10px 0 10px;\" >1</p>"\ "<form method=\"post\" action=\"https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/login/post\"><input type=\"hidden\" name=\"AccessToken\" value=\"" + jsondata['AccessToken'] + "\"><input type=\"hidden\" name=\"src\" value=\"" + jsondata['src'] + "\">"\ "<input type=\"hidden\" name=\"dst\" value=\"" + jsondata['dst'] + "\"><input type=\"hidden\" name=\"page\" value=\""+ str((int(page)+1)) + "\">"\ "<input type=\"hidden\" name=\"session\" value=\"" + session + "\"><input type=\"submit\" value=\">\" style=\"border-style: none; background-color: white; border: double 1px black; padding: 10px 20px 10px 20px; cursor :pointer;\">"\ "</form></div>" elif len(src_contents1) < 5: page_div = "<div style=\"display: flex; justify-content: center;\">"\ "<form method=\"post\" action=\"https://xxxxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/login/post\"><input type=\"hidden\" name=\"AccessToken\" value=\"" + jsondata['AccessToken'] + "\"><input type=\"hidden\" name=\"src\" value=\"" + jsondata['src'] + "\">"\ "<input type=\"hidden\" name=\"dst\" value=\"" + jsondata['dst'] + "\"><input type=\"hidden\" name=\"page\" value=\"" + str((int(page)-1)) + "\">"\ "<input type=\"hidden\" name=\"session\" value=\"" + session + "\"><input type=\"submit\" value=\"<\" style=\"border-style: none; background-color: white; border: double 1px black; padding: 10px 20px 10px 20px; cursor :pointer;\">"\ "</form><p style=\"margin: 10px 10px 0 10px;\" >" + str(page) + "</p></div>" else: page_div = "<div style=\"display: flex; justify-content: center;\"><form method=\"post\" action=\"https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/login/post\">"\ "<input type=\"hidden\" name=\"AccessToken\" value=\"" + jsondata['AccessToken'] + "\"><input type=\"hidden\" name=\"src\" value=\"" + jsondata['src'] + "\">"\ "<input type=\"hidden\" name=\"dst\" value=\"" + jsondata['dst'] + "\"><input type=\"hidden\" name=\"page\" value=\"" + str((int(page)-1)) + "\">"\ "<input type=\"hidden\" name=\"session\" value=\"" + session + "\"><input type=\"submit\" value=\"<\" style=\"border-style: none; background-color: white; border: double 1px black; padding: 10px 20px 10px 20px; cursor :pointer;\"></form>"\ "<p style=\"margin: 10px 10px 0 10px;\">" + str(page) + "</p>"\ "<form method=\"post\" action=\"https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/login/post\"><input type=\"hidden\" name=\"AccessToken\" value=\"" + jsondata['AccessToken'] + "\"><input type=\"hidden\" name=\"src\" value=\"" + jsondata['src'] + "\">"\ "<input type=\"hidden\" name=\"dst\" value=\"" + jsondata['dst'] + "\"><input type=\"hidden\" name=\"page\" value=\"" + str((int(page)+1)) + "\">"\ "<input type=\"hidden\" name=\"session\" value=\"" + session + "\"><input type=\"submit\" value=\">\" style=\"border-style: none; background-color: white; border: double 1px black; padding: 10px 20px 10px 20px; cursor :pointer;\">"\ "</form></div>" post_contents = [] count = 0 post_body = [] post_style = [] src_posts = [] dst_posts = [] for i in range(len(src_contents1)): count += 1 src_post = src_contents1[i].replace('\r\n', '</br>') dst_post = dst_contents1[i].replace('\r\n', '</br>') src_posts.append(src_post) dst_posts.append(dst_post) body_content = "<div style=\"background-color: white; border: double 2px black; margin: 10px 0 10px 0;\">"\ "<p style=\"margin: 5px 0 0 10px;\">" + username + "</p>"\ "<div style=\"display: flex; justify-content: center; margin: 0 0;\">"\ "<div style=\"background-color: white; width: 50%; padding: 0 10px; margin: 0 10px;\">Untranslated (" + jsondata['src'] + ")</div>"\ "<div style=\"background-color: white; width: 50%; padding: 0 10px; margin: 0 10px;\">Translated (" + jsondata['dst'] + ")</div>"\ "</div><div style=\"display: flex; justify-content: center; margin: 0 0 0 0;\">"\ "<div style=\"background-color: white; border: solid 1px black; width: 50%; padding: 0 10px; margin: 0 30px;\"><p>" + src_posts[i].replace('\r\n', '</br>') + "</p><audio controls src=\"https://xxxxxxxxxx.cloudfront.net/" + prefix_list[i] + "/src.mp3" + "\"></audio></div>"\ "<div style=\"background-color: white; border: solid 1px black; width: 50%; padding: 0 10px; margin: 0 30px;\"><p>" + dst_posts[i].replace('\r\n', '</br>') + "</p><audio controls src=\"https://xxxxxxxxxx.cloudfront.net/" + prefix_list[i] + "/dst.mp3" + "\"></audio></div></div>"\ "<div class=\"post-comment" + str(count) + "\" style=\"margin: 10px 0 10px 0;\">"\ "<label for=\"comment" + str(count) + "\">comment (" + str(comment_count[i]) + ")</label><input type=\"checkbox\" id=\"comment" + str(count) + "\">"\ "<div class=\"hidden\"><div style=\"margin: 0 30px 10px 30px; border: solid 1px black;\">"\ + comments_arr[i] \ + "<form method=\"post\" action=\"https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/login/post\"> "\ "<input type=\"hidden\" name=\"AccessToken\" value=\"" + jsondata['AccessToken'] + "\">"\ "<input type=\"hidden\" name=\"src\" value=\"" + jsondata['src'] + "\"><input type=\"hidden\" name=\"dst\" value=\"" + jsondata['dst'] + "\">"\ "<input type=\"hidden\" name=\"comment\" value=\"true\"><input type=\"hidden\" name=\"key\" value=\"" + prefix_list[i] + "\"><input type=\"hidden\" name=\"session\" value=\"" + session + "\">"\ "<div style=\"margin: 0 0 0 10px;\"><textarea name=\"post\" cols=\"50\" rows=\"5\" wrap=\"soft\"></textarea></br>"\ "<button type=\"submit\" style=\"background-color: white; border: double 1px black; padding: 10px 20px 10px 20px; margin: 0 0 10px 0; cursor :pointer;\">Submit</button></div>"\ "</form></div></div></div></div>" print (post_body) style_content = ".post-comment" + str(count) + " label {font-size: auto; padding: 0 20px 0 20px; margin: 0 30px 30px 30px; border: solid 1px black; cursor :pointer; background-color: white; }"\ ".post-comment" + str(count) + " label:hover {background: #efefef;}"\ ".post-comment" + str(count) + " input {display: none;}"\ ".post-comment" + str(count) +" .hidden {height: 0; padding: 0; overflow: hidden; opacity: 0; transition: 0.8s;}"\ ".post-comment" + str(count) + " input:checked ~ .hidden {padding: 0 30px 0 30px; height: auto; opacity: 1;}" print (post_style) post_body.append(body_content) post_style.append(style_content) return { 'body': json.dumps({ 'user': username, 'AccessToken': jsondata['AccessToken'], 'src': jsondata['src'], 'dst': jsondata['dst'], 'post_contents': post_contents, 'post_body': post_body, 'post_style': post_style, 'session': session, 'page': page, 'page_div': page_div }) } except Exception as e: print (e) raise ExtendException(500, "internal server error")
投稿処理以外の箇所に関しては、基本的にログイン用Lambdaと同様になります。
投稿処理の判定(投稿 or コメント or その他)は、formからLambdaに渡されるパラメータによって行っております。
投稿またはコメントの場合、適したS3のprefixにobjectをアップロードしています。
API Gateway
Lambdaの作成が完了したら、API Gatewayの設定を行います。
まず。ログイン用のPOSTメソッドを更新します。
前回の設定はそのままに、「統合レスポンス」のマッピングテンプレートに以下のものに更新します。
#set($params = $util.parseJson($input.path('$.body'))) <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>TOP</title> </head> <style type="text/css"> .post-form label { font-size: auto; padding: 10px 20px 10px 20px; border: solid 2px black; cursor :pointer; background-color: white; } .post-form label:hover { background: #efefef; } .post-form input { display: none; } .post-form .hidden_area { height: 0; padding: 0; overflow: hidden; opacity: 0; transition: 0.8s; } .post-form input:checked ~ .hidden_area { padding: 10px 0 0 0; height: auto; opacity: 1; } .post-div { float: left; position: fixed; top: 20px; right: 10px; } #foreach($style in $params.post_style) $style #end </style> <body> <div id="TOP"> <div id="header-menu" style="position: fixed; z-index: 100; top: 0; left: 0; background-color: white; border: solid 1px black; width: 100%; height: 250px; padding: 5px;"> <h1>Community TOP</h1> <div id="header-chil" style="padding: 0 20px 0 20px;"> <p>Welcome $params.user</p> <p>$params.src to $params.dst</p> <div id="my-page" style="float: left; margin: 0 5px;"> <form method='post' action='https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/login/mypage'> <input type="hidden" name="AccessToken" value="$params.AccessToken"> <input type="hidden" name="src" value="$params.src"> <input type="hidden" name="dst" value="$params.dst"> <input type="submit" value="My Page" style="border-style: none; background-color: white; border: double 1px black; padding: 10px 20px 10px 20px; cursor :pointer;"> </form> </div> <div id="reload-form" style="float: left; margin: 0 5px;"> <form method='post' action='https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/login/reload'> <input type="hidden" name="AccessToken" value="$params.AccessToken"> <input type="hidden" name="src" value="$params.src"> <input type="hidden" name="dst" value="$params.dst"> <input type="submit" value="reload" style="border-style: none; background-color: white; border: double 1px black; padding: 10px 20px 10px 20px; cursor :pointer;"> </form> </div> <div id="logout" style="float: left; margin: 0 5px;"> <input type="button" onclick="location.href='https://xxxxxxxxxx.cloudfront.net/'"value="Logout" style="border-style: none; background-color: white; border: double 1px black; padding: 10px 20px 10px 20px; cursor :pointer;"> </div> </div> </div> <div id="post-div" class="post-div" style="position: fixed; z-index: 200;"> <form method='post' action='https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/login/post'> <input type="hidden" name="AccessToken" value="$params.AccessToken"> <input type="hidden" name="src" value="$params.src"> <input type="hidden" name="dst" value="$params.dst"> <input type="hidden" name="session" value="$params.session"> <div class="post-form" id="post-form" style="text-align: right"> <label for="form">post</label> <input type="checkbox" id="form"> <div class="hidden_area"> <textarea name="post" cols="50" rows="10" wrap="soft"></textarea></br> <button type="submit" style="border-style: none; background-color: white; border: double 1px black; padding: 10px 20px 10px 20px; cursor :pointer;">Submit</button> </div> </div> </form> </div> <div id="body" style="margin-top: 300px"> <div id="community"> <h2>Community Posts</h2> <div id="posts"> #foreach($post in $params.post_body) $post #end </div> </div> </div> <div style="display: flex; justify-content: center;"> <p style="margin: 10px 10px 0 10px;" >1</p> <form method='post' action='https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/login/post'> <input type="hidden" name="AccessToken" value="$params.AccessToken"> <input type="hidden" name="src" value="$params.src"> <input type="hidden" name="dst" value="$params.dst"> <input type="hidden" name="page" value="2"> <input type="hidden" name="session" value="$params.session"> <input type="submit" value=">" style="border-style: none; background-color: white; border: double 1px black; padding: 10px 20px 10px 20px; cursor :pointer;"> </form> </div> </div> </body> </html>
ポスト用のステージは「/post」で作成しています。
基本的な設定はログイン用のPOSTメソッドと同じものを設定し、マッピングテンプレートは、以下のものを設定します。
#set($params = $util.parseJson($input.path('$.body'))) <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>TOP</title> </head> <style type="text/css"> .post-form label { font-size: auto; padding: 10px 20px 10px 20px; border: solid 2px black; cursor :pointer; background-color: white; } .post-form label:hover { background: #efefef; } .post-form input { display: none; } .post-form .hidden_area { height: 0; padding: 0; overflow: hidden; opacity: 0; transition: 0.8s; } .post-form input:checked ~ .hidden_area { padding: 10px 0 0 0; height: auto; opacity: 1; } .post-div { float: left; position: fixed; top: 20px; right: 10px; } #foreach($style in $params.post_style) $style #end </style> <body> <div id="TOP"> <div id="header-menu" style="position: fixed; z-index: 100; top: 0; left: 0; background-color: white; border: solid 1px black; width: 100%; height: 250px; padding: 5px;"> <h1>Community TOP</h1> <div id="header-chil" style="padding: 0 20px 0 20px;"> <p>Welcome $params.user</p> <p>$params.src to $params.dst</p> <div id="my-page" style="float: left; margin: 0 5px;"> <form method='post' action='https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/login/mypage'> <input type="hidden" name="AccessToken" value="$params.AccessToken"> <input type="hidden" name="src" value="$params.src"> <input type="hidden" name="dst" value="$params.dst"> <input type="submit" value="My Page" style="border-style: none; background-color: white; border: double 1px black; padding: 10px 20px 10px 20px; cursor :pointer;"> </form> </div> <div id="reload-form" style="float: left; margin: 0 5px;"> <form method='post' action='https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/login/reload'> <input type="hidden" name="AccessToken" value="$params.AccessToken"> <input type="hidden" name="src" value="$params.src"> <input type="hidden" name="dst" value="$params.dst"> <input type="submit" value="reload" style="border-style: none; background-color: white; border: double 1px black; padding: 10px 20px 10px 20px; cursor :pointer;"> </form> </div> <div id="logout" style="float: left; margin: 0 5px;"> <input type="button" onclick="location.href='https://xxxxxxxxxx.cloudfront.net/'"value="Logout" style="border-style: none; background-color: white; border: double 1px black; padding: 10px 20px 10px 20px; cursor :pointer;"> </div> </div> </div> <div id="post-div" class="post-div" style="position: fixed; z-index: 200;"> <form method='post' action='https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/login/post'> <input type="hidden" name="AccessToken" value="$params.AccessToken"> <input type="hidden" name="src" value="$params.src"> <input type="hidden" name="dst" value="$params.dst"> <input type="hidden" name="session" value="$params.session"> <div class="post-form" id="post-form" style="text-align: right"> <label for="form">post</label> <input type="checkbox" id="form"> <div class="hidden_area"> <textarea name="post" cols="50" rows="10" wrap="soft"></textarea></br> <button type="submit" style="border-style: none; background-color: white; border: double 1px black; padding: 10px 20px 10px 20px; cursor :pointer;">Submit</button> </div> </div> </form> </div> <div id="body" style="margin-top: 300px; "> <div id="community"> <h2>Community Posts</h2> <div id="posts"> #foreach($post in $params.post_body) $post #end </div> </div> </div> $params.page_div </div> </body> </html>
S3
最後にログインページのHTMLファイルを更新します。
前回のものと基本は同じですが、言語選択の項目をformに追加しています。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Login</title> </head> <body> <div id="Login"> <h1>Login</h1> <form method='post' action='https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/login/login'> <span style="display: inline-block; width: 150px;">User ID(Email)</span> <input type="email" name="email" required> <br/> <span style="display: inline-block; width: 150px;">Password</span> <input type="password" name="password" required pattern="^[a-zA-Z0-9!#?%_@]+" minlength="8" title="min length = 8, only use a-z, A-Z, 0-9, !#?%_@"> <br/> <span style="display: inline-block; width: 150px;">Translate</span> <select name="src" id="src-select" required> <option value="">--Please choose Language--</option> <option value="en">EN</option> <option value="ja">JP</option> </select> <label for="src_to_dst"> to </label> <select name="dst" id="dst-select" required> <option value="">--Please choose Language--</option> <option value="en">EN</option> <option value="ja">JP</option> </select> <br/><br/> <input type="hidden" name="page" value="1"> <input type="submit" value="Login"> </form> </div> <div id="signup"> <h1>SignUp</h1> <input type="button" onclick="location.href='https://xxxxxxxxxx.cloudfront.net/signup.html'"value="SignUp"> </div> </body> </html>
動作確認
CloudFrontエンドポイントにアクセスし、ログインページを開きます。
認証情報と言語を入力し、ログインします。
今回は「JP」to「EN」を指定し、日本語から英語への変換を行います。
ログインに成功すると、最新の投稿が表示されます。
右上の「POST」から投稿を行ってみます。
投稿が正常に行われると、ページがリロードされ、投稿した内容が最新の投稿として表示されているはずです。
また、音声データも併せて生成されており、サイトからテキストの読み上げ(音声の再生)が可能となっているはずです。
続いてコメントをしてみたいと思います。
投稿の下部にある「comment」をクリックし、コメントを入力します。
コメントを投稿した場合も、ページがリロードされ、最新化されます。
コメントが追加されていることが確認できます。
まとめ
2回にわたってCognitoとAPI Gatewayを利用したサーバレスサイトの構築をご紹介してきました。
Cognitoを利用することで、ログイン機能を簡単に実装することができますし、LambdaやAPI Gatewayを利用して、動的にサイトページを生成することも可能です。
向き不向きは御座いますが、低コストで管理も最低限なサイト構成となりますので、AWS上でサイトを動かす際には、サーバレスでの構築も考えてみてはいかがでしょうか。