一身上の都合により

プログラミングとかの話

FlaskでファイルアップロードAPIを実装し REST Clientで叩いた

:warning:この記事は2019/10/04 にQiita に投稿したものです

TL; DR

FlaskでファイルアップロードAPIを実装し、REST Clientで叩きました。

はじめに

この記事ではFlaskで簡単なGETのAPIを実装するところから、 実際にファイルをPOSTするAPIを実装するところまでを、3 Stepsで記載しています。 Flaskにあまり馴染みが無い方も超簡単にファイルアップロードAPIできるようになれます(きっと)。

環境

Python 3.7 Flask 1.1 VSCode 1.38

ディレクトリ構成 / ├ data/ ├ __init__.py ├ api.py ├ call.http ├ face.png └ requirements.txt

1. APIの実装(JSONのGET)

FlaskでのAPIの実装マジで簡単。やったぜ。

1-1. 実装

from flask import Flask, jsonify

app = Flask(__name__)


@app.route('/api/item', methods=['GET'])
def get_item():
   item = {"item_name": 'hogehoge'}

   return jsonify(item) 

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

これだけ。やったぜ。

1-2. 叩く

さっそく試しに叩いてみよう! ......あれよく考えたらあんまり叩き方知らないな?(情弱)

1-2-0. REST Client

ググってみたら次の記事を見つけました。

REST Client allows you to send HTTP request and view the response in Visual Studio Code directly.

VSCode上で HTTP リクエストができるとな?便利そう。 早速 REST Client を入れて叩いてみました。

1-2-1. Flask起動

$ python api.py 
* Serving Flask app "api" (lazy loading)
* Environment: production
  WARNING: This is a development server. Do not use it in a production deployment.
  Use a production WSGI server instead.
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 111-858-152

1-2-2. リクエス

### GET item
GET http://localhost:5000/api/item

Cmd + Option + R で送信! (Windows は Ctl + Alt + R)

1-2-3. 結果

HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 30
Server: Werkzeug/0.16.0 Python/3.7.3
Date: Thu, 03 Oct 2019 16:54:12 GMT

{
  "item_name": "hogehoge"
}

やったぜ。

2. API 実装(ユーザー情報のPOST)

GET はうまくいったので POST も試してみよう。 ということでまずはFlaskでAPIを実装。

2-1. 実装

from flask import Flask, jsonify, request
import bleach

app = Flask(__name__)

TOKEN = 'YOUR_TOKEN'

@app.route('/api/v1/user', methods=['POST'])
def add_user():
    # check token
    header = request.headers.get('Authorization', None)
    _, token = header.split()
    if token != TOKEN:
        return jsonify({'Forbidden': 'Access is denied'}), 403

    # method check
    if request.method != 'POST':
        return jsonify({'Method Not Allowed': 'Method is invalid.'}), 405

    # create new user
    new_user = {}
    for key in request.form.keys():
        new_user[key] = bleach.clean(request.form.get(key))

    return jsonify(new_user)


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

これだけ。やったぜ。 ほんとはDBと繋いで new_user を登録したりしたんですけど、今回は割愛。

Flask でリクエストを受け取る時は request を使用します。 今回は Content-Type は multipart/form-data で送信するつもりなので、request.form を使用しています。 他にもパラメータを受け取る場合は request.args 、json を受け取る時は request.json を使用します。

2-2. 叩く

2-2-1. Flask起動

さっきと同じようにapi.pyを動かしておきましょう。

2-2-2. リクエス

#######
### POST user info
POST http://localhost:5000/api/v1/user
Authorization: Bearer YOUR_TOKEN
Content-Type: multipart/form-data; boundary=HOGEHOGEBOUNDARY

--HOGEHOGEBOUNDARY
Content-Disposition: form-data; name=user_name

SAKAMOTO RYOMA
--HOGEHOGEBOUNDARY
Content-Disposition: form-data; name=user_email

ryoma.sakamoto@...
--HOGEHOGEBOUNDARY--

なるほど multipart 形式でPOSTする場合は boundary っていうのを設定するんですね。(情弱) 送信する要素ごとに --{your boundary} を記述して、 要素の最後は --{your boundary}-- で締めくくるんですねぇ。 このへんはRFC7578で規定されていますのでご確認ください。

Content-Disposition の name が記述されていますが、これはHTMLの

<form>
 <input name="user_name">
</form>

と同じです。

2-2-3. 結果

HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 98
Server: Werkzeug/0.16.0 Python/3.7.3
Date: Thu, 03 Oct 2019 17:24:57 GMT

{
  "user_email": "ryoma.sakamoto@...",
  "user_name": "SAKAMOTO RYOMA"
}

やったぜ。無事POSTできましたね。

3. APIの実装(アップロードファイルのPOST)

さて、ようやく本題です。

ユーザー情報送るなら顔写真とかも送れた方が良いよね?そうでもないですか? ファイルアップロードのAPIって実装めんどくさいよなぁと思ってましたが超簡単でした! そうFlaskとREST Clientならね。

3-1. 実装

(10月7日check token部分修正しました。)

from flask import Flask, jsonify, request
import bleach

app = Flask(__name__)

TOKEN = 'YOUR_TOKEN'


@app.route('/api/v1/user', methods=['POST'])
def add_user():
    # check token
    header = request.headers.get('Authorization', None)
    if header is not None:
        _, token = header.split()
        if token != TOKEN:
            return jsonify({'Forbidden': 'Access is denied'}), 403
    else:
        return jsonify({'Forbidden': 'Access is denied'}), 403

    # method check
    if request.method != 'POST':
        return jsonify({'Method Not Allowed': 'Method is invalid.'}), 405

    # create new user
    new_user = {}
    for key in request.form.keys():
        new_user[key] = bleach.clean(request.form.get(key))

    ### このへん追加
    # save uploaded file into data folder
    for file in request.files:
        if file is None:
            break
        upload_file = request.files.get(file)
        upload_path = 'data/%s' % upload_file.filename
        upload_file.save(upload_path)
        new_user[file] = upload_file.filename

    return jsonify(new_user)


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

これだけ。やったぜ。 Flaskでファイルを受け取る場合、 request.files の中に入っていきます。

3-2. 叩く

3-2-1. Flask起動

さっきと同じようにapi.pyを動かしておきましょう。 また、事前に顔写真 face.pngapi.py と同じディレクトリに置いておきましょう。

3-2-2. リクエス

#######
### OK
POST http://localhost:5000/api/v1/user
Authorization: Bearer YOUR_TOKEN
Content-Type: multipart/form-data; boundary=HOGEHOGEBOUNDARY

--HOGEHOGEBOUNDARY
Content-Disposition: form-data; name=user_name

SAKAMOTO RYOMA
--HOGEHOGEBOUNDARY
Content-Disposition: form-data; name=user_email

ryoma.sakamoto@...
--HOGEHOGEBOUNDARY
Content-Disposition: form-data; name="file"; filename="face.png"
Content-Type: application/octet-stream

< ./face.png
--HOGEHOGEBOUNDARY--

3-2-3. 結果

HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 98
Server: Werkzeug/0.16.0 Python/3.7.3
Date: Thu, 03 Oct 2019 18:01:26 GMT

{
  "file": "face.png",
  "user_email": "ryoma.sakamoto@...",
  "user_name": "SAKAMOTO RYOMA"
}

dataフォルダをのぞいてみるとちゃんと face.png が保存されていました! スクリーンショット 2019-10-04 3.03.35.png くぅ疲、これにて目的達成です。やったぜ。

感想

ほんとFlask はAPI さくっと書けて優秀だなぁと思います。 情弱の弁解ですが、普段はAPIJavascriptAjaxだったり、Pythonのrequestsだったりで叩いているのですが、 curlについて調べているときにREST Clientについて知ったので使ってみた次第です。

参考

Qiita

Auth0