みろりHP


緑色さんの多目的ブログ
みろりえいちぴー(旧)
引っ越し先 みろりHP: https://www.mrrhp.com
<< Python デコレータ奮闘記 | main | Python デコレータ奮闘記つづき >>
| カテゴリ:- |
スポンサーサイト

一定期間更新がないため広告を表示しています

| スポンサードリンク | - | - |
| カテゴリ:プログラミング |
Python python3でウェブサーバを作る(Bottleでリトライ)



一年前、pythonでウェブサーバを作ろうとしたことがあったんだけれど、道半ばで挫折した。確かcgiとかいう機能を使ったり、pyスクリプトとテンプレートを分けたり、中途半端に非同期処理を組み込んだりした挙句、日本語表示がうまくいかなくてイヤになってヤメちゃったんだよね。そのリトライをするぞ。あれから一年、趣味とはいえきちんとクラスを覚え、ちっちゃなものではあるがゲームを作り、Skype Botを作り、DialogFrameなんつー俺俺フレームワークまで作ったのだ。今ならばいける! かもしれない!
というわけで「1日1python」のお時間です。今回は前回のhttp.serverではなく、Bottleモジュールを使ってウェブサーバを作る。
先回のhttp.serverをもう一度触り直してみたのだけれど、「pythonを使ってページを表示するにはどうしてもURLが/cgi-bin/になっちまう。もっと自由にURLを決めたい」「htmlテンプレートの中でpython側の変数とか処理をかけない」なんつー不満点があったのよな。でもBottleがすべて解決してくれた。



目次。
0. とりあえずこれ書いとく
#!/usr/bin/env python
# coding: utf-8
from bottle import route, run, template, request
from bottle import error, redirect, HTTPResponse, static_file

# これは一番下に
run(host='localhost', port=8000, debug=True, reloader=True)
以降のスクリプトは全部この中に書く。なお localhost を 0.0.0.0 にすると、サーバが外からも見えるようになるようだぜ。俺はそうやって、スマホからチェックをしていた。

1. 一番シンプルなやつ
@route('/1/')
def page_01(name):
    return '<h1>YO!</h1>'
http://localhost:8000/1/ でアクセスできる。

2. URLにワイルドカードを使う
@route('/2/:name')
def page_02(name):
    return f'<h1>YO {name}!</h1>'
http://localhost:8000/2/Wada とかでアクセスできる。

3. テンプレートを使う
python側がこう。テンプレート側へ変数を渡すにはこういう書き方をする。
@route('/3/')
def page_03():
    a = 'LALALA'
    b = {'0':'AAA', '1':'BBB'}
    return template('template_03', a=a, b=b)
テンプレートはpythonスクリプトと同階層に
views
というディレクトリを作ってその中に置く。以下にpythonから受け取った変数の表示方法とかテンプレート内pythonスクリプトの書き方とかを記載する。
# template_03.tpl

■ pythonスクリプトから受け取った変数の表示
ふつーの変数: {{a}}
ディクショナリとか: {{b['0']}}

■ for文
<ul>
% for i in range(5)
    <li>{{i}}</li>
% end
</ul>

■ if文
% if b['1'] == 'BBB':
    <p>...</p>
% else:
    <p>...</p>
% end

■ いちいち % を行頭につけるのが面倒なアナタには
<%
    num = len(b)
    if num in [1]:
        pass
    elif num in [2]:
        pass
    end
%>

4. GETクエリを使う
@route('/4/')
def page_04():
    # get一覧を見たいならこれ
    print(request.query.__dict__)

    # クエリの取り出し方
    get_content = request.query.a

    return template('template_04', get_content=get_content)
テンプレート側は普通のgetフォームなので割愛。

5. POSTクエリを使う
@route('/5/')
@route('/5/', method='POST')
def page_05():
    # post一覧を見たいならこれ。
    print(request.params.__dict__)

    # 取り出し方。
    post_content = request.forms.get('name')

    return template('template_05', post_content=post_content)
テンプレート側は普通のpostフォームなので割愛。POSTのほうは、route()デコレータ表記をふたつ書かないといけないのに注意。

6. リダイレクトする
@route('/6/')
def page_06():
    return redirect('/5/')

7. ステータスコードでハンドリングする
えっとぶっちゃけ俺はステータスコードなるものは404くらいしかしらんので404の例を。存在しないURLにアクセスしたら、/5/にリダイレクトするようにしてみた。
@error(404)
def error_404(error):
    r = HTTPResponse(status=302)
    r.set_header('Location', '/5/')
    return r
errorを使った場合、6で使用したredirect()は使えないみたい。でも上述の書き方をしたらむりくりリダイレクトできた。HTTPヘッダにステータスコード302(リダイレクト)と飛び先を設定する方式。

8. 静的ファイルを使う
pyファイルと同階層に静的ファイルを置くディレクトリ(例では
static
)を作り、ソコを静的ファイル置き場として登録する。
@route('/static/:file_path')
def static(file_path):
    return static_file(file_path, root='./static')
HTMLから呼び出すときはこんな感じの相対パスで。
<img src="/static/image/python3.png">



こんなところで俺のリトライは無事成功した。自身の成長を感じられてふんすふんすしつつ改めて先回の奮闘を見ると、いやあpython全然関係ないところで詰まってんなあ。「ネットに情報がねえ」なんて理由で挫折しているが、まあ多分それは、当時もBottleとかDjangoとかそういう単語自体は目に入ってたんだろうけど、馴染みのないモジュールの蓋を開けてみることにまだ抵抗があったのだろう。ちなみにBottleの前にDjangoも試してみたが、あっちはなんかスゲー規模がでかいわディレクトリに色々ファイルが増えやがるわでタイヘンだったので、import一本で済むBottleを今回は採用した。
使用をヤメたhttp.serverについては、使うのがhtmlだけだったら即採用だと思うぜ。なにせターミナルから一行で立ち上がるからな。
$ python -m http.server ポート番号
学習の集大成として、小さなジャンケンゲームを作ったので下に載っけとく。スクリプトと、htmlを載せたところで、あっそうかhtmlを載せるってことはcssも載せないと締まらないのか…クソ長くなっちゃう…ヤだ…ってなったので折りたたんどく。




こんなゲーム。

スクリプトがこれ。janken.py
#!/usr/bin/env python
# coding: utf-8

from bottle import route, run, template, request
from bottle import error, HTTPResponse, static_file
import random


@error(404)
def error_404(error):
    r = HTTPResponse(status=302)
    r.set_header('Location', '/')
    return r


@route('/static/:file_path')
def static(file_path):
    return static_file(file_path, root='./static')


@route('/')
@route('/', method='POST')
def janken():
    req = request.params.__dict__['dict']
    counter = {
        'draw': int(request.forms.get('draw')) if ('draw' in req) else 0,
        'lost': int(request.forms.get('lost')) if ('lost' in req) else 0,
        'won': int(request.forms.get('won')) if ('won' in req) else 0,
    }
    hand = {
        'mine': -1,
        'his': int(request.forms.get('his')) if ('his' in req) else -1,
    }
    result = -1

    if (hand['his'] != -1
            and 0 <= int(hand['his']) <= 2):
        hand['his'] = int(hand['his'])
        hand['mine'] = random.randint(0, 2)
        result = (hand['his'] - hand['mine'] + 3) % 3
        if result in [0]:
            counter['draw'] += 1
        elif result in [1]:
            counter['lost'] += 1
        elif result in [2]:
            counter['won'] += 1

    return template('janken', counter=counter, hand=hand, result=result)


run(host='0.0.0.0', port=8000, debug=True, reloader=True)

テンプレートがこれ。views/janken.tpl
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <link rel="stylesheet" type="text/css" href="/static/janken.css" media="all">
        <title>BOTTLE SERVER</title>
    </head>

    <body>
        <div class="outer">
            <form action="/" method="post">

                <div class="inner_header" style="text-align:center;">
                    C'mon, J A N K E N...
                </div>

                <div class="inner_body">
                    <div class="shelf">
                        % if hand['mine'] != -1:
                        <button class="hand" style="background-color:orange;">
                            <%
                            hand_mine_name = ''
                            if hand['mine'] == 0:
                                hand_mine_name = '00_rock.png'
                            elif hand['mine'] == 1:
                                hand_mine_name = '01_scissors.png'
                            elif hand['mine'] == 2:
                                hand_mine_name = '02_paper.png'
                            end
                            %>
                            <img src="/static/{{hand_mine_name}}">
                        </button>
                        % end
                    </div>
                    <div class="shelf">
                        % if result != -1:
                            <%
                            result_class = ''
                            result_label = ''
                            if result == 0:
                                result_class = 'draw'
                                result_label = 'DRAW'
                            elif result == 1:
                                result_class = 'lost'
                                result_label = 'YOU LOST..'
                            elif result == 2:
                                result_class = 'won'
                                result_label = 'YOU WON!!'
                            end
                            %>
                            <span class="{{result_class}}">{{result_label}}</span>
                        % end
                    </div>
                    <div class="shelf">
                        <div class="box_row">
                            <div class="button box_row_left" style="background:none;width:40px;">
                                <span style="color:red;">●</span>{{counter['won']}}
                                <input type="hidden" name="won" value="{{counter['won']}}">
                            </div>
                            <div class="button box_row_left" style="background:none;width:40px;">
                                <span style="color:black;">●</span>{{counter['draw'] }}
                                <input type="hidden" name="draw" value="{{counter['draw']}}">
                            </div>
                            <div class="button box_row_left" style="background:none;width:40px;">
                                <span style="color:blue;">●</span>{{counter['lost'] }}
                                <input type="hidden" name="lost" value="{{counter['lost']}}">
                            </div>
                        </div>
                    </div>
                </div>

                <div class="inner_footer">
                    <div class="box_row">
                        <%
                        tmp = ['','','']
                        if hand['his'] != -1:
                            tmp[hand['his']] = 'background-color:orange;'
                        end
                        %>
                        <div class="button box_row_left">
                            <button class="hand" type="submit" name="his" value="0" style="{{tmp[0]}}">
                                <img src="/static/00_rock.png" alt="01_rock">
                            </button>
                        </div>
                        <div class="button box_row_left">
                            <button class="hand" type="submit" name="his" value="1" style="{{tmp[1]}}">
                                <img src="/static/01_scissors.png" alt="02_scissors">
                            </button>
                        </div>
                        <div class="button box_row_left">
                            <button class="hand" type="submit" name="his" value="2" style="{{tmp[2]}}">
                                <img src="/static/02_paper.png" alt="03_paper">
                            </button>
                        </div>
                    </div>
                </div>

            </form>
        </div>
    </body>
</html>

CSS。static/janken.css
@charset "UTF-8";

/********************
構造わかりやすさのための色つけ。消してOK。
********************/

/*body {
    border:1px solid black;
    background:lime;
}
div.outer {
    background:gray;
}
div.inner_header {
    background:red;
    border:1px solid black;
}
div.inner_body {
    background:green;
}
div.inner_footer {
    background:blue;
}*/

/********************
大まかな構造。
********************/

body {
    width:240px;
    height:320px;
}

div.outer {
    width:225px;
    height:305px;
}

div.inner_header {
    height:60px;
}

div.inner_body {
    height:185px;
}

div.inner_footer {
    height:60px;
}

/********************
細かな構造。
********************/

div.box_row {
    overflow:hidden;
}

div.box_row_left {
    float:left;
}

div.box_row_right {
    float:right;
}

/********************
個別の装飾。
********************/

div.button {
    background:#DBD9B5;
    margin:2px 10px;
    padding:0px 2px;
    text-align:center;
}

button.hand {
    width:50px;
    height:50px;
    padding:0;
    margin:0;
    border:0;
}

span.draw {
    text-shadow:black 1px 1px 0px, black -1px 1px 0px,
                black 1px -1px 0px, black -1px -1px 0px;
}

span.lost {
    text-shadow:blue 1px 1px 0px, blue -1px 1px 0px,
                blue 1px -1px 0px, blue -1px -1px 0px;
}

span.won {
    text-shadow:red 1px 1px 0px, red -1px 1px 0px,
                red 1px -1px 0px, red -1px -1px 0px;
    font-size:140%;
}

div.shelf {
    margin:2px 10px;
    padding:0px 2px;
    text-align:center;
    height:60px;
}
ブログの構造として折りたたみは好きじゃないんだけど、こういうときはあってよかったと思うぜ。


| 緑色 | プログラミング | comments(0) |
| カテゴリ:- |
スポンサーサイト
| スポンサードリンク | - | - |









   1234
567891011
12131415161718
19202122232425
262728293031 
<< May 2019 >>
+ みろりHP内検索
+ 閲覧記事
+ 過去記事アーカイブ
+ 年月選択
  • 2019年 02月 (1)
  • 2019年 01月 (2)
  • 2018年 12月 (6)
  • 2018年 11月 (6)
  • 2018年 10月 (3)
  • 2018年 09月 (8)
  • 2018年 08月 (4)
  • 2018年 07月 (6)
  • 2018年 06月 (5)
  • 2018年 05月 (4)
  • 2018年 04月 (7)
  • 2018年 03月 (6)
  • 2018年 02月 (6)
  • 2018年 01月 (8)
  • 2017年 12月 (9)
  • 2017年 11月 (9)
  • 2017年 10月 (4)
  • 2017年 09月 (6)
  • 2017年 08月 (6)
  • 2017年 07月 (8)
  • 2017年 06月 (4)
  • 2017年 05月 (7)
  • 2017年 04月 (8)
  • 2017年 03月 (7)
  • 2017年 02月 (10)
  • 2017年 01月 (6)
  • 2016年 12月 (8)
  • 2016年 11月 (8)
  • 2016年 10月 (5)
  • 2016年 09月 (5)
  • 2016年 08月 (7)
  • 2016年 07月 (9)
  • 2016年 06月 (6)
  • 2016年 05月 (8)
  • 2016年 04月 (10)
  • 2016年 03月 (10)
  • 2016年 02月 (8)
  • 2016年 01月 (10)
  • 2015年 12月 (9)
  • 2015年 11月 (6)
  • 2015年 10月 (5)
  • 2015年 09月 (4)
  • 2015年 08月 (8)
  • 2015年 07月 (5)
  • 2015年 06月 (3)
  • 2015年 05月 (7)
  • 2015年 04月 (8)
  • 2015年 03月 (12)
  • 2015年 02月 (8)
  • 2015年 01月 (4)
  • 2014年 12月 (5)
  • 2014年 11月 (5)
  • 2014年 10月 (7)
  • 2014年 09月 (4)
  • 2014年 08月 (7)
  • 2014年 07月 (6)
  • 2014年 06月 (4)
  • 2014年 05月 (12)
  • 2014年 04月 (9)
  • 2014年 03月 (6)
  • 2014年 02月 (6)
  • 2014年 01月 (8)
  • 2013年 12月 (7)
  • 2013年 11月 (10)
  • 2013年 10月 (10)
  • 2013年 09月 (9)
  • 2013年 08月 (11)
  • 2013年 07月 (10)
  • 2013年 06月 (9)
  • 2013年 05月 (15)
  • 2013年 04月 (11)
  • 2013年 03月 (5)
  • 2013年 02月 (7)
  • 2013年 01月 (6)
  • 2012年 12月 (9)
  • 2012年 11月 (10)
  • 2012年 10月 (10)
  • 2012年 09月 (4)
  • 2012年 08月 (2)
  • 2012年 07月 (7)
  • 2012年 06月 (13)
  • 2012年 05月 (13)
  • 2012年 04月 (15)
  • 2012年 03月 (4)
  • 2012年 02月 (12)
  • 2012年 01月 (9)
  • 2011年 12月 (5)
  • 2011年 11月 (13)
  • 2011年 10月 (2)
  • 2011年 09月 (2)
  • 2011年 08月 (1)
  • 2011年 06月 (1)
  • 2011年 05月 (4)
  • 2011年 04月 (10)
  • 2011年 03月 (8)
  • 2011年 02月 (11)
  • 2011年 01月 (14)
  • 2010年 12月 (14)
  • 2010年 11月 (17)
  • 2010年 10月 (17)
  • 2010年 09月 (19)
  • 2010年 08月 (22)
  • 2010年 07月 (18)
  • 2010年 06月 (16)
  • 2010年 05月 (19)
  • 2010年 04月 (15)
  • 2010年 03月 (22)
  • 2010年 02月 (18)
  • 2010年 01月 (18)
  • 2009年 06月 (2)
  • 2007年 12月 (10)
  • 2007年 11月 (7)
  • 2007年 10月 (9)
  • 2007年 09月 (4)
  • 2007年 07月 (5)
  • 2007年 06月 (11)
  • 2007年 05月 (6)
  • 2007年 04月 (4)
  • 2007年 03月 (1)
  • 2006年 01月 (21)
  • + カテゴリ
    + ブックマーク
    + 最近のコメント
    + アクセスカウンター
    全体(since 2010.02.03.)
    今日… 昨日…