Twisted Python networking library

How to implement Websocket server using Twisted.

HTTP is a request-response type one way protocol. For the web application where continuous data is to be send, websocket was introduced. Unlike HTTP, websocket provides full duplex communication. Websocket, which can be said as an upgraded version of HTTP, is standardized to be used over TCP like HTTP. In this article I will share my experience in implementing websocket with twisted, a framework of python for internet. If you are familiar with websocket, then you can skip to twisted.web or else below is a little introduction to websocket.

WebSocket

To initiate communication using websocket, a Handshake need to be done between client and server. This procedure is backward compatible to HTTP’s request – response structure. First the client sends a handshake request to the server which looks like:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

Sending Upgrade header in request with value websocket will acknowledge server about websocket communication. Now if server supports websocket with specified sub-protocols (Sec-WebSocket-Protocol) and version (Sec-WebSocket-Version), it will send adequate response . Possible response could be:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
In response, server will send 101 Switching Protocols code and Sec-WebSocket-Accept whose value is calculated using Sec-WebSocket-Key. you can find more information here. After a successful handshake, any of the peer can send data to each other which must be encoded in binary format described in websocket RFC. A high-level overview of the framing is given in the following figure.
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

Twisted.web

As in normal twisted.web server , at TCP level, we have HTTPChannel class (a child class of T.I.protocol.Protocol) and server.Site class (which is the child class of T.I.protocol.ServerFactory). Also a Resource instance needs to be passed to server.site class, so that it can serve GET request.

Whenever a data is received, DataReceived method of HTTPChannel is invoked. Now if data starts with ‘GET’, allow the HTTPChannel handle it, which will invoke the render function of the root resource provided to Site class. Render will set 101 response code and will compute the websocket response key. During handshake do not send any raw data, because if handshake is successful this will be considered as framed binary data. Even if you want to send, frame it and send.

If data doesn’t start with ‘GET’, that means we can assume it is a binary encoded message. Now this message can be decoded using Frame.py, which is a very simple data framing module following WebSocket specification. Data send to the client by server should be unmasked as per the websocket specification.

Below is code example of an echo websocket server.

import base64, hashlib
from twisted.internet import reactor
from twisted.web.server import (Site, http, resource)

class EchoResource(resource.Resource):
    isLeaf = 1

    def render(self, request):

        # Processing the Key as per RFC 6455
        key = request.getHeader('Sec-WebSocket-Key')
        h = hashlib.sha1(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
        request.setHeader('Sec-WebSocket-Accept', base64.b64encode(h.digest()))

        # setting response headers
        request.setHeader('Upgrade', 'websocket')
        request.setHeader('Connection', 'Upgrade')
        request.setResponseCode(101)
        return ''


class EchoChannel(http.HTTPChannel):

    def dataReceived(self, data):

        if data.startswith('GET'):
            # This will invoke the render method of resource provided
            http.HTTPChannel.dataReceived(self, data)

        else:
            # decoding Data using Frame module wrote by Morgan Philips.
            f = frame.Frame(bytearray(data))
            received_message = f.message()
            print received_message

            # Sending back the received message.
            msg = frame.Frame.buildMessage(received_message, mask=False)
            self.transport.write(str(msg))


class EchoSite(Site):
    def buildProtocol(self, addr):
        channel = EchoChannel()
        channel.requestFactory = self.requestFactory
        channel.site = self
        return channel

site = EchoSite(EchoResource())

if __name__ == '__main__':
    reactor.listenTCP(8080, site)
    reactor.run()

 


2 thoughts on “How to implement Websocket server using Twisted.

  1. Warning: Attempt to read property "entry" on string in /var/app/current/wp-content/plugins/jetpack/modules/gravatar-hovercards.php on line 190 Warning: Trying to access array offset on value of type null in /var/app/current/wp-content/plugins/jetpack/modules/gravatar-hovercards.php on line 190 Warning: Attempt to read property "displayName" on null in /var/app/current/wp-content/plugins/jetpack/modules/gravatar-hovercards.php on line 191
    Alfred Morgan says:

    from the looks of the code it looks like “frame” would be undefined. I don’t see you importing it from anywhere.

  2. Warning: Attempt to read property "entry" on string in /var/app/current/wp-content/plugins/jetpack/modules/gravatar-hovercards.php on line 190 Warning: Trying to access array offset on value of type null in /var/app/current/wp-content/plugins/jetpack/modules/gravatar-hovercards.php on line 190 Warning: Attempt to read property "displayName" on null in /var/app/current/wp-content/plugins/jetpack/modules/gravatar-hovercards.php on line 191
    Alfred Morgan says:

    Your websocket code makes an assumption that the entire frame is received in one dataReceived call. From Twisted docs: “Please keep in mind that you will probably need to buffer some data as partial (or multiple) protocol messages may be received! We recommend that unit tests for protocols call through to this method with differing chunk sizes, down to one byte at a time.”
    https://twistedmatrix.com/documents/current/api/twisted.internet.interfaces.IProtocol.html#dataReceived

Leave a Reply

Your email address will not be published. Required fields are marked *