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

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

バージョン 3.6 で非推奨: "asynchat" will be removed in Python 3.12
(see **PEP 594** for details). Please use "asyncio" instead.

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

注釈:

  このモジュールは後方互換性のためだけに存在します。新しいコードでは
  "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")。

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

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

async_chat.close_when_done()

   producer キューのトップに "None" をプッシュします。このproducerがキ
   ューからポップされると、チャネルが閉じられます。

async_chat.collect_incoming_data(data)

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

async_chat.discard_buffers()

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

async_chat.found_terminator()

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

async_chat.get_terminator()

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

async_chat.push(data)

   チャネルのキューにデータをプッシュして転送します。データをチャネル
   に書き出すために必要なのはこれだけですが、データの暗号化やチャンク
   化などを行う場合には独自の producer を使用する事もできます。

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()"
   の呼出し後に再びチャネルを読み込めば取得する事ができます。


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()
