分散イメージホスティングの改善。 このパートでは、アプリケーションの構成とcsrf保護の有効化について説明します。 次に、画像のサムネイルを作成する例を使用して、ブロッキングタスクを操作し、コルーチンを並行して実行し、それらで発生する例外を処理する方法を学習します。
アプリケーション構成
Applicationコンストラクターは、キーワード引数付きの構成パラメーターを受け入れます。 Applicationコンストラクターの2番目のパラメーター
debug=True
を渡すことで、これに既に遭遇しています。 ただし、このような設定をハードコーディングしないでください。そうでない場合、本番環境でスクリプトを実行する方法(このパラメーターは明らかに
False
する必要があります) djangoおよびその他のpythonフレームワークの標準的なトリックは、一般的な構成を
settings.py
ファイルに保存し、その最後に
settings_local.py
をインポートして、この環境に固有の
settings_local.py
を上書きすることです。 もちろん、このトリックをうまく使用できますが、
tornadoにはコマンドラインオプションを使用して特定の設定を変更する機能があります。 これがどのように実装されているか見てみましょう:
from tornado.options import define, options define('port', default=8000, help='run on the given port', type=int) define('db_uri', default='localhost', help='mongodb uri') define('db_name', default='habr_tornado', help='name of database') define('debug', default=True, help='debug mode', type=bool) options.parse_command_line() db = motor.MotorClient(options.db_uri)[options.db_name]
define
を使用
define
optparse構文でパラメーターを定義します。 そして、適切な場所で、オプションを使用してそれらを取得します。
options.parse_command_line()
を呼び出すことにより、コマンドラインからのデータでデフォルトのパラメーター値を上書きします。 つまり、実稼働
--debug=False
では、
--debug=False
パラメーターを使用してアプリケーションを開始するだけで十分です。
--help
オプションで開始すると、可能なすべてのオプションが表示されます。
$python3 app.py --help Usage: app.py [OPTIONS] Options: --db_name name of database (default habr_tornado) --db_uri mongodb uri (default localhost) --debug debug mode (default True) --help show this help information --port run on the given port (default 8000) /home/imbolc/.pyenv/versions/3.4.0/lib/python3.4/site-packages/tornado/log.py options: --log_file_max_size max size of log files before rollover (default 100000000) --log_file_num_backups number of log files to keep (default 10) --log_file_prefix=PATH Path prefix for log files. Note that if you are running multiple tornado processes, log_file_prefix must be different for each of them (eg include the port number) --log_to_stderr Send log output to stderr (colorized if possible). By default use stderr if --log_file_prefix is not set and no other logging is configured. --logging=debug|info|warning|error|none Set the Python log level. If 'none', tornado won't touch the logging configuration. (default info)
ご覧のとおり、竜巻は自動的にログオプションを追加しました。
CSRF
次に、アプリケーション設定に
xsrf_cookies=True
を追加します。 新しい画像をアップロードしようとすると、エラー:
HTTP 403: Forbidden ('\_xsrf' argument missing from POST)
ます。 これは
csrf保護を機能させました。 アプリケーションを復元するには
{% module xsrf_form_html() %}
ダウンロードフォームに
{% module xsrf_form_html() %}
追加するだけで、ページのHTMLコードで次のようになります。
<input type="hidden" name="_xsrf" value="2|a52d8046|a83cbd25c8b7c06e2c3ac476338982d8|1406302123"/>
。
画像のサムネイル
最近の画像のリストにサムネイルを表示するときは、簡単にするために完全な画像を使用しました。 この瞬間を修正する時が来ました。
枕が必要になります(これは、PILのモダンなフォークで、画像を扱うための有名なライブラリです)。
pip3 install pillow
ただし、竜巻はシングルスレッドであり、画像処理などのリソースを大量に消費する操作は、非同期ですべてのダンスを無効にします。 最も簡単な解決策は、このタスクを別のスレッドに入れることです。
import os import io from concurrent.futures import ThreadPoolExecutor from PIL import Image class UploadHandler(web.RequestHandler): executor = ThreadPoolExecutor(max_workers=os.cpu_count()) @gen.coroutine def post(self): file = self.request.files['file'][0] try: thumbnail = yield self.make_thumbnail(file.body) except OSError: raise web.HTTPError(400, 'Cannot identify image file') orig_id, thumb_id = yield [ gridfs.put(file.body, content_type=file.content_type), gridfs.put(thumbnail, content_type='image/png')] yield db.imgs.save({'orig': orig_id, 'thumb': thumb_id}) self.redirect('') @run_on_executor def make_thumbnail(self, content): im = Image.open(io.BytesIO(content)) im.convert('RGB') im.thumbnail((128, 128), Image.ANTIALIAS) with io.BytesIO() as output: im.save(output, 'PNG') return output.getvalue()
最初に、CPUコアの数に制限のあるワーカープールを作成します(これは、画像処理などのプロセッサを集中的に使用するタスクに最適です)。 さらに多くの画像が同時にロードされると、残りは順番に待機します。 次に、
run_on_executorデコレータでラップされた
make_thumbnail
メソッドを呼び出してサムネイルを非同期に作成します。これにより、タスクがいずれかのexecutorスレッドで実行されます。
イメージ形式を認識できない場合に枕をスローする
OSError
例外をどれほど美しくキャッチできるかに注目してください。 コールバックが非同期の場合(node.jsなど)に行われるため、応答でエラーを明示的に渡す必要はありません。 シンプルで、例外を同期スタイルで処理します。
次に、元の画像とサムネイルを
gridfsに保存します。 順番に呼び出す代わりに:
orig_id = yield gridfs.put(file.body, content_type=file.content_type) thumb_id = yield gridfs.put(thumbnail, content_type='image/png')
並列
orig_id, thumb_id = yield [ ... ]
を使用し
orig_id, thumb_id = yield [ ... ]
。 つまり、ファイルは同時に保存されます。 このようなcorutinの並列呼び出しは、相互に依存しない操作には意味があります。 たとえば、サムネイルの作成とオリジナルの保存を組み合わせることができますが、2番目の操作は最初の結果に依存するため、サムネイルの作成と保存を組み合わせることはできません。
imgs
画像情報を
imgs
コレクションに保存します。 このコレクションは、サムネイルと元の画像をリンクするために必要です。 また、将来的には、作成者、アクセス権など、画像に関するあらゆる情報を保存できます。 このコレクションの登場により、リストと個々の画像を表示する方法はそれに応じて変わります。
class UploadHandler(web.RequestHandler): ... @gen.coroutine def get(self): imgs = yield db.imgs.find().sort('_id', -1).to_list(20) self.render('upload.html', imgs=imgs) class ShowImageHandler(web.RequestHandler): @gen.coroutine def get(self, img_id, size): try: img_id = bson.objectid.ObjectId(img_id) except bson.errors.InvalidId: raise web.HTTPError(404, 'Bad ObjectId') img = yield db.imgs.find_one(img_id) if not img: raise web.HTTPError(404, 'Image not found') gridout = yield gridfs.get(img[size]) self.set_header('Content-Type', gridout.content_type) self.set_header('Content-Length', gridout.length) yield gridout.stream_to_handler(self)
ご覧のとおり、
ShowImageHandler.get
は追加のパラメーター
size
を受け取ります。これは、画像のサムネイルを取得するか、オリジナルのサムネイルを取得するかを指定します。 URLの規則性はそれに応じて変更されました。
web.url(r'/imgs/([\w\d]+)/(orig|thumb)', ShowImageHandler, name='show_image'),
そして、これらのURLをテンプレートに復元します:
{% for img in imgs %} <a href="{{ reverse_url('show_image', img['_id'], 'orig') }}"> <img src="{{ reverse_url('show_image', img['_id'], 'thumb') }}"> </a> {% end %}
おわりに
今日はこれで
終わりです。この部分と
前の部分のコードは
githubで入手できます。