summaryrefslogtreecommitdiff
path: root/docs/architecture.rst
diff options
context:
space:
mode:
Diffstat (limited to 'docs/architecture.rst')
-rw-r--r--docs/architecture.rst269
1 files changed, 269 insertions, 0 deletions
diff --git a/docs/architecture.rst b/docs/architecture.rst
new file mode 100644
index 00000000..53c326e1
--- /dev/null
+++ b/docs/architecture.rst
@@ -0,0 +1,269 @@
+.. index:: XMLStream, BaseXMPP, ClientXMPP, ComponentXMPP
+
+SleekXMPP Architecture
+======================
+
+The core of SleekXMPP is contained in four classes: ``XMLStream``,
+``BaseXMPP``, ``ClientXMPP``, and ``ComponentXMPP``. Along side this
+stack is a library for working with XML objects that eliminates most
+of the tedium of creating/manipulating XML.
+
+.. image:: _static/images/arch_layers.png
+ :height: 300px
+ :align: center
+
+
+.. index:: XMLStream
+
+The Foundation: XMLStream
+-------------------------
+``XMLStream`` is a mostly XMPP-agnostic class whose purpose is to read
+and write from a bi-directional XML stream. It also allows for callback
+functions to execute when XML matching given patterns is received; these
+callbacks are also referred to as :term:`stream handlers <stream handler>`.
+The class also provides a basic eventing system which can be triggered
+either manually or on a timed schedule.
+
+The Main Threads
+~~~~~~~~~~~~~~~~
+``XMLStream`` instances run using at least three background threads: the
+send thread, the read thread, and the scheduler thread. The send thread is
+in charge of monitoring the send queue and writing text to the outgoing
+XML stream. The read thread pulls text off of the incoming XML stream and
+stores the results in an event queue. The scheduler thread is used to emit
+events after a given period of time.
+
+Additionally, the main event processing loop may be executed in its
+own thread if SleekXMPP is being used in the background for another
+application.
+
+Short-lived threads may also be spawned as requested for threaded
+:term:`event handlers <event handler>`.
+
+How XML Text is Turned into Action
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+To demonstrate the flow of information, let's consider what happens
+when this bit of XML is received (with an assumed namespace of
+``jabber:client``):
+
+.. code-block:: xml
+
+ <message to="user@example.com" from="friend@example.net">
+ <body>Hej!</body>
+ </message>
+
+
+1. **Convert XML strings into objects.**
+
+ Incoming text is parsed and converted into XML objects (using
+ ElementTree) which are then wrapped into what are referred to as
+ :term:`Stanza objects <stanza object>`. The appropriate class for the
+ new object is determined using a map of namespaced element names to
+ classes.
+
+ Our incoming XML is thus turned into a ``Message`` :term:`stanza object`
+ because the namespaced element name ``{jabber:client}message`` is
+ associated with the class ``sleekxmpp.stanza.Message``.
+
+2. **Match stanza objects to callbacks.**
+
+ These objects are then compared against the stored patterns associated
+ with the registered callback handlers. For each match, a copy of the
+ :term:`stanza object` is paired with a reference to the handler and
+ placed into the event queue.
+
+ Our ``Message`` object is thus paired with the message stanza handler
+ ``BaseXMPP._handle_message`` to create the tuple::
+
+ ('stanza', stanza_obj, handler)
+
+3. **Process the event queue.**
+
+ The event queue is the heart of SleekXMPP. Nearly every action that
+ takes place is first inserted into this queue, whether that be received
+ stanzas, custom events, or scheduled events.
+
+ When the stanza is pulled out of the event queue with an associated
+ callback, the callback function is executed with the stanza as its only
+ parameter.
+
+ .. warning::
+ The callback, aka :term:`stream handler`, is executed in the main
+ processing thread. If the handler blocks, event processing will also
+ block.
+
+4. **Raise Custom Events**
+
+ Since a :term:`stream handler` shouldn't block, if extensive processing
+ for a stanza is required (such as needing to send and receive an
+ ``Iq`` stanza), then custom events must be used. These events are not
+ explicitly tied to the incoming XML stream and may be raised at any
+ time. Importantly, these events may be handled in their own thread.
+
+ When the event is raised, a copy of the stanza is created for each
+ handler registered for the event. In contrast to :term:`stream handlers <stream handler>`,
+ these functions are referred to as :term:`event handlers <event handler>`.
+ Each stanza/handler pair is then put into the event queue.
+
+ .. note::
+ It is possible to skip the event queue and process an event immediately
+ by using ``direct=True`` when raising the event.
+
+ The code for ``BaseXMPP._handle_message`` follows this pattern, and
+ raises a ``'message'`` event::
+
+ self.event('message', msg)
+
+ The event call then places the message object back into the event queue
+ paired with an :term:`event handler`::
+
+ ('event', 'message', msg_copy1, custom_event_handler_1)
+ ('event', 'message', msg_copy2, custom_evetn_handler_2)
+
+5. **Process Custom Events**
+
+ The stanza and :term:`event handler` are then pulled from the event
+ queue, and the handler is executed, passing the stanza as its only
+ argument. If the handler was registered as threaded, then a new thread
+ will be spawned for it.
+
+ .. note::
+ Events may be raised without needing :term:`stanza objects <stanza object>`.
+ For example, you could use ``self.event('custom', {'a': 'b'})``.
+ You don't even need any arguments: ``self.event('no_parameters')``.
+ However, every event handler MUST accept at least one argument.
+
+ Finally, after a long trek, our message is handed off to the user's
+ custom handler in order to do awesome stuff::
+
+ msg.reply()
+ msg['body'] = "Hey! This is awesome!"
+ msg.send()
+
+
+.. index:: BaseXMPP, XMLStream
+
+Raising XMPP Awareness: BaseXMPP
+--------------------------------
+While ``XMLStream`` attempts to shy away from anything too XMPP specific,
+``BaseXMPP``'s sole purpose is to provide foundational support for sending
+and receiving XMPP stanzas. This support includes registering the basic
+message, presence, and iq stanzas, methods for creating and sending
+stanzas, and default handlers for incoming messages and keeping track of
+presence notifications.
+
+The plugin system for adding new XEP support is also maintained by
+``BaseXMPP``.
+
+.. index:: ClientXMPP, BaseXMPP
+
+ClientXMPP
+----------
+``ClientXMPP`` extends ``BaseXMPP`` with additional logic for connecting to
+an XMPP server by performing DNS lookups. It also adds support for stream
+features such as STARTTLS and SASL.
+
+.. index:: ComponentXMPP, BaseXMPP
+
+ComponentXMPP
+-------------
+``ComponentXMPP`` is only a thin layer on top of ``BaseXMPP`` that
+implements the component handshake protocol.
+
+.. index::
+ double: object; stanza
+
+Stanza Objects: A Brief Look
+----------------------------
+.. seealso::
+ See :ref:`api-stanza-objects` for a more detailed overview.
+
+Almost worthy of their own standalone library, :term:`stanza objects <stanza object>`
+are wrappers for XML objects which expose dictionary like interfaces
+for manipulating their XML content. For example, consider the XML:
+
+.. code-block:: xml
+
+ <message />
+
+A very plain element to start with, but we can create a :term:`stanza object`
+using ``sleekxmpp.stanza.Message`` as so::
+
+ msg = Message(xml=ET.fromstring("<message />"))
+
+The ``Message`` stanza class defines interfaces such as ``'body'`` and
+``'to'``, so we can assign values to those interfaces to include new XML
+content::
+
+ msg['body'] = "Following so far?"
+ msg['to'] = 'user@example.com'
+
+Dumping the XML content of ``msg`` (using ``msg.xml``), we find:
+
+.. code-block:: xml
+
+ <message to="user@example.com">
+ <body>Following so far?</body>
+ </message>
+
+The process is similar for reading from interfaces and deleting interface
+contents. A :term:`stanza object` behaves very similarly to a regular
+``dict`` object: you may assign to keys, read from keys, and ``del`` keys.
+
+Stanza interfaces come with built-in behaviours such as adding/removing
+attribute and sub element values. However, a lot of the time more custom
+logic is needed. This can be provided by defining methods of the form
+``get_*``, ``set_*``, and ``del_*`` for any interface which requires custom
+behaviour.
+
+Stanza Plugins
+~~~~~~~~~~~~~~
+Since it is generally possible to embed one XML element inside another,
+:term:`stanza objects <stanza object>` may be nested. Nested
+:term:`stanza objects <stanza object>` are referred to as :term:`stanza plugins <stanza plugin>`
+or :term:`substanzas <substanza>`.
+
+A :term:`stanza plugin` exposes its own interfaces by adding a new
+interface to its parent stanza. To demonstrate, consider these two stanza
+class definitions using ``sleekxmpp.xmlstream.ElementBase``:
+
+
+.. code-block:: python
+
+ class Parent(ElementBase):
+ name = "the-parent-xml-element-name"
+ namespace = "the-parent-namespace"
+ interfaces = set(('foo', 'bar'))
+
+ class Child(ElementBase):
+ name = "the-child-xml-element-name"
+ namespace = "the-child-namespace"
+ plugin_attrib = 'child'
+ interfaces = set(('baz',))
+
+
+If we register the ``Child`` stanza as a plugin of the ``Parent`` stanza as
+so, using ``sleekxmpp.xmlstream.register_stanza_plugin``::
+
+ register_stanza_plugin(Parent, Child)
+
+Then we can access content in the child stanza through the parent.
+Note that the interface used to access the child stanza is the same as
+``Child.plugin_attrib``::
+
+ parent = Parent()
+ parent['foo'] = 'a'
+ parent['child']['baz'] = 'b'
+
+The above code would produce:
+
+.. code-block:: xml
+
+ <the-parent-xml-element xmlns="the-parent-namespace" foo="a">
+ <the-child-xml-element xmlsn="the-child-namespace" baz="b" />
+ </the-parent-xml-element>
+
+It is also possible to allow a :term:`substanza` to appear multiple times
+by using ``iterable=True`` in the ``register_stanza_plugin`` call. All
+iterable :term:`substanzas <substanza>` can be accessed using a standard
+``substanzas`` interface.