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
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
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()
from the looks of the code it looks like “frame” would be undefined. I don’t see you importing it from anywhere.
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