Home About Contact
Python , React , Bottle

Bottle に React から POST する

Python Web Framework の Bottle で GET/POST する方法を確認します。 その後、React からPOSTを使うところまでの備忘録です。

bottle and react 2

Bottle のセットアップ

環境は Ubuntu 20.04 です。

$ python3 --version
Python 3.8.10

$ python3 -m pip --version
pip 23.1.2 from /home/foo/.local/lib/python3.8/site-packages/pip (python 3.8)

venv を入れます。

$ sudo apt-get install python3-venv

mybottle 環境を作成して、有効にします。

$ python3 -m venv ./mybottle
$ source ./mybottle/bin/activate
(mybottle) $

bottle をインストール。

(mybottle) $ pip install bottle

これで環境の準備と bottle インストール完了です。

pip list すれば、この mybottle 環境にインストール済みのモジュールを確認できます。

(mybottle) $ pip list

GET を試す

app.py を用意。

from bottle import Bottle, run

app = Bottle()

@app.get('/hello')
def hello():
    return 'Hello, World!'

run(app, host='localhost', port=8080, debug=True)

実行:

(mybottle) $ python app.py
Bottle v0.12.25 server starting up (using WSGIRefServer())...
Listening on http://localhost:8080/
Hit Ctrl-C to quit.

別のターミナルから curl を実行。

$ curl http://localhost:8080/hello
Hello, World!

できました。

POST を試す

次に POST を試します。

app.py

from bottle import Bottle, run, request

app = Bottle()

@app.post('/echo')
def echo():
    return request.body.getvalue().decode('utf-8')

run(app, host='localhost', port=8080, debug=True)

実行:

(mybottle) $ python app.py
Bottle v0.12.25 server starting up (using WSGIRefServer())...
Listening on http://localhost:8080/
Hit Ctrl-C to quit.

別のターミナルから curl を実行。

$ curl -X POST "http://localhost:8080/echo" -d 'Hello!' -H "Content-Type: text/plain"
Hello!

できました。

おうむ返しするサーバなので、別のメッセージを入れてみます。

$ curl -X POST "http://localhost:8080/echo" -d 'Hello, World!' -H "Content-Type: text/plain"
Hello, World!

問題ありません。

React から使う

ここまではクライアントは curl を使いましたが、今度は React からこのサーバを使います。

現在のプロジェクトディレクトリに index.html, post.js を追加します。

(mybottle) $ touch index.html post.js

それぞれ内容は以下の通り。

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <meta http-equiv='cache-control' content='no-cache'>
        <meta http-equiv='expires' content='0'>
        <meta http-equiv='pragma' content='no-cache'>
        <title>client</title>
    </head>
    <body>
        <div id="root"></div>
        <script src="https://unpkg.com/react@16.8.6/umd/react.development.js"></script>
        <script src="https://unpkg.com/react-dom@16.8.6/umd/react-dom.development.js"></script>
        <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
        <script type="text/babel" src="./post.js"></script>
    </body>
</html>

post.js

const { useState } = React;

const SERVER_IP = 'localhost'
const SERVER_PORT = '8080'
const END_POINT_URL = `http://${SERVER_IP}:${SERVER_PORT}/echo`

const DEFAULT_TEXT = 'Hello, World!'


function Form({model, actions}){
    const submitStyle = { marginTop: '1em', marginBottom: '1.2em' }

    return (
        <div>
            <form onSubmit={actions.handleSubmit} >
                <textarea type='text' value={model.text} onChange={actions.handleChange} />
                <br/>
                <input style={submitStyle} type='submit' value='Post'/>
            </form>
        </div>
    )
}

function Result({model}){
    return (
        <>
            {model.result!='' && <div>{model.result}</div>}
        </>
    )
}

function Main({initialModel}){
    const [model, setModel] = useState(initialModel)

    const actions = {}
    actions.handleChange = (e)=>{
        setModel({
            text: e.target.value,
            result: model.result
        })
    }

    actions.handleSubmit = (evt)=>{
        evt.preventDefault()

        if( model.text.trim()!='' ){
            const text = model.text.trim()

            const requestOptions = {
                method: 'POST',
                headers: {'Content-Type': 'text/plain'},
                body: text
            }
    
            fetch(END_POINT_URL, requestOptions)
            .then(response => response.text())
            .then(result => {
                setModel({
                    text: text,
                    result: result
                })
            })
        }
    }

    return (
        <>
            <Form
                model={model}
                actions={actions}
            />
            <Result
                model={model}
            />
        </>
    )
}

const model = {
    text: DEFAULT_TEXT,
    result: ''
}

ReactDOM.render(
    <Main initialModel={model} />,
    document.getElementById("root"))

現在のプロジェクトファイルを確認します。

.
├── app.py
├── index.html
└── post.js

app.py を修正して、index.html, post.js を static file として扱えるようにします。

from bottle import Bottle, run, request, static_file

app = Bottle()

@app.post('/echo')
def echo():
    return request.body.getvalue().decode('utf-8')

@app.route('/<filename>')
def index(filename):
    return static_file(filename, root='.')

run(app, host='localhost', port=8080, debug=True)

import に static_file を追加し、def index(filename) で index.html と post.js を static file として publish できるようにしました。

これですべての準備ができたので、 python app.py でサーバを起動した上で、 ブラウザから http://localhost:8080/index.html にアクセスします。

bottle and react 1

Post ボタンをクリックして、テキストフィールドに入れた文字列が返ってくることを確認します。

bottle and react 2

もし、bottle を起動しているサーバとテストしているブラウザが別マシンの場合は、 localhost ではなく、IP アドレスを直接指定します。

localhost 指定部分は post.js と app.py にそれぞれあるので注意。

まとめ

Bottle 本当に簡単に使えました。 感動しました。