Websocket Intro

Websocket Intro

For some systems it is necessary to obtain the latest information quickly and regularly, especially if it's stock market information. It's not feasible to poll a HTTP endpoint as you will risk being throttled or even worse, miss events in-between polls and carry on as normal without noticing. What is needed is some way to convert the unary nature of HTTP 1.1 calls into a persistent streaming connection where updates can be pushed by the server as they come. The WebSocket Protocol to the rescue. This does exactly what we need. As a bonus there are plenty of production ready libraries for this protocol in a myriad of langauges.

This introduction seeks to demonstrate a very simple websocket client.  This can then be a launching point for more projects. We'll be using Python 3.8 and the excellent Autobahn library.

There is a completed example project you can peruse made for this post.

Setup

If you don't have python installed, go to https://www.python.org/ and install it. Also make sure to use virtualenv to separate your development environment from the system environment for Python. This way packages installed during development don't pollute the system package namespace which could cause problems for you down the line. I use virtualenvwrapper to make the process easier.

To create the virtual environment (assuming virtualenvwrapper is installed), in console run;

mkproject websockexample

This example shall use the built in asyncio features of Python.

Protocol Handler The autobahn library provides a protocol which needs to be inherited to handle websocket events such as connection, message recept and closing. A client and server protocol are provided. It is in the protocol derived class where websocket logic can be written.

This protocol derived class is then passed to a factory which is then passed to asyncio connection/server creation facilities.

Client

Import the autobahn classes;

from autobahn.asyncio.websocket import WebSocketClientProtocol
from autobahn.asyncio.websocket import WebSocketClientFactory

 You will need to create a derived class based on WebSocketClientProtocol.

class WSEStream(WebSocketClientProtocol):
    def onConnect(self, response):
        print('Connected to server: ' + str(response)) 

There are several more methods to override; onConnecting, onOpen, onMessage and onClose.   To instantiate the protocol, it needs to be assigned to a factory and moved into a coroutine.

factory = WebSocketClientFactory('ws://127.0.0.1:9000')
factory.protocol = WSEStream
coro = event_loop.create_connection(factory, '127.0.0.1', 9000)

The event loop is sourced by obtaining the global event loop from asyncio. To run the coroutine, pass it to the event loop.

event_loop.run_until_complete(coro)
Server

Setup of the server is nearly the same as the client. We do it to test that the client works.

from autobahn.asyncio.websocket import WebSocketServerProtocol
from autobahn.asyncio.websocket import WebSocketServerFactory

Create the derived class as before. You don't need to derive the onConnecting method.

 Instantiating is a little different as you need to ask asyncio to create a server connection.

factory = WebSocketServerFactory('ws://127.0.0.1:9000')
factory.protocol = WSEServe
coro = event_loop.create_server(factory, '127.0.0.1', 9000)

And we run the server for ever.

server = event_loop.run_until_complete(coro)

Result

With this simple setup you can send test messages to the server at an interval and have the server echo them back to you. This is happening over a single streaming TCP connection. Have a look at the websockexample.

Running the Examples

Due to the simple nature of the client and server, it is best for the server to be running and listening first before starting the client. Open two consoles, one for the client and one for the server. Make sure you are inside the active virtualenv for the project in both. Then run;

pip install .

This will install the example to your virtualenv environment as if you installed onto your computer proper. If you are not inside your virtualenv for this project, this command will install the example into your system wide namespace causing possible conflicts with pre-existing Python packages. Don't do this especially if you are on Linux and have a distro provided Python installation.

Then in the first console, start the server;

wseserver

In the second console, start the client;

wseclient

The two should connect and begin passing messages between one another. To exit, just ctrl-c.

Inspect the source code and make changes here and there and see what the effect is. After saving your changes, be sure to run pip install . to reinstall the script into your virtualenv for the project.

Another Example (KabuStation API)

In the example project you may have noticed a second client, one tailored for Kabu Station API. This example is built from the first example. There is an additional HTTP client for registering symbols. To be able to run the example you must have a Kabu.com account and KabuStation installed on the same host as the example project. To run the example;

kclient key

The key parameter is the API access key. Please consult Kabu.com for further instruction on how to configure and run Kabu Station API.

Conclusion

 This exercise is a starting point for delving into websockets on Python. With a simple test client operational, you can now tackle the data feed you want to subscribe to.