Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
ラベル web の投稿を表示しています。 すべての投稿を表示
ラベル web の投稿を表示しています。 すべての投稿を表示

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>

2013年2月3日日曜日

Bottle Tutorial

Bottle: Python Web Framework

CentOSでCUIのメール環境を構築する


CentOSでCUIのメール環境を構築する

ここでは、下記にミドルウエアの環境構築を行います。
1. Heirloom mailx
2. postfix

Heirloom mailx

1. 概要

Heirloom mailxとは、
/bin/mailの拡張版ともいえる、「Heirloom mailx」をインストールします。 「Heirloom mailx」は、IMAP、POP3、APOP、SSL/TLS、S/MIMEに対応したLinux版のCUIメーラです。

2. インストール

以下のコマンドでインストールを行います
$ cd /usr/local/src
$ wget http://sourceforge.net/projects/heirloom/files/latest/download
$ tar -jxf mailx-12.4.tar.bz2
$ cd mailx-12.4
# make
# make install UCBINSTALL=/usr/bin/install

3. 環境設定

利用するユーザ単位で、.mailrcを作成します ここでは、Gmailのメールサーバを利用することにします
$ vim ~/.mailrc
# .mailrc Gmail config
account gmail {
   set imap-cache=~/MailDir
   set imap-use-starttls
   set folder=imaps://Gmailのユーザ名@gmail.com@imap.gmail.com
   set password-Gmailのユーザ名@gmail.com@imap.gmail.com=Gmailのパスワード 
   set from=”お名前 <Gmailのユーザ名@gmail.com>”
   set record=+Sent
   set smtp-use-starttls
   set smtp=smtp://smtp.gmail.com:587
   set smtp-auth=login
   set smtp-auth-user=Gmailのユーザ名@gmail.com
   set smtp-auth-password=Gmailのパスワード
   set nss-config-dir=~/.mozilla/firefox/xxxxxxx.xxxx プロファイル情報
   shortcut allmail +[Gmail]/All\ Mail
   shortcut spam +[Gmail]/Spam
   shortcut trash +[Gmail]/Trash
}
標準では無いmailxを起動するように.bashrcの最終行に定義の追加 念のために、PAGERの定義も追加する。
# vi .bashrc

alias mailx=/usr/local/bin/mailx
export PAGER=/usr/bin/less

4. Gmailの参照

下記のコマンドでGmailを読みます。
# mailx -A gmail <---- gmailは、.mailrc で定義したアカウント
正しく設定されていれば、メールの一覧が表示されます。
mailxコマンドのオプション詳細

postfixの設定

1. Gmailに接続するには、認証用のモジュールをインストールする。

下記をインストール
# yum install cyrus-sasl
# yum install /usr/bin/c_rehash

2. Gmailの証明書を取得

# openssl s_client -connect smtp.gmail.com:465 -showcerts
0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=smtp.gmail.com
i:/C=US/O=Google Inc/CN=Google Internet Authority
ここから
-----BEGIN CERTIFICATE-----

-----END CERTIFICATE-----
ここまでを/etc/pki/tls/certs/google.pem として保存
1 s:/C=US/O=Google Inc/CN=Google Internet Authority
i:/C=US/O=Equifax/OU=Equifax Secure Certificate Authority
ここから
-----BEGIN CERTIFICATE-----

-----END CERTIFICATE-----
ここまでを/etc/pki/tls/certs/euqifax.pem として保存

3. postfixの認証データを作成

証明書をハッシュ化
# c_rehash /etc/pki/tls/certs
Doing /etc/pki/tls/certs
google.pem => xxxxxxxx.0
euqifax.pem => xxxxxxxx.0

4. gmailのパスワードファイル作成

# vi /etc/postfix/auth_passwd
[smtp.gmail.com]:587 xxxxx@gmail.com:password
# chown root. /etc/postfix/auth_passwd
# chown 600 /etc/postfix/auth_passwd
ファイルを更新したらpostmapを必ず実行
# postmap /etc/postfix/auth_passwd

5. Postfixの設定

# vi /etc/postfix/main.cf
relayhost = [smtp.gmail.com]:587
smtp_use_tls = yes

smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/auth_passwd
smtp_sasl_security_options = noanonymous
smtp_sasl_mechanism_filter = plain

smtp_tls_CApath = /etc/pki/tls/certs
smtpd_tls_session_cache_database = btree:/var/lib/postfix/smtpd_scache
Postfixを再起動
# /etc/init.d/postfix restart これで設定は終了です。

6. メール送信の確認

# mailx -s "TEST"  XXXXX@gmail.com
テストメールを送ってみます。
.
EOF

エラーがないか/var/log/maillogを確認ください。
# tail -1000 /var/log/maillog

参考URL

2012年7月18日水曜日

Windows Azure その後

Linux関連の資料は、出たばかりのようで日本語になっている資料が少ない。
どのみち、英語の資料を見る必要があるので、ついでに機械翻訳で
ざっくりとした資料作成をしてから、取りかかろうかと考えています。
ライセンス的に問題がないか?
お世話になった方に、問い合わせ中。
それと平行して、Windows Azure web siteのサービスを利用すべく
設定開始しましたが、ftp転送のところでログインできない現象が。。。。
道のりは遠い!