[Python-checkins] peps: Transports and protocols.

guido.van.rossum python-checkins at python.org
Thu Dec 13 23:40:28 CET 2012


http://hg.python.org/peps/rev/3b447f846b36
changeset:   4607:3b447f846b36
user:        Guido van Rossum <guido at google.com>
date:        Thu Dec 13 14:40:23 2012 -0800
summary:
  Transports and protocols.

files:
  pep-3156.txt |  154 ++++++++++++++++++++++++++++++++++++++-
  1 files changed, 151 insertions(+), 3 deletions(-)


diff --git a/pep-3156.txt b/pep-3156.txt
--- a/pep-3156.txt
+++ b/pep-3156.txt
@@ -319,6 +319,22 @@
 can assume automatic mutual exclusion with other callbacks scheduled
 in the same event loop.
 
+Exceptions
+----------
+
+There are two categories of exceptions in Python: those that derive
+from the ``Exception`` class and those that derive from
+``BaseException``.  Exceptions deriving from ``Exception`` will
+generally be caught and handled appropriately; for example, they will
+be passed through by Futures, and they will be logged and ignored when
+they occur in a callback.
+
+However, exceptions deriving only from ``BaseException`` are never
+caught, and will usually cause the program to terminate with a
+traceback.  (Examples of this category include ``KeyboardInterrupt``
+and ``SystemExit``; it is usually unwise to treat these the same as
+most other exceptions.)
+
 The DelayedCall Class
 ---------------------
 
@@ -381,7 +397,8 @@
   is never called immediately, and always in the context of the
   caller.  (Typically, a context is a thread.)  You can think of this
   as calling the callback through ``call_soon_threadsafe()``.  Note
-  that the callback (unlike all other callbacks defined in this PEP)
+  that the callback (unlike all other callbacks defined in this PEP,
+  and ignoring the convention from the section "Callback Style" below)
   is always called with a single argument, the Future object.
 
 The internal methods defined in PEP 3148 are not supported.
@@ -396,12 +413,143 @@
 Transports
 ----------
 
-TBD.
+A transport is an abstraction on top of a socket or something similar
+(for example, a UNIX pipe or an SSL connection).  Transports are
+strongly influenced by Twisted and PEP 3153.  Users rarely implement
+or instantiate transports -- rather, event loops offer utility methods
+to set up transports.
+
+Transports work in conjunction with protocols.  Protocols are
+typically written without knowing or caring about the exact type of
+transport used, and transports can be used with a wide variety of
+protocols.  For example, an HTTP client protocol implementation may be
+used with either a plain socket transport or an SSL transport.  The
+plain socket transport can be used with many different protocols
+besides HTTP (e.g. SMTP, IMAP, POP, FTP, IRC, SPDY).
+
+Most connections have an asymmetric nature: the client and server
+usually have very different roles and behaviors.  Hence, the interface
+between transport and protocol is also asymmetric.  From the
+protocol's point of view, *writing* data is done by calling the
+``write()`` method on the transport object; this buffers the data and
+returns immediately.  However, the transport takes a more active role
+in *reading* data: whenever some data is read from the socket (or
+other data source), the transport calls the protocol's
+``data_received()`` method.
+
+Transports have the following public methods:
+
+- ``write(data)``.  Write some bytes.  The argument must be a bytes
+  object.  Returns ``None``.  The transport is free to buffer the
+  bytes, but it must eventually cause the bytes to be transferred to
+  the entity at the other end, and it must maintain stream behavior.
+  That is, ``t.write(b'abc'); t.write(b'def')`` is equivalent to
+  ``t.write(b'abcdef')``, as well as to::
+
+    t.write(b'a')
+    t.write(b'b')
+    t.write(b'c')
+    t.write(b'd')
+    t.write(b'e')
+    t.write(b'f')
+
+  (TBD: What about datagram transports?)
+
+- ``writelines(iterable)``.  Equivalent to::
+
+    for data in iterable:
+      self.write(data)
+
+- ``write_eof()``.  Close the writing end of the connection.
+  Subsequent calls to ``write()`` are not allowed.  Once all buffered
+  data is transferred, the transport signals to the other end that no
+  more data will be received.  Some protocols don't support this
+  operation; in that case, calling ``write_eof()`` will raise an
+  exception.  (Note: This used to be called ``half_close()``, but
+  unless you already know what it is for, that name doesn't indicate
+  *which* end is closed.)
+
+- ``can_write_eof()``.  Return ``True`` if the protocol supports
+  ``write_eof()``, ``False`` if it does not.  (This method is needed
+  because some protocols need to change their behavior when
+  ``write_eof()`` is unavailable.  For example, in HTTP, to send data
+  whose size is not known ahead of time, the end of the data is
+  typically indicated using ``write_eof()``; however, SSL does not
+  support this, and an HTTP protocol implementation would have to use
+  the "chunked" transfer encoding in this case.  But if the data size
+  is known ahead of time, the best approach in both cases is to use
+  the Content-Length header.)
+
+- ``pause()``.  Suspend delivery of data to the protocol until a
+  subsequent ``resume()`` call.  Between ``pause()`` and ``resume()``,
+  the protocol's ``data_received()`` method will not be called.  This
+  has no effect on ``write()``.
+
+- ``resume()``.  Restart delivery of data to the protocol via
+  ``data_received()``.
+
+- ``close()``.  Sever the connection with the entity at the other end.
+  Any data buffered by ``write()`` will (eventually) be transferred
+  before the connection is actually closed.  The protocol's
+  ``data_received()`` method will not be called again.  Once all
+  buffered data has been flushed, the protocol's ``connection_lost()``
+  method will be called with ``None`` as the argument.  Note that
+  this method does not wait for all that to happen.
+
+- ``abort()``.  Immediately sever the connection.  Any data still
+  buffered by the transport is thrown away.  Soon, the protocol's
+  ``connection_lost()`` method will be called with ``None`` as
+  argument.  (TBD: Distinguish in the ``connection_lost()`` argument
+  between ``close()``, ``abort()`` or a close initated by the other
+  end?  Or add a transport method to inquire about this?  Glyph's
+  proposal was to pass different exceptions for this purpose.)
+
+TBD: Provide flow control the other way -- the transport may need to
+suspend the protocol if the amount of data buffered becomes a burden.
+One option: let the transport call ``protocol.pause()`` and
+``protocol.resume()`` if they exist; if they don't exist, the protocol
+doesn't support flow control.
 
 Protocols
 ---------
 
-TBD.
+Protocols are always used in conjunction with transports.  While a few
+common protocols are provided (e.g. decent though not necessary
+excellent HTTP client and server implementations), most protocols will
+be implemented by user code or third-party libraries.
+
+A protocol must implement the following methods, which will be called
+by the transport.  Consider these callbacks that are always called by
+the event loop in the right context.  (See the "Context" section
+above.)
+
+- ``connection_made(transport)``.  Indicates that the transport is
+  ready and connected to the entity at the other end.  The protocol
+  should probably save the transport reference as an instance variable
+  (so it can call its ``write()`` and other methods later), and may
+  write an initial greeting or request at this point.
+
+- ``data_received(data)``.  The transport has read some bytes from the
+  connection.  The argument is always a non-empty bytes object.  There
+  are no guarantees about the minimum or maximum size of the data
+  passed along this way.  ``p.data_received(b'abcdef')`` should be
+  treated exactly equivalent to::
+
+    p.data_received(b'abc')
+    p.data_received(b'def')
+
+  (TBD: What about datagram transports?)
+
+- ``connection_lost(exc)``.  The transport has been closed or aborted,
+  has detected that the other end has closed the connection cleanly,
+  or has encountered an unexpected error.  In the first three cases
+  the argument is ``None``; for an unexpected error, the argument is
+  the exception that caused the transport to give up.  (TBD: Do we
+  need to distinguish between the first three cases?)
+
+TBD: How do we detect a half-close (``write_eof()`` in our parlance)
+initiated by the other end?  Does this call connection_lost()?  Is the
+protocol then allowed to write more?  (I think it should be!)
 
 Coroutines and the Scheduler
 ----------------------------

-- 
Repository URL: http://hg.python.org/peps


More information about the Python-checkins mailing list