#1. japronto
japrontoはいろんなサイトで速いという記事を見かけます。
どうせ作るなら、処理が速いほうがいい。今のスキルで実業務で使えるレベルまで
実装できるかどうか、とりあえずやってみます。
#2. install
windowsにinstallするとエラーになります。WSLなどにインストールして使います。
pip install japronto
ちなみに私の環境は以下の通りです。
カテゴリ | 値 |
---|---|
os | ubuntu 18.04(WSL) |
python | Python 3.7.2 |
#3. Hello World
from japronto import Application
def hello(request):
return request.Response(text='Hello world!')
app = Application()
app.router.add_route('/', hello)
app.run(host='127.0.0.1',port=7777)
#4. 負荷試験
※ 書き直しました。
速いというので私の知っているそれぞれのHello Worldを使って、実際に計測してみました。
1 source
1 python+Responder
import responder
api = responder.API()
@api.route("/")
async def hello_world(req, resp):
resp.text = "hello world"
if __name__ == '__main__':
api.run()
2 python + japronto
from japronto import Application
def hello(request):
return request.Response(text='Hello world!')
app = Application()
app.router.add_route('/', hello)
app.run(host='127.0.0.1',port=7777)
3 elixir + phoenix
defmodule HelloworldWeb.PageController do
use HelloworldWeb, :controller
def index(conn, _params) do
# render(conn, "index.html")
text conn, "Hello World"
end
end
4 php + phalcon
<?php
$response = new \Phalcon\Http\Response();
$response->setStatusCode(200, "OK");
$response->setContent("Hello world");
$response->send();
5 rust+actix_web
extern crate actix_web;
use actix_web::{server, App, HttpRequest};
fn index(_req: &HttpRequest) -> &'static str {
"Hello world!"
}
fn main() {
server::new(|| App::new().resource("/", |r| r.f(index)))
.bind("127.0.0.1:8088")
.unwrap()
.run();
}
2 command
ab -c 100 -n 1000 http://localhost:XXXX
3. result
||python + responder|python + japronto|elixir + phoenix|php + phalcon|rust+actix-web
|:---|:---|:---|:---|:---|:---|:---|:---
|Server Software| uvicorn|| Cowboy|Apache|
|Document Length| 11 bytes| 12 bytes| 11 bytes| 11 bytes| 12 bytes
|Concurrency Level|100|100|100|100|100
|Time taken for tests| 2.447 seconds| 0.344 seconds| 2.421 seconds| 5.469 seconds| 1.298 seconds
|Complete requests|1000|1000|1000|1000|1000
|Failed requests|0|0|0|0|0
|Total transferred| 147000 bytes| 92000 bytes| 449000 bytes| 189000 bytes| 129000 bytes
|HTML transferred| 11000 bytes| 12000 bytes| 11000 bytes| 11000 bytes| 12000 bytes
|Requests per second| 408.65 [#/sec] (mean)| 2909.80 [#/sec] (mean)| 413.10 [#/sec] (mean)| 182.86 [#/sec] (mean)| 770.26 [#/sec] (mean)
|Time per request| 244.705 [ms] (mean)| 34.367 [ms] (mean)| 242.070 [ms] (mean)| 546.871 [ms] (mean)| 129.826 [ms] (mean)
|Time per request| 2.447 [ms] (mean, across all concurrent requests)| 0.344 [ms] (mean, across all concurrent requests)| 2.421 [ms] (mean, across all concurrent requests)| 5.469 [ms] (mean, across all concurrent requests)| 1.298 [ms] (mean, across all concurrent requests)
|Transfer rate| 58.66 [Kbytes/sec] received| 261.43 [Kbytes/sec] received| 181.14 [Kbytes/sec] received| 33.75 [Kbytes/sec] received| 97.03 [Kbytes/sec] received
#5. 基本
exampleにあります。こちらを参照ください。
#6. 実装したアプリ
他のフレームワークのような機能が備わっていません。ここでは、作成したアプリを掲載しておきます。
##(1). テンプレートエンジン
Jinja2を使う
│ main.py
│
├─static
│ ├─css
│ │ a.css
│ │ w2ui.min.css
│ │
│ └─js
│ jquery-3.2.1.min.js
│ w2ui.min.js
│
└─templates
index.html
from japronto import Application
from jinja2 import Template
def get_jinja2_template(filename):
with open(filename) as html_file:
return Template(html_file.read())
def jinja2(request):
template = get_jinja2_template('index.html')
return request.Response(text=template.render(name='Jinja2'),
mime_type='text/html')
app = Application()
app.router.add_route('/', jinja2)
app.run(host='127.0.0.1', port=7777)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>japronto</title>
<meta name="description" content="japronto">
</head>
<body>
<h1>Hello World!</h1>
<p>Behold, the power of japronto!</p>
</body>
</html>
##(2). static file
staticファイルも実装しないといけないのかな・・・。
from japronto import Application
async def static(request):
with open(request.path[1:], 'rb') as static_file:
return request.Response(body=static_file.read())
def index(request):
return request.Response(
text=
'<link rel="stylesheet" href="/static/css/main.css">' \
'<script type="text/javascript" src="/static/js/jquery-3.2.1.min.js"></script>' \
'<h1>Hello World</h1>',
mime_type='text/html')
app = Application()
app.router.add_route('/', index)
# staticの下の階層が2
app.router.add_route('/static/{1}/{2}', static)
app.run(host='127.0.0.1', port=7777)
(3). database
トランザクション毎にデータベースに接続していたら、japrontoの高速レスポンスを台無しにします。
データベースへの接続は、コネクションプールを使うことで対応します。
install and add package
sudo apt-get install -y python3-dev
sudo apt-get install -y libpq-dev
pip install psycopg2
source
import psycopg2
import psycopg2.extras
import psycopg2.pool
from japronto import Application
def resultset_as_dict(sql):
conn = connection_pool.getconn()
cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
cur.execute (sql)
results = cur.fetchall()
dict_result = []
for row in results:
dict_result.append(dict(row))
cur.close()
connection_pool.putconn(conn)
return dict_result
def hello(request):
dict_result = resultset_as_dict('select * from User')
print(dict_result)
return request.Response(text='Hello world!')
connection_pool = psycopg2.pool.SimpleConnectionPool(
minconn=10, maxconn=50,
host="localhost", port="5432",
dbname="postgres", user="testuser", password="testuser")
app = Application()
app.router.add_route('/', hello)
app.run(host='127.0.0.1',port=7777)
bench mark
100人同時に、それぞれ1000回アクセスしても速い。
h2load -p http/1.1 -c 100 -n 1000 http://localhost:7777
starting benchmark...
finished in 1.49s, 670.75 req/s, 60.26KB/s
requests: 1000 total, 1000 started, 1000 done, 1000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 1000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 89.84KB (92000) total, 51.76KB (53000) headers (space savings 0.00%), 11.72KB (12000) data
min max mean sd +/- sd
time for request: 6.77ms 218.46ms 133.15ms 53.95ms 55.80%
time for connect: 2.56ms 20.35ms 11.32ms 5.02ms 59.00%
time to 1st byte: 14.83ms 238.81ms 156.18ms 57.93ms 57.00%
req/s : 6.73 10.02 7.53 0.82 82.00%
(4). file download
ファイルダウンロードは、Responseのbodyパラメータにファイルオブジェクトをセットします。
あとは、ファイルの種類に合わせてmimetypeを設定しておけばOK
from japronto import Application
import urllib
import os
from pathlib import Path
download_path=os.path.join(Path(__file__).resolve().parents[0], 'download')
def get_file(filename):
fpath = os.path.join(download_path, filename)
with open(fpath, 'rb') as f:
return f.read()
return
def excel(request):
excel_file=get_file('report.xlsx')
return request.Response(body=excel_file,
mime_type='application/vnd.ms-excel'
)
def pdf(request):
pdf_file=get_file('sample.pdf')
return request.Response(body=pdf_file,
mime_type='application/force-download'
)
app = Application()
app.router.add_route('/excel/report.xlsx', excel)
app.router.add_route('/pdf/sample.pdf', pdf)
app.run(host='127.0.0.1',port=7777)
(5). file upload
ファイルアップロードは、requestのfilesにデータが入ります。
なお、japrontoは、formのenctypeを正しくセットしてやらないと
files属性にうまくセットされないことに注意しましょう。
from japronto import Application
def get(request):
return request.Response(
text='<form method="post" enctype="multipart/form-data">' \
'<input type="file" name="upload_file" /><br/>' \
'<input type="submit" value="送信" /><br/>' \
'</form>',
mime_type='text/html')
def post(request):
files=request.files
if 'upload_file' in files:
upload_data = files['upload_file']
upload_file = upload_data.body
upload_name = upload_data.name
outfile = open('upload/%s' % upload_name, 'wb')
outfile.write(upload_file)
return request.Response(text='OK')
app = Application()
app.router.add_route('/', get, method="GET")
app.router.add_route('/', post, method="POST")
app.run(host='127.0.0.1',port=7777)
(6). ユーザ認証
ユーザ認証です。本来は、以下の実装が別途必要ですが、省略しています。
-
本来なら、ユーザ名はUserテーブルの存在をチェックするような実装となりますが、
ここでは、ログインユーザがadminならOKとしています。 -
ログイン認証が完了したら、SessionIDを辞書にセットしますが、ログアウトしたら
削除するとか、セッションタイムアウトで、辞書内のセッションIDを削除するなどの処理
が別途必要です。
from japronto import Application
from http.cookies import SimpleCookie
import hashlib
import datetime
import time
sessions = {}
# todo session timeout
timeout_minute=30
def is_user(username, password):
if (username=='admin'):
return True
return False
def create_sessionid(userid):
# sessionを生成
session_id = hashlib.sha256(userid.encode()).hexdigest()
# sessionは、辞書内に管理する。
sessions[session_id] = userid
return session_id
def redirect(request, url, cookies=None):
return request.Response(headers={'Location': url}, code=302, cookies=cookies)
def loginhtml(message=None):
return '' \
'<h1>Login</h1>' \
'<div>%s</div>' \
'<form method="post" enctype="application/x-www-form-urlencoded">' \
'<label>username:</label><br/>' \
'<input type="text" name="username" required/><br/>' \
'<br/>' \
'<label>password:</label><br/>' \
'<input type="password" name="password" required/><br/>' \
'<br/>' \
'<input type="submit" value="送信"/>' \
'</form>' \
'<br/>' % message
def tophtml(username, message=None):
return '' \
'<h1>Dash Board</h1>' \
'<div>%s</div>' \
'welcome to %s' \
'<br/>' % (message, username)
# Views handle logic, take request as a parameter and
# returns Response object back to the client
def login(request):
html = loginhtml('ログインしてください。')
return request.Response(text=html, mime_type='text/html')
def login_post(request):
form = request.form
username=form['username']
password=form['password']
if is_user(username, password):
session_id = create_sessionid(username)
cookies = SimpleCookie()
cookies['SessionID'] = session_id
return redirect(request, '/', cookies)
else:
html = loginhtml('ユーザIDまたはパスワードが違います')
return request.Response(text=html, mime_type='text/html')
def index(request):
if 'Cookie' in request.headers:
cookies = SimpleCookie()
cookies.load(request.headers['Cookie'])
if 'SessionID' in cookies:
session_id = cookies['SessionID'].value
if session_id in sessions:
userid = sessions[session_id]
html = tophtml(userid, 'こんにちは')
return request.Response(text=html, mime_type='text/html', cookies=cookies)
# 未ログインの状態
return redirect(request, '/login')
app = Application()
app.router.add_route('/', index, method='GET')
app.router.add_route('/login', login, method='GET')
app.router.add_route('/login', login_post, method='POST')
app.run(host='127.0.0.1', port=7777)
#7. まとめ
Japrotoは速くて、Pythonなのに負荷の高い環境でもさくさく動くと考えます。
一方で、DjangoやFlaskのような、フルスタックフレームワークではないので、いろいろ実装が必要です。
残念ですが、japrontoはまだアルファ版のようです。
This is an early preview with alpha quality implementation.
APIs are provisional meaning that they will change between versions and more testing is needed.
Don't use it for anything serious for now and definitely don't use it in production. Please try it though and report back feedback. If you are shopping for your next project's framework I would recommend Sanic.
このあたりは要件毎に選択するフレームワークを選択してシステムを開発するのがいいでしょう。