FlaskでAPI作って素のJavaScriptでAPI実行する

シンプルにFlaskでAPIを作って、jQueryとかAjaxとか使わない素のJavaScriptでAPIを実行してブラウザ上で表示したいときのまとめ。
JavaScript単体では難しい処理を実装したいときや、Pythonで実装した処理のフロントを手っ取り早く実装したいときに使えると思います。

# TL;DR

- JavaScriptのFetch APIでAPI実行する
- Flask（Python製のウェブアプリケーションフレームワーク）でAPI自作する
- おまけで、Flask環境をDockerで作る場合のファイル一式紹介

ブラウザでの実行例の画像
![image.png](https://qiita-image-store.s3.amazonaws.com/0/116209/bb03b97d-28ca-5e4b-b0bf-d0438466d18c.png)

ちょっと作り込んだバージョン
![image.png](https://qiita-image-store.s3.amazonaws.com/0/116209/7ca8ca4b-a40d-9573-6d43-ca0d3307ad3e.png)

# JavaScriptでAPI実行

Fetch APIを使うと素のJavaScriptでも簡単にAPIを実行できることを知った。

- [お疲れさまXMLHttpRequest、こんにちはfetch](https://qiita.com/tomoyukilabs/items/9b464c53450acc0b9574)
- [Fetch 概説](https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch)

具体的には、例えば以下のようなコードを実行すると``url``に指定したAPIの実行結果を取得/加工/表示することができる。

```Javascript
function get(url) {
  fetch(url)
  .then(function(response) {
    return response.text();  // ここのtextをjsonやblobに変えると取得形式が変わる
  })
  .then(function(text) {  // 引数指定すると↑でreturnしたオブジェクトが入る（thenでチェーンしていける）
    // 加工や表示などの処理
  });
}
```

簡単なHTMLと加工部分をまとめて一緒に書くと以下のような感じ。
ボタンを押すとテキストボックスに入力されたテキストがPostされ、Post後にGetの実行結果が表示される。
PostとGetの具体的な処理は下記のFlaskのコード``hoge.py``を参照

Postで送るときのパラメータの指定方法で困ったが、以下のstackoverflowに書いてあった方法で解決した。
[How do I post form data with fetch api?](https://stackoverflow.com/questions/46640024/how-do-i-post-form-data-with-fetch-api)

```html:index.html
<html>
  <head>
    <title>APIテスト</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <script>
    function get_func(url) {
      fetch(url)
      .then(function(response) {
        return response.text();
      })
      .then(function(text) {
        let view = document.getElementById("view")
        view.textContent = ""
        // 取得テキストを一行ごとにループ（ただ改行して表示しているだけ）
        text.split("\n").forEach((value) => {
          view.insertAdjacentHTML('beforeend', value);
          view.insertAdjacentHTML('beforeend', "<br>");
        })
      });
    }
    
    function post_func(url) {
      // Postで送るパラメータを作成
      let formData = new FormData();
      formData.append('param', document.getElementById('post_param').value);
      fetch(url, {
        method: 'POST',  // methodを指定しないとGETになる
        body: formData,  // Postで送るパラメータを指定
      })
      .then(function() {  // Postした後に結果をGetする（コールバックなのでPostが実行完了してから実行される）
        get_func('APIのGet用URL');  // 今回は「http://192.168.1.100:4000/get」
      });
      // ここにget_funcを書くとPostとGetがほぼ同時に行われてしまい、Post結果をGetできない
      // get_func('APIのGet用URL');
    }
    </script>
  </head>
  <body>
    <input type="text" id="post_param" size=20 value="hoge">
    <input type="submit" value="送信" onClick="post_func('APIのPost用URL')">  <!-- 今回は「http://192.168.1.100:4000/post」-->
    <div id="view"></div>
  </body>
</html>
```

# FlaskでAPI作成

> Flask（フラスク）は、プログラミング言語Python用の、軽量なウェブアプリケーションフレームワークである。
> 出展：Wikipedia「https://ja.wikipedia.org/wiki/Flask」

基本的な使い方はググればたくさん出てくるので省略

なお、ローカルや別ドメインからAPIを実行しようとするとクロスドメイン制約で拒否られる。
[Flask-CORS](https://flask-cors.readthedocs.io/en/latest/)を入れてCORS（Cross Origin Resource Sharing）を有効にしておくとこれを防げる。

```py:hoge.py
from flask import Flask, jsonify, abort, make_response, render_template, request
from flask_cors import CORS

api = Flask(__name__)
CORS(api)  # CORS有効化

@api.route('/get', methods=['GET'])  # Getだけ受け付ける
def get():  # 関数名は重複していなければなんでもよい
    result = ""
    # ローカルのファイルを全部読み込んで返すだけ
    with open("./datafile", mode='r') as f:
        result += f.read()
    return result

@api.route('/post', methods=['POST'])  # Postだけ受け付ける
def post():
    result = request.form["param"]  # Postで送ったときのパラメータの名前を指定する
    # パラメータをローカルのファイルに書き込むだけ
    with open("./datafile", mode='a') as f:
        f.write(result + "\n")
    return make_response(result)

# 4000番ポートでWebサーバを起動する
if __name__ == '__main__':
    api.run(host='0.0.0.0', port=4000, debug=True)
```

これを実行するとWebサーバが起動し、``http://サーバIP:4000/get``などのURLで実行することが可能になる。

前述したJavaScriptと合わせると、簡易的なフローは以下になる。

1. ``post_param``に文字を入力して``送信ボタン``を押下（<font color=blue>クライアント側処理</font>）
2. JavaScriptで定義した``post_func``関数が実行される（<font color=blue>クライアント側処理</font>）
3. ``http://サーバIP:4000/post``が呼ばれる（<font color=green>クライアントからサーバへのRequest</font>）
4. Flaskで定義した``post``関数が実行される（<font color=red>サーバ側処理</font>）
5. ``post``関数実行結果が返ってくる（<font color=purple>サーバからクライアントへのResponse</font>）
6. Post処理実行完了後にJavaScriptで定義した``get_func``関数が実行される（<font color=blue>クライアント側処理</font>）
7. ``http://サーバIP:4000/get``が呼ばれる（<font color=green>クライアントからサーバへのRequest</font>）
8. Flaskで定義した``get``関数が実行される（<font color=red>サーバ側処理</font>）
9. ``get``関数実行結果が返ってくる（<font color=purple>サーバからクライアントへのResponse</font>）
10. ``get_func``関数内で``get``関数の実行結果を加工して表示する（<font color=blue>クライアント側処理</font>）

これでAPIを自作して呼び出すことができるようになった。

# Dockerの諸々のファイル紹介

特に説明はしません。

```Dockerfile:Dockerfile
FROM python:3-alpine

RUN mkdir /test
WORKDIR /test
COPY requirements.txt /test/

RUN pip install -r requirements.txt

CMD ["python3","code/hoge.py"]
```

```bash:requirements.txt
flask
flask-cors
```

```yml:docker-compose.yml
version: '3'

services:
  test:
    build: ./test/build/
    tty: true
    restart: always
    ports: 
      - 4000:4000
    volumes:
      - /etc/localtime:/etc/localtime:ro  # ホストと時刻動機
      - ./test/data/:/test
```

```bash:ディレクトリ構成
.
├── docker-compose.yml
└── test
    ├── build
    │   ├── Dockerfile
    │   └── requirements.txt
    └── data
        ├── code
        │   ├── hoge.py    # Flaskのコード
        │   ├── static     # サーバ内にHTMLファイルなど置きたかったらこのあたりを使う
        │   └── templates  # サーバ内にHTMLファイルなど置きたかったらこのあたりを使う
        └── datafile       # Post時に自動生成されるファイル
```

なお、冒頭の画像では``templates``に``index.html``を配置して``hoge.py``に``/``アクセス時に``index.html``を表示するコードを書いてアクセスしていますが、
``index.html``をローカルに置いてアクセスしても動きます。