FlaskアプリをIBM Cloudにデプロイする

# はじめに
仕事柄、Watson Machine Learning / Watson APIを呼び出すアプリをIBM Cloud上でよく作っています。
対象がWatson APIの時は [Watson APIを使ったNode.jsアプリの標準実装パターン](https://qiita.com/makaishi2/items/239bebf432436e6f3b88)のとおり、サーバーサイドをNode.jsで実装することがほとんどだったのですが、相手がWatson Machine Learningになると、トークン取得、予測API呼出しの二段階のAPI呼出しが必要になり、非同期通信しか使えないNode.jsでの実装が面倒になってきました。
そんなこともあり、最近はひたすらPython + Flaskでサーバーサイドを実装するということをしています。
そこで、PythonをIBM Cloudで動かすためのお作法、クライアントサイドのJavaScriptからサーバーサイドのFlaskにパラメータを渡すときのお作法をメモとしてまとめました。

# サンプルコード
サンプルコードはいつものように[Github](https://github.com/makaishi2/flask-sample)にあげてあります。
アプリの画面イメージはこちら。

![flask-web1.png](https://qiita-image-store.s3.amazonaws.com/0/206130/42741f2e-c013-574c-3cfe-29b311ba6f5f.png)

ブラウザから引数1、引数2を読み込んで、/sendメソッドでサーバーサイドのFlaskに送り、サーバー側で足し算した結果を返すというシンプルなものです。

IBM Cloudへの導入手順はリンク先のGithub README.mdに記載していますが、

```
$ cf login
$ cf push <service name>
```

とするだけで可能です。

# IBM Cloudにデプロイするためのお作法
Githubにアップしているソースのトップレベルのリストを以下に示します。

```
-rw-r--r--@  1 makaishi  staff      5  2  8 20:01 .cfignore
drwxr-xr-x  13 makaishi  staff    416  6  2 15:47 .git
-rw-r--r--@  1 makaishi  staff     15  4 30 09:26 .gitignore
-rw-r--r--   1 makaishi  staff      6  6  2 15:03 .python-version
-rw-r--r--@  1 makaishi  staff  11357  2  8 09:06 LICENSE
-rw-r--r--@  1 makaishi  staff     22  2  8 09:06 Procfile
-rw-r--r--@  1 makaishi  staff   3674  6  2 15:31 README.md
-rw-r--r--@  1 makaishi  staff     43  6  2 14:43 manifest.yml
drwxr-xr-x   5 makaishi  staff    160  6  2 15:30 readme_images
-rw-r--r--@  1 makaishi  staff     15  6  2 14:42 requirements.txt
-rw-r--r--@  1 makaishi  staff    970  6  2 15:42 server.py
drwxr-xr-x   5 makaishi  staff    160  5 29 14:08 static
drwxr-xr-x   5 makaishi  staff    160  6  2 14:49 templates
```

この中で重要なものについてそれぞれ説明します。

## manifest.yml
Cloud Foundryにデプロイする際に必要な情報を定義するファイルです。
今回のサンプルでは利用していませんが,IBM Cloudの他のサービスをバインドする際には、このファイルにバインド先定義名を記載します。

```
---
applications:
- path: .
  memory: 128M
```

例えば、Pythonの中でWatson Machine Learningを呼び出している場合は、次のような形になります。

```
---
declared-services:
  machine-learning-1:
    label: pm-20
    plan: lite
applications:
- path: .
  memory: 128M
  services:
  - machine-learning-1
```

## requirements.txt

Cloud Foundry標準のPython環境で不足するモジュール名を列挙して、デプロイ時に追加導入するモジュールを指定します。今回のサンプルでは以下の2行があるのみです。

```
requests
flask
```

例えば、Watson Machine Learningを呼び出すアプリの場合、こんな設定になっています。

```
cfenv
requests
flask
Image
python-dotenv
numpy
```

## Procfile

WEBサービス起動時のコマンドを指定します。今回の例では以下の通りです。

```
web: python server.py
```

## server.py

サーバーサービスのPythonプログラムです。今回の例では以下のようになっています。

```py3:server.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import urllib3, requests, json
import os
from os.path import join, dirname
from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/')
def top():
    name = "Top"
    return render_template('sample.html', title='Flask Sample Web', name=name)

# 「送信」ボタンが押された時の処理
@app.route('/send', methods=['POST'])
def send():
    # パラメータの受け取り
    req_json = request.json
    arg1 = req_json['ARG1']
    arg2 = req_json['ARG2']

    # ダミーのサーバー処理
    sum = arg1 + arg2
    print(arg1, arg2, sum)

    # 戻り用変数の定義
    ret = {'ARG1': arg1, 'ARG2': arg2, 'SUM': sum}
    
    # json.dumps の結果を戻り値とする
    return(json.dumps(ret))

@app.route('/favicon.ico')
def favicon():
   return ""

port = os.getenv('VCAP_APP_PORT', '8000')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=int(port), debug=True)
```

この中で重要なのは、クライアントからのパラメータ受け取りの実装と、戻り値の受け渡し方法です。
それぞれ、次のようになっています。どちらもJSONで行うところがポイントです。

受け取り

```py
# パラメータの受け取り
req_json = request.json
arg1 = req_json['ARG1']
arg2 = req_json['ARG2']
```

戻し

```py
# 戻り用変数の定義
ret = {'ARG1': arg1, 'ARG2': arg2, 'SUM': sum}

# json.dumps の結果を戻り値とする
return(json.dumps(ret))
```

## templates/sample.html
FlaskをWebサーバーとして動かす際のHTMLファイルです。
server.pyの中のrender_templare関数呼び出しで、ファイル名を指定します。
具体的なソースは次のとおりです。

```html
<html>
<head>

{% extends "layout.html" %}
{% block body %}
</head>
<body>
<h2>Flask サンプルアプリ</h2>
<hr>
<div>
<label  for="arg1_id">引数1:</label>
<input type="text" value="123.45" name="arg1_id" id="arg1_id" />
<br>
<label  for="arg1_id">引数2:</label>
<input type="text" value="0.678" name="arg2_id" id="arg2_id" />
<br>
<label  for="sum_id">合計: </label>
<input type="text" name="sum_id" disabled="disabled" id="sum_id" />
<br>
<br>
<input type="button" name="button" value="送信" id="send"/>
<hr>

</body>
</html>

<script>

$(function(){
    $('#send').click(send) 
});

function call_flask( type, url, error ) {

    // 画面からパラメータを取得して辞書データにする
    data1 = {
        "ARG1": parseFloat($('#arg1_id').val()),
        "ARG2":  parseFloat($('#arg2_id').val())
    };
    console.log(data1);

    // JSON.stringifyで辞書データをJSON形式にシリアライズする
    json1 = JSON.stringify(data1);

    // ajax関数でサーバーサイド機能の呼出し
    $.ajax({
        type: type,
        url: url,
        dataType: "json",
        data: json1,
        processData: false,
        contentType: "application/json",
        cache: false,
        timeout: 600000,
        success: function (data2) {
            console.log('send callback')
            console.log(data2);
            sum = data2['SUM']
            console.log(sum)
            $('#sum_id').val(sum)
        }
    });
}

function send() {
    call_flask( 'POST', '/send', 
            function(XMLHttpRequest,textStatus,errorThrown){alert('error');} );
}

</script>

{% endblock %}
```

このHTMLで重要なのは、画面項目から値を取ってきて、ajaxで引数を渡すところです。
その実装は以下のとおりとなっています。
画面から値を取ってきて、Javascriptオブジェクトとして組み立てた後、JSON.stringify関数でデータをJSON形式にシリアライズしてからajax関数の引数dataで渡すところがポイントになります。

```javascript
// 画面からパラメータを取得して辞書データにする
data1 = {
   "ARG1": parseFloat($('#arg1_id').val()),
   "ARG2":  parseFloat($('#arg2_id').val())
};

// JSON.stringifyで辞書データをJSON形式にシリアライズする
json1 = JSON.stringify(data1);

// ajax関数でサーバーサイド機能の呼出し
$.ajax({
    type: type,
    url: url,
    dataType: "json",
    data: json1,
    processData: false,
    contentType: "application/json",
    cache: false,
    timeout: 600000,
    success: function (data2) { // 以下略
```

## templates/layout.html
sample.htmlから呼び出される雛形HTMLです。
2つのHTMLを合成するレンダリング処理はFlaskが行います。

```html
<!doctype html>
<title>Python Flask サンプル</title>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<link href='https://fonts.googleapis.com/css?family=Lato:300,400,300' rel='stylesheet' type='text/css'>
<script src="{{ url_for('static', filename='jquery.js') }}"></script>

<div class=page>
  {% block body %}{% endblock %}
</div>
```

## LICENSE

ソースをWebで公開する際のライセンスの記述を行います。
私はApacheライセンスを使っています。