1. Atsushi Odagiri
  2. translations

Source

translations / do-it-yourself-framework_ja.txt

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
A Do-It-Yourself Framework
++++++++++++++++++++++++++

:author: Ian Bicking <ianb@colorstudy.com>
:translator: Atsushi Odagiri <aodagx@gmail.com>
:revision: $Rev: 7181 $
:date: $LastChangedDate: 2008-01-08 09:46:01 +0900 (火, 08  1 2008) $
:original: http://pythonpaste.org/do-it-yourself-framework.html

.. contents::

.. comments:

   Explain SCRIPT_NAME/PATH_INFO better

Introduction and Audience
=========================

この短いチュートリアルは、Pasteが可能にし、推奨するアーキテクチャの例です。
また、WSGIについても軽く触れます。

これは、Pasteのすべてに関する紹介ではありません。
説明しているのはほんの一部です。
また、すべての人に、フレームワークを乗り換え、自らのフレームワークを作成することを推奨するものでもありません。(正直なところそうなっても気にしません)。
目的は、この文書を読み終えた方が、このアーキテクチャを用いたフレームワークをより快適に使えるようになることと、覆い隠された内部をより理解することにあります。

What is WSGI?
=============

最も単純言えば、WSGIとは、WebサーバーとWebアプリケーション間のインターフェイスです。
以下で説明するのは、WSGIの構造についです。
上位のレベルから見れば、WSGIはwebリクエストを非常に公平で公式な方法で、コードに引き渡します。
しかし、それだけではありません!
WSGIは、単なるHTTP以上のものなのです。
これらの内容は、HTTPとほんの *ささいな* 違いにしか見えないでしょう。
しかし、この小さな違いが重要なのです。:


* CGIのように環境をコードに引き渡します。 ``REMOTE_USER`` などのデータもsecureに引き渡されます。

* CGIのような環境が、もっと多くのコンテキストと渡されます。特に、1つのパスではなく2つのパスが渡されます。 ``SCRIPT_NAME`` と ``PATH_INFO`` です。
  ``SCRIPT_NAME`` はどのようにしてここまできたか、 ``PATH_INFO`` は、なにが残っているかを表しています。

* 独自の拡張をWSGI環境に置くことができます。また通常はそうすべきです。
  コールバックや追加情報など、あなたが望むあらゆるPythonオブジェクトを置くことが許されています。
  これらは、HTTPヘッダーに置くことができないものです。

これらの特徴により、WSGIはWebサーバーとアプリケーションの間を取り持つだけでなく、すべてのレベルでのやりとりが可能になります。
つまり、Webアプリケーションを再利用可能なカプセル化したライブラリのように扱えるようになり、機能を再利用可能にします。

Writing a WSGI Application
==========================

最初に説明するのは、
`WSGI
<http://www.python.org/peps/pep-0333.html>`_ の初歩的な使い方です。
ここで簡単に説明しますが、WSGIの規格を参照してください。


* *WSGI アプリケーション* を書こうとしています。これは、要求に応じるオブジェクトです。
  アプリケーションは単に ``environ`` と ``start_response`` の2つの引数を受け取る呼び出し可能(関数のようなもの)なオブジェクトです。

* 環境は、 ``REQUEST_METHOD``, ``HTTP_HOST`` など、CGI環境変数を持っています。

* また、 ``wsgi.input`` (POSTリクエストの入力内容) のような特殊なキーも持っています。

* ``start_response`` は、レスポンスを返し始めるための関数です。
  ここで、ステータスやヘッダを与えます。

* 最終的に、アプリケーションは、レスポンスボディをイテレータで返します。
  (通常は、複数の文字列のリストか、すべてをまとめた1つだけの文字列を含むリストになります。)


そして、一番簡単なアプリケーションは以下のようになります::

    def app(environ, start_response):
        start_response('200 OK', [('content-type', 'text/html')])
        return ['Hello world!']

しかし、これではまだ不十分です。
想像がつくと思いますが、これではブラウザでアクセスできません。

良い方法はたくさんありましが、このチュートリアルでは良い方法ではなく分かりやすい方法を使います。

なので、以下の文をファイルの末尾に付け加えるだけにしましょう。::

    if __name__ == '__main__':
        from paste import httpserver
        httpserver.serve(app, host='127.0.0.1', port='8080')

そして、http://localhost:8080 を確認してみましょう。
このアプリケーションが動いています。
WSGIサーバーの動作を理解したければ、WSGI規格の `CGI WSGI server
<http://www.python.org/peps/pep-0333.html#the-server-gateway-side>`_  を確認することをお勧めします。

An Interactive App
------------------

さきほどのアプリケーションはとりたてて興味深いものではありませんでした。
最低限の対話性を付け加えてみましょう。
そうするには、フォームを表示し、そのフォームフィールドをパースするようにします。::

    from paste.request import parse_formvars

    def app(environ, start_response):
        fields = parse_formvars(environ)
        if environ['REQUEST_METHOD'] == 'POST':
            start_response('200 OK', [('content-type', 'text/html')])
            return ['Hello, ', fields['name'], '!']
        else:
            start_response('200 OK', [('content-type', 'text/html')])
            return ['<form method="POST">Name: <input type="text" '
                    'name="name"><input type="submit"></form>']

``parse_formvars`` 関数は、WSGI環境を受け取り、
`cgi <http://python.org/doc/current/lib/module-cgi.html>`_ モジュール(``FieldStorage`` クラス)
を呼び出して、MultiDictにつめて返します。

Now For a Framework
===================

これまでの内容は少々粗雑に感じるでしょう。
このままでは、REQUEST_METHODのようなものをもっと判定しなくてはならなくなりますし、
複数ページを取り扱う方法が分かりません。

一般的なアプリケーションのためのフレームワークが欲しくなります。
このチュートリアルでは、*オブジェクトパブリッシャー* を実装します。
これは、ZopeやQuixote、Cherrypy で見たことがあるかもしれません。

Object Publishing
-----------------

典型的なPythonオブジェクトパブリッシャーは、 ``/`` を ``.`` に変換します。
つまり、 ``/articles/view?id=5`` は、 ``root.articles.view(id=5)`` と変換されます。

もちろん、なんらかのルートオブジェクトが必要なので、それを渡すようにします。

    class ObjectPublisher(object):

        def __init__(self, root):
            self.root = root

        def __call__(self, environ, start_response):
            ...

    app = ObjectPublisher(my_root_object)

``__call__`` をオーバーライドするのは、
``ObjectPublisher`` のインスタンスを関数のように呼び出し可能なWSGIアプリケーションにするためです。
このあとやらなくてはならないのは、 ``environ`` を表示しようとしているものに変換してWSGIのレスポンスとして返すことです。

The Path
--------

WSGIは要求されたパスを ``SCRIPT_NAME`` と ``PATH_INFO`` の2つの変数にします。
``SCRIPT_NAME`` は *たどり着く* のに使われたものです。
``PATH_INFO`` は、まだ残されているものです。
フレームワークがオブジェクトを発見するために使うのは、この部分です。
この2つを一緒にすると、今リクエストされているフルパスになります。
これは、正しいURLを作成するのに役立ちます。
この状態を必ず守るようにします。

そして、これが ``__call__`` の実装です。::

    def __call__(self, environ, start_response):
        fields = parse_formvars(environ)
        obj = self.find_object(self.root, environ)
        response_body = obj(**fields.mixed())
        start_response('200 OK', [('content-type', 'text/html')])
        return [response_body]

    def find_object(self, obj, environ):
        path_info = environ.get('PATH_INFO', '')
        if not path_info or path_info == '/':
            # We've arrived!
            return obj
        # PATH_INFO always starts with a /, so we'll get rid of it:
        path_info = path_info.lstrip('/')
        # Then split the path into the "next" chunk, and everything
        # after it ("rest"):
        parts = path_info.split('/', 1)
        next = parts[0]
        if len(parts) == 1:
            rest = ''
        else:
            rest = '/' + parts[1]
        # Hide private methods/attributes:
        assert not next.startswith('_')
        # Now we get the attribute; getattr(a, 'b') is equivalent
        # to a.b...
        next_obj = getattr(obj, next)
        # Now fix up SCRIPT_NAME and PATH_INFO...
        environ['SCRIPT_NAME'] += '/' + next
        environ['PATH_INFO'] = rest
        # and now parse the remaining part of the URL...
        return self.find_object(next_obj, environ)

これで、フレームワークを獲得しました。

Taking It For a Ride
--------------------

では、小さなアプリケーションを書いてみましょう。
さきほどの ``ObjectPublisher`` クラスは、 ``objectpub`` モジュールにあります。::

    from objectpub import ObjectPublisher

    class Root(object):

        # The "index" method:
        def __call__(self):
            return '''
            <form action="welcome">
            Name: <input type="text" name="name">
            <input type="submit">
            </form>
            '''

        def welcome(self, name):
            return 'Hello %s!' % name

    app = ObjectPublisher(Root())

    if __name__ == '__main__':
        from paste import httpserver
        httpserver.serve(app, host='127.0.0.1', port='8080')

これだけです!
ただ、ちょっと待ってください。
まだ大きな問題が残っています。
例えば、ヘッダーを設定するにはどうすればいいのでしょう?
``404 Not Found`` を返す代わりに、アトリビュートエラーとしたい場合もあるでしょう。
ここから少しずつ修正していきましょう。

Give Me More!
-------------

なにかが足りないと感じていることでしょう。

特に、ヘッダを設定する方法がありませんし、リクエストの情報が少ししかありません。

::

    # これは、単なるdictのようなオブジェクトで、ケースセンシティブなキーを持ちます。
    from paste.response import HeaderDict

    class Request(object):
        def __init__(self, environ):
            self.environ = environ
            self.fields = parse_formvars(environ)

    class Response(object):
        def __init__(self):
            self.headers = HeaderDict(
                {'content-type': 'text/html'})


では、ちょっとしたトリックを説明しましょう。(原文ではeachとなっているが、teachの間違い?)
メソッドのシグネチャを変えたくはありません。
しかし、リクエストとレスポンスのオブジェクトを普通のグローバル変数に置くわけにはいきません。
なぜなら、スレッドセーフにしたいからです。
全てのスレッドが同じグローバル変数を見てしまうからです。(たとえそれが、違うリクエストの処理だったとしても)

しかし、Python2.4で、"スレッドローカル値"の概念が導入されました。
あるスレッドからしかアクセスできない値です。
これは、 `threading.local <http://docs.python.org/lib/module-threading.html>`_ にあります。
``local`` インスタンスを作成したら、それに設定したすべてのアトリビュートは、
設定したスレッドからしかアクセスできないようになります。
では、リクエストとレスポンスのオブジェクトを設定するようにしましょう。

まず、 ``__call__`` 関数が以下のようになっていることを思い出しましょう。::

    class ObjectPublisher(object):
        ...

        def __call__(self, environ, start_response):
            fields = parse_formvars(environ)
            obj = self.find_object(self.root, environ)
            response_body = obj(**fields.mixed())
            start_response('200 OK', [('content-type', 'text/html')])
            return [response_body]

このように変更します。::

    import threading
    webinfo = threading.local()

        def __call__(self, environ, start_response):
            webinfo.request = Request(environ)
            webinfo.response = Response()
            obj = self.find_object(self.root, environ)
            response_body = obj(**dict(webinfo.request.fields))
            start_response('200 OK', webinfo.response.headers.items())
            return [response_body]


メソッドでは、このように使えます。::

    class Root:
        def rss(self):
            webinfo.response.headers['content-type'] = 'text/xml'
            ...

その気になれば、 `cookies
<http://python.org/doc/current/lib/module-Cookie.html>`_ のハンドルもこれらのオブジェクトでできます。
しかし、今回はそこまでやらないことにします。
フレームワークができました。

WSGI Middleware
===============

`Middleware
<http://www.python.org/peps/pep-0333.html#middleware-components-that-play-both-sides>`_ WSGIやPasteの中で、Middlwareはややとっつきにくく感じるでしょう。

ミドルウェアは何ものでしょう?
ミドルウェアは、仲介者として働くソフトウェアです。


早速1つ書いてみましょう。
あなたの挨拶を誰からでも見られてしまわないように、認証ミドルウェアを書いてみます。


HTTP認証を使いましょう。人々を少しだけけむに巻くことができます。
HTTP認証は非常に簡単です。:

* 認証が必要になったら、 ``401 Authentication
  Required``  ステータスを、 ``WWW-Authenticate: Basic realm="This
  Realm"`` ヘッダーとともに、返します。

* クライアントは、 ``Authorization: Basic
  encoded_info`` ヘッダーを送り返します。

* この "encoded_info" は、 ``username:password`` をbase-64エンコーディングしたものです。

どうすれば実現できるでしょう?
そう、"middleware"を書きます。
つまり、リクエストを違うアプリケーションに渡してしまいます。
リクエスト変更したり、レスポンスを変更したりできます。
ただし、この場合は、ときにリクエストを渡さないようにするだけです。
(401レスポンスを返したい場合など)


とても簡単なミドルウェアの例です。
レスポンスを大文字にしています。::

    class Capitalizer(object):

        # We generally pass in the application to be wrapped to
        # the middleware constructor:
        def __init__(self, wrap_app):
            self.wrap_app = wrap_app

        def __call__(self, environ, start_response):
            # We call the application we are wrapping with the
            # same arguments we get...
            response_iter = self.wrap_app(environ, start_response)
            # then change the response...
            response_string = ''.join(response_iter)
            return [response_string.upper()]

技術的には、完全に正しい方法というわけではありません。
なぜなら、レスポンスボディを返す方法は2通りあるからです。
しかし、詳細には立ち入らないことにします。

`paste.wsgilib.intercept_output
<http://pythonpaste.org/module-paste.wsgilib.html#intercept_output>`_ は、
この部分をきちんと実装したものです。

それでは、いくらか使い物になるコードです。認証::


    class AuthMiddleware(object):

        def __init__(self, wrap_app):
            self.wrap_app = wrap_app

        def __call__(self, environ, start_response):
            if not self.authorized(environ.get('HTTP_AUTHORIZATION')):
                # Essentially self.auth_required is a WSGI application
                # that only knows how to respond with 401...
                return self.auth_required(environ, start_response)
            # But if everything is okay, then pass everything through
            # to the application we are wrapping...
            return self.wrap_app(environ, start_response)

        def authorized(self, auth_header):
            if not auth_header:
                # If they didn't give a header, they better login...
                return False
            # .split(None, 1) means split in two parts on whitespace:
            auth_type, encoded_info = auth_header.split(None, 1)
            assert auth_type.lower() == 'basic'
            unencoded_info = encoded_info.decode('base64')
            username, password = unencoded_info.split(':', 1)
            return self.check_password(username, password)

        def check_password(self, username, password):
            # Not very high security authentication...
            return username == password

        def auth_required(self, environ, start_response):
            start_response('401 Authentication Required',
                [('Content-type', 'text/html'),
                 ('WWW-Authenticate', 'Basic realm="this realm"')])
            return ["""
            <html>
             <head><title>Authentication Required</title></head>
             <body>
              <h1>Authentication Required</h1>
              If you can't get in, then stay out.
             </body>
            </html>"""]



どうやって使えばいいでしょう?
::

    app = ObjectPublisher(Root())
    wrapped_app = AuthMiddleware(app)

    if __name__ == '__main__':
        from paste import httpserver
        httpserver.serve(wrapped_app, host='127.0.0.1', port='8080')


これでミドルウェアもできました! ヒャッホー!

Give Me More Middleware!
------------------------

自分で作ったもの以外にも、他の人々が作ったミドルウェアを使うのも簡単です。
自分で作る必要はありません。
ここまで読んできたのであれば、恐らく何回か例外に出会っているでしょう。
そして、コンソールに表示されている例外レポートを目の当たりにしているに違いありません。
少し簡単にしましょう、ブラウザ上で例外を確認できるようにします。

::

    app = ObjectPublisher(Root())
    wrapped_app = AuthMiddleware(app)
    from paste.exceptions.errormiddleware import ErrorMiddleware
    exc_wrapped_app = ErrorMiddleware(wrapped_app)

簡単ですね!しかし、 *もっと* いいものが欲しいなら...

::

    app = ObjectPublisher(Root())
    wrapped_app = AuthMiddleware(app)
    from paste.evalexception import EvalException
    exc_wrapped_app = EvalException(wrapped_app)

エラーを発生させてみましょう。
ちいさな + をクリックしてください。
ボックスの中に何か入力してみましょう。

Configuration
=============

フレームワークとアプリケーションを作成しました。
(私が示してきたものよりもはるかにいいものになっていると確信しています。)
これらを手でつなぎ合わせるのは、幾分粗雑と感じるかもしれません。

そうでなくても、誰かが他の場所にインストールしたり、違う構成にしたりするときに、困ることでしょう。

だから、アプリケーションの構成から、設定を分離しておきたいのです。

What's Next?
============

まだ続きがあります, 今後構成についても話す予定です。(`Paste Deploy
<http://pythonpaste.org/deploy/>`_ を使います)。
そして、パッケージングやプラグインについても短い紹介ができたらいいと思います。
そのときは、 `私のブログ <http://blog.ianbicking.org>`_ で、告知します。