Engineering

How to Implement WebSocket Server Using Twisted

November 25, 2015bySumeet Pujara10 min read

HTTP is a request-response type one way protocol. For web applications where continuous data needs to be sent, 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 the Twisted.web section.

WebSocket

To initiate communication using WebSocket, a handshake needs 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:

text
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 and version, it will send adequate response:

text
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. After a successful handshake, any of the peers can send data to each other which must be encoded in binary format described in WebSocket RFC.

WebSocket Frame Structure

A high-level overview of the framing:

text
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 Implementation

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

Whenever data is received, dataReceived method of HTTPChannel is invoked. If data starts with 'GET', allow the HTTPChannel to handle it, which will invoke the render function of the root resource provided to Site class.

Echo WebSocket Server Example

python
1import base64, hashlib
2from twisted.internet import reactor
3from twisted.web.server import Site, http, resource
4
5class EchoResource(resource.Resource):
6    isLeaf = 1
7    
8    def render(self, request):
9        # Processing the Key as per RFC 6455
10        key = request.getHeader('Sec-WebSocket-Key')
11        h = hashlib.sha1(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
12        
13        request.setHeader('Sec-WebSocket-Accept', base64.b64encode(h.digest()))
14        
15        # Setting response headers
16        request.setHeader('Upgrade', 'websocket')
17        request.setHeader('Connection', 'Upgrade')
18        request.setResponseCode(101)
19        
20        return ''
21
22class EchoChannel(http.HTTPChannel):
23    def dataReceived(self, data):
24        if data.startswith('GET'):
25            # This will invoke the render method of resource
26            http.HTTPChannel.dataReceived(self, data)
27        else:
28            # Decoding data using Frame module
29            f = frame.Frame(bytearray(data))
30            received_message = f.message()
31            print(received_message)
32            
33            # Sending back the received message
34            msg = frame.Frame.buildMessage(received_message, mask=False)
35            self.transport.write(str(msg))
36
37class EchoSite(Site):
38    def buildProtocol(self, addr):
39        channel = EchoChannel()
40        channel.requestFactory = self.requestFactory
41        channel.site = self
42        return channel
43
44site = EchoSite(EchoResource())
45
46if __name__ == '__main__':
47    reactor.listenTCP(8080, site)
48    reactor.run()

This implementation creates a simple echo server that upgrades HTTP connections to WebSocket and echoes back any messages it receives. The key components are:

  • EchoResource: Handles the WebSocket handshake
  • EchoChannel: Processes incoming WebSocket frames
  • EchoSite: Factory for creating channel instances

Data sent to the client by server should be unmasked as per the WebSocket specification. The Frame.py module handles the binary framing according to the WebSocket RFC, making it simple to encode and decode messages.

Need Help With Your Project?

We've delivered production-ready solutions for startups and enterprises. Let's discuss your project.

Get in Touch