目次

前のトピックへ

18.6. asyncore — 非同期ソケットハンドラ

次のトピックへ

19. インターネット上のデータの操作

このページ

18.7. asynchat — 非同期ソケットコマンド/レスポンスハンドラ

asynchat を使うと、 asyncore を基盤とした非同期なサーバ・クライアントをより簡単に開発する事ができます。 asynchat では、プロトコルの要素が任意の文字列で終了するか、または可変長の文字列であるようなプロトコルを容易に制御できるようになってい ます。 asynchat は、抽象クラス async_chat を定義してお り、 async_chat を継承して collect_incoming_data() メソッド と found_terminator() メソッドを実装すれば使うことができます。 async_chatasyncore は同じ非同期ループを使用してお り、 asyncore.dispatcherasynchat.async_chat も同じチャネ ルマップに登録する事ができます。通常、 asyncore.dispatcher はサーバチャネルとして使用し、リクエストの受け付け時に asynchat.async_chat オブジェクトを生成します。

class asynchat.async_chat

このクラスは、 asyncore.dispatcher から継承した抽象クラスです。 使用する際には async_chat のサブクラスを作成し、 collect_incoming_data()found_terminator() を定義し なければなりません。 asyncore.dispatcher のメソッドを使用する事 もできますが、メッセージ/レスポンス処理を中心に行う場合には使えないメソッドもあります。

asyncore.dispatcher と同様に、 async_chatselect() 呼出し後のソケットの状態からイベントを生成します。ポーリングループ開始後、イベント処理フレームワークが自動的に async_chat のメソッドを呼び出しますので、プログラマが処理を記述する必要はありません。

パフォーマンスの向上やメモリの節約のために、2つのクラス属性を調整することができます。

ac_in_buffer_size

非同期入力バッファサイズ (デフォルト値: 4096)

ac_out_buffer_size

非同期出力バッファサイズ (デフォルト値: 4096)

asyncore.dispatcher と違い、 async_chat では producer の first-in-first- outキュー(fifo)を作成する事ができます。producerは more() メソッドを必ず持ち、このメソッドで チャネル上に送出するデータを返します。producerが枯渇状態 (i.e. これ以上のデータを持たない状態)にある場合、 more() は空文字列を返します。この時、 async_chat は枯渇 状態にあるproducerをfifoから除去し、次のproducerが存在すればそのproducerを使用します。fifoにproducerが存在しない場合、 handle_write() は何もしません。リモート端点からの入力の終了や 重要な中断点を検出する場合は、 set_terminator() に記述します。

async_chat のサブクラスでは、入力メソッド collect_incoming_data()found_terminator() を定義 し、チャネルが非同期に受信するデータを処理します。これらのメソッドについては後ろで解説します。

async_chat.close_when_done()

producer fifoのトップに None をプッシュします。このproducerがポップされると、チャネルがクローズします。

async_chat.collect_incoming_data(data)

チャネルが受信した不定長のデータを data に指定して呼び出されます。このメソッドは必ずオーバライドする必要があり、デフォルトの実装では、 NotImplementedError 例外を送出します。

async_chat.discard_buffers()

非常用のメソッドで、全ての入出力バッファとproducer fifoを廃棄します。

async_chat.found_terminator()

入力データストリームが、 set_terminator() で指定した終了条件と一 致した場合に呼び出されます。このメソッドは必ずオーバライドする必要があり、デフォルトの実装では、 NotImplementedError 例外を送出します。入力データを参照する必要がある場合でも引数としては与えられないため、入力バッファをインスタンス属性として参照しなければなりません。

async_chat.get_terminator()

現在のチャネルの終了条件を返します。

async_chat.handle_close()

チャネル閉じた時に呼び出されます。デフォルトの実装では単にチャネルのソケットをクローズします。

async_chat.handle_read()

チャネルの非同期ループでreadイベントが発生した時に呼び出され、デフォル トの実装では、 set_terminator() で設定された終了条件をチェックします。終了条件として、特定の文字列か受信文字数を指定する事ができま す。終了条件が満たされている場合、 handle_read() は終了条件が成立 するよりも前のデータを引数として collect_incoming_data() を呼び 出し、その後 found_terminator() を呼び出します。

async_chat.handle_write()

アプリケーションがデータを出力する時に呼び出され、デフォルトの実装では initiate_send() を呼び出します。 initiate_send() では refill_buffer() を呼び出し、チャネルのproducer fifoからデータを取得します。

async_chat.push(data)

dataを持つ simple_producer (後述)オブジェクトを生成し、チ ャネルの producer_fifo にプッシュして転送します。データをチャネルに書き出すために必要なのはこれだけですが、データの暗号化やチャンク化な どを行う場合には独自のproducerを使用する事もできます。

async_chat.push_with_producer(producer)

指定したproducerオブジェクトをチャネルのfifoに追加します。これより前にpushされたproducerが全て枯渇した後、チャネルはこのproducer から more() メソッドでデータを取得し、リモート端点に送信します。

async_chat.readable()

select() ループでこのチャネルの読み込み可能チェックを行う場合には、 True を返します。

async_chat.refill_buffer()

fifoの先頭にあるproducerの more() メソッドを呼び出し、出力バッファを補充します。先頭のproducerが枯渇状態の場合にはfifoからポ ップされ、その次のproducerがアクティブになります。アクティブなproducerが None になると、チャネルはクローズされます。

async_chat.set_terminator(term)

チャネルで検出する終了条件を設定します。 term は入力プロトコルデータの処理方式によって以下の3つの型の何れかを指定します。

term 説明
string 入力ストリーム中でstringが検出された時、 found_terminator() を呼び出します。
integer 指定された文字数が読み込まれた時、 found_terminator() を呼び出します。
None 永久にデータを読み込みます。

終了条件が成立しても、その後に続くデータは、 found_terminator() の呼出し後に再びチャネルを読み込めば取得する事ができます。

async_chat.writable()

Should return True as long as items remain on the producer fifo, or the channel is connected and the channel’s output buffer is non-empty.

producer fifoが空ではないか、チャネルが接続中で出力バッファが空でない場合、 True を返します。

18.7.1. asynchat - 補助クラスと関数

class asynchat.simple_producer(data[, buffer_size=512])

simple_producer には、一連のデータと、オプションとしてバッファ サイズを指定する事ができます。 more() が呼び出されると、その都度 buffer_size 以下の長さのデータを返します。

more()

producerから取得した次のデータか、空文字列を返します。

class asynchat.fifo([list=None])

各チャネルは、アプリケーションからプッシュされ、まだチャネルに書き出さ れていないデータを fifo に保管しています。 fifo では、必 要なデータとproducerのリストを管理しています。引数 list には、producerかチャネルに出力するデータを指定する事ができます。

is_empty()

fifoが空のとき(のみ)に True を返します。

first()

fifoに push() されたアイテムのうち、最も古いアイテムを返します。

push(data)

データ(文字列またはproducerオブジェクト)をproducer fifoに追加します。

pop()

fifoが空でなければ、 (True, first()) を返し、ポップされたアイテムを削除します。 fifoが空であれば (False, None) を返します。

asynchat は、ネットワークとテキスト分析操作で使えるユーティリティ関数を提供しています。

asynchat.find_prefix_at_end(haystack, needle)

文字列 haystack の末尾が needle の先頭と一致したとき、 True を返します。

18.7.2. asynchat 使用例

以下のサンプルは、 async_chat でHTTPリクエストを読み込む処理の一部です。Webサーバは、クライアントからの接続毎に http_request_handler オブジェクトを作成します。最初はチャネルの終 了条件に空行を指定してHTTPヘッダの末尾までを検出し、その後ヘッダ読み込み済みを示すフラグを立てています。

ヘッダ読み込んだ後、リクエストの種類がPOSTであればデータが入力ストリームに流れるため、 Content-Length: ヘッダの値を数値として終了条件に指定し、適切な長さのデータをチャネルから読み込みます。

必要な入力データを全て入手したら、チャネルの終了条件に None を指定して残りのデータを無視するようにしています。この後、 handle_request() が呼び出されます。

class http_request_handler(asynchat.async_chat):

    def __init__(self, sock, addr, sessions, log):
        asynchat.async_chat.__init__(self, sock=sock)
        self.addr = addr
        self.sessions = sessions
        self.ibuffer = []
        self.obuffer = ""
        self.set_terminator("\r\n\r\n")
        self.reading_headers = True
        self.handling = False
        self.cgi_data = None
        self.log = log

    def collect_incoming_data(self, data):
        """Buffer the data"""
        self.ibuffer.append(data)

    def found_terminator(self):
        if self.reading_headers:
            self.reading_headers = False
            self.parse_headers("".join(self.ibuffer))
            self.ibuffer = []
            if self.op.upper() == "POST":
                clen = self.headers.getheader("content-length")
                self.set_terminator(int(clen))
            else:
                self.handling = True
                self.set_terminator(None)
                self.handle_request()
        elif not self.handling:
            self.set_terminator(None) # browsers sometimes over-send
            self.cgi_data = parse(self.headers, "".join(self.ibuffer))
            self.handling = True
            self.ibuffer = []
            self.handle_request()