Mesh Socket ~~~~~~~~~~~ Basic Usage ----------- The mesh schema is used as an alert and messaging network. Its primary purpose is to ensure message delivery to every participant in the network. To connect to a mesh network, use the :py:class:`~py2p.mesh.MeshSocket` object. This is instantiated as follows: .. code-block:: python >>> from py2p import mesh >>> sock = mesh.MeshSocket('0.0.0.0', 4444) Using ``'0.0.0.0'`` will automatically grab your LAN address. Using an outbound internet connection requires a little more work. First, ensure that you have a port forward set up (NAT busting is not in the scope of this project). Then specify your outward address as follows: .. code-block:: python >>> from py2p import mesh >>> sock = mesh.MeshSocket('0.0.0.0', 4444, out_addr=('35.24.77.21', 44565)) In addition, SSL encryption can be enabled if `cryptography `_ is installed. This works by specifying a custom :py:class:`~py2p.base.Protocol` object, like so: .. code-block:: python >>> from py2p import mesh, base >>> sock = mesh.MeshSocket('0.0.0.0', 4444, prot=base.Protocol('mesh', 'SSL')) Eventually that will be the default, but while things are being tested it will default to plaintext. If `cryptography `_ is not installed, this will generate an :py:exc:`ImportError` Specifying a different protocol object will ensure that the node *only* can connect to people who share its object structure. So if someone has ``'mesh2'`` instead of ``'mesh'``, it will fail to connect. You can see the current default by looking at :py:data:`py2p.mesh.default_protocol`. Unfortunately, this failure is currently silent. Because this is asynchronous in nature, raising an :py:exc:`Exception` is not possible. Because of this, it's good to perform the following check after connecting: .. code-block:: python >>> from py2p import mesh >>> import time >>> sock = mesh.MeshSocket('0.0.0.0', 4444) >>> sock.connect('192.168.1.14', 4567) >>> time.sleep(1) >>> assert sock.routing_table To send a message, use the :py:meth:`~py2p.mesh.MeshSocket.send` method. Each argument supplied will correspond to a packet that the peer receives. In addition, there is a keyed argument you can use. ``flag`` will specify how other nodes relay this. These flags are defined in :py:class:`py2p.base.flags`. ``broadcast`` will indicate that other nodes are supposed to relay it. ``whisper`` will indicate that your peers are *not* supposed to relay it. .. code-block:: python >>> sock.send('this is', 'a test') Receiving is a bit simpler. When the :py:meth:`~py2p.mesh.MeshSocket.recv` method is called, it returns a :py:class:`~py2p.base.Message` object (or ``None`` if there are no messages). This has a number of methods outlined which you can find by clicking its name. Most notably, you can get the packets in a message with :py:attr:`.Message.packets`, and reply directly with :py:meth:`.Message.reply`. .. code-block:: python >>> sock.send('Did you get this?') # A peer then replies >>> msg = sock.recv() >>> print(msg) message(type=2, packets=[b'yes', b'I did'], sender=b'6VnYj9LjoVLTvU3uPhy4nxm6yv2wEvhaRtGHeV9wwFngWGGqKAzuZ8jK6gFuvq737V') >>> print(msg.packets) [2, b'yes', b'I did'] >>> for msg in sock.recv(10): ... msg.reply("Replying to a list") Events ------ In addition to the above, the :py:class:`~py2p.mesh.MeshSocket` object has two Events (as supplied by :py:class:`pyee.EventEmitter` . First there's |MeshSocket_onconnect|_. This is called whenever you finalize a connection to your distributed service. It is *also* called if you reconnect to the service after some failure. .. code-block:: python >>> @sock.once('connect') >>> def call_once(conn): ... # conn is a reference to the socket, in case you're in a new scope ... # the .once() indicates that this event should only be called once ... pass ... >>> # sock.once('connect', call_once) >>> # This syntax also works >>> >>> @sock.on('connect') >>> def call_always(conn): ... # conn is still a reference to the socket ... # the .on() indicates that this event should be called *every* time ... pass ... This class has one other event: |MeshSocket_onmessage|_. This one is a little bit trickier to use, and it's recommended that you only have one callback in place at any given time. The event is called any time you receive a message that *is not* handled by one of the "privileged" callbacks. Such callbacks include the ones for dealing with new peers on the network. .. code-block:: python >>> @sock.on('message') >>> def handle_msg(conn): ... # note that you are not passed a reference to the message. ... # This means that you must explicitly recv(). ... msg = conn.recv() ... if msg is not None: ... # note the guard clause for if someone else registered a callback ... msg.reply('this is an example') ... Advanced Usage -------------- In addition to this, you can register a custom handler for incoming messages. This is appended to the end of the default handlers. These handlers are then called in a similar way to Javascripts ``Array.some()``. In other words, when a handler returns something true-like, it stops calling handlers. When writing your handler, keep in mind that you are only passed a :py:class:`~py2p.base.Message` object and a :py:class:`~py2p.mesh.MeshConnection`. Fortunately you can get access to everything you need from these objects. .. code-block:: python >>> from py2p import mesh, base >>> def register_1(msg, handler): # Takes in a Message and MeshConnection ... packets = msg.packets # This grabs a copy of the packets. Slightly more efficient to store this once. ... if packets[1] == b'test': # This is the condition we want to act under ... msg.reply(b"success") # This is the response we should give ... return True # This tells the daemon we took an action, so it should stop calling handlers ... >>> def register_2(msg, handler): # This is a slightly different syntax ... packets = msg.packets ... if packets[1] == b'test': ... handler.send(base.flags.whisper, base.flags.whisper, b"success") # One could instead reply to the node who relayed the message ... return True ... >>> sock = mesh.MeshSocket('0.0.0.0', 4444) >>> sock.register_handler(register_1) # The handler is now registered If this does not take two arguments, :py:meth:`~py2p.base.base_socket.register_handler` will raise a :py:exc:`ValueError`. This library also supports the :py:class:`~pyee.EventEmitter` API. This enables you to have methods like: .. code-block:: python >>> from py2p import mesh >>> sock = mesh.MeshSocket('0.0.0.0', 4444) >>> @sock.on('connect') ... def on_connect(conn): ... print("Hey! You got connected!") ... >>> sock.connect('example.com', 12345) Hey! You got connected! The mesh socket supports |MeshSocket_onconnect|_ To help debug these services, you can specify a :py:attr:`~py2p.base.base_socket.debug_level` in the constructor. Using a value of 5, you can see when it enters into each handler, as well as every message which goes in or out. .. |MeshSocket_onconnect| replace:: :py:func:`~py2p.mesh.MeshSocket.Event 'connect'` .. _MeshSocket_onconnect: ../mesh.html#MeshSocket.Event%20'connect' .. |MeshSocket_onmessage| replace:: :py:func:`~py2p.mesh.MeshSocket.Event 'message'` .. _MeshSocket_onmessage: ../mesh.html#MeshSocket.Event%20'message'