PythonのWEBフレームワーク「Responder」を使ったWEBアプリケーションを作成してみます。
「Quick Start!」, 「Feature Tour」を参考に、私自身に最低限必要なものだけを取り上げています。
- 動作環境
カテゴリ | |
---|---|
os | windows10 home |
python | Python 3.7.3 |
responder | 1.3.1 |
- install
pipでインストールするだけです。
cmd.prompt
pip install responder
- 以下、すべて「run.py」でWEBサーバを起動します。
cmd.prompt
python run.py
1.hello world
run.py
import responder
api = responder.API()
@api.route("/")
def hello_world(req, resp):
resp.text = "hello, world!"
if __name__ == '__main__':
api.run()
2.Template Engine
- directory / file
root
│ run.py
│
└─templates
hello.html
templateは、「templates」フォルダの下に作成する。
- run.py
run.py
import responder
api = responder.API()
@api.route("/html/{who}")
def hello_html(req, resp, *, who):
resp.content = api.template('hello.html', who=who)
if __name__ == '__main__':
api.run()
- hello.html
hello.html
<h1>Hello {{ who }}</h1>
- 確認
http://127.0.0.1:5042/html/johnにアクセスする
3.Returning JSON
- Quick Startでは、以下のようになっている。
run.py
import responder
api = responder.API()
@api.route("/json/{who}")
def hello_to(req, resp, *, who):
resp.media = {"hello": who}
if __name__ == '__main__':
api.run()
- しかし、日本語だとunicodeで表示されてしまう。回避策として、json.dumpsを使う。
run.py
import responder
import json
api = responder.API()
@api.route("/")
def hello_to(req, resp):
# resp.media = {"hello": "斎藤さん"}
resp.headers = {"Content-Type": "application/json; charset=utf-8"}
resp.content = json.dumps({"hello": "斎藤さん"}, ensure_ascii=False)
if __name__ == '__main__':
api.run()
4.File Upload
ここでは、staticファイルを参照する方法も載せておきます。
- ファイル・フォルダ構成
fileupload
│ run.py ・・・(1)
└─templates
index.html ・・・(2)
layout.html ・・・(3)
success.html ・・・(4)
static
└─css
main.css ・・・(5)
(1) run.py
run.py
from pathlib import Path
import responder
import requests
def parentdir(path='.', layer=0):
return Path(path).resolve().parents[layer]
BASE_DIR = parentdir(__file__,1)
# ・・※1
api = responder.API(
static_dir=str(BASE_DIR.joinpath('static')),
)
@api.route("/")
async def greet_world(req, resp):
resp.content = api.template('index.html')
@api.route("/upload")
async def upload_file(req, resp):
@api.background.task
def process_data(data):
file=data['file']
f = open('./{}'.format(data['file']['filename']), 'wb')
f.write(file['content'])
f.close()
data = await req.media(format='files')
process_data(data)
resp.content = api.template('success.html')
if __name__ == '__main__':
api.run()
※1・・staticファイルを使用するときは、APIを定義するときに「static_dir」を指定します。
(2) index.html
index.html
{% extends "layout.html" %}
{% block content %}
<h1>File Upload With Responder</h1>
<form id="fileupload" action="/upload" method="post" enctype="multipart/form-data">
<input name="file" type="file"/>
<button type="submit">送信</button>
</form>
{% endblock %}
(3) layout.html
layout.html
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>{{ title }}</title>
<link rel="shortcut icon" href="">
<link href="/static/css/main.css" rel="stylesheet" type="text/css" media="all">
</head>
<body>
{% block content %}
<!-- ここにメインコンテンツを書く -->
{% endblock %}
</body>
</html>
(4) success.html
success.html
{% extends "layout.html" %}
{% block content %}
<h1>File Upload With Responder(Result)</h1>
<p>ファイルをアップロードしました。</p>
<a href="#" onclick="history.back(-1); return false;">戻る</a>
{% endblock %}
(5) main.css
main.css
body {
background: rgb(50, 54, 57);
color: white;
}
- darkmode風にしています。
5.ユーザ認証
- ファイル・フォルダ構成
userauth
│ run.py ・・・・(1)
│
└─templates
layout.html ・・・・(2)
login.html ・・・・(3)
logout.html ・・・・(4)
top.html ・・・・(5)
static
└─css
main.css
(1)run.py
run.py
# -*- coding: utf-8 -*-
from pathlib import Path
import responder
def parentdir(path='.', layer=0):
return Path(path).resolve().parents[layer]
BASE_DIR = parentdir(__file__,1)
api = responder.API(
static_dir=str(BASE_DIR.joinpath('static')),
)
users = {
"admin": "password",
"guest": "password"
}
@api.route(before_request=True)
def prepare_response(req, resp):
# 静的ファイルはOK
if req.url.path.startswith('/static/'):
return
# パスがログインページならOK
if req.url.path == '/login':
return
# セッションにusernameが保存されている場合、OK
if req.session.get('username') is not None:
return
# 上記以外はすべて認証が必要
api.redirect(resp, '/login')
@api.route("/")
async def hello(req, resp):
api.redirect(resp, "/login")
def isauthenticated(username, password):
if not username in users:
return False
if users.get(username) != password:
return False
return True
@api.route("/login")
class login:
async def on_get(self, req, resp): # or on_get..
# getの場合
resp.content = api.template('login.html')
async def on_post(self, req, resp): # or on_get..
data = await req.media()
# postの場合
errmessages=[]
username=data.get('username')
password=data.get('password')
if not isauthenticated(username, password):
errmessages.append('ユーザ名またはパスワードに誤りがあります')
loginredirect="/top"
resp.content = api.template('login.html', errmessages=errmessages)
return
resp.session['username']=username
api.redirect(resp, "/top")
@api.route("/top")
async def top(req, resp):
authenticateduser = req.session.get('username')
resp.content = api.template('top.html', authenticateduser=authenticateduser)
@api.route("/logout")
async def logout(req, resp):
req.session.pop('username')
resp.content = api.template('logout.html')
if __name__ == '__main__':
api.run()
(2)layout.html
layout.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title></title>
<meta name="description" content="">
<meta name="author" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/static/css/main.css" rel="stylesheet" type="text/css" media="all">
<link rel="shortcut icon" href="">
</head>
<body>
<main>
<div id="content">
{% for errmessage in errmessages %}
<p class="error">{{ errmessage }}</p>
{% endfor %}
{% block content %}
{% endblock %}
</div>
<nav>
{% if authenticateduser %}
<a href="/logout" style="color:white;">ログアウト</a>
{% endif %}
</nav>
</main>
</body>
</html>
(3)login.html
login.html
{% extends "layout.html" %}
{% block content %}
<form id="loginForm" action="/login" method="post">
<h1 style="color: white;">ログイン認証</h1>
<label>username:</label>
<input type="text" name="username" placeholder="username" value="" required>
<br/>
<label>password:</label>
<input type="password" name="password" placeholder="password" value="" required>
<br/>
<br/>
<input type="submit" value="ログイン">
</form>
{% endblock %}
(4)logout.html
logout.html
{% extends "layout.html" %}
{% block content %}
<h1>ログアウト</h1>
<p>ご利用ありがとうございました。</p>
<a href="/login" style="color:white;">ログイン</a>
{% endblock %}
(5)top.html
top.html
{% extends "layout.html" %}
{% block content %}
<h1>Hello {{ authenticateduser }} さん</h1>
{% endblock %}
6.データベース接続
python+responderでRest APIを作るにて。
7.その他
まだまだ他にもあります。本家サイトを参考してください。
- 2019.05.03 追記
8.download file
サーバ上のファイル(CSV, PDF)をダウンロードします。
- ファイル・フォルダ構成
filedownload
run.py ・・・・(1)
sample.pdf
所属.csv
(1).run.py
run.py
import responder
import urllib.parse
from pathlib import Path
api = responder.API()
def parentdir(path='.', layer=0):
return Path(path).resolve().parents[layer]
BASE_DIR = parentdir(__file__,0)
CONTENTTYPES = {
'plain':'text/plain',
'csv':'text/csv',
'html':'text/html',
'js':'text/javascript',
'json':'application/json',
'pdf':'application/pdf',
'xlsx':'application/vnd.ms-excel',
'ppt':'application/vnd.ms-powerpoint',
'docx':'application/msword',
'jpeg':'image/jpeg',
'png':'image/png',
'gif':'image/gif',
'bmp':'image/bmp',
'zip':'application/zip',
'tar':'application/x-tar'
}
def readf(filename):
path=BASE_DIR.joinpath(filename)
with open(path,'rb') as f:
dt = f.read()
return dt
@api.route("/csv/{filename}")
def hellocsv(req, resp, *, filename):
downloadfilename=urllib.parse.unquote(filename, 'utf-8')
resp.headers['Content-Type'] = CONTENTTYPES.get('csv')
resp.headers['Content-Disposition'] = 'attachment; filename='+ filename
resp.content=readf(downloadfilename)
@api.route("/pdf/{filename}")
def hellopdf(req, resp, *, filename):
downloadfilename=urllib.parse.unquote(filename, 'utf-8')
resp.headers['Content-Type'] = CONTENTTYPES.get('pdf')
resp.headers['Content-Disposition'] = 'attachment; filename='+ filename
resp.content=readf(downloadfilename)
if __name__ == '__main__':
api.run()
(2).確認
http://127.0.0.1:5042/csv/確認.csv
http://127.0.0.1:5042/pdf/sample.pdf