AWS Startup ブログ
【お手軽ハンズオンで AWS を学ぶ】サーバーレスな RESTful API を構築しよう! Chaliceで実現する Python アプリ開発
Chalice は、 Amazon API Gateway と AWS Lambda for Python による API 環境を実現してくれる、 AWS 製のアプリケーションフレームワークです。「サクッと Web API を書きたいんだけれど、 AWS SAM を使うほどでもない」という場合にはもってこいの手軽さで、スタートアップにおける API 開発を助けてくれます。
この記事では、2019年8月8日に開催された「AWS Amplify & Chalice ハンズオン 〜怠惰なプログラマ向けお手軽アプリ開発手法〜」の内容をベースに、 Chalice を用いてサーバーレスな RESTful API をスピーディーに構築する手順をハンズオン形式で解説していきます。
同じ日に開催した、AWS Amplify の誌上ハンズオン記事は、こちらをご覧下さい。
環境構築
作業に入る前に、まずは以下の環境構築を行いましょう。
Python のバージョン確認
Chalice は、 AWS Lambda がサポートするすべての Python バージョン(2019年8月現在 2.7 および 3.6)をサポートしています。以下のコマンドを実行して、 2.7, 3.6 または 3.7 を使っていることを確認してください。
$ python -V
Python 3.6.0
なお、 Chalice はソースコードの書き方を元にして自動的に Python のバージョンを検出してくれるため、バージョン情報を設定ファイルに書くなどの作業は必要ありません。
可能であれば、 virtualenv を用いてハンズオン用の仮想環境を作ることをおすすめします。仮想環境でも Python バージョンが想定どおりであることを確認してください。
$ pip install virtualenv
$ virtualenv ~/.virtualenvs/chalice-handson
$ source ~/.virtualenvs/chalice-handson/bin/activate
Chalice のインストール
以下のコマンドを実行し、 Chalice をインストールします。
$ pip install chalice
Collecting chalice
(略)
Installing collected packages: chalice
Successfully installed chalice-1.9.1
$ chalice --help
Usage: chalice [OPTIONS] COMMAND [ARGS]...
AWS の認証情報を設定
AWS CLI では通常、機密性の高い認証情報を「~/.aws/credentials」というファイル内に、機密性の低い設定オプションを「~/.aws/config」というファイル内に区別して保存します(※)。これらの情報を元に「どの AWS 環境に対して操作を行うか」を AWS CLI が決定しているのです。
※…AWS_CONFIG_FILE という環境変数を別のパスに変更することで、これらのファイルをデフォルト以外の場所に設置することも可能です。
これまで、自分のローカル端末に AWS の認証情報を設定していない場合は、以下のように設定してください。
~/.aws/credentials
[default]
aws_access_key_id=************************
aws_secret_access_key=****************************************
~/.aws/config
[default]
region=us-west-2
output=json
Access Key ID 、 Secret Access Key などの認証情報が手元にない場合は、こちらの手順を元にして、 IAM ユーザーを作成し認証情報を取得しましょう。ユーザー作成完了画面に表示される「アクセスキー ID」および「シークレットアクセスキー」をコピーし、「~/.aws/credentials」の設定ファイルに保存してください。ここまでできれば、準備完了です。次章からさっそく API を作成していきましょう。
Step 1 : プロジェクトの新規作成
まずは、以下のコマンドでプロジェクトを作成します。
$ chalice new-project helloworld
このコマンドでは、 helloworld ディレクトリを作成します。ディレクトリの構造は次のようになっています。
$ cd helloworld
$ ls -la
drwxr-xr-x .chalice
-rw-r--r-- app.py
-rw-r--r-- requirements.txt
.chalice ディレクトリにはプロジェクト管理用のメタデータが格納されます。 Chalice が利用するための設定情報を持っているのです。 app.py ファイルはアプリケーション本体の処理が記述されているファイルです。 API のルーティング情報や、対応するメソッドの処理などが定義されています。では、好きなエディタで app.py を開いてみましょう。
from chalice import Chalice
app = Chalice(app_name='helloworld')
@app.route('/')
def index():
return {'hello': 'world'}
スクリプトに書かれている処理は非常にシンプルです。「/」という GET リクエストに対して {'hello': 'world'}
を返すメソッドが作成されています。以下のコマンドでローカルサーバーを起動して、ブラウザやコマンドなどでアクセスしてみましょう。
$ chalice local
Serving on http://127.0.0.1:8081
{"hello":"world"}
が返ってくれば成功です。問題なさそうであれば、 Ctrl-C
でローカルサーバーを停止します。
Step 2 : AWS 上へのデプロイ
「AWS 上へのデプロイ」と聞くと「AWS マネジメントコンソールを開く必要があるのでは」「AWS CloudFormation のテンプレートを編集しなければ」と考える方もいるかもしれません。ですが、 Chalice を使用する場合にはそういった作業は必要ありません。デプロイの際には、開発者のローカル端末で chalice deploy
コマンドを実行するだけです。
「~/.aws/credentials」や「~/.aws/config」に設定されている AWS の認証情報を元に、 Chalice がデプロイを行ってくれます。デプロイ作業が非常に簡単であることが、 Chalice の大きな特徴です。
なお、 Chalice を使う場合には「Lambda 用の IAM ロールにどういったポリシーを付与すべきか」を開発者が考える必要はありません。 Chalice では API の内容を自動的に判断して、最小限のポリシーを付与してくれます。それでは、 chalice deploy
コマンドを実行してみましょう(※)。
※…もし「No region configured. Either export the AWS_DEFAULT_REGION environment variable or set the region value in our ~/.aws/config file.」というエラーが表示された場合は、「~/.aws/config」ファイルに「region=任意のリージョン」の設定を記述するか、環境変数 AWS_DEFAULT_REGION に任意のリージョン名(us-east-1, ap-northeast-1 など)を設定して、再度 chalice deploy を実行してください。
$ chalice deploy
Creating deployment package.
Creating IAM role: helloworld-dev
Creating lambda function: helloworld-dev
Creating Rest API
Resources deployed:
- Lambda ARN: arn:aws:lambda:us-east-1:************:function:helloworld-dev
- Rest API URL: https://*********.execute-api.us-east-1.amazonaws.com/api/
これで、 Amazon API Gateway と AWS Lambda ファンクションのデプロイが完了しました。コマンド出力の最後に表示された Rest API URL にアクセスして、 {"hello":"world"}
が返ってくることを確認してください。また、デプロイされた API の URL は chalice url
コマンドを叩くことでいつでも確認できます。
$ chalice url
https://*********.execute-api.us-east-1.amazonaws.com/api/
Step 3 : URL パラメータを取得
@app.route()
のパスパターンに、パラメータとして {paramname}
を記述すると、メソッド内で使用できます。例として、先ほど作成した app.py に、アメリカの都市名をパラメータとして渡すと州名を返す state_of_city
メソッドを追加してみましょう。
from chalice import Chalice
app = Chalice(app_name='helloworld')
CITIES_TO_STATE = {
'seattle': 'WA',
'portland': 'OR',
}
@app.route('/')
def index():
return {'chalice': 'handson'}
@app.route('/cities/{city}')
def state_of_city(city):
return {'state': CITIES_TO_STATE[city]}
AWS 上へデプロイを行い、 /cities/{city}
という URL に都市名のパラメーターを渡すことで、想定した値が返ってくることを確認しましょう(※)。
※…以降の解説では、 curl コマンドではなく httpie を用いて API へのアクセスを行っています。
$ chalice deploy
$ http https://*********.execute-api.us-east-1.amazonaws.com/api/cities/seattle
{
"state": "WA"
}
$ http https://*********.execute-api.us-east-1.amazonaws.com/api/cities/portland
{
"state": "OR"
}
Step 4 : エラーハンドリングの追加
Python のソースコードを読んでいて気づかれた方もいるかもしれませんが、ここまでの state_of_city
メソッドの書き方では、想定していない都市名のパラメーターが渡されると、例外が発生してしまいます。
デフォルトの設定では、 Chalice で書かれた API は例外発生時に「500(Internal Serverl Error)」のステータスコードを返すようになっています。ですが、 API ユーザーの指定したURLが誤っているというだけで「500」のステータスを返すのは少し変です。 Web の仕組みに慣れている人なら「『400』や『404』などのステータスコードを返したい」と考えるでしょう。
Chalice では、こうした場合に「特定のエラークラスをraiseする」ことによって、返却するステータスコードを変更できます。つまり、 Amazon API Gateway の統合レスポンスによるステータスコードの割り当てや、 AWS Lambdaのプロキシ統合によるステータスコードの割り当てなどが必要ないのです。これにより、開発者がコーディングや運用の際に意識すべきことが少なくなり、作業の負担が軽減されます。
デフォルトでは、以下のエラークラスが用意されています。
* BadRequestError - return a status code of 400
* UnauthorizedError - return a status code of 401
* ForbiddenError - return a status code of 403
* NotFoundError - return a status code of 404
* ConflictError - return a status code of 409
* UnprocessableEntityError - return a status code of 422
* TooManyRequestsError - return a status code of 429
* ChaliceViewError - return a status code of 500
今回のケースでは「サポートされていない都市名を渡してきたリクエストが悪い」ということにして、 API ユーザーには「400 Bad Request」を返しましょう。 BadRequestError クラスを import し、例外をキャッチして処理を行います。
app.py
from chalice import Chalice
from chalice import BadRequestError
app = Chalice(app_name='helloworld')
app.debug = True
# レスポンスの詳細情報がわかるように、debugモードを有効にします
CITIES_TO_STATE = {
'seattle': 'WA',
'portland': 'OR',
}
@app.route('/')
def index():
return {'chalice': 'handson'}
@app.route('/cities/{city}')
def state_of_city(city):
try:
return {'state': CITIES_TO_STATE[city]}
except KeyError:
raise BadRequestError("Unknown city '%s', valid choices are: %s" % (
city, ', '.join(CITIES_TO_STATE.keys())))
スクリプトを修正できたら、 AWS 上へ再度デプロイを行いましょう。 /cities/{city}
という URL に「seattle」または「portland」以外の都市名を指定し、「400(Bad Request)」が返ってくることを確認します。
また、 Response オブジェクトを import して使うことで、以下のようにレスポンスの内容を柔軟にカスタマイズできることも覚えておくといいでしょう。
from chalice import Chalice, Response
…
中略
…
@app.route('/cities/{city}')
def state_of_city(city):
try:
city = CITIES_TO_STATE[city]
return Response(body=city,
status_code=200,
headers={'Content-Type': 'text/plain'})
おわりに
本ハンズオンでは、 Chalice を用いてシンプルなレスポンスを返す API を開発しました。非常にスピーディーに、サーバレスな RESTful API を開発できることがご理解いただけたと思います。
ハンズオンに取り組んだり、 AWS のマネージドサービスを使っていて分からないことがあれば、AWS を利用中のスタートアップおよびデベロッパーのためのコワーキングおよびイベントスペース AWS Loft Tokyo の Ask An Expert(AWS のエキスパートである中の人に技術的な質問ができるカウンター)で聞いてみるのもおすすめです。
今回ご紹介した AWS Amplify & Chalice ハンズオンは、10月25日には AWS Pop-up Loft Osaka で開催します。詳しくはこちら。また、AWS Loft Tokyoでも、11月中に開催する予定なので、イベント・セミナー一覧の情報をぜひチェックしてください!