1WebSocket Programming
2=====================
3
4This guide introduces WebSocket programming with |Ab|.
5
6You'll see how to create WebSocket server (":ref:`creating-websocket-servers`") and client applications (":ref:`creating-websocket-clients`").
7
8*Resources:*
9
10* Example Code for this Guide: `Twisted-based <https://github.com/crossbario/autobahn-python/tree/master/examples/twisted/websocket/echo>`_ or `asyncio-based <https://github.com/crossbario/autobahn-python/tree/master/examples/asyncio/websocket/echo>`_
11* More :ref:`WebSocket Examples <websocket_examples>`
12
13.. _creating-websocket-servers:
14
15Creating Servers
16----------------
17
18Using |Ab| you can create WebSocket servers that will be able to talk to any (compliant) WebSocket client, including browsers.
19
20We'll cover how to define the behavior of your WebSocket server by writing *protocol classes* and show some boilerplate for actually running a WebSocket server using the behavior defined in the server protocol.
21
22
23Server Protocols
24~~~~~~~~~~~~~~~~
25
26To create a WebSocket server, you need to **write a protocol class to specify the behavior** of the server.
27
28For example, here is a protocol class for a WebSocket echo server that will simply echo back any WebSocket message it receives:
29
30.. code-block:: python
31
32   class MyServerProtocol(WebSocketServerProtocol):
33
34      def onMessage(self, payload, isBinary):
35         ## echo back message verbatim
36         self.sendMessage(payload, isBinary)
37
38This is just three lines of code, but we will go through each one carefully, since writing protocol classes like above really is core to WebSocket programming using |ab|.
39
40The **first thing** to note is that you **derive** your protocol class from a base class provided by |ab|. Depending on whether you write a Twisted or a asyncio based application, here are the base classes to derive from:
41
42* :class:`autobahn.twisted.websocket.WebSocketServerProtocol`
43* :class:`autobahn.asyncio.websocket.WebSocketServerProtocol`
44
45So a Twisted-based echo protocol would import the base protocol from ``autobahn.twisted.websocket`` and derive from :class:`autobahn.twisted.websocket.WebSocketServerProtocol`
46
47*Twisted:*
48
49.. code-block:: python
50
51   from autobahn.twisted.websocket import WebSocketServerProtocol
52
53   class MyServerProtocol(WebSocketServerProtocol):
54
55      def onMessage(self, payload, isBinary):
56         ## echo back message verbatim
57         self.sendMessage(payload, isBinary)
58
59while an asyncio echo protocol would import the base protocol from ``autobahn.asyncio.websocket`` and derive from :class:`autobahn.asyncio.websocket.WebSocketServerProtocol`
60
61*asyncio:*
62
63.. code-block:: python
64
65   from autobahn.asyncio.websocket import WebSocketServerProtocol
66
67   class MyServerProtocol(WebSocketServerProtocol):
68
69      def onMessage(self, payload, isBinary):
70         ## echo back message verbatim
71         self.sendMessage(payload, isBinary)
72
73.. note:: In this example, only the imports differ between the Twisted and the asyncio variant. The rest of the code is identical. However, in most real world programs you probably won't be able to or don't want to avoid using network framework specific code.
74
75----------
76
77.. _receiving-messages:
78
79Receiving Messages
80~~~~~~~~~~~~~~~~~~
81
82The **second thing** to note is that we **override a callback** ``onMessage`` which is called by |ab| whenever the callback related event happens.
83
84In case of ``onMessage``, the callback will be called whenever a new WebSocket message was received. There are more WebSocket related callbacks, but for now the ``onMessage`` callback is all we need.
85
86When our server receives a WebSocket message, the :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onMessage` will fire with the message ``payload`` received.
87
88The ``payload`` is always a Python byte string. Since WebSocket is able to transmit **text** (UTF8) and **binary** payload, the actual payload type is signaled via the ``isBinary`` flag.
89
90When the ``payload`` is **text** (``isBinary == False``), the bytes received will be an UTF8 encoded string. To process **text** payloads, the first thing you often will do is decoding the UTF8 payload into a Python string:
91
92.. code-block:: python
93
94   s = payload.decode('utf8')
95
96.. tip::
97
98   You don't need to validate the bytes for actually being valid UTF8 - |ab| does that already when receiving the message.
99
100When using WebSocket text messages with JSON ``payload``, typical code for receiving and decoding messages into Python objects that works on both Python 2 and 3 would look like this:
101
102.. code-block:: python
103
104   import json
105   obj = json.loads(payload.decode('utf8'))
106
107We are using the Python standard JSON module :py:mod:`json`.
108
109The ``payload`` (which is of type ``bytes`` on Python 3 and ``str`` on Python 2) is decoded from UTF8 into a native Python string, and then parsed from JSON into a native Python object.
110
111----------
112
113.. _sending-messages:
114
115Sending Messages
116~~~~~~~~~~~~~~~~
117
118The **third thing** to note is that we **use methods** like ``sendMessage`` provided by the base class to perform WebSocket related actions, like sending a WebSocket message.
119
120As there are more methods for performing other actions (like closing the connection), we'll come back to this later, but for now, the ``sendMessage`` method is all we need.
121
122:meth:`autobahn.websocket.interfaces.IWebSocketChannel.sendMessage` takes the ``payload`` to send in a WebSocket message as Python bytes. Since WebSocket is able to transmit payloads of **text** (UTF8) and **binary** type, you need to tell |ab| the actual type of the ``payload`` bytes. This is done using the ``isBinary`` flag.
123
124Hence, to send a WebSocket text message, you will usually *encode* the payload to UTF8:
125
126.. code-block:: python
127
128   payload = s.encode('utf8')
129   self.sendMessage(payload, isBinary = False)
130
131.. warning::
132
133   |ab| will NOT validate the bytes of a text ``payload`` being sent for actually being valid UTF8. You MUST ensure that you only provide valid UTF8 when sending text messages. If you produce invalid UTF8, a conforming WebSocket peer will close the WebSocket connection due to the protocol violation.
134
135When using WebSocket text messages with JSON ``payload``, typical code for encoding and sending Python objects that works on both Python 2 and 3 would look like this:
136
137.. code-block:: python
138
139   import json
140   payload = json.dumps(obj, ensure_ascii = False).encode('utf8')
141
142We are using the Python standard JSON module :py:mod:`json`.
143
144The ``ensure_ascii == False`` option allows the JSON serializer to use Unicode strings. We can do this since we are encoding to UTF8 afterwards anyway. And UTF8 can represent the full Unicode character set.
145
146----------
147
148
149Running a Server
150~~~~~~~~~~~~~~~~
151
152Now that we have defined the behavior of our WebSocket server in a protocol class, we need to actually start a server based on that behavior.
153
154Doing so involves two steps:
155
1561. Create a **Factory** for producing instances of our protocol class
1572. Create a TCP **listening server** using the former Factory
158
159Here is one way of doing that when using Twisted
160
161*Twisted:*
162
163.. code-block:: python
164   :emphasize-lines: 9-11
165
166   if __name__ == '__main__':
167
168      import sys
169
170      from twisted.python import log
171      from twisted.internet import reactor
172      log.startLogging(sys.stdout)
173
174      from autobahn.twisted.websocket import WebSocketServerFactory
175      factory = WebSocketServerFactory()
176      factory.protocol = MyServerProtocol
177
178      reactor.listenTCP(9000, factory)
179      reactor.run()
180
181What we are doing here is
182
1831. Setup Twisted logging
1842. Create a :class:`autobahn.twisted.websocket.WebSocketServerFactory` and set our ``MyServerProtocol`` on the factory (the highlighted lines)
1853. Start a server using the factory, listening on TCP port 9000
186
187Similar, here is the asyncio way
188
189*asyncio:*
190
191.. code-block:: python
192   :emphasize-lines: 9-11
193
194   if __name__ == '__main__':
195
196      try:
197         import asyncio
198      except ImportError:
199         ## Trollius >= 0.3 was renamed
200         import trollius as asyncio
201
202      from autobahn.asyncio.websocket import WebSocketServerFactory
203      factory = WebSocketServerFactory()
204      factory.protocol = MyServerProtocol
205
206      loop = asyncio.get_event_loop()
207      coro = loop.create_server(factory, '127.0.0.1', 9000)
208      server = loop.run_until_complete(coro)
209
210      try:
211         loop.run_forever()
212      except KeyboardInterrupt:
213         pass
214      finally:
215         server.close()
216         loop.close()
217
218What we are doing here is
219
2201. Import asyncio, or the Trollius backport
2212. Create a :class:`autobahn.asyncio.websocket.WebSocketServerFactory` and set our ``MyServerProtocol`` on the factory (the highlighted lines)
2223. Start a server using the factory, listening on TCP port 9000
223
224.. note::
225   As can be seen, the boilerplate to create and run a server differ from Twisted, but the core code of creating a factory and setting our protocol (the highlighted lines) is identical (other than the differing import for the WebSocket factory).
226
227You can find complete code for above examples here:
228
229* `WebSocket Echo (Twisted-based) <https://github.com/crossbario/autobahn-python/tree/master/examples/twisted/websocket/echo>`_
230* `WebSocket Echo (Asyncio-based) <https://github.com/crossbario/autobahn-python/tree/master/examples/asyncio/websocket/echo>`_
231
232
233.. _connection-lifecycle:
234
235Connection Lifecycle
236--------------------
237
238As we have seen above, |ab| will fire *callbacks* on your protocol class whenever the event related to the respective callback occurs.
239
240It is in these callbacks that you will implement application specific code.
241
242The core WebSocket interface :class:`autobahn.websocket.interfaces.IWebSocketChannel` provides the following *callbacks*:
243
244* :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onConnect`
245* :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onConnecting`
246* :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onOpen`
247* :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onMessage`
248* :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onClose`
249
250We have already seen the callback for :ref:`receiving-messages`. This callback will usually fire many times during the lifetime of a WebSocket connection.
251
252In contrast, the other four callbacks above each only fires once for a given connection.
253
254Opening Handshake
255~~~~~~~~~~~~~~~~~
256
257Whenever a new client connects to the server, a new protocol instance will be created and the :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onConnect` callback fires as soon as the WebSocket opening handshake is begun by the client.
258
259For a WebSocket server protocol, ``onConnect()`` will fire with
260:class:`autobahn.websocket.protocol.ConnectionRequest` providing information on the client wishing to connect via WebSocket.
261
262.. code-block:: python
263
264   class MyServerProtocol(WebSocketServerProtocol):
265
266      def onConnect(self, request):
267         print("Client connecting: {}".format(request.peer))
268
269For a WebSocket client protocol, ``onConnecting()`` is called
270immediately before the handshake to the server starts. It is called
271with some details about the underlying transport. This may return
272``None`` (the default) to get default values for several options
273(which are gotten from the Factory) or it may return a
274:class:`autobahn.websocket.types.ConnectingRequest` instance to
275indicate options for this handshake. This allows using different
276options on each request (as opposed to using a static set of options
277in the Factory).
278
279Then, once the server has responded, a WebSocket client protocol will
280fire ``onConnect()`` with a
281:class:`autobahn.websocket.protocol.ConnectionResponse` providing
282information on the WebSocket connection that was accepted by the
283server.
284
285.. code-block:: python
286
287   class MyClientProtocol(WebSocketClientProtocol):
288
289      def onConnect(self, response):
290         print("Connected to Server: {}".format(response.peer))
291
292In this callback you can do things like
293
294* checking or setting cookies or other HTTP headers
295* verifying the client IP address
296* checking the origin of the WebSocket request
297* negotiate WebSocket subprotocols
298
299For example, a WebSocket client might offer to speak several WebSocket subprotocols. The server can inspect the offered protocols in ``onConnect()`` via the supplied instance of :class:`autobahn.websocket.protocol.ConnectionRequest`. When the server accepts the client, it'll chose one of the offered subprotocols. The client can then inspect the selected subprotocol in it's ``onConnect()`` callback in the supplied instance of :class:`autobahn.websocket.protocol.ConnectionResponse`.
300
301Connection Open
302~~~~~~~~~~~~~~~
303
304The :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onOpen` callback fires when the WebSocket opening handshake has been successfully completed. You now can send and receive messages over the connection.
305
306.. code-block:: python
307
308   class MyProtocol(WebSocketProtocol):
309
310      def onOpen(self):
311         print("WebSocket connection open.")
312
313
314Closing a Connection
315~~~~~~~~~~~~~~~~~~~~
316
317The core WebSocket interface :class:`autobahn.websocket.interfaces.IWebSocketChannel` provides the following *methods*:
318
319* :meth:`autobahn.websocket.interfaces.IWebSocketChannel.sendMessage`
320* :meth:`autobahn.websocket.interfaces.IWebSocketChannel.sendClose`
321
322We've already seen one of above in :ref:`sending-messages`.
323
324The :meth:`autobahn.websocket.interfaces.IWebSocketChannel.sendClose` will initiate a WebSocket closing handshake. After starting to close a WebSocket connection, no messages can be sent. Eventually, the :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onClose` callback will fire.
325
326After a WebSocket connection has been closed, the protocol instance will get recycled. Should the client reconnect, a new protocol instance will be created and a new WebSocket opening handshake performed.
327
328
329Connection Close
330~~~~~~~~~~~~~~~~
331
332When the WebSocket connection has closed, the :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onClose` callback fires.
333
334.. code-block:: python
335
336   class MyProtocol(WebSocketProtocol):
337
338      def onClose(self, wasClean, code, reason):
339         print("WebSocket connection closed: {}".format(reason))
340
341When the connection has closed, no messages will be received anymore and you cannot send messages also. The protocol instance won't be reused. It'll be garbage collected. When the client reconnects, a completely new protocol instance will be created.
342
343
344.. _creating-websocket-clients:
345
346Creating Clients
347----------------
348
349.. note::
350   Creating WebSocket clients using |Ab| works very similar to creating WebSocket servers. Hence you should have read through :ref:`creating-websocket-servers` first.
351
352As with servers, the behavior of your WebSocket client is defined by writing a *protocol class*.
353
354
355Client Protocols
356~~~~~~~~~~~~~~~~
357
358To create a WebSocket client, you need to write a protocol class to **specify the behavior** of the client.
359
360For example, here is a protocol class for a WebSocket client that will send a WebSocket text message as soon as it is connected and log any WebSocket messages it receives:
361
362.. code-block:: python
363
364   class MyClientProtocol(WebSocketClientProtocol):
365
366      def onOpen(self):
367         self.sendMessage(u"Hello, world!".encode('utf8'))
368
369      def onMessage(self, payload, isBinary):
370         if isBinary:
371            print("Binary message received: {0} bytes".format(len(payload)))
372         else:
373            print("Text message received: {0}".format(payload.decode('utf8')))
374
375Similar to WebSocket servers, you **derive** your WebSocket client protocol class from a base class provided by |ab|. Depending on whether you write a Twisted or a asyncio based application, here are the base classes to derive from:
376
377* :class:`autobahn.twisted.websocket.WebSocketClientProtocol`
378* :class:`autobahn.asyncio.websocket.WebSocketClientProtocol`
379
380So a Twisted-based protocol would import the base protocol from ``autobahn.twisted.websocket`` and derive from :class:`autobahn.twisted.websocket.WebSocketClientProtocol`
381
382*Twisted:*
383
384.. code-block:: python
385
386   from autobahn.twisted.websocket import WebSocketClientProtocol
387
388   class MyClientProtocol(WebSocketClientProtocol):
389
390      def onOpen(self):
391         self.sendMessage(u"Hello, world!".encode('utf8'))
392
393      def onMessage(self, payload, isBinary):
394         if isBinary:
395            print("Binary message received: {0} bytes".format(len(payload)))
396         else:
397            print("Text message received: {0}".format(payload.decode('utf8')))
398
399while an asyncio-based protocol would import the base protocol from ``autobahn.asyncio.websocket`` and derive from :class:`autobahn.asyncio.websocket.WebSocketClientProtocol`
400
401*asyncio:*
402
403.. code-block:: python
404
405   from autobahn.asyncio.websocket import WebSocketClientProtocol
406
407   class MyClientProtocol(WebSocketClientProtocol):
408
409      def onOpen(self):
410         self.sendMessage(u"Hello, world!".encode('utf8'))
411
412      def onMessage(self, payload, isBinary):
413         if isBinary:
414            print("Binary message received: {0} bytes".format(len(payload)))
415         else:
416            print("Text message received: {0}".format(payload.decode('utf8')))
417
418.. note:: In this example, only the imports differs between the Twisted and the asyncio variant. The rest of the code is identical. However, in most real world programs you probably won't be able to or don't want to avoid using network framework specific code.
419
420-------
421
422Receiving and sending WebSocket messages as well as connection lifecycle in clients works exactly the same as with servers. Please see
423
424* :ref:`receiving-messages`
425* :ref:`sending-messages`
426* :ref:`connection-lifecycle`
427
428Running a Client
429~~~~~~~~~~~~~~~~
430
431Now that we have defined the behavior of our WebSocket client in a protocol class, we need to actually start a client based on that behavior.
432
433Doing so involves two steps:
434
4351. Create a **Factory** for producing instances of our protocol class
4362. Create a TCP **connecting client** using the former Factory
437
438Here is one way of doing that when using Twisted
439
440*Twisted:*
441
442.. code-block:: python
443   :emphasize-lines: 9-11
444
445   if __name__ == '__main__':
446
447      import sys
448
449      from twisted.python import log
450      from twisted.internet import reactor
451      log.startLogging(sys.stdout)
452
453      from autobahn.twisted.websocket import WebSocketClientFactory
454      factory = WebSocketClientFactory()
455      factory.protocol = MyClientProtocol
456
457      reactor.connectTCP("127.0.0.1", 9000, factory)
458      reactor.run()
459
460What we are doing here is
461
4621. Setup Twisted logging
4632. Create a :class:`autobahn.twisted.websocket.WebSocketClientFactory` and set our ``MyClientProtocol`` on the factory (the highlighted lines)
4643. Start a client using the factory, connecting to localhost ``127.0.0.1`` on TCP port 9000
465
466Similar, here is the asyncio way
467
468*asyncio:*
469
470.. code-block:: python
471   :emphasize-lines: 9-11
472
473   if __name__ == '__main__':
474
475      try:
476         import asyncio
477      except ImportError:
478         ## Trollius >= 0.3 was renamed
479         import trollius as asyncio
480
481      from autobahn.asyncio.websocket import WebSocketClientFactory
482      factory = WebSocketClientFactory()
483      factory.protocol = MyClientProtocol
484
485      loop = asyncio.get_event_loop()
486      coro = loop.create_connection(factory, '127.0.0.1', 9000)
487      loop.run_until_complete(coro)
488      loop.run_forever()
489      loop.close()
490
491What we are doing here is
492
4931. Import asyncio, or the Trollius backport
4942. Create a :class:`autobahn.asyncio.websocket.WebSocketClientFactory` and set our ``MyClientProtocol`` on the factory (the highlighted lines)
4953. Start a client using the factory, connecting to localhost ``127.0.0.1`` on TCP port 9000
496
497.. note::
498   As can be seen, the boilerplate to create and run a client differ from Twisted, but the core code of creating a factory and setting our protocol (the highlighted lines) is identical (other than the differing import for the WebSocket factory).
499
500You can find complete code for above examples here:
501
502* `WebSocket Echo (Twisted-based) <https://github.com/crossbario/autobahn-python/tree/master/examples/twisted/websocket/echo>`_
503* `WebSocket Echo (Asyncio-based) <https://github.com/crossbario/autobahn-python/tree/master/examples/asyncio/websocket/echo>`_
504
505
506WebSocket Options
507-----------------
508
509You can pass various options on both client and server side WebSockets; these are accomplished by calling :meth:`autobahn.websocket.WebSocketServerFactory.setProtocolOptions` or :meth:`autobahn.websocket.WebSocketClientFactory.setProtocolOptions` with keyword arguments for each option.
510
511
512Common Options (server and client)
513~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
514
515 - logOctets: if True, log every byte
516 - logFrames: if True, log information about each frame
517 - trackTimings: if True, enable debug timing code
518 - utf8validateIncoming: if True (default), validate all incoming UTF8
519 - applyMask: if True (default) apply mask to frames, when available
520 - maxFramePayloadSize: if 0 (default), unlimited-sized frames allowed
521 - maxMessagePayloadSize: if 0 (default), unlimited re-assembled payloads
522 - autoFragmentSize: if 0 (default), don't fragment
523 - failByDrop: if True (default), failed connections are terminated immediately
524 - echoCloseCodeReason: if True, echo back the close reason/code
525 - openHandshakeTimeout: timeout in seconds after which opening handshake will be failed (default: no timeout)
526 - closeHandshakeTimeout: timeout in seconds after which close handshake will be failed (default: no timeout)
527 - tcpNoDelay: if True (default), set NODELAY (Nagle) socket option
528 - autoPingInterval: if set, seconds between auto-pings
529 - autoPingTimeout: if set, seconds until a ping is considered timed-out
530 - autoPingSize: bytes of random data to send in ping messages (between 4 [default] and 125)
531
532
533Server-Only Options
534~~~~~~~~~~~~~~~~~~~
535
536- versions: what versions to claim support for (default 8, 13)
537- webStatus: if True (default), show a web page if visiting this endpoint without an Upgrade header
538- requireMaskedClientFrames: if True (default), client-to-server frames must be masked
539- maskServerFrames: if True, server-to-client frames must be masked
540- perMessageCompressionAccept: if provided, a single-argument callable
541- serveFlashSocketPolicy: if True, server a flash policy file (default: False)
542- flashSocketPolicy: the actual flash policy to serve (default one allows everything)
543- allowedOrigins: a list of origins to allow, with embedded `*`'s for wildcards; these are turned into regular expressions (e.g. `https://*.example.com:443` becomes `^https://.*\.example\.com:443$`). When doing the matching, the origin is **always** of the form `scheme://host:port` with an explicit port. By default, we match with `*` (that is, anything). To match all subdomains of `example.com` on any scheme and port, you'd need `*://*.example.com:*`
544- maxConnections: total concurrent connections allowed (default 0, unlimited)
545- trustXForwardedFor: number of trusted web servers (reverse proxies) in front of this server which set the X-Forwarded-For header
546
547
548Client-Only Options
549~~~~~~~~~~~~~~~~~~~
550
551- version: which version we are (default: 18)
552- acceptMaskedServerFrames: if True, accept masked server-to-client frames (default False)
553- maskClientFrames: if True (default), mask client-to-server frames
554- serverConnectionDropTimeout: how long (in seconds) to wait for server to drop the connection when closing (default 1)
555- perMessageCompressionOffers:
556- perMessageCompressionAccept:
557
558
559Upgrading
560---------
561
562From < 0.7.0
563~~~~~~~~~~~~
564
565Starting with release 0.7.0, |Ab| now supports both Twisted and asyncio as the underlying network library. This required renaming some modules.
566
567Hence, code for |ab| **< 0.7.0**
568
569.. code-block:: python
570
571     from autobahn.websocket import WebSocketServerProtocol
572
573should be modified for |ab| **>= 0.7.0** for (using Twisted)
574
575.. code-block:: python
576
577     from autobahn.twisted.websocket import WebSocketServerProtocol
578
579or (using asyncio)
580
581.. code-block:: python
582
583     from autobahn.asyncio.websocket import WebSocketServerProtocol
584
585Two more small changes:
586
5871. The method ``WebSocketProtocol.sendMessage`` had parameter ``binary`` renamed to ``isBinary`` (for consistency with ``onMessage``)
5882. The ``ConnectionRequest`` object no longer provides ``peerstr``, but only ``peer``, and the latter is a plain, descriptive string (this was needed since we now support both Twisted and asyncio, and also non-TCP transports)
589