twistedその3

上位プロキシ。上位プロキシにはProxomitronを使った。
ソースは
http://lab.hde.co.jp/python/twisted/
で紹介されているものほぼそのままデス。
だいぶショートカットできました。感謝です。

from twisted.web import http, proxy
from twisted.internet import reactor
from twisted.python import log
import urlparse

PROXY_HOST='localhost'
PROXY_PORT=8080

class UpperProxyRequest(proxy.ProxyRequest):
    def process(self):
        parsed = urlparse.urlparse(self.uri)
        protocol = parsed[0]
        host = parsed[1]
        print self.method

        port = self.ports[protocol]
        if ':' in host:
            host, port = host.split(':')
            port = int(port)
        rest = urlparse.urlunparse(('', '') + parsed[2:])
        if not rest:
            rest = rest + '/'
        class_ = self.protocols[protocol]
        headers = self.getAllHeaders().copy()
        if 'host' not in headers:
            headers['host'] = host
        self.content.seek(0, 0)
        s = self.content.read()

        clientFactory = class_(
                self.method, self.uri, self.clientproto, headers, s, self)
        assert(PROXY_HOST)
        assert(PROXY_PORT)
        self.reactor.connectTCP(PROXY_HOST, PROXY_PORT, clientFactory)


proxy.Proxy.requestFactory=UpperProxyRequest


if __name__=="__main__":
    import sys
    log.startLogging(sys.stdout)

    f=http.HTTPFactory()
    f.protocol=proxy.Proxy

    reactor.listenTCP(10080, f)
    reactor.run()
+-------+
|reactor|
+-------+---------+  +-----+  +-----------------+
+10080 HTTPFactory|->|Proxy|->|UpperProxyRequest|
+-----------------+  +-----+  +----------------------------------+
A                                   |process-> ProxyClientFactory|
|                                   +------------------V---------+------+
+-------+                           |ProxyClient 上位Proxyから取ってくる|
|browser|                           +-----------------------------------+
+-------+

ProxyClientが地味に優秀で直接でもProxy経由でもどっちでも取ってこれるようだ。

twistedその1

やっと使い方がわかってきたのでメモ。
twistedはイベントドリブンなネットワークフレームワークとか説明されるのだが、最初は何のことか分からんわけであります。
何かすごそうな気がするのだが妙に敷居が高い(3回は習得が頓挫している)。
twistedの部品を組み合わせてfilter串を作るまでの順を追ってみることにした。

HTTPFactory

とりあえずコード。
まずはproxyにする前に通常のHTTPアクセスに応答するサーバを作る。実行してからブラウザでhttp://localhost:10080にアクセスする。

from twisted.web import http
from twisted.internet import reactor
from twisted.python import log


class MyRequest(http.Request):
    def process(self):
        self.write("Hello")
        self.finish()


if __name__=="__main__":
    import sys
    log.startLogging(sys.stdout)

    f=http.HTTPFactory()
    f.protocol.requestFactory=MyRequest

    reactor.listenTCP(10080, f)
    reactor.run()

10080ポートをリッスンしてリクエストが来たらHelloとだけ返すようにした。
ヘッダまで含めると以下のように応答する。

HTTP/1.1 200 OK
Transfer-Encoding: chunked

Hello

ちゃんとHTTP/1.1に対応しているみたいだ(proxomitronで見た)。


main部のソースの意味は最初の2行はログ出力の設定なので気にしない。
あとはFactoryを継承したクラスのオブジェクトを作って、reactorでポートを指定してListenするというtwistedの定石通りなのだが、

    f.protocol.requestFactory=MyRequest

という部分が込み入っている。この辺にtwistedのとっつき難さがあらわれている。twistedを使いこなすにはリファレンスを読むだけでは不十分で、ソースを読んで適当なクラス変数をカスタムのクラスで置き換えるというまさに上記のような使い方がどうしても必要になる。個々のソースは短いので読み易いのだが、クラスとオブジェクトが絡み合っていて混乱する。紙に呼び出し関係を書いて理解しましたとも。


その1ではHTTPFactoryでHTTPサーバを作って、中身はRequest#processで何とかするという枠組みを見た。

twistedその2

ただのproxy。

from twisted.web import http, proxy
from twisted.internet import reactor
from twisted.python import log


if __name__=="__main__":
    import sys
    log.startLogging(sys.stdout)

    f=http.HTTPFactory()
    f.protocol=proxy.Proxy

    reactor.listenTCP(10080, f)
    reactor.run()

前より簡単になってRequestを継承したクラスが消滅した。proxy.Proxy(http.HTTPChannel)がrequestFactoryとしてProxyRequestを保持しているからそのまま利用できるわけです。

+-------+
|reactor|
+-------+---------+  +-----------+  +---------+
+10080 HTTPFactory|->|HTTPChannel|->|MyRequest|
+-----------------+  +-----------+  +---------+-------------+
A                                   |process->write("Hello")|
|                                   +-----------------------+
+-------+
|browser|
+-------+

+-------+
|reactor|
+-------+---------+  +-----+  +------------+
+10080 HTTPFactory|->|Proxy|->|ProxyRequest|
+-----------------+  +-----+  +------------+---------------------+
A                                   |process-> ProxyClientFactory|
|                                   +------------------V---------++
+-------+                           |ProxyClient urlから取ってくる|
|browser|                           +-----------------------------+
+-------+

となった。たぶん