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

**ソースコード:** Lib/asynchat.py

バージョン 3.6 で非推奨: 代わりに "asyncio" を使ってください。

======================================================================

注釈:

  このモジュールは後方互換性のためだけに存在します。新しいコードでは
  "asyncio" を利用することを推奨します。

"asynchat" を使うと、 "asyncore" を基盤とした非同期なサーバ・クライア
ントをより簡単に開発する事ができます。 "asynchat" では、プロトコルの要
素が任意の文字列で終了するか、または可変長の文字列であるようなプロトコ
ルを容易に制御できるようになっています。 "asynchat" は、抽象クラス
"async_chat" を定義しており、 "async_chat" を継承して
"collect_incoming_data()" メソッドと "found_terminator()" メソッドを実
装すれば使うことができます。 "async_chat" と "asyncore" は同じ非同期ル
ープを使用しており、 "asyncore.dispatcher" も "asynchat.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_chat" も "select()" 呼出し
   後のソケットの状態からイベントを生成します。ポーリングループ開始後
   、イベント処理フレームワークが自動的に "async_chat" のメソッドを呼
   び出しますので、プログラマが処理を記述する必要はありません。

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

   ac_in_buffer_size

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

   ac_out_buffer_size

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

   Unlike "asyncore.dispatcher", "async_chat" allows you to define a
   FIFO (first-in, first-out) queue of *producers*. A producer need
   have only one method, "more()", which should return data to be
   transmitted on the channel. The producer indicates exhaustion
   (*i.e.* that it contains no more data) by having its "more()"
   method return the empty bytes object. At this point the
   "async_chat" object removes the producer from the queue and starts
   using the next producer, if any. When the producer queue is empty
   the "handle_write()" method does nothing. You use the channel
   object's "set_terminator()" method to describe how to recognize the
   end of, or an important breakpoint in, an incoming transmission
   from the remote endpoint.

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

async_chat.close_when_done()

   Pushes a "None" on to the producer queue. When this producer is
   popped off the queue it causes the channel to be closed.

async_chat.collect_incoming_data(data)

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

async_chat.discard_buffers()

   In emergencies this method will discard any data held in the input
   and/or output buffers and the producer queue.

async_chat.found_terminator()

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

async_chat.get_terminator()

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

async_chat.push(data)

   Pushes data on to the channel's queue to ensure its transmission.
   This is all you need to do to have the channel write the data out
   to the network, although it is possible to use your own producers
   in more complex schemes to implement encryption and chunking, for
   example.

async_chat.push_with_producer(producer)

   Takes a producer object and adds it to the producer queue
   associated with the channel.  When all currently-pushed producers
   have been exhausted the channel will consume this producer's data
   by calling its "more()" method and send the data to the remote
   endpoint.

async_chat.set_terminator(term)

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

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

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


18.7.1. asynchat 使用例
=======================

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

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

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

   import asynchat

   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 = b""
           self.set_terminator(b"\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(b"".join(self.ibuffer))
               self.ibuffer = []
               if self.op.upper() == b"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, b"".join(self.ibuffer))
               self.handling = True
               self.ibuffer = []
               self.handle_request()
