Serverless Architectures on AWS ~Cognito + API Gateway②~

こんにちは。katoです。

 

前回に引き続き、CognitoとAPI Gatewayを利用したサーバレスサイトの構築を進めていきたいと思います。

 

前回の記事は以下になります。

 

xp-cloud.jp

 

概要

 

前回、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上でサイトを動かす際には、サーバレスでの構築も考えてみてはいかがでしょうか。

 

このブログの著者