18.7. "asynchat" --- 异步 socket 指令/响应 处理器
*************************************************

**源代码:** Lib/asynchat.py

3.6 版後已棄用: 请使用 "asyncio" 替代。

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

備註:

  该模块仅为提供向后兼容。我们推荐在新代码中使用 "asyncio" 。

此模块在 "asyncore" 框架之上构建，简化了异步客户端和服务器并使得处理元
素为以任意字符串结束或者为可变长度的协议更加容易。 "asynchat" 定义了一
个可以由你来子类化的抽象类 "async_chat"，提供了
"collect_incoming_data()" 和 "found_terminator()" 等方法的实现。 它使
用与 "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" 对象的方法就会被事件处理框架调用而无须程序员方面
   做任何操作。

   两个可被修改的类属性，用以提升性能，甚至也可能会节省内存。

   ac_in_buffer_size

      异步输入缓冲区大小 (默认为 "4096")。

   ac_out_buffer_size

      异步输出缓冲区大小 (默认为 "4096")。

   与 "asyncore.dispatcher" 不同，"async_chat" 允许你定义一个 FIFO (先
   进先出) 队列 *producers*。 其中的生产者只需要一个方法 "more()"，该
   方法应当返回要在通道上传输的数据。 生产者通过让其 "more()" 方法返回
   空字节串对象来表明其处于耗尽状态 (*意即* 它已不再包含数据)。 此时
   "async_chat" 对象会将该生产者从队列中移除并开始使用下一个生产者，如
   果有下一个的话。 当生产者队列为空时 "handle_write()" 方法将不执行任
   何操作。 你要使用通道对象的 "set_terminator()" 方法来描述如何识别来
   自远程端点的入站传输的结束或是重要的中断点。

   要构建一个可用的 "async_chat" 子类，你的输入方法
   "collect_incoming_data()" 和 "found_terminator()" 必须要处理通道异
   步接收的数据。 这些参数的描述见下文。

async_chat.close_when_done()

   将 "None" 推入生产者队列。 当此生产者被弹出队列时它将导致通道被关闭
   。

async_chat.collect_incoming_data(data)

   调用时附带 *data*，其中包含任意数量的已接收数据。 必须被重载的默认
   方法将引发一个 "NotImplementedError" 异常。

async_chat.discard_buffers()

   在紧急情况下此方法将丢弃输入和/或输出缓冲区以及生产者队列中的任何数
   据。

async_chat.found_terminator()

   当输入数据流能匹配 "set_terminator()" 所设定的终结条件时会被调用。
   必须被重载的默认方法将引发一个 "NotImplementedError" 异常。 被缓冲
   的输入数据应当可以通过实例属性来获取。

async_chat.get_terminator()

   返回通道的当前终结器。

async_chat.push(data)

   将数据推入通道的队列以确保其被传输。 要让通道将数据写到网络中你只需
   要这样做就足够了，虽然以更复杂的方式使用你自己的生产者也是有可能的
   ，例如为了实现加密和分块。

async_chat.push_with_producer(producer)

   获取一个生产者对象并将其加入到与通道相关联的生产者队列中。 当所有当
   前已推入的生产者都已被耗尽时通道将通过调用其 "more()" 方法来耗用此
   生产者的数据并将数据发送至远程端点。

async_chat.set_terminator(term)

   设置可在通道上被识别的终结条件。 "term" 可以是三种类型值中的任意一
   种 ，对应于处理入站协议数据的三种不同方式。

   +-------------+-----------------------------------------------+
   | term        | 描述                                          |
   |=============|===============================================|
   | *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" 以确保忽
略掉 Web 客户端所发送的任何无关数据之后调用 "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()
