Python responder の Quick Start を写経してみる
responder は Python の Web フレームワークです。 類似のものには Flask や Falcon があります。 公式ページには以下の機能がある、と書かれています。
- A pleasant API, with a single import statement.
- Class-based views without inheritance.
- ASGI framework, the future of Python web services.
- WebSocket support!
- The ability to mount any ASGI / WSGI app at a subroute.
- f-string syntax route declaration.
- Mutable response object, passed into each view. No need to return anything.
- Background tasks, spawned off in a ThreadPoolExecutor.
- GraphQL (with GraphiQL) support!
- OpenAPI schema generation, with interactive documentation!
- Single-page webapp support!
今回は Quick Start! を写経して勉強した際のメモです。
インストールする
事前に responder をインストールしておきます。
基本機能のみ、実装したシンプルなサンプルです。
ソースコード
responder はデフォルトで 127.0.0.1
を Listen するようです。 その為、address='0.0.0.0'
を指定して外部からアクセス出来るようにしています。
| import responder
api = responder.API()
@api.route("/")
def hello_world(req, resp):
resp.text = "hello, world!"
if __name__ == '__main__':
api.run(address='0.0.0.0', port=5042)
|
以下のように実行します。
| # python app.py
INFO: Started server process [18284]
INFO: Uvicorn running on http://0.0.0.0:5042 (Press CTRL+C to quit)
INFO: Waiting for application startup.
INFO: Application startup complete.
|
結果
| $ curl http://127.0.0.1:5042/
hello, world!
|
引数を処理するサンプルです。 結果は文字列として返します。
ソースコード
| import responder
api = responder.API()
@api.route("/hello/{who}")
def hello_to(req, resp, *, who):
resp.text = f"hello, {who}!"
if __name__ == '__main__':
api.run(address='0.0.0.0', port=5042)
|
結果
| curl http://127.0.0.1:5042/hello/alice
hello, alice!
|
(文字列では無く) JSON を返すサンプルです。
ソースコード
| import responder
api = responder.API()
@api.route("/hello/{who}/json")
def hello_to(req, resp, *, who):
resp.media = {"hello": who}
if __name__ == '__main__':
api.run(address='0.0.0.0', port=5042)
|
Quick Start には下記と書かれていました。
If you want your API to send back JSON, simply set the resp.media property to a JSON-serializable Python object:
結果
| curl http://127.0.0.1:5042/hello/alice/json
{"hello": "alice"}
|
テンプレートを使ってレンダリングしてみます。
ソースコード
テンプレート自体は templates
ディレクトリに保存しておきます。 今回は templates/hello.html
を以下の内容で新規作成しました。
1
2
3
4
5
6
7
8
9
10
11
12 | <html>
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.3.0/dist/css/uikit.min.css" />
<script src="https://cdn.jsdelivr.net/npm/uikit@3.3.0/dist/js/uikit.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/uikit@3.3.0/dist/js/uikit-icons.min.js"></script>
</head>
<body>
<div class="uk-section uk-section-primary">
<h1 class='uk-heading-primary'>Hello, {{ who }}!</h1>
</div>
</body>
</html>
|
Python のサンプルコードは以下です。
| import responder
api = responder.API()
@api.route("/hello/{who}/html")
def hello_html(req, resp, *, who):
resp.html = api.template('hello.html', who=who)
if __name__ == '__main__':
api.run(address='0.0.0.0', port=5042)
|
結果
レスポンスコードを指定するサンプルです。
ソースコード
| import responder
api = responder.API()
@api.route("/416")
def teapot(req, resp):
resp.status_code = api.status_codes.HTTP_416
if __name__ == '__main__':
api.run(address='0.0.0.0', port=5042)
|
結果
| $ curl -I http://127.0.0.1:5042/416
HTTP/1.1 416 Requested Range Not Satisfiable
date: Sat, 25 Jan 2020 03:41:00 GMT
server: uvicorn
content-type: application/json
content-length: 4
|
レスポンスヘッダを追加するサンプルです。
ソースコード
| import responder
api = responder.API()
@api.route("/pizza")
def pizza_pizza(req, resp):
resp.headers['X-Pizza'] = '42'
if __name__ == '__main__':
api.run(address='0.0.0.0', port=5042)
|
結果
| curl -I http://127.0.0.1:5042/pizza
HTTP/1.1 200 OK
date: Sat, 25 Jan 2020 03:45:48 GMT
server: uvicorn
content-type: application/json
x-pizza: 42
content-length: 4
|
ソースコード
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | import responder
api = responder.API()
@api.route("/")
async def upload_file(req, resp):
@api.background.task
def process_data(data):
f = open('./{}'.format(data['file']['filename']), 'w')
f.write(data['file']['content'].decode('utf-8'))
f.close()
data = await req.media(format='files')
process_data(data)
resp.media = {'success': 'ok'}
if __name__ == '__main__':
api.run(address='0.0.0.0', port=5042)
|
結果 (Python でのテスト)
Responder 公式サイトのテスト用サンプルコードは、なぜか /file
を指定しつつポート番号も 8210
になっています。 これはサーバ側のサンプルコードに合わせて /
ディレクトリに対してアップロードするように修正します。
| import requests
data = {'file': ('hello.txt', 'hello, world!', "text/plain")}
r = requests.post('http://127.0.0.1:5042/', files=data)
print(r.text)
|
実行すると以下のように表示されます。 サーバと同じディレクトリに hello.txt
ファイルがアップロードされているはずです。
| $ python uploader.py
{"success": "ok"}
|
結果 (curl でのテスト)
同じ内容を curl で試してみます。 以下の内容で data.txt
というファイルを作成しておきます。
| $ cat data.txt
hello, world!
|
これを curl で POST します。
| $ curl -F 'file=@data.txt; type=text/plain' http://127.0.0.1:5042/
{"success": "ok"}
|