What's that?
TRANZIT is an asynchronous micro web framework supporting websockets (full duplex socket communication). With tranzit you can easily maintain both push & pull websocket messaging.
In fact, tranzit is a just a tiny wrapper for wonderful aiohttp lib providing async websocket server (which can be run separately) and a little CLI tool.
Installation
You can install tranzit via pip:
$ pip install tranzit
Quickstart
You can use CLI tool for that:
$ tranzit project <PROJECT_NAME>
$ cd <PROJECT_NAME>
$ tranzit run
Now your project runs on http://0.0.0.0:3000/ .
The first command creates a skeleton project:
<PROJECT_NAME>/
__init__.py
apps/
__init__.py
hello/
__init__.py
static/
camel.html
common/
static/
tz.js
main.py
server.yml
The main.py file starts the whole thing. It gets configs from server.yml such as:
- HTTP server host
- HTTP server port
- To start websocket server or not
- Websocket server host
- Websocket server port
- Which apps to run
- Is this a production start or not (not meaningful yet)
Info: You should always pass PROJECT_DIR to MainServer() constructor
Routing and Views
All routing (for both http requests and websocket requests) is handled by apps/<app_name>/routes.py file.
PATH_PREFIX
variable contains prefix for urls handled by this
application (like [http://0.0.0.0:3000/<PATH_PREFIX>/some_url]).
APP_STATIC_DIR
variable contains absolute path to the static
folder for this application.
All views for http requests are dispatched by routes dict. Http views are ordinary aiohttp views.
All websocket views are dispatched by ws_rules dict. See explanation below.
Middlewares
You can pass a list of your middlewares in main.py:
MainServer(
'server.yml', PROJECT_DIR, middlewares=[my_middleware]
)
More on aiohttp middlewares here.
Staticfiles
There are two types of static files directories in the project skeleton.
The first is <PROJECT_NAME>/common/static/ . It contains common project static files and is available via
http://0.0.0.0:3000/static/common/YOUR_GLORIOUS.js
The second type is app-specific: <PROJECT_NAME>/apps/<APP_NAME>/static . As you may suggest, it can be found here:
http://0.0.0.0:3000/static/APP_NAME/ANOTHER_GLORIOUS.js
Few words about websockets
To establish websocket messaging you must add websocket rule in apps/<app_name>/routes.py file just like that:
ws_rules = {
'get_hello': WSPushHandle.get_hello
}
Now this view is available via websockets. To call this view function, do something like this on the client side:
var ws = new WebSocket('ws://0.0.0.0:19719');
ws.onmessage = function(response) {
console.log(response.data);
};
ws.send('get_hello|');
We send message to call the view. Here is an ugly syntax for that message:
<function_name>|arg1, arg2, arg3, ...
Now take a look at the websocket view:
class WSPushHandle(object):
@staticmethod
async def get_hello(*args, **kwargs):
send_func = kwargs['send_func']
writer = kwargs['writer']
while True:
await asyncio.sleep(1)
await send_func(writer, 'HELLO!')
Last line is a way to send message back to the client:
await send_func(writer, 'HELLO!')
As you can see, send_func
and writer
instances are accessible via **kwargs
.
Warning:
beware of yellow snow!all websocket routes are in the same namespace! Avoid collisions.
Another warning: No security provided! Be sure to verify websocket request (e.g. pass user session key in WS view parameters)
Using aiohttp module
All of aiohttp.web
module is accessible via tranzit.web
.
Starting stand alone websocket server
You can use websocket server without http server.
All you need is an instance of:
tranzit.web.WebSocketServer(host, port, api)
api is a handler class instance which implements 3 functions as shown below:
class TranzitWSHandler(object):
async def handle_text(self, loop, writer, msg):
pass
async def handle_binary(self, loop, writer, msg):
pass
async def handle_buffered(self, loop, reader, writer, first_msg):
pass
Example starting stand alone websocket server:
from tranzit.web import WebSocketServer
class MyHandler():
def __init__(self, rules={}):
self.rules = rules
async def handle_text(self, loop, writer, msg):
response = msg.upper()
await WebSocketServer.send_text(writer, response)
async def handle_binary(self, loop, writer, msg):
pass
async def handle_buffered(self, loop, reader, writer, first_msg):
pass
ws_server = WebSocketServer(
host='0.0.0.0', port=19719, api=MyHandler()
)
ws_server.run_forever()