2013年2月4日月曜日

Bottleのプラグインの動作方法:基本


プラグインの動作方法:基本

プラグインAPIは、デコレータの概念に基づいています。簡潔に言えば、プラグインは、アプリケーションのすべての単一のルートコールバックに適用されるデコレータです。
もちろん、これは単に単純化したものです。プラグインは、単なる装飾ルートのコールバックより多くを行うことができますが、それは良い出発点です。貸し付けは、いくつかのコードを見てみましょう:
from bottle import response, install
import time

def stopwatch(callback):
    def wrapper(*args, **kwargs):
        start = time.time()
        body = callback(*args, **kwargs)
        end = time.time()
        response.headers['X-Exec-Time'] = str(end - start)
        return body
    return wrapper

bottle.install(stopwatch)
このプラグインはリクエストごとに実行時間を測定し、応答に適切なX-EXECタイムヘッダーを追加します。あなたが見ることができるように、プラグインがラッパーを返し、ラッパーは、再帰的に元のコールバックを呼び出します。これは、デコレータは、通常の動作方法です。
最後の行は、デフォルトのアプリケーションにプラグインをインストールするには、ボトルに指示します。これは、プラグインは自動的にそのアプリケーションのすべてのルートに適用されるようになります。言い換えれば、ストップウォッチ()は、各ルートのコールバックに対して一度呼び出され、戻り値は、元のコールバックの代わりとして使用されています。
プラグインは、ルートが初めて要求されるとすぐに、つまりオンデマンドで適用されます。このマルチスレッド環境で正しく動作させるには、プラグインはスレッド·セーフでなければなりません。これはほとんどの時間の問題ではありませんが、それに留意してください。
すべてのプラグインがルートに適用されると、ラップされたコールバックは、キャッシュされ、後続の要求は、直接キャッシュされたバージョンによって処理されます。これは、プラグインは通常、特定のルートに一度だけ適用されることを意味します。そのキャッシュは、しかし、たびにインストールされているプラ​​グインの変更のリストがクリアされます。あなたのプラグインは、複数回同じルートを飾ることができるはずです。
デコレータのAPIは非常にかかわらず、制限されています。あなたがデコレートされたルートまたは、関連付けられたアプリケーションオブジェクトについて何を知っていて、効率的にすべてのルート間で共有されるデータを格納する方法がありませんありません。しかし、恐怖はありません!プラグインは、単にデコレータ関数に限定されるものではない。ボトルは、それが呼び出し可能であるか、または拡張APIを実装している限り、プラグインとして何かを受け入れます。このAPIは、以下に説明すると、処理全体の制御の多くを提供しています。

プラグインAPI

プラグインは、実際のクラス(あなたがボトルからそれをインポートすることはできません)が、プラグインが実装すると予想されているインタフェースではありません。ボトルは、プラグインとして任意の型の任意のオブジェクトを受け入れる限り、それは以下のAPIに準拠している。
class Plugin(object)
プラグインは呼び出し可能であるかは、apply()を実装する必要があります。
apply()適用されるが定義されている場合、それは常に直接プラグインを呼び出すことが優先されます。 他のすべてのメソッドや属性はオプションです。
name
両方Bottle.uninstall()とBottle.routeのスキップ·パラメータは、()プラグインまたはプラグインタイプを参照するために名前の文字列を受け入れます。これは、name属性を持っているプラ​​グインに対してのみ機能します。
api
プラグインAPIは、まだ進化しています。この整数型の属性は、どちらのバージョンを使用するボトルを指示します。最初のバージョンにそれが欠落している場合は、ボトルをデフォルトとします。現在のバージョンは2です。詳細については、プラグインAPIの変更を参照してください。
setup(self, app)
とすぐにプラグインがアプリケーションにインストールされていると呼ばれる(Bottle.install()を参照)。唯一のパラメータは、関連付けられたアプリケーションオブジェクトです。
call(self, callback)
限り定義されていません)(適用されるように、プラグイン自体は、デコレータとして使用され、各ルートのコールバックに直接適用されます。唯一のパラメータは、飾るためのコールバックです。このメソッドによって返される任意の元のコールに置き換えられます。与えられたコールバック関数をラップまたは交換する必要はありません場合は、単に変更されていないコールバックパラメータを返します。
apply(self, callback, route)¶
定義されている場合、このメソッドはルートのコールバックを飾るためにcallを通じて()に有利に使用されています。追加ルートパラメータは、ルートのインスタンスであり、そのルートのメタ情報とコンテキストの多くを提供しています。詳細については、ルートコンテキストを参照してください。
close(self)¶
プラグインがアンインストールされるか、またはアプリケーションが閉じられる直前に呼び出されます。
(Bottle.uninstall()またはBottle.close()を参照してください)​​
Plugin.setup()とPlugin.close()の両方がBottle.route()デコレータを経由して、だけのアプリケーションにインストールされているプラ​​グインのルートに直接適用されているプラ​​グインを呼び出されません。

プラグインAPIの変更

プラグインAPIはまだ発展途上とルートコンテキストの辞書を持つ特定の問題に対処するためにボトル0.10で変更されます。 0.9プラグインとの下位互換性を確保するために、我々は、どのAPIが使用するように瓶を指示するオプションのPlugin.api属性を追加しました。 APIの違いはここに要約されています。
ボトル0.9 API 1(Plugin.api存在しない)
0.9のドキュメントで説明するようにオリジナルのプラグインAPI。
ボトル0.10 API 2(Plugin.apiは2に等しい)
Plugin.apply()メソッドのcontextパラメータは現在、代わりにコンテキスト辞書のルートのインスタンスです。

ルートコンテキスト

Plugin.applyに渡されたルート·インスタンスは、()関連付けられたルートに関する詳細な情報を提供しています。最も重要な属性は以下にまとめています:

属性の説明

アプリは、アプリケーションオブジェクトは、このルートは次のようにインストールされています。
AttributeDescription
appThe application object this route is installed to.
ruleThe rule string (e.g. /wiki/:page).
methodThe HTTP method as a string (e.g. GET).
callbackThe original callback with no plugins applied. Useful for introspection.
nameThe name of the route (if specified) or None.
pluginsA list of route-specific plugins. These are applied in addition to application-wide plugins. (see Bottle.route()).
skiplistA list of plugins to not apply to this route (again, see Bottle.route()).
configAdditional keyword arguments passed to the Bottle.route() decorator are stored in this dictionary. Used for route-specific configuration and meta-data.
プラグインについては、Route.configは、おそらく最も重要な属性です。この辞書は、ルートに対してローカルであることを心に留めておくが、すべてのプラグイン間で共有されます。それは常にあなたのプラグインが設定ディクショナリ内の別の名前空間に格納し、コンフィギュレーションの多くを必要とする場合、一意の接頭辞を追加したりすることをお勧めします。これは、プラグイン間での名前の衝突を回避するのに役立ちます。

Routeオブジェクトを変更する

いくつかのルートの属性は可変ですが、変更が他のプラグイン上で望ましくない影響があるかもしれません。それは猿パッチを適用する可能性が最も高い代わりに、有用なエラーメッセージを提供し、ユーザーが問題を解決させるの壊れたルートは悪い考えです。
まれに、しかし、それはこのルールを破ることが正当かもしれません。あなたはルートインスタンスに変更を加えた後、例外としてRouteResetが発生します。これは、キャッシュから現在のルートを削除し、すべてのプラグインが再適用されるようになります。ルータは、しかし、更新されません。ルールまたはメソッドの値の変更は、ルータに影響を与えませんが、プラグインだけで。ただし、これは将来変更されるかもしれません。

ランタイムの最適化

すべてのプラグインがルートに適用されると、ラップされたルートのコールバックは、後続の要求をスピードアップするためにキャッシュされます。プラグインの動作は設定に依存し、実行時にその設定を変更することができるようにしたい場合は、要求ごとに設定を読む必要があります。十分に簡単です。
パフォーマンス上の理由から、しかし、それは、現在のニーズに基づいて、さまざまなラッパーを選択して、クロージャで動作する、または実行時にプラグインを有効または無効にする価値があるかもしれません。の例として、組み込みHooksPluginてみましょう。ないフックがインストールされていない場合、プラグインが影響を受けるすべてのルートから自身を削除し、virtaullyないオーバーヘッドがありません。できるだけ早くあなたが最初のフックをインストールすると、プラグイン自体を活性化し、再び有効になります。
これを実現するには、コールバックのキャッシュを制御する必要があります。Route.reset()が一度にアプリケーションのすべてのルートのすべてのキャッシュをクリアし、単一のルートとBottle.reset()のキャッシュをクリアします。それが最初に要求されたかのように次のリクエストに応じて、すべてのプラグインは、ルートに再適用されます。
原因のルートコールバック内から呼び出された場合、両方のメソッドは、現在の要求には影響しません。現在の要求の再起動を強制するには、例外としてRouteResetが発生します。

プラグインの例:SQLitePlugin

このプラグインは、ラップされたコールバックに追加のキーワード引数としてsqlite3のデータベース接続ハンドルを提供していますが、コールバックは、それを期待している場合のみです。されていない場合、ルートは無視され、オーバーヘッドが追加されません。ラッパーは、戻り値に影響を与えますが、適切にプラグインに関連する例外を処理していません。 Plugin.setup()が競合しているプラ​​グイン用のアプリケーションを検索して検査するために使用されています。
import sqlite3
import inspect

class SQLitePlugin(object):
    ''' This plugin passes an sqlite3 database handle to route callbacks
    that accept a `db` keyword argument. If a callback does not expect
    such a parameter, no connection is made. You can override the database
    settings on a per-route basis. '''

    name = 'sqlite'
    api = 2

    def __init__(self, dbfile=':memory:', autocommit=True, dictrows=True,
                 keyword='db'):
         self.dbfile = dbfile
         self.autocommit = autocommit
         self.dictrows = dictrows
         self.keyword = keyword

    def setup(self, app):
        ''' Make sure that other installed plugins don't affect the same
            keyword argument.'''
        for other in app.plugins:
            if not isinstance(other, SQLitePlugin): continue
            if other.keyword == self.keyword:
                raise PluginError("Found another sqlite plugin with "\
                "conflicting settings (non-unique keyword).")

    def apply(self, callback, context):
        # Override global configuration with route-specific values.
        conf = context.config.get('sqlite') or {}
        dbfile = conf.get('dbfile', self.dbfile)
        autocommit = conf.get('autocommit', self.autocommit)
        dictrows = conf.get('dictrows', self.dictrows)
        keyword = conf.get('keyword', self.keyword)

        # Test if the original callback accepts a 'db' keyword.
        # Ignore it if it does not need a database handle.
        args = inspect.getargspec(context.callback)[0]
        if keyword not in args:
            return callback

        def wrapper(*args, **kwargs):
            # Connect to the database
            db = sqlite3.connect(dbfile)
            # This enables column access by name: row['column_name']
            if dictrows: db.row_factory = sqlite3.Row
            # Add the connection handle as a keyword argument.
            kwargs[keyword] = db

            try:
                rv = callback(*args, **kwargs)
                if autocommit: db.commit()
            except sqlite3.IntegrityError, e:
                db.rollback()
                raise HTTPError(500, "Database Error", e)
            finally:
                db.close()
            return rv

        # Replace the route callback with the wrapped one.
        return wrapper
このプラグインは、実際に役に立つとボトルにバンドルされているバージョンと非常に似ています。コー​​ド未満の60行は悪くない、あなたは思いませんか?ここで使用例は、次のとおりです。
sqlite = SQLitePlugin(dbfile='/tmp/test.db')
bottle.install(sqlite)

@route('/show/:page')
def show(page, db):
    row = db.execute('SELECT * from pages where name=?', page).fetchone()
    if row:
        return template('showpage', page=row)
    return HTTPError(404, "Page not found")

@route('/static/:fname#.*#')
def static(fname):
    return static_file(fname, root='/some/path')

@route('/admin/set/:db#[a-zA-Z]+#', skip=[sqlite])
def change_dbfile(db):
    sqlite.dbfile = '/tmp/%s.db' % db
    return "Switched DB to %s.db" % db
最初のルートは、データベース接続を必要とdbのキーワード引数を要求することにより、ハンドルを作成するためのプラグインに指示します。 2つ目のルートは、データベースを必要としないので、プラグインによって無視されます。第三のルートは 'db'とキーワード引数を期待していますが、明示的にsqliteのプラグインをスキップします。この方法では引数はプラグインによって無効にしても同じ名前のURL引数の値が含​​まれていません。

Bottleのレシピ


Bottleのレシピ

これは、コードスニペットと一般的なユースケースの例のコレクションです。

セッションの追跡

(マイクロフレームワークで)それをする正しい方法はありませんので、ありませんセッションのサポートを内蔵しています。 要件や環境によっては、フィッティングエンドとビーカーのミドルウェアを使用するか、自分でそれを実装することができます。ここでは、ファイルベースのバックエンドを持つビーカーセッションの例は、次のとおりです。
import bottle
from beaker.middleware import SessionMiddleware

session_opts = {
    'session.type': 'file',
    'session.cookie_expires': 300,
    'session.data_dir': './data',
    'session.auto': True
}
app = SessionMiddleware(bottle.app(), session_opts)

@bottle.route('/test')
def test():
  s = bottle.request.environ.get('beaker.session')
  s['test'] = s.get('test',0) + 1
  s.save()
  return 'Test counter: %d' % s['test']

bottle.run(app=app)

スタイルでのデバッグ:デバッグミドルウェア

ボトルは、クラッシュからWSGIサーバを防ぐために、あなたのアプリケーションコード内で発生したすべての例外をキャッチします。組み込みのdebug()モードでは十分ではありませんし、デバッグのミドルウェアに伝播する例外が必要な場合は、この動作をオフにすることができます。
import bottle
app = bottle.app()
app.catchall = False #Now most exceptions are re-raised within bottle.
myapp = DebuggingMiddleware(app) #Replace this with a middleware of your choice (see below)
bottle.run(app=myapp)
今、ボトルだけで独自の例外をキャッチ(HTTPError、HttpResponseとBottleException)とミドルウェアが残りを処理することができます。
非常に強力なデバッグWSGIミドルウェアとwerkzeug&ペーストライブラリが両方同梱されています。 ペーストのwerkzeugとpaste.evalexception.middleware.EvalExceptionためwerkzeug.debug.DebuggedApplicationを見てみましょう。 それら両方はスタックを検査を行うことができ、さらに、スタックのコンテキスト内でのpythonコードを実行するので 、生産にそれらを使用しないでください。

ユニット·テスト·ボトルアプリケーション

ユニット·テストは通常、WSGI環境を実行せずにWebアプリケーションで定義されているメソッドに対して実行されます。
単純な例では、Noseを使用して:
import bottle

@bottle.route('/')
def index():
    return 'Hi!'

if __name__ == '__main__':
    bottle.run()
Test script:
import mywebapp

def test_webapp_index():
    assert mywebapp.index() == 'Hi!'
例では、Bottleのroute()メソッドが実行されることはありません - index()はテストされています。

機能テストボトルアプリケーション

任意のHTTPベースのテストシステムは、実行中のWSGIサーバで使用されますが、いくつかのテストフレームワークがWSGIとより緊密に連携し、トレースバック機能とデバッグツールを駆使して制御された環境でコールWSGIアプリケーションを提供することができます。 WSGIのテストツールは良い出発点です。
from webtest import TestApp
import mywebapp

def test_functional_login_logout():
    app = TestApp(mywebapp.app)

    app.post('/login', {'user': 'foo', 'pass': 'bar'}) # log in and get a cookie

    assert app.get('/admin').status == '200 OK'        # fetch a page successfully

    app.get('/logout')                                 # log out
    app.reset()                                        # drop the cookie

    # fetch the same page, unsuccessfully
    assert app.get('/admin').status == '401 Unauthorized'

他のWSGIアプリを埋め込む

これは、おすすめの方法(あなたがこれを行うには、ボトルの前で、ミドルウェアを使用する必要があります)ではありませんが、あなたのボトルのアプリ内から他のWSGIアプリケーションを呼び出して、擬似ミドルウェアとしてボトルの行為をさせることができます。次に例を示します。
from bottle import request, response, route
subproject = SomeWSGIApplication()

@route('/subproject/:subpath#.*#', method='ALL')
def call_wsgi(subpath):
    new_environ = request.environ.copy()
    new_environ['SCRIPT_NAME'] = new_environ.get('SCRIPT_NAME','') + '/subproject'
    new_environ['PATH_INFO'] = '/' + subpath
    def start_response(status, headerlist):
        response.status = int(status.split()[0])
        for key, value in headerlist:
            response.add_header(key, value)
    return app(new_environ, start_response)
繰り返しますが、これはサブプロジェクトを実装するためのお勧めの方法ではありません。多くの人々は、このために要請し、WSGIにどのようにボトルの地図を表示するので、ここだけです。

末尾のスラッシュを無視する

ボトル/exampleと/example/ 2つの異なる経路である[1]。両方のURLを同じに扱うためには、2つの@ルートデコレータを追加することができます。
@route('/test')
@route('/test/')
def test(): return 'Slash? no?'
またはWSGIミドルウェアを追加してすべてのURLから末尾のスラッシュをストリップ。
class StripPathMiddleware(object):
  def __init__(self, app):
    self.app = app
  def __call__(self, e, h):
    e['PATH_INFO'] = e['PATH_INFO'].rstrip('/')
    return self.app(e,h)

app = bottle.app()
myapp = StripPathMiddleware(app)
bottle.run(app=myapp)
脚注 [1]それらはあるので。 <http://www.ietf.org/rfc/rfc3986.txt>を参照してください。

キープアライブ要求

注意

詳細な説明については、非同期アプリケーションへの入門を参照してください。
マルチXHRのようないくつかの"Push"メカニズムは、レスポンスヘッダと一緒に接続を閉じずに、応答データを書き込む機能が必要"Connection: keep-alive"。 WSGIは、この動作を容易に自分自身を貸すわけではありませんが、gevent非同期フレームワークを使用して、ボトルでそうすることも可能です。ここでgevent HTTPサーバまたはペーストHTTPサーバ(これは他の人と動作する可能性がありますが、私は試していません)のいずれかで動作するサンプルです。単にサーバーを変更するserver='gevent'とserver='paste'とユーザが貼り付けたサーバーを使用する:
from gevent import monkey; monkey.patch_all()

import time
from bottle import route, run

@route('/stream')
def stream():
    yield 'START'
    time.sleep(3)
    yield 'MIDDLE'
    time.sleep(5)
    yield 'END'

run(host='0.0.0.0', port=8080, server='gevent')
あなたがhttp://localhost:8080/streamを参照する場合、あなたは、"START" "MIDDLE"、と'END'の時間(むしろ一度にすべてを見るために8秒を待っているよりも)少なくとも一つを表示されるはずです。

ボトルのgzip圧縮

注意

詳細については、圧縮を参照してください。
要求時に静的リソース(CSSやJSファイルなど)を圧縮することで、サイトをスピードアップGzip圧縮をサポートするために、ボトルに共通の機能要求である。
Gzip圧縮をサポートするには、コーナー·ケースの数は頻繁にその作物アップのために単純な命題ではありません。適切なGzipでの実装を行う必要があります。
  • オンザフライで圧縮し、高速そうすることができます。
  • それをサポートしていないブラウザのために圧縮されません。
  • (画像、ビデオ)は、既に圧縮されているファイルを圧縮しないでください。
  • 動的なファイルを圧縮しないでください。
  • サポート2つ(GZIPおよびDeflate)圧縮アルゴリズムを異なっていた。
  • 頻繁に変更されませんキャッシュの圧縮ファイル。
  • いずれかのファイルが変更された場合はとにかくキャッシュを非検証します。
  • キャッシュが大きいに到達しないことを確認してください。
  • シークディスクがオンザフライ圧縮よりも長くかかるため、小さなファイルをキャッシュしません。
これらの要件から、それはgzip圧縮が最高のWSGIサーバのボトルの上で動作によって処理されているボトルプロジェクトの勧告です。そのようなCherryPyはなどのWSGIサーバはこれを達成するために使用することができGzipFilterミドルウェアを提供しています。

フックプラグインを使用して

あなたのURLのすべてによって返されたコンテンツのクロスオリジンリソースの共有を許可する場合たとえば、あなたはフックデコレータを使用してコールバック関数を設定することができます。
from bottle import hook, response, route

@hook('after_request')
def enable_cors():
    response.headers['Access-Control-Allow-Origin'] = '*'

@route('/foo')
def say_foo():
    return 'foo!'

@route('/bar')
def say_bar():
    return {'type': 'friendly', 'content': 'Hi!'}
また、すべての関数が呼び出される前にアクションを取ることbefore_callbackを使用することができます。

Herokuのとボトルを使用して

Herokuの、人気の高いクラウド·アプリケーション·プラットフォームは、今ではinfastructureでPythonアプリケーションを実行するためのサポートを提供しています。
このレシピは置き換えボトル固有のコードで、Herokuのクイックスタートに基づいていますHeroku/シーダーガイドのPython入門のあなたのアプリケーションのセクションを記述します。
import os
from bottle import route, run

@route("/")
def hello_world():
        return "Hello World!"

run(host="0.0.0.0", port=int(os.environ.get("PORT", 5000)))
Herokuのアプリのスタックは、アプリケーションがos.environ辞書を使用して、要求をlistenするために必要なポートを渡します。

Bottleの非同期アプリケーションへのプライマー


非同期アプリケーションへのプライマー

非同期デザインパターンは、WSGIの同期性とよく混在させないでください。ほとんどの非同期フレームワークは(tornado, twisted, ...)彼らの非同期機能を公開するために特殊なAPIを実装する理由はここにあります。ボトルはWSGIフレームワークはもともと備えてたWSGIとの同期性及び、素晴らしいgeventプロジェクトのおかげで、Bottleで非同期アプリケーションを作成することも可能です。この資料では、BottleのWSGIと非同期の使用を説明します。

同期WSGIの限界

簡単に言葉で表現すると、WSGI仕様(PEP 3333)は、要求/応答のサイクルを処理します。呼び出し可能なアプリケーションは各要求に対して1回ずつ呼び出され、ボディの反復子を返す必要があります。次に、サーバーは、ボディを反復処理し、ソケットに各チャンクを書き込みます。ボディの反復子が使い果たされるとすぐに、クライアント接続が閉じられます。
十分に単純ですが、思わぬ障害があります:これはすべて同期として発生します。アプリケーションがデータ(IO、ソケット、データベース、...)を待つ必要がある場合、それは空の文字列(ビジーウェイト)を得たり、現在のスレッドをブロックする必要があります。両方のソリューションは、処理スレッドを占有し、新しい要求に答えてからそれを防ぐことができます。その結果スレッドごとに1つだけ継続的な要求となります。
ほとんどのサーバーは、その比較的高いオーバーヘッドを回避するために、スレッドの数を制限します。 20以下のスレッドプールが共通しています。できるだけ早くすべてのスレッドが占有された場合は、任意の新しい接続が停止されています。サーバは他の皆のために効果的に終了させます。あなたはリアルタイムでアップデートを取得するためにロングポーリングAjaxリクエストを使用してチャットを実装する場合は、20の同時接続に制限された状態に到達すると思います。それはかなり小さいチャットです。

救助にGreenlets

ほとんどのサーバは切り替え処理において、新しいスレッドの作成にかかわる高いオーバーヘッドのために、同時実行スレッドの比較的低い数字にOSのワーキングプールのサイズを制限します。スレッドがプロセスをspawn(fork)に比べて安いですが、彼らはまだ新しい接続ごとに作成することが高価である。
geventモジュールを加え、greenletsを混在させます。 Greenletsは、従来のスレッドに似て動作しますが、作成することが非常にローコストです。 geventベースのサーバーは、ほとんどオーバーヘッドなしでgreenlets何千もの(接続ごとに1つ)を生成することができます。個々のgreenletsをブロックすると、新しい要求を受け入れるようにサーバの能力に影響を与えません。同時接続数は事実上無制限です。
彼らが見て、同期アプリケーションのように感じるので、これは驚くほど簡単に非同期アプリケーションを作成することができます。 geventベースのサーバーは、実際に非同期ですが、大規模なマルチスレッドではありません。次に例を示します。
from gevent import monkey; monkey.patch_all()

from time import sleep
from bottle import route, run

@route('/stream')
def stream():
    yield 'START'
    sleep(3)
    yield 'MIDDLE'
    sleep(5)
    yield 'END'

run(host='0.0.0.0', port=8080, server='gevent')
最初の行は重要です。それは、現在のスレッドをブロックしないように、PythonのブロッキングAPIのほとんどのmonkey-patchがgevent発生し、次のgreenletにCPUを渡す。それは実際にgeventベースの疑似スレッドでPythonのスレッドを置き換えます。あなたはまだ正常に全体のスレッドをブロックするtime.sleep()を使用することができます。あなたはmonkey-patchをpythonの組込みに快適に感じていない場合は、(この例ではgevent.sleep())に対応するgevent機能を使用することができます。
このスクリプトを実行してhttp://localhost:8080/streamをブラウザに表示した場合は、start、middle、およびEND1(むしろ一度にすべてを見るために8秒を待っているより)ずつを表示を表示する必要があります。それは通常のスレッドと全く同じに動作しますが、現在、サーバーは問題なく同時要求の数千を処理することができます。
注意

彼らはページのレンダリングを開始する前に、一部のブラウザでは、一定量のデータをバッファリングする。これらのブラウザで効果を確認するためにいくつかのバイト以上を得るために必要があるかもしれません。さらに、多くのブラウザはURLごとに同時接続の制限があります。このような場合は、2番目のブラウザやパフォーマンスを測定するベンチマーク·ツール(例えば、ABまたはhttperfの)を使用することができます。

イベントコールバック

非同期フレームワーク(including tornado, twisted, node.js and friends)で良く似た,共通のデザインパターンは非ブロッキングAPIと非同期イベントへのバインドのコールバックを使用することです。それはコールバックは後の時点で、ソケットへの書込みを許可する明示的にクローズされるまでのソケットオブジェクトが開いたままにしています。ここでtornadoライブラリに基づいて記述する例は、次のとおりです。
class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        worker = SomeAsyncWorker()
        worker.on_data(lambda chunk: self.write(chunk))
        worker.on_finish(lambda: self.finish())
主な利点は、要求ハンドラが早期に終了することです。コー​​ルバックは以前の要求のソケットへの書き込みを続行している間に処理スレッドが上に移動し、新しい要求を受け付けることができます。これは、これらのフレームワークはOSのスレッド数が少なく同時要求の多くを処理する管理方法です。
Gevent + WSGIと、処理は異なっています:まず、我々は新しい接続を受け入れるために(疑似)スレッドの無限のプールを持っているので、利点はありません早期終了します。それはソケットを(WSGIで必要)を閉じてしまうため第二に、我々は早期に終了することはできません。第三に、我々はWSGIに準拠するiterableを返さなければなりません。
WSGI標準に準拠するために、我々は非同期に書き込むことができるボディのiterableを返すことをしなければならない。 gevent.queueの助けを借りて、我々は切り離されたソケットをシミュレートし、次のように前の例を書き換えることができます。
@route('/fetch')
def fetch():
    body = gevent.queue.Queue()
    worker = SomeAsyncWorker()
    worker.on_data(lambda chunk: body.put(chunk))
    worker.on_finish(lambda: body.put(StopIteration))
    return body
サーバの観点から、キューオブジェクトは順次処理が可能です。それはブロックであれば、空と同時にそれがStopIterationに達すると停止します。これはWSGIに準拠しています。アプリケーション側では、キューオブジェクトが非ブロッキングソケットと同じように動作します。あなたは、いつでもそれに書き込み、それを周りに通過しさらにそれを非同期的に書き込みを行う新たな(疑似)スレッドを起動することができます。これは、ロングポーリングは時間のほとんどを実装する方法です。

最後に:WebSockets

しばらくの間、低レベルの詳細を忘れてWebSocketsについて話すことができます。ブラウザ(クライアント)とWebアプリケーション(サーバ)間の双方向通信チャネル:あなたがこの記事を読んでいるので、おそらくWebSocketsを理解されるでしょう。
ありがたいことに gevent-websocket packageは私たちの代わりに、すべてのハードワークを行います。ここでメッセージを受信し、ちょうどそれをクライアントに送り返す単純なはWebSocketエンドポイントは、次のとおりです。
from bottle import request, Bottle, abort
app = Bottle()

@app.route('/websocket')
def handle_websocket():
    wsock = request.environ.get('wsgi.websocket')
    if not wsock:
        abort(400, 'Expected WebSocket request.')

    while True:
        try:
            message = wsock.receive()
            wsock.send("Your message was: %r" % message)
        except WebSocketError:
            break

from gevent.pywsgi import WSGIServer
from geventwebsocket import WebSocketHandler, WebSocketError
server = WSGIServer(("0.0.0.0", 8080), app,
                    handler_class=WebSocketHandler)
server.serve_forever()
クライアントが接続を閉じるまで、whileループが実行されます。あなたのアイデア:)を得る
クライアントサイトのJavaScript APIも、まっすぐ実際に次のとおりです。
<!DOCTYPE html>
<html>
<head>
  <script type="text/javascript">
    var ws = new WebSocket("ws://example.com:8080/websocket");
    ws.onopen = function() {
        ws.send("Hello, world");
    };
    ws.onmessage = function (evt) {
        alert(evt.data);
    };
  </script>
</head>
</html>

Bottleのチュートリアル:To-Do - リスト·アプリケーション



チュートリアル:To-Do - リスト·アプリケーション

注意
このチュートリアルでは、progessの作品であるとnoisefloorによって書かれた。
このチュートリアルでは、Bottle-WSGIフレームワークについて、簡単に紹介をする必要があります。このチュートリアルでは、ボトルWSGIフレームワークを簡単に紹介を与える必要があります。主な目標は、ボトルを使用してプロジェクトを作成するには、このチュートリアルを読んだ後、できるようにすることです。 このドキュメント内で、すべての機能は、説明されますが、少なくともBottleのテンプレート能力を利用したフォーマット出力の取扱いと、主要なルーティングとGET/POSTパラメータ。
ここで内容を理解するために、WSGIの基本的な知識を持っている必要はありません。Bollteは、とにかくユーザーからWSGIを遠ざけるしようとしています。あなたは、Pythonプログラミング言語を正しく理解しておく必要があります。 また、チュートリアルの取得およびSQL databseにデータを格納に使用される例ですので、SQLに関する基本的な考え方は役立ちますが、Bollteの概念を理解する為には必要ありません。ここで、SQLiteは使用されています。 ブラウザに送信されるBollteの出力は、HTMLの助けを借りていくつかの例でフォーマットされています。 したがって、一般的なHTMLタグについての基本的な考え方は同様に役立ちます。
Bollteを導入するために、"in between" Pythonのコードは、フォーカスを維持するために、短くされています。 また、チュートリアル内のすべてのコードは、正常に動作しています. しかし、あなたは必ずしも例えば、 "in the wild" それを使用することはできません 公共のWebサーバー上の。 そのためには、あなたは、例えばを追加することができ,より多くのエラー処理、パスワードを使用してデータベースを保護するP-:

目標

このチュートリアルの最後に、我々は、シンプルなWebベースのToDoリストがあります。リストには、各項目のテキスト(最大100文字)と状態(閉じた場合は0、開いている1)が含まれています。 Webベースのユーザーインターフェイスを介して、開いているアイテムは、ビューすることができ、編集し、新しいアイテムを追加することができます。
開発中に、すべてのページはlocalhost上で利用できるようになりますが、後でその上には、Apacheのmod_wsgiを一緒に使用する方法など、"本物の"サーバ用のアプリケーションに適応する方法を表示されます。
Bollteには、テンプレートの助けを借りて、ルーティングとフォーマット出力を行います。リストの項目は、SQLiteデータベース内に格納されます。データベースの読み取りと書き込みはPythonコードによって行われます。

始める前に…

ボトルをインストールする

あなたがPython(バージョン2.5以上)の比較的新しいインストールされていると仮定して、あなただけのそれに加えてボトルをインストールする必要があります。ボトルは、Python自体よりも他の依存関係を持っていません。
手動でボトルをインストールするか、Pythonのeasy_installを使用することができ、次のいずれかeasy_installを

ボトルを

さらにソ​​フトウェアの必需品
我々はデータベースとしてSQLite3を使うとして、それがインストールされていることを確認してください。 Linuxシステムでは、ほとんどのディストリビューションではsqlite3デフォルトでインストールされています。 SQLiteは、同様にWindowsとMacOS Xのために利用可能で、sqlite3モジュールは、Python標準ライブラリの一部です。

SQLデータベースを作成します。

最初に、我々は後で使用するデータベースを作成する必要があります。これを行うには、プロジェクトディレクトリに次のスクリプトを保存およびPythonを使用して、それを実行します。あなたも、対話型インタプリタを使用することができます。
import sqlite3
con = sqlite3.connect('todo.db') # Warning: This file is created in the current directory
con.execute("CREATE TABLE todo (id INTEGER PRIMARY KEY, task char(100) NOT NULL, status bool NOT NULL)")
con.execute("INSERT INTO todo (task,status) VALUES ('Read A-byte-of-python to get a good introduction into Python',0)")
con.execute("INSERT INTO todo (task,status) VALUES ('Visit the Python website',1)")
con.execute("INSERT INTO todo (task,status) VALUES ('Test various editors for and check the syntax highlighting',1)")
con.execute("INSERT INTO todo (task,status) VALUES ('Choose your favorite WSGI-Framework',0)")
con.commit()
これは、データベースファイルのTODOと呼ばれるテーブルと3つの列idを持つtodo.db、タスク、およびステータスを生成します。 idは、行を参照するために後で使用される行ごとに、一意のIDです。列タスクは、タスクを説明するテキストを保持し、それは最大100文字の長さにすることができます。最後に、列のステータスはopen(値1)かクローズ(値0)としてタスクをマークするために使用されています。

Bottleを使用したWebベースのToDoリスト

今ではWebベースのアプリケーションを作成するために、Bottleを導入する必要がある。 routes:しかし、最初に、我々は、ボトルの基本的な概念に注目する必要があります。

routesを理解する

ページアドレスが呼び出されたときに、基本的には、ブラウザに表示され、各ページが動的に生成されます。したがって、静的なコンテンツはありません。サーバー上の特定のアドレス:これは、ボトル内の "routes"と呼ばれるものとまったく同じです。 "TODO"のルートに定義されている(Python)関数がある場合は、したがって、たとえば、ページhttp://localhost:8080/todoがブラウザから呼び出されたときに、Bottleには、コールとのチェックを “grabs” 。そうだとすれば、ボトルには、対応するPythonコードを実行し、その結果を返します。

最初のステップ - すべてのアイテムを表示

したがって、ルートの概念を理解した後に、まず1を作成することができます。目標は、ToDoリストから全ての項目を開いて表示する事です。
import sqlite3
from bottle import route, run

@route('/todo')
def todo_list():
    conn = sqlite3.connect('todo.db')
    c = conn.cursor()
    c.execute("SELECT id, task FROM todo WHERE status LIKE '1'")
    result = c.fetchall()
    return str(result)

run()
ファイルと同じディレクトリに、好ましくは、コードのtodo.pyを保存todo.db. そうでなければ、sqlite3.connect()ステートメントでtodo.dbへのパスを追加する必要があります。
我々だけで何をしたかを見てみましょう: 私たちは、SQLiteデータベースにアクセスするためにsqlite3の必要なモジュールをインポートし、 Bottleから我々はroutesとrunをインポートしました。run()文は、単にBottleに含まれるWebサーバーを起動します。デフォルトでは、Webサーバーは、localhostのポート8080上のページを提供します。さらに、我々はBottleのルーティングの責任関数であるルートをインポートします。あなたが見ることができるように、我々は、データベースからの読み込み、数行のコードで、一つの関数todo_list()を定義しました。重要な点は、右のdef todo_list()ステートメントの前に、デコレータ文@route('/todo')です。これにより、我々はroot/todoには、この関数をバインドするので、毎回ブラウザがhttp://localhost:8080/todoを呼び出して、Bottleは、関数todo_list()の結果を返します。それは瓶の作品内のルーティング方法です。
実際には、関数に複数のルートにバインドすることができます。次のコードは、だから:
@route('/todo')
@route('/my_todo_list')
def todo_list():
    ...
あまりにも、正常に動作します。何が動作しませんと、1つ以上の関数に1つのルートをバインドすることです。
どのブラウザで表示されますと、このように、返されるreturn文で指定された値です。この例では、Bottleにはreturn文から文字列または文字列のリストを期待するようには、str()によって文字列に結果を変換する必要があります。しかし、ここでは、データベースクエリの結果は、Python DB APIで定義されている標準であるタプルのリストです。
今、少し上のスクリプトを理解した上で、それを実行し、結果自分自身を監視する時間です。 Linuxベース/Unixベースのシステム上のファイルtodo.pyを最初に実行する必要があることを覚えておいてください。その後、ちょうどのpython todo.pyを実行し、ブラウザでページhttp://localhost:8080/todoを呼び出します。スクリプトを書き込んでもミスを犯しません場合、出力は次のようになります
[(2, u'Visit the Python website'), (3, u'Test various editors for and check the syntax highlighting')]
もし、上記の表示なら - おめでとうございます!あなたは、ボトルの成功したユーザーです。もし、動作しなかった場合は、スクリプトにいくつかの変更を加える必要があります、その時にページを提供しているボトルを停止することを忘れないでください。別の方法としての改訂版がロードは提供されません。 実際に、それは、SQLクエリから返される生の結果ですので、出力結果を読むことは本当にエキサイティングでも良いものでもありません。そこで、次の手順で私たちはよりよい方法で出力をフォーマットします。しかし我々はそれを行う前に、私たちは私たちの作業がが容易になる方法があります。

デバッグとオートリロード

たぶん、あなたはすでにスクリプトが間違って、例えば、Bollteの中に、何かのブラウザに短いエラーメッセージを送信することに気づいたり、データベースへの接続が動作していなかったり。 デバッグの目的のために、それはより多くの細かい情報を得るために非常に便利は方法があります。 これは簡単にスクリプトに次のステートメントを追加することによって達成することができます:
from bottle import run, route, debug
...
#add this at the very end:
debug(True)
run()
"debug"を有効にすることで、通常のバグを見つけるための有用な情報が含まれているPythonインタプリタの完全なスタックトレースを取得します。さらに、テンプレートは(下記参照)、キャッシュされませんので、テンプレートへの変更は、サーバーを停止せずに有効になります。
警告
(真)のみの開発に使用されることになって、デバッグは、実稼働環境で使用すべきではありません。
さらに静かな便利な機能)は、ステートメントの実行を(変更することによって有効になっている自動リロードで、
run(reloader=True)
これは自動的にスクリプトへの変更を検出し、それが再び呼び出された後、サーバーを停止および起動することなく、新しいバージョンをリロードします。
再び、この機能は、主に開発中ではなく、生産性の高いシステムで使用されることになっています。

出力をフォーマットするボトルテンプレート

今度は、適切な形式にスクリプトの出力をキャストを見てみましょう。
実際にボトルには、文字列または関数から文字列のリストを受け取ることを想定し、ブラウザへの組み込みサーバの助けによってそれらを返します。ボトルは、文字列自体の内容については気にしないので、テキストも、HTMLマークアップを使用してフォーマットすることができます。
ボトルには、それに自身の使いやすいテンプレートエンジンをもたらします。テンプレートは、TPL拡張子を持つ別々のファイルとして保存されます。テンプレートは、関数内から次に呼び出すことができます。テンプレートは、テキストの任意の型を(ほとんどのHTMLマークアップPython文と混合される)を含めることができます。さらに、テンプレートは、例えば、引数を取ることができます結果は、テンプレート内にきれいにフォーマットされるデータベースクエリのセット。
右ここで、我々は2つ​​の列を持つ単純なテーブルに開いているのToDo項目を表示し、クエリの結果をキャストしようとしている最初の列は、番目の列のテキスト、アイテムのIDが含まれています。結果セットは、上で見たように、タプルのリストで、各タプルは、結果の1セットが含まれています。
この例ではテンプレートを含めるには、単に次の行を追加します。
from bottle import route, run, debug, template
...
result = c.fetchall()
c.close()
output = template('make_table', rows=result)
return output
...
だから我々はここで2つのことを行う: まず、我々は、テンプレートを使用できるようにするためにボトルからテンプレートをインポートします。第二に、我々は、戻された変数の出力へのmake_tableテンプレートの出力を割り当てることができます。テンプレートを呼び出すことに加えて、我々は、後にテンプレート内で使用される変数の行に、我々は、データベースクエリから受け取った結果を割り当てます。必要であれば、テンプレートに複数の変数/値を割り当てることができます。
テンプレートは常に文字列のリストを返すので、何かを変換する必要はありません。もちろん、我々は正確に上記と同じ結果が得られるリターンテンプレート( 'make_table'、行=結果)を書き込むことによって、コードの1行を保存することができます。
今ではこのようになり、対応するテンプレートを書くための時間です。
%#template to generate a HTML table from a list of tuples (or list of lists, or tuple of tuples or ...)
<p>The open items are as follows:</p>
<table border="1">
%for row in rows:
  <tr>
  %for col in row:
    <td>{{col}}</td>
  %end
  </tr>
%end
</table>
todo.pyが格納されている同じディレクトリにmake_table.tplとしてコードを保存します。
それでは、コードを見てみましょう:%で始まるすべての行は、Pythonコードとして解釈されます。もちろん、唯一の有効なPythonのステートメントが許可され、それ以外のテンプレートは他のPythonコードとして、例外が発生しますのでご注意ください。他の行はプレーンなHTMLマークアップがあります。
あなたが見ることができるように、我々は、行を通過するためには、for文を2回Pythonのを使用しています。このように、行がデータベースクエリの結果を保持する変数ですので、タプルのリストです。文の最初にリスト内のタプルは、テーブルのセルにそれぞれ配置されたタプル内の第1項目にアクセスします。 %終了したなどの文間に、それ以外の出力はあなたが期待するものではないかもしれない、場合は、あなたがすべてを閉じておくことが重要です。
テンプレート内の非Pythonのコードライン内の変数にアクセスする必要がある場合は、二重括弧に入れてする必要があります。これは、代わりに変数、右の実際の値を挿入するテンプレートを示します。
スクリプトを再度実行し、出力を見てください。それでも本当に素敵でなく、タプルのリストよりも少なくとも読みやすくなります。もちろん、あなたが上記のスパイスアップは非常に単純なHTMLマークアップは、例えば缶見栄えの良い出力を得るためにインラインスタイルを使用します。

GETとPOSTの値を使用して、

我々は適切に開いているすべての項目を確認することができますように、我々はToDoリストに新しい項目を追加して次のステップに移動します。新しいアイテムは、GETメソッドでそのデータを送信し、通常のHTMLベースのフォームから受信する必要があります。
これを行うには、まず私たちのスクリプトに新しいルートを追加し、それがGETデータを取得しなければならないルートを教えて:
from bottle import route, run, debug, template, request
...
return template('make_table', rows=result)
...

@route('/new', method='GET')
def new_item():

    new = request.GET.get('task', '').strip()

    conn = sqlite3.connect('todo.db')
    c = conn.cursor()

    c.execute("INSERT INTO todo (task,status) VALUES (?,?)", (new,1))
    new_id = c.lastrowid

    conn.commit()
    c.close()

    return '<p>The new task was inserted into the database, the ID is %s</p>' % new_id
GET(またはPOST)データにアクセスするには、我々は、ボトルからの要求をインポートする必要があります。変数に実際のデータを割り当てるには、我々は、request.GET.get('task',).strip() を使用します。タスクは、我々がアクセスするGETデータの名前であるストリップ()ステートメントを、。それがすべてです。あなたのGETデータ(複数の変数は、複数のrequest.GET.getを持っている場合)ステートメントを使用し、他の変数に割り当てることができます。
コー​​ドのこの部分の残りの部分はちょうど得られたデータの処理は次のとおりです。データベースへの書き込み、データベースから対応するIDを取得し、出力を生成します。
しかし、我々はどこからGETデータを取得するのですか?まあ、我々は、フォームを保持する静的なHTMLページを使用することができます。または、我々は今何をすべきか、root/newがGETデータなしで呼び出されると、出力されるテンプレートを使用することです。
コー​​ドでは、拡張する必要があります。
...
@route('/new', method='GET')
def new_item():

if request.GET.get('save','').strip():

    new = request.GET.get('task', '').strip()
    conn = sqlite3.connect('todo.db')
    c = conn.cursor()

    c.execute("INSERT INTO todo (task,status) VALUES (?,?)", (new,1))
    new_id = c.lastrowid

    conn.commit()
    c.close()

    return '<p>The new task was inserted into the database, the ID is %s</p>' % new_id
else:
    return template('new_task.tpl')
new_task.tplは次のようになります。
<p>Add a new task to the ToDo list:</p>
<form action="/new" method="GET">
<input type="text" size="100" maxlength="100" name="task">
<input type="submit" name="save" value="save">
</form>
それがすべてです。あなたが見ることができるように、テンプレートはプレーン今回はHTMLです。
今、我々は行うにはリストを拡張することができます。
ところで、POSTデータを使用したい場合:これは、まったく同じように動作しますが、だけではなく、request.POST.get()を使用します。

既存のアイテムの編集

行うための最後のポイントは、既存のアイテムの編集を有効にすることです。
ルートだけを使用することによって我々はそれが可能である今のところ知っているが、かなりトリッキーかもしれません。しかし、ボトルは、このタスクは非常に簡単に "ダイナミックルート"と呼ばれる何かを知っている。
ダイナミックルートの基本的なステートメントは次のようになります。
@route('/myroute/:something')
ここでキーポイントはコロンです。これがために受け入れるようにボトルに指示します。次のスラッシュまで何か任意の文字列を。また、何かの値が、そのルートに割り当てられた関数に渡されるので、データが関数内で処理することができます。
無編集するアイテムのIDです:私たちのToDoリストについては、我々はルート@ルート(無 '/ /編集)を作成します。
コー​​ドは次のようになります。
@route('/edit/:no', method='GET')
def edit_item(no):

    if request.GET.get('save','').strip():
        edit = request.GET.get('task','').strip()
        status = request.GET.get('status','').strip()

        if status == 'open':
            status = 1
        else:
            status = 0

        conn = sqlite3.connect('todo.db')
        c = conn.cursor()
        c.execute("UPDATE todo SET task = ?, status = ? WHERE id LIKE ?", (edit, status, no))
        conn.commit()

        return '<p>The item number %s was successfully updated</p>' % no
    else:
        conn = sqlite3.connect('todo.db')
        c = conn.cursor()
        c.execute("SELECT task FROM todo WHERE id LIKE ?", (str(no)))
        cur_data = c.fetchone()

        return template('edit_task', old=cur_data, no=no))
いや、ここで対応する関数の番号を渡します。これはかなり基本的に我々はすでにここでの主な追加が動的経路を使用しているなどのデータをGETを使用して同じように、新しい項目を追加する際に、上記したものと同じです。あなたが見ることができるように、NOは、データベース内のデータの右側の行にアクセスする関数内で使用されていません。
関数内で呼ばれるテンプレートedit_task.tplは次のようになります。
%#template for editing a task
%#the template expects to receive a value for "no" as well a "old", the text of the selected ToDo item
<p>Edit the task with ID = {{no}}</p>
<form action="/edit/{{no}}" method="get">
<input type="text" name="task" value="{{old[0]}}" size="100" maxlength="100">
<select name="status">
<option>open</option>
<option>closed</option>
</select>
<br/>
<input type="submit" name="save" value="save">
</form>
既に上述したように再び、このテンプレートは、Pythonの文とHTMLが混在しています。
ダイナミックルートの最後の単語:後で示すようにあなたは、動的経路の正規表現を使用していてもすることができます。

ダイナミックルートの検証

ダイナミックルートを使用すると良いですが、多くのケースでそれはルートの動的な部分を検証するために理にかなっています。たとえば、我々は上記の編集のために私たちのルートの整数を期待しています。しかし、浮動小数点の場合、その文字が受信され、Pythonインタプリタは、我々が望むものではありません例外をスローします。
そのような場合には、ボトルの前に関数に渡すに "入力"を検証する@検証デコレータを提供しています。バリデータを適用するためには、次のようにコードを拡張します。
from bottle import route, run, debug, template, request, validate
...
@route('/edit/:no', method='GET')
@validate(no=int)
def edit_item(no):
...
最初に、我々は@検証 - デコレータを適用するよりも、ボトルのフレームワークから検証するインポートされます。何が整数の場合、右ここで、我々は検証しません。基本的には、検証がフロートのようなすべての種類のデータで動作し、リストなど
コー​​ドを保存し、ために "403"の値を使用して再度ページを呼び出す:いいえ、例えば、フロート。整数が期待されていたという、エラー - あなたがいない例外が、 "紫禁城403"が表示されます。
正規表現を使用した動的経路
ボトルもルートの "動的な部分が"正規表現することができますダイナミックルートを扱うことができます。
そう、ちょうど、私たちのToDoリスト内のすべての単一の項目は、例えばのような用語で、それらのプレーン番号でアクセス可能であるべきであると仮定していることを実証する"項目1"。明白な理由については、各項目のルートを作成する必要はありません。さらに、単純なダイナミックルートは、ルートの一部として、用語 "アイテム"は静的であり、いずれかの動作しません。
上記に述べたように、解決策は、正規表現です。
@route('/item:item#[1-9]+#')
def show_item(item):
    conn = sqlite3.connect('todo.db')
    c = conn.cursor()
    c.execute("SELECT task FROM todo WHERE id LIKE ?", (item))
    result = c.fetchall()
    c.close()
    if not result:
        return 'This item number does not exist!'
    else:
        return 'Task: %s' %result[0]
もちろん、この例では、何らかの形で人為的に構築される - それが唯一の検証と組み合わせて単純な動的経路を使用する方が簡単でしょう。それにもかかわらず、我々は、正規表現のルートの作業方法を見たい:行@ルート(/アイテム:item_#[1-9] +#)通常のルートと同様に起動しますが、#で囲まれた部分が正規表現として解釈され、どのルートの動的な部分です。したがって、この場合には、我々は、0〜9の間の任意の数字を一致させる必要があります。次の関数 "show_item"は単に与えられた項目がデータベースかどうかに存在するかどうかを確認します。それが存在する場合には、タスクの対応するテキストが返されます。あなたが見ることができるように、ルートの唯一の正規表現の部分は前方に渡されます。さらに、それは常に、それがこのケースのように、単純な整数である場合でも、文字列として転送されます。

静的ファイルを返す

時にはそれがないPythonの関数へのルートを関連付けることが必要になるかもしれませんが、単に静的なファイルを返します。あなたは、たとえばアプリケーションのヘルプ·ページを持っているなら、あなたは普通のHTMLとしてこのページを返すようにすることができます。これは次のように動作します。
from bottle import route, run, debug, template, request, validate, static_file

@route('/help')
def help():
    return static_file('help.html', root='/path/to/file')
まず、ボトルからstatic_file関数をインポートする必要があります。あなたが見ることができるように、戻りstatic_fileステートメントは、returnステートメントを置き換えます。返されるファイルの名前とファイルへのパス:これは、少なくとも2つの引数を取ります。ファイルがアプリケーションと同じディレクトリにある場合でも、パスは記載する必要があります。しかし、この場合には、使用することができます '。'パスとして、あまりにも。ボトルを推測し、自動的にファイルのMIMEタイプが、場合には、ここでMIMEタイプ= "テキスト/html'であろうstatic_file、3番目の引数を追加し、明示的に述べたい。 static_fileは、動的なものも含めて、ルートの任意のタイプで動作します。

JSONデータを返す

あなたのアプリケーションに直接出力を生成したいのですが、データがでさらに処理されるように返されない場合、例えば、あるかもしれませんJavaScriptによる。そのような場合には、ボトルには、Webアプリケーション間でデータを交換するための標準の一種であるJSONオブジェクトを返すように可能性を提供しています。さらに、JSONはPythonを含め、多くのプログラミング言語で処理することができ
だから、Let 'sは、我々はJSONオブジェクトとして正規表現の経路の例で生成されたデータを返すようにしたいと仮定します。コー​​ドは次のようになります。
@route('/json:json#[1-9]+#')
def show_json(json):
    conn = sqlite3.connect('todo.db')
    c = conn.cursor()
    c.execute("SELECT task FROM todo WHERE id LIKE ?", (json))
    result = c.fetchall()
    c.close()

    if not result:
        return {'task':'This item number does not exist!'}
    else:
        return {'Task': result[0]}
することができますように、その非常に簡単です:単に通常のPython辞書とボトルを返すと、送信する前にJSONオブジェクトに自動的に変換されます。ですから、例えばもし"http://localhost/json1"ボトルと呼ぶこの場合にはJSONオブジェクトを返す必要があります{"タスク":["Pythonに優れた入門書を取得するために、バイトの-pythonを読む"]}。

エラーのキャッチ

次のステップは、アプリケーションのユーザーからのエラーメッセージのいずれかのタイプを遠ざけるために、ボトル自体にエラーをキャッチすることであるかもしれません。これを行うには、ボトルは、HTMLエラーに割り当てることができます "というエラー·ルート"を持っています。
私たちのケースでは、403エラーをキャッチしたいと思います。次のようにコードは次のとおりです。
from bottle import error

@error(403)
def mistake(code):
    return 'The parameter you passed has the wrong format!'
だから、最初に我々は、ボトルからエラーをインポートし、すべての "403 Forbidden"エラーをキャッチし、エラー(403)によって経路を定義する必要があります。関数は "間違い"がそれに割り当てられています。あなたがそれを必要としない場合でも、 - エラー()は常に関数にエラーコードを渡すことに注意してください。したがって、関数は常に一つの引数を受け取る必要があり、それ以外の場合は動作しません。
再び、関数に複数のエラーのルートを割り当てる、または1つの機能それぞれにさまざまなエラーをキャッチすることができます。このコードのように:
@error(404)
@error(403)
def mistake(code):
    return 'There is something wrong!'
次の1つは同様に、正常に動作します。
@error(403)
def mistake403(code):
    return 'The parameter you passed has the wrong format!'

@error(404)
def mistake404(code):
    return 'Sorry, this page does not exist!'

要約

上記のすべてのセクションを通過した後は、ボトルWSGIフレームワークがどのように動作する簡単な理解しておく必要があります。さらに、あなたのアプリケーションのためにボトルを使用するために必要なすべての知識を持っています。
次の章では、大規模なプロジェクトのためにボトルに適応する方法を簡単に紹介を提供します。さらに、我々はこれまでに使用されるものよりも高い負荷/より多くのWebトラフィックのパフォーマンスが向上し、Webサーバーとボトルを操作する方法を示します。

サーバーのセットアップ

これまでのところ、我々は、Pythonに付属のWSGIリファレンスサーバーであるボトルで使用される標準的なサーバを使用していました。このサーバは、開発目的のために完璧に適していますが、それは大規模なアプリケーションのために本当に適していません。我々は選択肢を見ている前に、のは、まず標準的なサーバーの設定を微調整する方法を見てみましょう。

別のポートとIPアドレスにボトルを実行する

標準として、ボトルもlocalhostとして知られているIPアドレス127.0.0.1上、ポート8080でページを提供します。追加のパラメータは、ポートとアドレスを変更するには、ボトルのrun()関数に渡すことができるように設定を変更するには、非常に単純です。
ポートを変更するには、runコマンドにポート=ポート番号を追加します。したがって、たとえば:
run(port=80)
ボトルは、ポート80をリッスンするでしょう。
:ボトルがリッスンしているIPアドレスを変更するには
run(host='123.45.67.89')
もちろん、両方のパラメータが同じように、組み合わせることができます。
run(port=80, host='123.45.67.89')
ボトルは別のサーバーで実行されているときに、ポートおよびホストパラメータは、次のセクションに示すように、適用することができます。

別のサーバーにボトルを実行する

上記に述べたように、標準のサーバ、開発、個人的な使用のみボトルに基づいてアプリケーションを使用している人々の小さなグループのために完璧に適しています。それはシングルスレッドであるとして大規模なタスクのために、標準のサーバーは、ボトルネックになる可能性があり、したがって、それだけで一度に1つの要求を提供することができます。
しかし、ボトルは、すでに高い負荷でパフォーマンスが向上し、ボード上のマルチスレッド·サーバーへの各種アダプタを持っています。ボトルは、CherryPyをFapws3、flupのと貼り付けをサポートしています。
[貼り付け]サーバとの例では、ボトルのために実行する場合は、次のコードを使用します。
from bottle import PasteServer
...
run(server=PasteServer)
これはFlupServer、CherryPyServerとFapwsServerとまったく同じように動作します。

mod_wsgiのApacheとでボトルを実行する

多分あなたは既にApacheがありますか、ボトルベースの​​アプリケーションを大規模に実行したい - それはmod_wsgiでApacheを考える時間です。
私達はあなたのApacheサーバーが稼働中で、mod_wsgiをも同様に正常に動作していることを前提としています。 Linuxディストリビューションの多くに、mod_wsgiのが容易に使用されているどのようなパッケージ管理システムを介してインストールすることができます。
ボトルはそれとmod_wsgiのためのアダプタをもたらすので、あなたのアプリケーションを提供するのは簡単な作業です。
次の例では、我々は、あなたのアプリケーションが "ToDoリスト"を作成することを前提としていhttp://www.mypage.com/todo、コード、テンプレート、SQLiteデータベースを介してアクセスがパスに格納されているの/ var / www / TODO 。
あなたがmod_wsgiを介してアプリケーションを実行すると、それはrun()あなたのコードからステートメントを削除することが不可欠です、それ以外の場合は、ここでは動作しません。
その後、次の内容のadapter.wsgiというファイルを作成します。
import sys, os, bottle

sys.path = ['/var/www/todo/'] + sys.path
os.chdir(os.path.dirname(__file__))

import todo # This loads your application

application = bottle.default_app()
との/ var / www / TODO、同じパスに保存してください。実際にファイルの名前は、長い拡張子があるので、何もすることができます。WSGI。名前は、仮想ホストからファイルを参照するために使用されています。
最後に、我々は次のようになります。Apacheの設定に仮想ホストを追加する必要があります。
<VirtualHost *>
    ServerName mypage.com

    WSGIDaemonProcess todo user=www-data group=www-data processes=1 threads=5
    WSGIScriptAlias / /var/www/todo/adapter.wsgi

    <Directory /var/www/todo>
        WSGIProcessGroup todo
        WSGIApplicationGroup %{GLOBAL}
        Order deny,allow
        Allow from all
    </Directory>
</VirtualHost>
サーバを再起動した後、あなたのToDoリストには、http://www.mypage.com/todoでアクセス可能でなければなりません

最後の言葉

今、私たちはボトルには、この導入とチュートリアルの最後にあります。我々は、ボトルの基本的な概念について学んだとボトルのフレームワークを使用した最初のアプリケーションを作成しました。それに加えて、我々は、大規模なタスクのためのボトルを適応し、mod_wsgiのでApacheウェブサーバを介してボトルを提供する方法を説明しました。
導入で述べたように、このチュートリアルでは、ボトルのすべての色合いと可能性を示していません。私たちはここではスキップすること例えばですファイルオブジェクトとストリームを受信する方法と、認証データを処理する。さらに、テンプレートを別のテンプレート内から呼び出すことができますどのように表示されませんでした。これらの点への導入については、フルボトルのマニュアルを参照してください。 完全な例リスト
ToDoリストの例は、ピース単位で開発されたとして、ここでは、完全なリストは、次のとおりです。
アプリケーションtodo.pyのメインのコード:
import sqlite3
from bottle import route, run, debug, template, request, validate, static_file, error

# only needed when you run Bottle on mod_wsgi
from bottle import default_app

@route('/todo')
def todo_list():

    conn = sqlite3.connect('todo.db')
    c = conn.cursor()
    c.execute("SELECT id, task FROM todo WHERE status LIKE '1';")
    result = c.fetchall()
    c.close()

    output = template('make_table', rows=result)
    return output

@route('/new', method='GET')
def new_item():

    if request.GET.get('save','').strip():

        new = request.GET.get('task', '').strip()
        conn = sqlite3.connect('todo.db')
        c = conn.cursor()

        c.execute("INSERT INTO todo (task,status) VALUES (?,?)", (new,1))
        new_id = c.lastrowid

        conn.commit()
        c.close()

        return '<p>The new task was inserted into the database, the ID is %s</p>' % new_id

    else:
        return template('new_task.tpl')

@route('/edit/:no', method='GET')
@validate(no=int)
def edit_item(no):

    if request.GET.get('save','').strip():
        edit = request.GET.get('task','').strip()
        status = request.GET.get('status','').strip()

        if status == 'open':
            status = 1
        else:
            status = 0

        conn = sqlite3.connect('todo.db')
        c = conn.cursor()
        c.execute("UPDATE todo SET task = ?, status = ? WHERE id LIKE ?", (edit,status,no))
        conn.commit()

        return '<p>The item number %s was successfully updated</p>' %no

    else:
        conn = sqlite3.connect('todo.db')
        c = conn.cursor()
        c.execute("SELECT task FROM todo WHERE id LIKE ?", (str(no)))
        cur_data = c.fetchone()

        return template('edit_task', old = cur_data, no = no)

@route('/item:item#[1-9]+#')
def show_item(item):

        conn = sqlite3.connect('todo.db')
        c = conn.cursor()
        c.execute("SELECT task FROM todo WHERE id LIKE ?", (item))
        result = c.fetchall()
        c.close()

        if not result:
            return 'This item number does not exist!'
        else:
            return 'Task: %s' %result[0]

@route('/help')
def help():

    static_file('help.html', root='.')

@route('/json:json#[1-9]+#')
def show_json(json):

    conn = sqlite3.connect('todo.db')
    c = conn.cursor()
    c.execute("SELECT task FROM todo WHERE id LIKE ?", (json))
    result = c.fetchall()
    c.close()

    if not result:
        return {'task':'This item number does not exist!'}
    else:
        return {'Task': result[0]}


@error(403)
def mistake403(code):
    return 'There is a mistake in your url!'

@error(404)
def mistake404(code):
    return 'Sorry, this page does not exist!'


debug(True)
run(reloader=True)
#remember to remove reloader=True and debug(True) when you move your application from development to a productive environment
テンプレートmake_table.tpl:
%#template to generate a HTML table from a list of tuples (or list of lists, or tuple of tuples or ...)
<p>The open items are as follows:</p>
<table border="1">
%for row in rows:
  <tr>
  %for col in row:
    <td>{{col}}</td>
  %end
  </tr>
%end
</table>
テンプレートedit_task.tpl:
%#template for editing a task
%#the template expects to receive a value for "no" as well a "old", the text of the selected ToDo item
<p>Edit the task with ID = {{no}}</p>
<form action="/edit/{{no}}" method="get">
<input type="text" name="task" value="{{old[0]}}" size="100" maxlength="100">
<select name="status">
<option>open</option>
<option>closed</option>
</select>
<br/>
<input type="submit" name="save" value="save">
</form>
Template new_task.tpl:
%#template for the form for a new task
<p>Add a new task to the ToDo list:</p>
<form action="/new" method="GET">
<input type="text" size="100" maxlength="100" name="task">
<input type="submit" name="save" value="save">
</form>