Pages - Menu

Pages - Menu

Pages

2019年9月5日木曜日

WEBプログラマーの石塚 正浩です。転職活動中です。第一希望は在宅リモートワーク(週一なら出社可能)。第二希望は、立川市、八王子市、三鷹市当たりまで通勤も可能です。

案件に一人称と書いてある場合でも、何か分からない事が出て来たらその都度打ち合わせしたりチャットで打ち合わせしたいと思いますが、可能ですか?

Python&Djangoは学習経験のみですが、今後は第1希望です。Japrotoフレームワークは一秒間に百万アクセス可能な様です。研究の対象です。

PHPはフレームワーク無しで実務一年位です。第2希望です。

HTML5、CSS3、JavaScript、TypeScript、BootStrap、DATABASEなどの実務経験も御座います。フロントエンドのお仕事は、第3希望です。

Java11年経験がありますが、ブランクが長いです。それでも良いようでしたら参加したいです。第4希望です。

ーーー

python+japrontoを試してみる

シャアしました。

1. japronto

japrontoはいろんなサイトで速いという記事を見かけます。
どうせ作るなら、処理が速いほうがいい。今のスキルで実業務で使えるレベルまで
実装できるかどうか、とりあえずやってみます。

2. install

windowsにinstallするとエラーになります。WSLなどにインストールして使います。
pip install japronto
ちなみに私の環境は以下の通りです。
カテゴリ
osubuntu 18.04(WSL)
pythonPython 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 + responderpython + japrontoelixir + phoenixphp + phalconrust+actix-web
Server SoftwareuvicornCowboyApache
Document Length11 bytes12 bytes11 bytes11 bytes12 bytes
Concurrency Level100100100100100
Time taken for tests2.447 seconds0.344 seconds2.421 seconds5.469 seconds1.298 seconds
Complete requests10001000100010001000
Failed requests00000
Total transferred147000 bytes92000 bytes449000 bytes189000 bytes129000 bytes
HTML transferred11000 bytes12000 bytes11000 bytes11000 bytes12000 bytes
Requests per second408.65 #/sec2909.80 #/sec413.10 #/sec182.86 #/sec770.26 #/sec
Time per request244.705 ms34.367 ms242.070 ms546.871 ms129.826 ms
Time per request2.447 ms0.344 ms2.421 ms5.469 ms1.298 ms
Transfer rate58.66 [Kbytes/sec] received261.43 [Kbytes/sec] received181.14 [Kbytes/sec] received33.75 [Kbytes/sec] received97.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)

index.html
<!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.
このあたりは要件毎に選択するフレームワークを選択してシステムを開発するのがいいでしょう。

0 件のコメント:

コメントを投稿