25
18

More than 5 years have passed since last update.

Python+ResponderでWEBアプリケーションを構築する。

Last updated at Posted at 2019-05-02

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風にしています。

image.png

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

25
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
18