LoginSignup
1
4

More than 3 years have passed since last update.

GAE Standard で API Server を作ってみる with Python3.7, Flask

Last updated at Posted at 2019-10-14

0. 前準備

※Google Cloud SDKの導入まで済んでいる前提です。
未導入な場合、拙記事で申し訳ないですが以下が参考になるかもしれません。

また、作業を進めるにはPython3.7の環境が必要です。pyenvについては以下の記事がとても参考になります。

1. 雛形の作成

$ ls
LICENSE                 deploy.sh               main.py
app.yaml                gunicorn.conf.py        requirements.txt

以下を参考に、ファイル構成が上記のようになるようにします。

1-2. ファイルの中身

main.pyはサーバのコードです。
単純なJSONレスポンスを返すだけのルートを作っておきます。

main.py
from flask import Flask
import json

app = Flask(__name__)


@app.route('/')
def hello():
    return json.dumps({'key': 'value'})


if __name__ == '__main__':
    app.run()

app.yamlはデプロイ用の設定ファイル。

  • serviceはデプロイする際のサービス名(ネームスペースのようなもの)を指します。
  • 任意の文字列を指定できますが、defaultサービスのインスタンスが1個以上生成されていることが条件になります。
  • max_instancesはスケールアウト時の最大インスタンス数です。推奨設定では何も書かなくて良いですが、意図しない課金を防ぎたい場合は1や0 (0は最大1になるが、アイドル時は0になる) にしておきます。
app.yaml
runtime: python37
service: default
entrypoint: gunicorn -b :$PORT main:app --config gunicorn.conf.py

automatic_scaling:
  max_instances: 1

gunicornを利用するので、gunicorn用の設定ファイルを用意。

gunicorn.conf.py
import multiprocessing

# Worker Processes
workers = 2
worker_class = 'sync'

# Logging
loglevel = 'info'
logconfig = None

必要なライブラリリスト。
pip install Flask gunicorn としてから、 pip freeze > requirements.txt としても良いです。

requirements.txt
Flask
gunicorn

デプロイ用のスクリプト。
--project プロジェクト名 は省略しても構いません。
--version を指定するとデプロイされたインスタンスのバージョン名を指定できます。
これにより、複数のバージョンがAppEngine上で作られないようにできます(インスタンスを最大1に保つため)。

deploy.sh
gcloud app deploy --project <PROJECT_NAME: プロジェクト名> --version main

1.3 デプロイ

上記で作成した deploy.sh を実行すればデプロイができます。
gcloud app browse を実行すると、gcloudの設定情報に読み込まれたプロジェクト名を使ってページを開くことができます。

$ ./deploy.sh
Services to deploy:

descriptor:      [/path/to/server-root/app.yaml]
source:          [/path/to/server-root]
target project:  [<プロジェクト名>]
target service:  [default]
target version:  [main]
target url:      [https://<プロジェクト名>.appspot.com]
Do you want to continue (Y/n)?  y

Beginning deployment of service [default]...
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 4 files to Google Cloud Storage                ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [default]...done.
Setting traffic split for service [default]...done.
Deployed service [default] to [https://<プロジェクト名>.appspot.com]

You can stream logs from the command line by running:
  $ gcloud app logs tail -s default

To view your application in the web browser run:
  $ gcloud app browse

以下のように表示されたら、デプロイが成功しています。
image.png

GCPコンソールの AppEngineで、サービス名 default 、バージョン名 main でインスタンスが作られていることを確認します。
- https://console.cloud.google.com/appengine/versions

2. 認証、エンドポイントの追加

上記でAPIサーバの雛形は完成していますが、実際に利用する際に必要な手順も進めておきます。

2-1. Digest認証

https://qiita.com/msrks/items/7de68cde6c3ab9d5e177 を参考にさせていただきました。

ライブラリを追加します。 requirements.txt の更新を忘れずに。

pip3 install flask_httpauth
pip3 freeze > requirements.txt

そして、サーバのソースコードを以下のように書き換えます。

main.py
import json
import os

from flask import Flask
from flask_httpauth import HTTPDigestAuth

app = Flask(__name__)

app.config['SECRET_KEY'] = os.environ['SECRET_KEY']
auth = HTTPDigestAuth()

# キーがID, バリューがパスワードになります。書き換え推奨
users = {
    'user': 'passwowrd'
}


@auth.get_password
def get_pw(username):
    if username in users:
        return users.get(username)
    return None


@app.route('/')
@auth.login_required
def index():
    return json.dumps({'authenticate': 'success'})


if __name__ == '__main__':
    app.run()

os.environ['SECRET_KEY'] は、環境変数 SECRET_KEY から値を取得するという意味です。
app.yamlに env_variables キーを追加することで、環境変数を読ませることができます。

app.yaml
runtime: python37
service: default
entrypoint: gunicorn -b :$PORT main:app --config gunicorn.conf.py

automatic_scaling:
  max_instances: 1

env_variables:
  SECRET_KEY: <SECRET_KEY>

また、ローカルで起動する場合、対象の環境変数がない場合はエラーになってしまうので、以下のようなシェルスクリプトを使って、起動する際に SECRET_KEY がセットされている状態にしておきましょう。

run_local.sh
export SECRET_KEY=<SECRET_KEY>
gunicorn -b :8000 main:app --config gunicorn.conf.py

ローカル、あるいはデプロイされたURLを開いて、ID/PASSを求められるウィンドウが出たら成功です。

  • パスワードは main.py で設定したものを入力して下さい。
  • 認証のID、パスワードについても環境変数にしておくのがおすすめです。

2-2. POSTメソッドに対応する

GET以外のメソッドに対応するには、 @app.routemethods キーを追加します。(省略するとGETメソッドのみになります)
main.py を以下のように書き換えます。
app.route で指定している関数名がかぶってしまうとエラーになるので、違う名前をつけましょう。(以下では index_get, index_post に変更している)

main.py
import json
import os

from flask import Flask
from flask_httpauth import HTTPDigestAuth

app = Flask(__name__)

app.config['SECRET_KEY'] = os.environ['SECRET_KEY']
auth = HTTPDigestAuth()

users = {
    'user': 'password',
}


@auth.get_password
def get_pw(username):
    if username in users:
        return users.get(username)
    return None


@app.route('/get')
@auth.login_required
def index_get():
    data = {
        'authenticate': 'success',
        'requestType': 'get',
    }
    return json.dumps(data)

@app.route('/post', methods=['POST'])
@auth.login_required
def index_post():
    data = {
        'authenticate': 'success',
        'requestType': 'post',
    }
    return json.dumps(data)


if __name__ == '__main__':
    app.run()

作成した index_post に実際にアクセスできるかを確認します。
コマンドラインからでも確認できますが、このような場合は Postman というツールが非常に便利です。

postのエンドポイントに対してGETを試みると、 405 エラーが取得できました。これは Method Not Allowed エラーで、文字通り「このエンドポイントでは GET は許可されていないよ、という意味です。

image.png

methods キーには複数のメソッドも追加できるので、例えば以下のようにすると同じエンドポイントでもHTTPメソッドによって処理を変更できます。

# requestのインポートを追加
from flask import Flask, request, abort

@app.route('/post', methods=['POST', 'GET', 'PUT', 'DELETE', 'PATCH'])
def restful():
    if request.method == 'GET':
        _get()
    elif request.method == 'POST':
        _post()
    elif request.method == 'PUT':
        _put()
    elif request.method == 'DELETE':
        _delete()
    elif request.method == 'PATCH':
        _patch()
    # (書かなくても良い) それ以外はMethod not allowed
    abort(405)

HTTPメソッドをPOST(URL入力バーの左側にあるボタン)に変更してSendを押すと、今度は想定するレスポンスが得られました。

image.png

3. パラメータの処理

3-1. クエリパラメータ

https://..../?a=b のようなパラメータを取得する場合、 request.args を利用します。
importに request を追加するのを忘れずに。

from flask import request

def index_get():
    # "?q=b"のbを取得, 存在しなかったら空文字として対処する
    param = request.args.get('q') or ''

3-2. リクエストボディ

リクエストボディを処理する場合、 request.get_data() を利用します。

import json

@app.route('/post', methods=['POST'])
@auth.login_required
def post():
    param = request.get_data().decode()
    body = json.loads(param)
    # 辞書として扱える
    key = body.get('key') or ''

Postman側で試すと、取得できていることがわかります。

image.png

まとめ

Flaskを利用してAPIサーバを構築し、GAEのデプロイまで実施できるようにしました。

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4