はじめに
HTTPサーバーを作るPythonのresponderというパッケージが新しくKenneth Reitzさんによって作られたということをHacker Newsで読んだので、 使ってみようかと思い、ついでに他のサーバーパッケージも 入れたすごく簡単な動作比較をします。少ないですが、次のフレームワークを比較します。
環境構築
pythonとnodejsの環境構築です。python
TypeError: 'module' object is not callable エラーが出るとき:$ pipenv --python 3.6 $ pipenv install responder flask
Pipenvのバグで、pip18.1を使っているとエラーがでます。(10/17/2018現在) pip18.0にダウングレードすると直ります。
$ pipenv run pip install pip==18.0
node.js (v11.0.0)
$ npm init -y
$ npm i -S express
REST API(Hello World)
localhost:3000/hello へGET
すると Hello World!
が返る基本的なREST APIです。responder
# server_responder.py
import responder
api = responder.API()
@api.route("/hello")
async def hello(req, resp):
resp.text = "Hello World!"
if __name__ == "__main__":
api.run(port=3000)
サーバー開始
$ pipenv run python server_responder.py
INFO: Started server process [91078]
INFO: Uvicorn running on http://127.0.0.1:3000 (Press CTRL+C to quit)
curlでチェック
$ curl localhost:3000/hello
Hello World!$
返ってきました。サーバー側のログも出ています。
INFO: ('127.0.0.1', 53672) - "GET /hello HTTP/1.1" 200
flask
# server_flask.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
サーバー開始
$ FLASK_APP=server_flask.py pipenv run flask run --port 3000
* Serving Flask app "server_flask.py"
* Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
Flaskでは
if __name__ == "__main__": ...
でもできますが、コマンドで起動することを推奨しています。 ポートもコマンドで指定します。
curlでチェック
$ curl localhost:3000/hello
Hello World!$
サーバーログ
127.0.0.1 - - [07/Nov/2018 22:17:04] "GET /hello HTTP/1.1" 200 -
express
// server_express.js
const express = require("express");
const app = express();
app.get("/hello", (req, res) => res.send("Hello World!"))
app.listen(3000, () => console.log("server on"))
サーバー開始
$ node server_express.js
server on
Expressはログを自動で返しません。サーバーが起動したことを示すためにコールバック関数で
server on
とコンソールに出力しました。curlでチェック
$ curl localhost:3000/hello
Hello World!$
ログは一切出ません。
ログを出すには、別のモジュールである morgan をインストールして使います。
$ npm i -S morgan
// server_express.js const express = require("express"); const app = express(); // morganをミドルウェアとして使う const logger = require("morgan"); app.use(logger("dev")) app.get("/hello", (req, res) => res.send("Hello World!")) app.listen(3000, () => console.log("server on"))
起動してから、curlテストした際のログ
GET /hello 200 7.132 ms - 12
サーバーサイドレンダリングされたHTMLページ
HTMLを返すエンドポイントを作ります。サーバーサイドでそのままHTMLを書いても使えますが、変数を渡したりするにはテンプレートエンジンというものを使います。
pythonのパッケージのresponderとflaskではデフォルトでjinja2が使われていて、node.jsでは色々選べますがここではexpressで公式に紹介されているpugを使います。
それぞれ、
Hello, {名前}!
と名前の部分を変数としたHTMLを作成します。responder
実は上でサーバーを起動した際に、自動でstatic/
とtemplates/
の二つのファイルがルートに作成されています。ここに返すHTMLを作ります。 自動で作られた
templates/
内にpage.html
を作り、Jinja templateを使ってテンプレーティングします。<!-- templates/page.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>html page</title>
</head>
<body>
<h1>Hello, {{ name }}!</h1>
</body>
</html>
エンドポイント
# server_responder.py
import responder
api = responder.API()
@api.route("/hello/{name}")
async def hello(req, resp, *, name):
resp.content = api.template("page.html", name=name)
if __name__ == "__main__":
api.run(port=3000)
ブラウザで
localhost:3000/hello/kazuya
にアクセスすると、Hello, kazuya!
と表示され、タイトルはhtml page
となっています。curlでもテスト
$ curl localhost:3000/hello/kazuya
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>html page</title>
</head>
<body>
<h1>Hello, kazuya!</h1>
</body>
</html>
このコードを実行した時、エラーが出て、イシューをあげたら直してもらえました。 https://github.com/kennethreitz/responder/issues/76
flask
同じJinja2を使ってレンダリングしているので、HTMLは同じで大丈夫です。気をつけるのは、ここではresponderが自動でtemplatesフォルダを作ってるのでそれを使いますが、flaskだけでやる場合は自分でフォルダを作らないといけないことです。
# server_flask.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/hello/<name>")
def hello(name=None):
return render_template("page.html", name=name)
結果はresponderと一緒です。
express
pugをインストールします。npm i -S pug
pug は書き方が独特で、yamlみたいな書き方をします。
html
body
h1 Hello, #{ name }!
expressにテンプレートエンジンと、HTMLファイルのある場所の設定をします。
const express = require("express");
const path = require("path");
const app = express();
// viewsフォルダを見るように設定
app.set("views", path.join(__dirname, "views"));
// テンプレートエンジンをpugに設定
app.set("view engine", "pug")
app.get("/hello/:name", (req, res) => {
res.render("page", { name: req.params.name })
})
app.listen(3000, () => console.log("server on"))
結果はpythonのパッケージと同じになります。
考察
pythonはデコレータを使って、node.jsはコールバック関数を使ってHTTPリクエストに対する挙動をプログラムしています。responderとexpressは両方エンドポイントで用いる関数にrequest, response的なオブジェクトが渡されることになっていたり、そのうちresponse側のオブジェクトで返答を定義するところが似ています。flaskはそういうのが無いですね。
あとrequestsとexpressではasync/awaitがサポートされているけど、flaskには無い。でもサーバー側で非同期処理ってどのくらいメリットがあるのか自分は知らないです。
pythonのパッケージはログとかテンプレートエンジンとか諸々最初からサポートされているけど、expressは全部自分で入れていかないといけない。Unix的にはexpressの方が良いパッケージなんだろうが、ちょっとめんどくさい。今回までのセットアップでも若干pythonパッケージの方が便利でした。
その代わりパフォーマンスは、間違いなくexpressの方が良いはずです。