time

Realtime web application with Tornado and WebSocket

Websocket is a feature of HTML5 that allow for asynchronous communication between the web server and the client. WebSocket is a naturally full-duplex, bidirectional, single-socket connection. With WebSocket, your HTTP request becomes a single request to open a WebSocket connection and reuses the same connection from the client to the server, and the server to the client.

WebSocket reduces latency because once the WebSocket connection is established, the server can send messages as they become available. For example, unlike polling, WebSocket makes a single request. The server does not need to wait for a request from the client. Similarly, the client can send messages to the server at any time. This single request greatly reduces latency over polling, which sends a request at intervals, regardless of whether messages are available.

A WebSocket connection is established by upgrading from the HTTP protocol to the WebSocket Protocol during the initial handshake between the client and the server, over the same underlying TCP connection.

The WebSocket API is purely (and truly) event driven. Once the full-duplex connection is established, when the server has data to send to the client, or if resources that you care about change their state, it automatically sends the data or notifications.

Following is comparison between the polling and WebSocket applications

Server

Tornado is a scalable, non-blocking web server and web application framework written in Python. It was developed for use by FriendFeed; the company was acquired by Facebook in 2009 and Tornado was open-sourced soon after.

Install Tornado

pip install tornado

Simple server

import tornado.ioloop
import tornado.web
import tornado.websocket

from tornado.options import define, options, parse_command_line

define("port", default=8888, type=int)


class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("index.html")


class WebSocketHandler(tornado.websocket.WebSocketHandler):
    def open(self, *args):
        print "New connection"
        self.write_message("Welcome!")

    def on_message(self, message):
        print "New message {}".format(message)
        self.write_message(message.upper())

    def on_close(self):
        print "Connection closed"


app = tornado.web.Application([
    (r'/', IndexHandler),
    (r'/ws/', WebSocketHandler),
])


if __name__ == '__main__':
    app.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

index.html template you can find here.

Run server

python tornado_ws.py

Open in browser http://127.0.0.1:8888.

Client

Lets install websocket-client (websocket client for python) and test our WebSocket server.

pip install websocket-client

Simple client on python

from websocket import create_connection

ws = create_connection("ws://localhost:8888/ws/")
print "Sending 'Hello, World'..."
ws.send("Hello, World")
print "Sent"

print "Reeiving..."
result =  ws.recv()
print "Received '%s'" % result

ws.close()

Simple client on javascript (you can find full code in index.html above)

if ("WebSocket" in window) {
    log("WebSocket is supported by your browser.");

    var ws = new WebSocket("ws://127.0.0.1:8888/ws/");

    ws.onopen = function() {
        log("Connection is opened ...");
        ws.send("Hello there!");
    };

    ws.onmessage = function (evt) { 
        var msg = evt.data;
        log("Message is received: " + msg);
    };

    ws.onclose = function() { 
        log("Connection is closed ...");
    };

} else {
    log("WebSocket not supported by your browser.");
}

Nginx in front

The usual strategy when building a Python app is to put Nginx in front of Python as a reverse proxy that serves any static content. This was a problem if you wanted to use WebSockets though, as Nginx didn’t know how to proxy those requests. Until version 1.3.13 of Nginx.

Now you can set this directives

location / {
    proxy_pass http://localhost:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
}

The proxy_http_version directive tells Nginx to use HTTP/1.1 when communicating to the Python backend, which is required for WebSockets. The next two tell Nginx to respond to the Upgrade request which is initiated over HTTP by the browser when it wants to use a WebSocket.

Useful links

comments powered by Disqus