実装の解説
では、この非常に単純なクラスを見ていきましょう。
class fsPicture(object):
def __init__(self, root, template=base_template):
self.template = template
self.root = root
def split_path_from_item(self, item):
"""removes the root directory from the path.
This lets us use the result as a web path."""
return "/" + item.replace(self.root, '')
def directory_listing(self, directory, path):
"""returns html for a directory listing"""
files = ""
directories = ""
for item in glob(directory + "/*"):
web_path = self.split_path_from_item(item)
if os.path.isdir(item):
directories += """<div class="directory">
<a href="%s" class="directory">%s
</a></div>""" %
(web_path, web_path)
elif os.path.isfile(item) and
item.lower().endswith('.jpg'):
files += """<div class="image">
<img src="%s?thumbnail=200"><br/>
<a href="%s">%s</a>
</div>""" % (web_path, web_path, web_path)
html = ""
if directories:
html += """<h2>Directories</h2>
<div id="directories">%s</div>""" % directories
if files:
html += """<h2>Pictures</h2>
<div id="pictures">%s</div>""" % files
return html
def picture(self, image, path):
"""returns raw binary image. If query string of "thumbnail"
is passed to the app the image is resized to a maximum of
the argument. For instance: /some_image.jpg?thumbnail=100"""
i = Image.open(image)
if self.query_string:
try:
size = cgi.parse_qs(self.query_string)
size = size['thumbnail'][0]
i.thumbnail((int(size), int(size)))
except:
pass
s = StringIO()
i.save(s, 'JPEG')
return s.getvalue()
def find_object(self, path):
"""finds the directory or picture referenced, returns the
response and the mimetype"""
item = os.path.join(self.root, *path.split('/'))
if os.path.isdir(item):
return ([self.template %
self.directory_listing(item, path),], 'text/html')
elif os.path.isfile(item) and item.lower().endswith('.jpg'):
return ([self.picture(item, path),], 'image/jpeg')
else:
return ([self.template % 'not found'], 'text/html')
def __call__(self, environ, start_response):
"""the entry point to the application"""
self.query_string = environ.get('QUERY_STRING', False)
response, mimetype = self.find_object(environ['PATH_INFO'])
start_response('200 OK', [('content-type',mimetype)])
return response
__call__はWSGIサーバーが呼び出すメインエントリポイントです。__call__は、WSGI呼び出し可能関数のように引数としてenvironとstart_responseをとり、オブジェクト必須のself引数もとります。また、オブジェクトプロパティquery_stringをenvironのQUERY_STRING値に設定しますが、これはURL内の?の後ろに続くすべてのものです。後でこれを使用して、写真のサイズを変更すべきかどうかを判断します。オブジェクトメソッドfind_objectの呼び出しから応答(バイナリJPEGまたはHTML)とMIME Typeを取得したら、そこにenviron変数PATH_INFOを渡します。これはURL内のドメインの後ろに続き、かつ?の前にあるすべてのものです。
find_objectの役目は、PATH_INFOをファイルシステム上のオブジェクトに変換することです。この変換は実はオブジェクトの__init__メソッドの中で始まります。これがルートプロパティの設定されるところです。この場合、ルートとはファイルシステム上のベースディレクトリ(すべてのjpegを検索したい場所)のことです。また、引数としてテンプレートをとるか、上で宣言されたベーステンプレート(単に巨大な文字列)を使用します。find_objectが呼び出されると、Webパスとルートプロパティ(すべてのjpegが入っているファイルシステム上のディレクトリ)とを結合し、参照されたファイルがディレクトリかjpegかを判定します。
そして、ファイルがディレクトリなら、directory_listingを呼び出して、ファイルシステム上のそのディレクトリとWebパスを渡し、そのディレクトリの内容をリストした大きなHTMLを取得します。ファイルがjpegなら、その呼び出しをpictureに引き渡します。pictureは"image/jpeg"というMIME Typeと、jpegからのraw jpegエンコードバイナリデータを返します。ユーザーがクエリ文字列 "thumbnail" を渡した場合は、指定の制約に合わせて実行時にイメージのサイズが変更されます。これにより、ディレクトリ内の各写真の小さなサムネールが得られます。
まとめ
実際のところ、小さなWebアプリケーションを作成する場合以外は、DjangoやPylonsといった既存のWebフレームワークを使用したほうがよいでしょう(この2つのフレームワークは、それ自体でWSGIアプリケーションとして機能します)。とはいえ、高レベルのフレームワークではなく、WSGIでアプリケーションを作成すれば、自分が選んだPython Webフレームワークで何が行われているのか知ることができます。
本稿では、WSGIによるWebアプリケーションの作成に焦点を当て、WSGIミドルウェアには触れませんでした。WSGIミドルウェアでは、アプリケーションによるサーバー要求の処理の前または後にコードを挿入できます。WSGIで複雑なアプリケーションを本気で作成するつもりなら、Paste(Ian Bicking作の、一般的なWebパターンをクリーンなAPIでラップしたライブラリ集)とWebOb(やはりIan Bicking作の、Paste上に構築された最小限のフレームワーク)を検討してください。IanもWebObの使用例として、同じようなファイルサービングアプリケーションを書いています。
