Edited at

Pythonしか書きたくない人が、AWS上にWEBアプリを作ってみました。


成果物

Jul-22-2019 07-31-03


モチベーションと概要


  • Windows, Macの両方で使えるような社内ツールを作りたい。


    • Pythonが手軽だがUIも無いし、パッケージのインストールなど環境依存の問題があります。

    • WEBで完結していたらみんな幸せよね。

    • しかしWEB系の言語を全く知らないし、そちらに勉強コストをかける(精神的)余裕もない。

    • AWS Lambdaを使えばPythonで実際の処理を行えるそう!



  • 筆者はHTMLからAWSに渡ってド素人です。なのでPython以外は極力お手軽に行きたい方針です。

  • 見よう見まねの構成図は以下の通り。


    • 今回使用したAWSのサービスはS3API GatewayAWS Lambdaです。

    • 図はdraw.ioで作成。AWS用の図があり、WEBで完結している便利ツール。

    • なんかいっぱいあって把握が大変そうに見えるけど、一旦理解したあとは、目的のLambdaの処理(Pythonで書ける)に注力できると思います。



使うもの
役割

HTML
入力フォームを提供する

Javascript
入力フォームの内容を持って、API GatewayへHTTPリクエストを行う

API Gateway
リクエストされた内容からパラメータを取得してLambdaへjson形式で渡す。

AWS Lambda
与えられたパラメータの処理をここで行う。返り値はAPI Gatewayに渡す。(入力と逆の流れ)


実装


Lambda関数(Python)の作成


参考


作業例


  • 関数の作成をクリック。

-w1155


  • 今回は一から作成、を選択。

-w1071


  • 関数名を入力。

  • ランタイム(関数を記述する言語)にはPythonを指定します。

  • 実行ロールは目的の動作に必要なものを指定します。


    • S3上のファイルにアクセスをしたい場合などは、このときに詳細にロールを設定できます



  • 関数の作成をクリック。

-w1053


  • Lambda関数を記述します。

  • 今回はメッセージが入力されたら、「メッセージ」という文章をLambdaで受信しました。と返すだけのシンプルな処理をPythonで記述します。


limbda_function.py

import json

def lambda_handler(event, context):
message = event['input']
edited_message = "「" + message + "」という文章をLambdaで受信しました。"

return {
'statusCode': 200,
'body': json.dumps(edited_message)
}



  • 引数eventのキーにinputを取っていますが、これはAPI Gatewayの所で設定するキーです。

 message = event['input']


  • 返り値は以下の辞書型。

  • 具体的な値はキーbodyの値に、json形式で返します。

return {

'statusCode': 200,
'body': json.dumps(edited_message)
}


  • プログラムの作成が終わったら保存を行います。

-w1079


  • 右上のテストから、Lambda単体で関数の動作をテストすることができます。

-w1096


  • 右上を選択するとテストパターンを作成画面となります。

  • 先程のコードのeventの値を設定しています。

  • キーinputを設定します。

-w862


  • 作成したテストパターンでテストします。

  • ログが表示され、Lambda関数の返り値を確認することができます。

  • 漢字が符号位置になっていますが、恐らく所望の返り値となっていることが見てとれます。

-w1051


  • Lambda側の作業は以上です。


API Gatewayの作成


参考


作業例


  • 入力パラメータを1つ受け取る、GETリクエストを作成していきます。

  • 新規APIの作成を行います。

  • 新しいAPIを選択して、APIを作成をクリックします。

-w1193


  • リソースの作成を選択。

-w602


  • リソース名を入力。

  • またこのとき「API Gateway CORS を有効にする」にチェックを入れておきます。


    • TODO:簡単な説明



  • リソースの作成をクリック。

  • この時点でHTTPリクエストを送るURLの準備をしたことになります。

  • 続いてGETリクエストを受け取る設定を行います。

-w1183


  • メソッドの作成、を選択

-w623


  • GET、を選択

-w266


  • GETリクエストで呼び出すLambda関数、先程作成した関数を指定します。

  • 保存をクリックします。

-w991


  • OKを選択。

-w885

-w1253


  • 統合リクエストを選択します。

-w963


  • マッピングテンプレートの追加を行います。


    • ここではLambdaにわたすjsonパラメータの設定を行います。



  • 「 テンプレートが定義されていない場合 (推奨) 」を選択

  • Content-Typeにapplication/jsonを追加

  • 下記の通り入力します。


    • Lambda関数の引数で使用していたevent['input']は、このときのキーとなっています。



  • 入力が終わったら保存を選択します。

{

"input": "$input.params('param1')"
}

-w765


  • Lambda同様に、API Gateway単体でテストを行えます。

  • テストを選択します。

-w1072


  • 先程マッピングテンプレートで設定した通り、param1=helloと渡してみると、うまくLambda関数から返り値を受け取れていることがわかります。

-w846


  • APIのデプロイを行い、実際にHTTPリクエストをネットで行えるようにします。

-w344


  • ステージを作成します。

  • デプロイを選択します。


    • ちなみに、API Gatewayの設定を変更した場合、都度デプロイし直さないと反映されないようです。変に嵌らないように注意しましょう。



-w607


  • GETを選択すると呼び出しのURLが作成されています。

-w1337


  • 上記のURLのあとにパラメータを付与することで、HTTPリクエストを行うことができます。


  • URL + ? + キー(URLエンコーディングした値)

  • ターミナルで確認してみると、Lambdaで処理した値が帰ってきていることが分かります。

curl -X GET 'https://<ユーザ固有値>.execute-api.us-east-1.amazonaws.com/prod/mywebapplicationtestapi?param1=Hello'

{"statusCode": 200, "body": "\"\\u300cHello\\u300d\\u3068\\u3044\\u3046\\u6587\\u7ae0\\u3092Lambda\\u3067\\u53d7\\u4fe1\\u3057\\u307e\\u3057\\u305f\\u3002\""}


  • もちろんブラウザでもURLを入力することでも確認できます。

https://<ユーザ固有値>.execute-api.us-east-1.amazonaws.com/prod/mywebapplicationtestapi?param1=Hello

-w1173


作業例_APIを呼び出すための認証キーを作成


  • 今の状態だとURLが分かる人全てが利用できてしまいます。

  • そのため、HTTPリクエストを行う際の認証用のAPIキーを設定します。

  • サイドバーの「APIキー」を選択し、「アクション>APIキーの作成」を選択します。

-w524


  • 名前を入力し保存します。

-w904


  • 作成したAPIキーを控えておきます。後述のHTTPリクエストを行う際に使用します。

-w899


  • 先程作成したGETメソッドを選択し、「メソッドリクエスト」を開きます。

-w1330


  • 「APIキーの必要性」をtrueにします。

-w902


  • 再度「APIのデプロイ」を行います。

  • 設定を変更した際には、都度「APIのデプロイ」を行わないと反映されないようなので、注意ポイントです。

-w345


  • 最後の仕上げとして、APIキーの設定する場合、「使用量プラン」を設定する必要があります。

  • 今回は簡便化のため、スロットリング・クォータは設定しないこととします。

-w1429

-w1191

-w998


  • ターミナルからHTTPリクエストを行いAPIキーが設定されているかどうかを確認します。

  • まず以前行ったHTTPリクエストをそのまま投げてみます。

  • すると{"message":"Forbidden"}と返され、APIキーがないためにHTTPリクエストに失敗していることが分かります。

curl -X GET 'https://<ユーザ固有値>.execute-api.us-east-1.amazonaws.com/prod/mywebapplicationtestapi?param1=Hello'

{"message":"Forbidden"}


  • 次に下記の通りヘッダにAPIキーを設定したHTTPリクエストを行います。

  • 希望する値が返ってきているならば、APIキーの設定がうまく行っていることが確認できます。

curl -X GET 'https://<ユーザ固有値>.execute-api.us-east-1.amazonaws.com/prod/mywebapplicationtestapi?param1=Hello' -H 'x-api-key:<APIキー>'

{"statusCode": 200, "body": "\"\\u300cHello\\u300d\\u3068\\u3044\\u3046\\u6587\\u7ae0\\u3092Lambda\\u3067\\u53d7\\u4fe1\\u3057\\u307e\\u3057\\u305f\\u3002\""}


  • 以上でAPI Gatewayの設定は完了です。

  • あとはHTMLでAPI Gatewayを呼び出すためのUIを作成していきます。


    • (ここまでが本編で、後はおまけに近いですね…)




WEBページの作成(HTML)


参考


実装


  • HTMLは下記のような超簡単なページを作成します。

-w638

<!DOCTYPE html>

<heml lang="ja">
<head>
<meta charset="utf-8">
<title>私のWebアプリケーション</title>
</head>
<body>

<article>
<h1>API Gatewayを呼び出すツール</h1>
<p>入力した文章をAWS Lambdaで処理します。Outputに処理した結果が表示されます。</p>
<section>
<h2>Input</h2>
<textarea id="input_textfield" name="input" rows="4" cols="60"></textarea>
<br>
<input type="submit" value="変換" onclick="send_message();">
<script src="my_util.js"></script>
<h2>Output</h2>
<p id="output_label">(ここに結果が表示されます)</p>
</section>
</article>
</body>
</heml>


  • 入力のテキストボックスと出力のテキストに、id要素(input_textfieldoutput_label)を設定しています。


    • Javascript内ではこれを使用してオブジェクトを取得することで、入力を受け取ったり出力の表示をしています。



<textarea id="input_textfield" name="input" rows="4" cols="60"></textarea>

<p id="output_label">(ここに結果が表示されます)</p>


  • ボタン押下時の処理は下記の通りです。

  • 具体的な処理は同一フォルダ内にあるJavascriptファイル(my_util.jssend_message();)に記載しています。


    • HTMLに結果を動的に表示したいので、ボタンを押下したときの処理をJavascriptファイル書くようにしています。



<input type="submit" value="変換" onclick="send_message();">

<script src="my_util.js"></script>


WEBページの作成(Javascript)


参考


実装


  • コード全体は下記の通りです。

  • (TODO:javascriptファイル内にAPIキーが直打ちになってしまっていますが、社内ツールということでご容赦を…)


    • (ログインで認証させて…の実装が必要になるのでしょうか?)



const send_message = () => {

// URLを作成
let input_label = document.getElementById("input_textfield");
var parameter = input_label.value;
parameter = parameter.replace(/\r?\n/g, '\\r\\n'); // 改行コードを入れるとAWSでの処理が怪しかったので、文字列に置換している(TODO:改善)
parameter = encodeURI(parameter);
console.log(parameter);

request_url = "https://<ユーザ固有値>.execute-api.us-east-1.amazonaws.com/prod/mywebapplicationtestapi?param1=";
request_url = request_url + parameter;
console.log(request_url);

// リクエストオブジェクトの作成
var request = new XMLHttpRequest();
request.open('GET', request_url, true);
request.setRequestHeader("x-api-key", "<APIキー>");
request.responseType = 'json';

// リクエストが成功したときに呼ばれる関数
request.onload = function () {
var json_data = this.response;
var return_message = JSON.parse(json_data["body"]);
// 結果をhtmlに表示する
let output_label = document.getElementById("output_label");
output_label.innerText = return_message
};

request.send(); // URLリクエストを送信する
}


  • 前にターミナルで行ったのと同様に、API Gatewayで作成したURLにパラメータを付与した文字列を作成します。



    • URL + ?<キー名>=<URLエンコーディングした値>



  • スペース等を考慮して、パラメータ文字列にはURLエンコーディングを行っておきます。


    • (TODO:改行コードをURLエンコーディングして渡すと、JSONデータへの変換に失敗しているのかLambdaからの返り値がnilになってしまいました。今回は置換でお茶を濁していますが、AWS側をどう変えれば良いのでしょうか…?)



let input_label = document.getElementById("input_textfield");

var parameter = input_label.value;
parameter = parameter.replace(/\r?\n/g, '\\r\\n'); // 改行コードを入れるとAWSでの処理が怪しかったので、文字列に置換している(TODO:改善)
parameter = encodeURI(parameter);

request_url = "https://<ユーザ固有値>.execute-api.us-east-1.amazonaws.com/prod/mywebapplicationtestapi?param1=";   

request_url = request_url + parameter;


  • HTTPリクエストのオブジェクトを作成します。

  • オブジェクトには、HTTPリクエストの種類(今回はGET)・リクエストURL、またヘッダにAPIキーの情報を与えています。

// リクエストオブジェクトの作成

var request = new XMLHttpRequest();
request.open('GET', request_url, true);
request.setRequestHeader("x-api-key", "<APIキー>");
request.responseType = 'json';


  • リクエストオブジェクトの成功したときの処理を実装します。

  • API Gatawayからの返り値は以下のようにJSONデータとして受け取ります。

  • 今回必要なのはキーbodyの値です。これをHTMLのテキストへ表示してやります。

  • また今回は実装していませんが、例えばrequest.onerrorでエラーハンドリングをしておくとデバッグ時に便利でした。個人で使うものなので自分が便利なように実装したら良いと主ます。

// リクエストが成功したときに呼ばれる関数

request.onload = function () {
var json_data = this.response;
var return_message = JSON.parse(json_data["body"]);
// 結果をhtmlに表示する
let output_label = document.getElementById("output_label");
output_label.innerText = return_message
};


WEBページのHTTPリクエストのテストを行う際の注意点(Chromeの場合)


  • ログは検証のConsoleを参照します。


    • Javascriptのconsole.log()はここに表示されます。



-w1115


  • ChromeローカルファイルのWEBページからHTTPリクエストを行うと、APIリクエストでエラーが発生します。

-w1260


Macの場合

open /Applications/Google\ Chrome.app/ --args --disable-web-security --user-data-dir



まとめ


  • WEBページでは単純な入力だけをさせ、具体的な処理をLambdaに任せることで、Pythonで具体的な処理を書けるようになりました。

  • また残りの作業として、WEBページをS3へ公開し、社内のIPアドレスのみでアクセスするように設定することで、社内で使えるWEBツールの完成となります。(後日書きたいと思っています!)


S3の参考になりそうなページ