読者です 読者をやめる 読者になる 読者になる

MyEnigma

とあるエンジニアのブログです。#Robotics #Programing #C++ #Python #MATLAB #Vim #Mathematics #Book #Movie #Traveling #Mac #iPhone

Pythonの軽量WebサーバBottleの使い方

Web Programming Python

Pythonエンジニア養成読本[いまどきの開発ノウハウ満載!] (Software Design plus)

Pythonエンジニア養成読本[いまどきの開発ノウハウ満載!] (Software Design plus)

目次

 

はじめに

最近、趣味でWebアプリを作っているのですが、

bootleというPythonのWebサーバを使っています。

github.com

f:id:meison_amsl:20151128075520p:plain

myenigma.hatenablog.com

 

Pythonは昔から少し使ったことがあったので、

Pythonでサーバサイドのコードを書きたいなと思っていました。

 

PythonのWebサーバライブラリではDjangoやFlaskが有名ですが、

Web初心者には少し高機能すぎるようなので、

自分は軽量サーバライブラリであるBottleを選びました。

 

Bottleはライブラリと言いながらも、

一つのpythonファイルのみで構成されており、

あまり凝ったことをしない場合には非常にシンプルで使いやすいらしいです。

 

今回はこのBotttleに関する簡単な説明を書きたいと思います。

詳細は冒頭の『Python エンジニア養成読本』の第5章で説明されているため、

詳しく知りたい方は読んでみることをおすすめします。

 

ルーティング

bottleには、接続されたURLとpython関数をリンクさせる

ルーティング機能があります。

例えば、下記のようなコードを使うと

@route('/hoge')
def hoge():
      return template('index')

/hogeというURLに接続すると、

hoge関数が呼ばれて、

viewフォルダの中にあるindex.htmlが

ブラウザに返されます。

 

HTTP用ツール

BottleにはHTTPの用の様々なメソッドが準備されています。

HTTPそのものの説明に関してはこちらを参照下さい

myenigma.hatenablog.com

各HTTPメソッドに応じて関数を振り分けたい場合は、

下記のようにデコレータを設定します。

GETメソッド

@route('/hoge')

or

@get('/hoga')

POSTメソッド

@route('/hoge', method='POST')

or

@post('/hoga')

PUTメソッド

@route('/hoge', method='PUT')

or

@put('/hoga')

DELETEメソッド

@route('/hoge', method='DELETE')

or

@delete('/hoga')

 

Request

Requestオブジェクトを使うと、

下記のようなクライアントから送信されるデータを利用することができます。

まず、

from bottle import request

した状態で、

  • HTTPヘッダ request.headers

  • クッキー request.cookies.get('key')

  • クエリパラメータ request.query

クエリパラメータは、HTTPのGETメソッドで取得できる

URLの後ろに付いてくるデータです。

「URLクエリパラメータ」とは何か、 どのような場合に「除外」するべきなのか?[第4回] | Googleアナリティクスとは/衣袋教授のGoogleアナリティクス入門講座 | Web担当者Forum

  • POSTパラメータ request.forms

  • 添付ファイル request.files.get('name')

などからデータを取得することができます。

 

Response

逆に、サーバからクライアントにデータを送信する時は、

Respinseオブジェクトを使います。

まず、

from bottle import response

した状態で、

  • HTTPステータスコードの設定 response.status=404

  • HTTPヘッダの設定 response.set_header()

  • クッキーの設定 response.set_cookie("hoge","hoga")

とすることで、クライアント側にデータを送ることができます。

 

テンプレートエンジン

bottleには、テンプレートエンジンという機能があり、

htmlのひな形を読み込んで、

その中を引数で置き換えたページを表示することができます。

サーバアプリの中で、

return template('form', name='hogehoge')

とすると、./views/form.tplというテンプレートhtmlファイルを読み込んで

その中の{{name}}という部分をhogehogeと置き換えたhtmlが送信されます。

 

bottleのテンプレートエンジンで

個人的にすごいなと思ったことは、

HTMLファイル内でpythonコードを埋め込むことができることです。

例えば、下記のように行頭に%を置くと、

pythonコードを記述することができます。

% from time import gmtime, strftime
% d=strftime("%Y", gmtime())
% wareki=int(d)-1988
  <h1>今年は平成{{wareki}}年です。(西暦{{d}}年)</h1>

% import datetime
% today = datetime.date.today()
% newyearsday = datetime.date(int(d), 1, 1)
% dday=today-newyearsday
% remainratio=round((365.0-float(dday.days))/365.0*100.0,2)
% remaindays=str(remainratio)
  <h1>今日で今年の約{{remainratio}}%が過ぎました。</h1>

こちらをtemplate関数で返すようにすると、

下記のようにpythonの計算結果が、ブラウザに表示されます。

f:id:meison_amsl:20150624224312p:plain

 

データ表示以外にも、

python条件分岐による

HTMLの表示の制御などもできます。

Web初心者には非常に嬉しいですね。

 

ファイルの送受信

Bottleを使ったファイルの送受信方法について説明します。

ファイルの受信

クライアント側からファイルを受信する場合は、

まずHTMLでPOSTメソッドのファイルアップロードの

インターフェースを作ります。

<form action="/upload" method="post" enctype="multipart/form-data">
<div class="form-group">
<label class="control-label" for="upload">Select a CSV file:
<input type="file" name="upload">
</div>
<div class="form-group">
<input type="submit" value="Upload" class="btn btn-primary">
</div>
</form>

続いて、Webサーバ側では、

下記のようにrequestオブジェクトの

files.getメソッドを使って

ファイルオブジェクトを取得できます。

@route('/upload', method='POST')
def do_upload():
    upload   = request.files.get('upload')
    name, ext = os.path.splitext(upload.filename)

    #file check
    if ext not in ('.csv'):
        return template("index",msg="The file extension is not allowed.")

    upload.save("/tmp",overwrite=True)

ファイルオブジェクトはfilenameという変数でファイル名を取得でき、

saveメソッドで指定してディレクトリにファイルを保存できます。

overwrite引数をtrueにするとファイルがある場合でも上書きされます。

 

ファイルの送信

ファイルの送信は

static_fileメソッドの返り値をreturnすることで、

クライアント側にファイル送信することができます。

return static_file("sample.txt", root='/tmp')

上記のサンプルプログラムの場合、

/tmpディレクトリのsample.txtというファイルを

クライアント側に送信しています。

 

Cookieの設定

Webサービスで、クライアントを認識したい時などに使われる

Cookieの設定は下記のAPIを使うことで設定するができます。

Cookieの設定

クライアントにCookieを設定する場合は、

responseオブジェクトのset_cookie関数を使います。

hogeというキーのクッキーを設定する場合は、

下記のようにします。

requst.set_cookie("hoge", 10) #hogeというkeyのデータに10を代入

 

クッキーのデータを暗号化したい場合は、secret引数にシークレットキーを入れます。

requst.set_cookie("hoge", 10, secret="secretkey")

 

また、クッキーの有効期限やドメイン指定などは、

パラメータを設定すればOKです。

詳しくはAPIドキュメントを参照下さい。

 

Cookieの取得

リクエストを受け取った

クライアントからのCookie情報を取得する場合は、

requestオブジェクトのget_cookie関数を使います。

hogeというキーのクッキーのデータを取得する場合は、

下記のようにします。

request.get_cookie("hoge")

 

クッキーが設定されていない場合に、

デフォルト値を返してもらいたい時は、

default引数に設定すると、そのように動いてくれます。

request.get_cookie("hoge",default=0) #hogeが設定されていない時は0が返ってくる

default引数を設定しなかった場合はNoneが返ります。

 

また、前述のクッキーの設定の際にsecretを設定した場合、

secret引数に同じシークレットキーを設定しなkれば

 

Cookieの削除

Cookieの削除は、responseオブジェクトから、

delete_cookie関数を使います。

クライアントのhogeというクッキーを削除したい場合は、

下記のようにします。

response.delete_cookie("hoge");

 

HTTPステータスコードのハンドリング

下記の記事で説明されている通り、

HTTPにはステータスコードというものがあります。

myenigma.hatenablog.com

 

bottleでは上記のステータスコードが発生した時に、

それを拾ってエラーハンドリングをすることも簡単です。

 

例えば、500番 Internal Server Errorの

ステータスコードが来た時に、

トップURLにリダイレクトしたい時は、

下記のようにerrorというデコレータで、

ステータスコードを指定し、

その後に実行したい関数を書くだけです。

@error(500)
def error_500(error):
    """Internal Server Errorの場合"""
 
    # トップにリダイレクト
    redirect("/")

 

HerokuでBottleを使う時にIPアドレスとポートを設定する

bottleではrun関数でサーバを起動しますが、

run関数ではIPアドレスとポートを指定する必要があります。

 

ローカル環境と、Herokuのデプロイ環境では、

IPとポート、そしてデバックモードなどは変わるので、

自分は下記のように、ローカル環境とHerokuのデプロイ環境を認識した上で、

run関数を実行するようにします。

まず、下記のherokuのコマンドで環境変数を設定します。

heroku config:set HEROKU=true

そして、下記のコードで先ほどの環境変数の有無で

run関数の引数を変えるようにします。

if __name__ == '__main__':
    if os.getenv("HEROKU")==None:       
        run(host="localhost", port=(os.environ.get("PORT",5200)), debug=True, reloader=True)
    else:
        run(host="0.0.0.0", port=(os.environ.get("PORT",5200)))

このようにすることで、

ローカル開発環境とHerokuデプロイ環境を

同じソースコードで実行できます。

 

webサーバを起動した時に、自動でwebブラウザを開く

デバック時は、webサーバが立ち上がった時に

自動でブラウザでアクセスしてくれると便利です。

そんな時は、pythonのデフォルトモジュールである

webbrowserを使うと便利です。

下記のようにすると、

webサーバを立ち上げた時に、自動でブラウザを開いてくれます。

import webbrowser as web
web.open('http://localhost:5200')
run(host="localhost", port=5200, debug=True, reloader=True)

 

他のWebフレームワークとのパフォーマンス比較

下記の記事でBottleのWebサーバとしての

パフォーマンスが評価されています。

 

上記の比較結果によると、

テンプレートエンジンなどの最低限の

Webフレームワークとしての機能を持っていながらも

他のフレームワークと比べて非常に高速であることがわかります。

 

bottleでwebsocketを使って非同期通信をする方法

上記の方法を使って、

イベント的な通信は出来ますが、

HTTPは基本的にリクエスト、レスポンス型の通信システムなので

定期的な通信をするのは難しいです。

 

一般的なWeb技術で非同期通信をする場合には、

HTML5から導入された、

websocketという技術を使うことが多いようですが、

bottleのデフォルトに機能にはこのwebsocketは含まれていません。

-WebSocket - Wikipedia

 

そんな時は、下記のリンクの

bottole-webscketを使うと簡単に

bottleのWebサーバで、websocketを使うことができます。

 

まずサーバ側は下記のようにします。

from bottle import get, run, template
from bottle.ext.websocket import GeventWebSocketServer
from bottle.ext.websocket import websocket
import time

@get('/')
def index():
    return template('index')

seq=1

@get('/websocket', apply=[websocket])
def echo(ws):
    global seq
    while True:
        ws.send("Get"+str(seq))
        seq+=1
        time.sleep(1)

run(host='192.168.3.5', port=8001, server=GeventWebSocketServer)

 

そして、テンプレートは下記のようにします。

<!doctype html>
<head>
    <meta charset="utf-8" />
    <title>WebSocket Echo Test</title>

    <style>
        li { list-style: none; }
    </style>

    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
    <script>
        $(document).ready(function() {
            if (!window.WebSocket) {
                if (window.MozWebSocket) {
                    window.WebSocket = window.MozWebSocket;
                } else {
                    $('#messages').append("<li>Your browser doesn't support WebSockets.</li>");
                }
            }
            ws = new WebSocket('ws://192.168.3.5:8001/websocket');
            ws.onopen = function(evt) {
                $('#messages').append('<li>WebSocket connection opened.</li>');
            }
            ws.onmessage = function(evt) {
                $('#messages').append('<li>' + evt.data + '</li>');
            }
            ws.onclose = function(evt) {
                $('#messages').append('<li>WebSocket connection closed.</li>');
            }
            $('#send').submit(function() {
                ws.send($('input:first').val());
                $('input:first').val('').focus();
                return false;
            });
        });
    </script>
</head>
<body>
    <h2>Bottle Websockets!</h2>
    <form id="send" action='.'>
        <input type="text" value="message" />
        <input type="submit" value="Send" />
    </form>
    <div id="messages"></div>
</body>
</html>

 

上記のサーバを立ち上げた状態で、

PCのWebサーバに接続すると、

下記のように一秒毎に一つづつ増える

メッセージが表示されます。

f:id:meison_amsl:20160213195948p:plain

ちなみに、

iPhoneでアクセスしても問題なく表示されました。

 

Webサーバの情報を気軽にスマホなどで見たい場合には、

便利ですね。

 

参考資料

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

Pythonエンジニア養成読本[いまどきの開発ノウハウ満載!] (Software Design plus)

Pythonエンジニア養成読本[いまどきの開発ノウハウ満載!] (Software Design plus)