ws4py - A WebSocket package for Python¶
Author: | Sylvain Hellegouarch |
---|---|
Release: | 0.5.1 |
License: | BSD |
Source code: | https://github.com/Lawouach/WebSocket-for-Python |
Build status: | https://travis-ci.org/Lawouach/WebSocket-for-Python |
ws4py is a Python package implementing the WebSocket protocol as defined in RFC 6455.
It comes with various server and client implementations and runs on CPython 2/3, PyPy and Android.
Overview¶
Requirements¶
Python¶
Tested environments:
- Python 2.7+
- Python 3.3+
- PyPy 1.8+
- Android 2.3 via SL4A
Note
ws4py will not try to automatically install dependencies and will let you decide which one you need.
Client¶
ws4py comes with three client implementations:
Server¶
ws4py comes with three server implementations:
Testing¶
ws4py uses the Autobahn functional test suite to ensure it respects the standard. You must install it to run that test suite against ws4py.
- Autobahn python
- Autobahn test suite
Install ws4py¶
Get the code¶
ws4py is hosted on github and can be retrieved from there:
$ git clone git@github.com:Lawouach/WebSocket-for-Python.git
Installing the ws4py package is performed as usual:
$ python setup.py install
However, since ws4py is referenced in PyPI, it can also be installed through easy_install, distribute or pip:
$ pip install ws4py
$ easy_install ws4py
Note
ws4py explicitly will not automatically pull out its dependencies. Please install them manually depending on which implementation you’ll be using.
Conformance¶
ws4py tries hard to be as conformant as it can to the specification. In order to validate this conformance, each release is run against the Autobahn testsuite which provides an extensive coverage of various aspects of the protocol.
Online test reports can be found at: http://www.defuze.org/oss/ws4py/testreports/servers/0.3.5.
Browser Support¶
ws4py has been tested using:
- Chromium 22
- Firefox 16
See http://caniuse.com/websockets to determine the current implementation’s status of various browser vendors.
Bear in mind that time is a premium and maintaining obsolete and unsecure protocols is not one of ws4py’s goals. It’s therefore unlikely it will ever support older versions of the protocol.
Performances¶
ws4py doesn’t perform too bad but it’s far from being the fastest WebSocket lib under heavy load. The reason is that it was first designed to implement the protocol with simplicity and clarity in mind. Future developments will look at performances.
Note
ws4py runs faster in some cases on PyPy than it does on CPython.
Note
The wsaccel package replaces some internal bottleneck with a Cython implementation.
Credits¶
Many thanks to the pywebsocket and Tornado projects which have provided a the starting point for ws4py. Thanks also to Jeff Lindsay (progrium) for the initial gevent server support. A well deserved thank you to Tobias Oberstein for Autobahn test suite.
Obviously thanks to all the various folks who have provided bug reports and fixes.
Tutorial¶
Basics¶
ws4py provides a high-level, yet simple, interface to provide your application with WebSocket support. It is simple as:
from ws4py.websocket import WebSocket
The WebSocket
class should be sub-classed by your application. To the very least we suggest you override the received_message(message)
method so that you can process incoming messages.
For instance a straightforward echo application would look like this:
class EchoWebSocket(WebSocket):
def received_message(self, message):
self.send(message.data, message.is_binary)
Other useful methods to implement are:
opened()
which is called whenever the WebSocket handshake is done.closed(code, reason=None)
which is called whenever the WebSocket connection is terminated.
You may want to know if the connection is currently usable or terminated
.
At that stage, the subclass is still not connected to any data source. The way ws4py is designed, you don’t necessarily need a connected socket, in fact, you don’t even need a socket at all.
>>> from ws4py.messaging import TextMessage
>>> def data_source():
>>> yield TextMessage(u'hello world')
>>> from mock import MagicMock
>>> source = MagicMock(side_effect=data_source)
>>> ws = EchoWebSocket(sock=source)
>>> ws.send(u'hello there')
Client¶
ws4py comes with various client implementations and they roughly share the same interface.
Built-in¶
The built-in client relies only on modules provided by the Python stdlib. The client’s inner loop runs within a thread and therefore holds the thread alive until the websocket is closed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | from ws4py.client.threadedclient import WebSocketClient
class DummyClient(WebSocketClient):
def opened(self):
def data_provider():
for i in range(1, 200, 25):
yield "#" * i
self.send(data_provider())
for i in range(0, 200, 25):
print i
self.send("*" * i)
def closed(self, code, reason=None):
print "Closed down", code, reason
def received_message(self, m):
print m
if len(m) == 175:
self.close(reason='Bye bye')
if __name__ == '__main__':
try:
ws = DummyClient('ws://localhost:9000/', protocols=['http-only', 'chat'])
ws.connect()
ws.run_forever()
except KeyboardInterrupt:
ws.close()
|
In this snippet, when the handshake is successful, the opened()
method is called and within this method we immediately send a bunch of messages to the server. First we demonstrate how you can use a generator to do so, then we simply send strings.
Assuming the server echoes messages as they arrive, the received_message(message)
method will print out the messages returned by the server and simply close the connection once it receives the last sent messages, which length is 175.
Finally the closed(code, reason=None)
method is called with the code and reason given by the server.
See also
Tornado¶
If you are using a Tornado backend you may use the Tornado client that ws4py provides as follow:
from ws4py.client.tornadoclient import TornadoWebSocketClient
from tornado import ioloop
class MyClient(TornadoWebSocketClient):
def opened(self):
for i in range(0, 200, 25):
self.send("*" * i)
def received_message(self, m):
print m
if len(m) == 175:
self.close(reason='Bye bye')
def closed(self, code, reason=None):
ioloop.IOLoop.instance().stop()
ws = MyClient('ws://localhost:9000/echo', protocols=['http-only', 'chat'])
ws.connect()
ioloop.IOLoop.instance().start()
gevent¶
If you are using a gevent backend you may use the gevent client that ws4py provides as follow:
from ws4py.client.geventclient import WebSocketClient
This client can benefit from gevent’s concepts as demonstrated below:
ws = WebSocketClient('ws://localhost:9000/echo', protocols=['http-only', 'chat'])
ws.connect()
def incoming():
"""
Greenlet waiting for incoming messages
until ``None`` is received, indicating we can
leave the loop.
"""
while True:
m = ws.receive()
if m is not None:
print str(m)
else:
break
def send_a_bunch():
for i in range(0, 40, 5):
ws.send("*" * i)
greenlets = [
gevent.spawn(incoming),
gevent.spawn(send_a_bunch),
]
gevent.joinall(greenlets)
Server¶
ws4py comes with a few server implementations built around the main WebSocket
class.
CherryPy¶
ws4py provides an extension to CherryPy 3 to enable WebSocket from the framework layer. It is based on the CherryPy plugin and tool mechanisms.
The WebSocket tool
plays at the request level on every request received by the server. Its goal is to perform the WebSocket handshake and, if it succeeds, to create the WebSocket
instance (well a subclass you will be implementing) and push it to the plugin.
The WebSocket plugin
works at the CherryPy system level and has a single instance throughout. Its goal is to track websocket instances created by the tool and free their resources when connections are closed.
Here is a simple example of an echo server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import cherrypy
from ws4py.server.cherrypyserver import WebSocketPlugin, WebSocketTool
from ws4py.websocket import EchoWebSocket
cherrypy.config.update({'server.socket_port': 9000})
WebSocketPlugin(cherrypy.engine).subscribe()
cherrypy.tools.websocket = WebSocketTool()
class Root(object):
@cherrypy.expose
def index(self):
return 'some HTML with a websocket javascript connection'
@cherrypy.expose
def ws(self):
# you can access the class instance through
handler = cherrypy.request.ws_handler
cherrypy.quickstart(Root(), '/', config={'/ws': {'tools.websocket.on': True,
'tools.websocket.handler_cls': EchoWebSocket}})
|
Note how we specify the class which should be instanciated by the server on each connection. The great aspect of the tool mechanism is that you can specify a different class on a per-path basis.
gevent¶
gevent is a coroutine, called greenlets, implementation for very concurrent applications. ws4py offers a server implementation for this library on top of the WSGI protocol. Using it is as simple as:
1 2 3 4 5 6 7 | from gevent import monkey; monkey.patch_all()
from ws4py.websocket import EchoWebSocket
from ws4py.server.geventserver import WSGIServer
from ws4py.server.wsgiutils import WebSocketWSGIApplication
server = WSGIServer(('localhost', 9000), WebSocketWSGIApplication(handler_cls=EchoWebSocket))
server.serve_forever()
|
First we patch all the standard modules so that the stdlib runs well with as gevent. Then we simply create a WSGI server and specify the class which will be instanciated internally each time a connection is successful.
wsgiref¶
wsgiref
is a built-in WSGI package that provides various classes and helpers to develop against WSGI. Mostly it provides a basic WSGI server that can be usedfor testing or simple demos. ws4py provides support for websocket on wsgiref for testing purpose as well. It’s not meant to be used in production, since it can only initiate web socket connections one at a time, as a result of being single threaded. However, once accepted, ws4py takes over, which is multithreaded by default.
1 2 3 4 5 6 7 8 9 10 | from wsgiref.simple_server import make_server
from ws4py.websocket import EchoWebSocket
from ws4py.server.wsgirefserver import WSGIServer, WebSocketWSGIRequestHandler
from ws4py.server.wsgiutils import WebSocketWSGIApplication
server = make_server('', 9000, server_class=WSGIServer,
handler_class=WebSocketWSGIRequestHandler,
app=WebSocketWSGIApplication(handler_cls=EchoWebSocket))
server.initialize_websockets_manager()
server.serve_forever()
|
asyncio¶
asyncio
is the implementation of PEP 3156, the new asynchronous framework for concurrent
applications.
1 2 3 4 5 6 7 8 9 10 11 | from ws4py.async_websocket import EchoWebSocket
loop = asyncio.get_event_loop()
def start_server():
proto_factory = lambda: WebSocketProtocol(EchoWebSocket)
return loop.create_server(proto_factory, '', 9007)
s = loop.run_until_complete(start_server())
print('serving on', s.sockets[0].getsockname())
loop.run_forever()
|
Warning
The provided HTTP server used for the handshake is clearly not production ready. However, once the handshake is performed, the rest of the code runs the same stack as the other server implementations. It should be easy to replace the HTTP interface with any asyncio aware HTTP framework.
Managing a pool of WebSockets¶
ws4py provides a ws4py.manager.WebSocketManager
class that takes care of
ws4py.websocket.WebSocket
instances once they the HTTP upgrade handshake
has been performed.
The manager is not compulsory but makes it simpler to track and let them run in your application’s process.
When you add(websocket)
a
websocket to the manager, the file-descriptor is registered with the
manager’s poller and the opened()
method on is called.
Polling¶
The manager uses a polling mechanism to dispatch on socket incoming events.
Two pollers are implemented, one using the traditionnal select
and another one based on select.epoll
which is used only if available on the system.
The polling is executed in its own thread, it keeps looping until
the manager stop()
method.
On every loop, the poller is called to poll for all registered file-descriptors. If any one of them is ready, we retrieve the websocket using that descriptor and, if the websocket is not yet terminated, we call its once method so that the incoming bytes are processed.
If the processing fails in anyway, the manager terminates the websocket and remove it from itself.
Client example¶
Below is a simple example on how to start 2000 clients against a single server.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | from ws4py.client import WebSocketBaseClient
from ws4py.manager import WebSocketManager
from ws4py import format_addresses, configure_logger
logger = configure_logger()
m = WebSocketManager()
class EchoClient(WebSocketBaseClient):
def handshake_ok(self):
logger.info("Opening %s" % format_addresses(self))
m.add(self)
def received_message(self, msg):
logger.info(str(msg))
if __name__ == '__main__':
import time
try:
m.start()
for i in range(2000):
client = EchoClient('ws://localhost:9000/ws')
client.connect()
logger.info("%d clients are connected" % i)
while True:
for ws in m.websockets.itervalues():
if not ws.terminated:
break
else:
break
time.sleep(3)
except KeyboardInterrupt:
m.close_all()
m.stop()
m.join()
|
Once those are created against the echo_cherrypy_server
example for instance,
point your browser to http://localhost:9000/ and enter a message. It will be
broadcasted to all connected peers.
When a peer is closed, its connection is automatically removed from the manager so you should never need to explicitely remove it.
Note
The CherryPy and wsgiref servers internally use a manager to handle connected websockets. The gevent server relies only on a greenlet group instead.
Built-in Examples¶
Real-time chat¶
The echo_cherrypy_server
example provides a simple Echo server.
It requires CherryPy 3.2.3. Open a couple of tabs pointing at http://localhost:9000
and chat around.
Android sensors and HTML5¶
The droid_sensor_cherrypy_server
broadcasts sensor metrics to clients.
Point your browser to http://localhost:9000
Then run the droid_sensor
module from your Android device using
SL4A.
A screenshot of what this renders to can be found here.
You will find a lovely video of this demo in action on YouTube thanks to Mat Bettinson for this.
Maintainer Guide¶
This section describes the steps to work on ws4py itself and its release process, as well as other conventions and best practices.
Coding Rules¶
Python is a rather flexible language which favors conventions over configurations. This is why, over the years, some community rules were published that most Python developers follow. ws4py tries to follow those principles for the most part and therefore.
Therefore please carefully read:
Design¶
Workflow¶
ws4py’s design is actually fairly simple and straigtforward. At a high level this is what is going on:
The initial connection is made by a WebSocket aware client to a WebSocket aware web server. The first exchange is dedicated to perform the upgrade handshake as defined by RFC 6455#section-4.
If the exchange succeeds, the socket is kept opened, wrapped into a ws4py.websocket.WebSocket
instance
which is passed on to the global ws4py ws4py.manager.WebSocketManager
instance which will handle its lifecycle.
Most notably, the manager will poll for the socket’s receive events so that, when bytes are available, the websocket object can read and process them.
Implementation¶
ws4py data model is rather simple and follows the protocol itself:
- a highlevel
ws4py.websocket.WebSocket
class that determines actions to carry based on messages that are parsed. - a
ws4py.streaming.Stream
class that handles a single message at a time - a
ws4py.framing.Frame
class that performs the low level protocol parsing of frames
Each are inter-connected as russian dolls generators. The process heavily relies on the capacity to send to a generator. So everytime one of those layers requires something, it yields and then its holder sends it back whatever was required.
The Frame parser yields the number of bytes it needs at any time, the stream parser forwards it back to the WebSocket class which gets data from the underlying data provider it holds a reference to (a socket typically). The WebSocket class sends bytes as they are read from the socket down to the stream parser which forwards them to the frame parser.
Eventually a frame is parsed and handled by the stream parser which in turns yields a complete message made of all parsed frames.
The interesting aspect here is that the socket provider is totally abstracted from the protocol implementation which simply requires bytes as they come.
This means one could write a ws4py socket provider that doesn’t read from the wire but from any other source.
It’s also pretty fast and easy to read.
Testing Overview¶
ws4py is a Python library which means it can be tested in various fashion:
- unit testing
- functional testing
- load testing
Though all of them are of useful, ws4py mostly relies on functional testing.
Unit Testing¶
Unit testing solves complex issues in a simple fashion:
- Micro-validation of classes and functions
- Ensure non-regression after modifications
- Critique the design as early as possible for minimum impact
Too often, developers focus solely on the first two and fail to realise how much feedback they can get by writing a simple unit tests. Usually starting writing unit tests can take time because your code is too tightly coupled with itself or external dependencies. This should not the case, most of the time anyway. So make sure to reflect on your code design whenever you have difficulties setting up proper unit tests.
Note
Unfortunately, for now ws4py has a rather shallow coverage as it relies more on the functional testing to ensure the package is sane. I hope to change this in the future.
Framework¶
ws4py uses the Python built-in unittest
module. Make sure you read
its extensive documentation.
Execution¶
Test execution can be done as follow:
cd test
python -m unittest discover # execute all tests in the current directory
Tests can obviously be executed via nose, unittest2 or py.test if you prefer.
Functional Testing¶
ws4py relies heavily on the extensive testing suite provided by the Autobahn project.
The server test suite is used by many other WebSocket implementation out there and provides a great way to validate interopability so it must be executed before each release to the least. Please refer to the Requirements page to install the test suite.
Execution¶
Start the CherryPy server with PyPy 1.9
pypy test/autobahn_test_servers.py --run-cherrypy-server-pypy
Start the CherryPy server with Python 3.2 and/or 3.3 if you can.
python3 test/autobahn_test_servers.py --run-cherrypy-server-py3k
Start all servers with Python 2.7
python2 test/autobahn_test_servers.py --run-all
Finally, execute the test suite as follow:
wstest -m fuzzingclient -s test/fuzzingclient.json
The whole test suite will take a while to complete so be patient.
Documentation process¶
Basic principles¶
Document in that order: why, what, how¶
Documenting ws4py is an important process since the code doesn’t always carry enough information to understand its design and context. Thus, documenting should target the question “why?” first then the “what?” and “how?”. It’s actually trickier than it sound.
Explicit is better than implicit¶
When you have your nose in the code it may sound straightforward enough not to document certain aspects of pyalm. Remember that PEP 20 principle: Explicit is better than implicit.
Be clear, not verbose¶
Finding the right balance between too much and not enough is hard. Writing good documentation is just difficult. However, you should not be too verbose either.
Add enough details to a section to provide context but don’t flood the reader with irrelevant information.
Show me where you come from¶
Every piece of code should be contextualized and almost every time you should explicitely indicate the import statement so that the reader doesn’t wonder where an object comes from.
Documentation Toolkit¶
pyalm uses the Sphinx documentation generator toolkit so refer to its documentation to learn more on its usage.
Building the documentation is as simple as:
cd docs make html
The generated documentation will be available in docs\_build\html
.
Release Process¶
ws4py’s release process is as follow:
Update the release minor or micro version.
If necessary change also the major version. This should be saved only for major modifications and/or API compatibility breakup.
Edit
ws4py/__init__.py
accordingly. This will propagate to thesetup.py
anddocs/conf.py
appropriately on its own.See also
How to version? You should read this.
Run the unit test suites
It’s simple, fast and will make you sleep well at night. So do it.
If the test suite fails, do not release. It’s a simple rule we constantly fail for some reason. So if it fails, go back and fix it.
Rebuild the documentation
It may sound funny but a release with an out of date documentation has little value. Keeping your documentation up to date is as important as having no failing unit tests.
Add to subversion any new documentation pages, both their sources and the resulting HTML files.
Build the source package
First delete the
build
directory.Run the following command:
python setup.py sdist --formats=gztar
This will produce a tarball in the
dist
directory.Push the release to PyPI
Tag the release in github
Announce it to the world :)
Packages¶
ws4py Package¶
ws4py
Package¶
exc
Module¶
-
exception
ws4py.exc.
WebSocketException
[source]¶ Bases:
exceptions.Exception
-
exception
ws4py.exc.
ProtocolException
[source]¶ Bases:
ws4py.exc.WebSocketException
-
exception
ws4py.exc.
FrameTooLargeException
[source]¶ Bases:
ws4py.exc.WebSocketException
-
exception
ws4py.exc.
UnsupportedFrameTypeException
[source]¶ Bases:
ws4py.exc.WebSocketException
-
exception
ws4py.exc.
UnsupportedFrameTypeException
[source] Bases:
ws4py.exc.WebSocketException
-
exception
ws4py.exc.
TextFrameEncodingException
[source]¶ Bases:
ws4py.exc.WebSocketException
-
exception
ws4py.exc.
TextFrameEncodingException
[source] Bases:
ws4py.exc.WebSocketException
-
exception
ws4py.exc.
InvalidBytesError
[source]¶ Bases:
ws4py.exc.WebSocketException
-
exception
ws4py.exc.
StreamClosed
[source]¶ Bases:
exceptions.Exception
-
exception
ws4py.exc.
HandshakeError
(msg)[source]¶ Bases:
ws4py.exc.WebSocketException
framing
Module¶
-
class
ws4py.framing.
Frame
(opcode=None, body='', masking_key=None, fin=0, rsv1=0, rsv2=0, rsv3=0)[source]¶ Bases:
object
Implements the framing protocol as defined by RFC 6455.
1 2 3 4 5 6 7 8 9 10
>>> test_mask = 'XXXXXX' # perhaps from os.urandom(4) >>> f = Frame(OPCODE_TEXT, 'hello world', masking_key=test_mask, fin=1) >>> bytes = f.build() >>> bytes.encode('hex') '818bbe04e66ad6618a06d1249105cc6882' >>> f = Frame() >>> f.parser.send(bytes[0]) 1 >>> f.parser.send(bytes[1]) 4
See also
Data Framing http://tools.ietf.org/html/rfc6455#section-5.2
-
parser
¶
-
build
()[source]¶ Builds a frame from the instance’s attributes and returns its bytes representation.
-
mask
(data)[source]¶ Performs the masking or unmasking operation on data using the simple masking algorithm:
-
unmask
(data)¶ Performs the masking or unmasking operation on data using the simple masking algorithm:
-
manager
Module¶
The manager module provides a selected classes to handle websocket’s execution.
Initially the rationale was to:
- Externalize the way the CherryPy server had been setup as its websocket management was too tightly coupled with the plugin implementation.
- Offer a management that could be used by other server or client implementations.
- Move away from the threaded model to the event-based model by relying on select or epoll (when available).
A simple usage for handling websocket clients:
from ws4py.client import WebSocketBaseClient
from ws4py.manager import WebSocketManager
m = WebSocketManager()
class EchoClient(WebSocketBaseClient):
def handshake_ok(self):
m.add(self) # register the client once the handshake is done
def received_message(self, msg):
print str(msg)
m.start()
client = EchoClient('ws://localhost:9000/ws')
client.connect()
m.join() # blocks forever
Managers are not compulsory but hopefully will help your workflow. For clients, you can still rely on threaded, gevent or tornado based implementations of course.
-
class
ws4py.manager.
SelectPoller
(timeout=0.1)[source]¶ Bases:
object
A socket poller that uses the select implementation to determines which file descriptors have data available to read.
It is available on all platforms.
-
class
ws4py.manager.
EPollPoller
(timeout=0.1)[source]¶ Bases:
object
An epoll poller that uses the
epoll
implementation to determines which file descriptors have data available to read.Available on Unix flavors mostly.
-
class
ws4py.manager.
KQueuePoller
(timeout=0.1)[source]¶ Bases:
object
An epoll poller that uses the
epoll
implementation to determines which file descriptors have data available to read.Available on Unix flavors mostly.
-
class
ws4py.manager.
WebSocketManager
(poller=None)[source]¶ Bases:
threading.Thread
An event-based websocket manager. By event-based, we mean that the websockets will be called when their sockets have data to be read from.
The manager itself runs in its own thread as not to be the blocking mainloop of your application.
The poller’s implementation is automatically chosen with
epoll
if available elseselect
unless you provide your ownpoller
.-
add
(websocket)[source]¶ Manage a new websocket.
First calls its
opened()
method and register its socket against the poller for reading events.
-
remove
(websocket)[source]¶ Remove the given
websocket
from the manager.This does not call its
closed()
method as it’s out-of-band by your application or from within the manager’s run loop.
-
run
()[source]¶ Manager’s mainloop executed from within a thread.
Constantly poll for read events and, when available, call related websockets’ once method to read and process the incoming data.
If the
once()
method returns a False value, itsterminate()
method is also applied to properly close the websocket and its socket is unregistered from the poller.Note that websocket shouldn’t take long to process their data or they will block the remaining websockets with data to be handled. As for what long means, it’s up to your requirements.
-
messaging
Module¶
-
class
ws4py.messaging.
Message
(opcode, data='', encoding='utf-8')[source]¶ Bases:
object
A message is a application level entity. It’s usually built from one or many frames. The protocol defines several kind of messages which are grouped into two sets:
- data messages which can be text or binary typed
- control messages which provide a mechanism to perform in-band control communication between peers
The
opcode
indicates the message type anddata
is the possible message payload.The payload is held internally as a a
bytearray
as they are faster than pure strings for append operations.Unicode data will be encoded using the provided
encoding
.-
single
(mask=False)[source]¶ Returns a frame bytes with the fin bit set and a random mask.
If
mask
is set, automatically mask the frame using a generated 4-byte token.
-
fragment
(first=False, last=False, mask=False)[source]¶ Returns a
ws4py.framing.Frame
bytes.The behavior depends on the given flags:
first
: the frame usesself.opcode
else a continuation opcodelast
: the frame has itsfin
bit setmask
: the frame is masked using a automatically generated 4-byte token
-
completed
¶ Indicates the the message is complete, meaning the frame’s
fin
bit was set.
-
class
ws4py.messaging.
TextMessage
(text=None)[source]¶ Bases:
ws4py.messaging.Message
-
is_binary
¶
-
is_text
¶
-
-
class
ws4py.messaging.
BinaryMessage
(bytes=None)[source]¶ Bases:
ws4py.messaging.Message
-
is_binary
¶
-
is_text
¶
-
-
class
ws4py.messaging.
CloseControlMessage
(code=1000, reason='')[source]¶ Bases:
ws4py.messaging.Message
-
class
ws4py.messaging.
PingControlMessage
(data=None)[source]¶ Bases:
ws4py.messaging.Message
-
class
ws4py.messaging.
PongControlMessage
(data)[source]¶ Bases:
ws4py.messaging.Message
streaming
Module¶
-
class
ws4py.streaming.
Stream
(always_mask=False, expect_masking=True)[source]¶ Bases:
object
Represents a websocket stream of bytes flowing in and out.
The stream doesn’t know about the data provider itself and doesn’t even know about sockets. Instead the stream simply yields for more bytes whenever it requires them. The stream owner is responsible to provide the stream with those bytes until a frame can be interpreted.
1 2 3 4 5 6 7 8 9
>>> s = Stream() >>> s.parser.send(BYTES) >>> s.has_messages False >>> s.parser.send(MORE_BYTES) >>> s.has_messages True >>> s.message <TextMessage ... >
Set
always_mask
to mask all frames built.Set
expect_masking
to indicate masking will be checked on all parsed frames.-
message
= None¶ Parsed test or binary messages. Whenever the parser reads more bytes from a fragment message, those bytes are appended to the most recent message.
-
pings
= None¶ Parsed ping control messages. They are instances of
ws4py.messaging.PingControlMessage
-
pongs
= None¶ Parsed pong control messages. They are instances of
ws4py.messaging.PongControlMessage
-
closing
= None¶ Parsed close control messsage. Instance of
ws4py.messaging.CloseControlMessage
-
errors
= None¶ Detected errors while parsing. Instances of
ws4py.messaging.CloseControlMessage
-
parser
¶
-
text_message
(text)[source]¶ Returns a
ws4py.messaging.TextMessage
instance ready to be built. Convenience method so that the caller doesn’t need to import thews4py.messaging.TextMessage
class itself.
-
binary_message
(bytes)[source]¶ Returns a
ws4py.messaging.BinaryMessage
instance ready to be built. Convenience method so that the caller doesn’t need to import thews4py.messaging.BinaryMessage
class itself.
-
has_message
¶ Checks if the stream has received any message which, if fragmented, is now completed.
-
close
(code=1000, reason='')[source]¶ Returns a close control message built from a
ws4py.messaging.CloseControlMessage
instance, using the given statuscode
andreason
message.
-
ping
(data='')[source]¶ Returns a ping control message built from a
ws4py.messaging.PingControlMessage
instance.
-
pong
(data='')[source]¶ Returns a ping control message built from a
ws4py.messaging.PongControlMessage
instance.
-
receiver
()[source]¶ Parser that keeps trying to interpret bytes it is fed with as incoming frames part of a message.
Control message are single frames only while data messages, like text and binary, may be fragmented accross frames.
The way it works is by instanciating a
wspy.framing.Frame
object, then running its parser generator which yields how much bytes it requires to performs its task. The stream parser yields this value to its caller and feeds the frame parser.When the frame parser raises
StopIteration
, the stream parser tries to make sense of the parsed frame. It dispatches the frame’s bytes to the most appropriate message type based on the frame’s opcode.Overall this makes the stream parser totally agonstic to the data provider.
-
utf8validator
Module¶
-
class
ws4py.utf8validator.
Utf8Validator
[source]¶ Bases:
object
Incremental UTF-8 validator with constant memory consumption (minimal state).
Implements the algorithm “Flexible and Economical UTF-8 Decoder” by Bjoern Hoehrmann (http://bjoern.hoehrmann.de/utf-8/decoder/dfa/).
-
UTF8VALIDATOR_DFA
= [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 0, 1, 2, 3, 5, 8, 7, 1, 1, 1, 4, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]¶
-
UTF8_ACCEPT
= 0¶
-
UTF8_REJECT
= 1¶
-
decode
(b)[source]¶ Eat one UTF-8 octet, and validate on the fly.
Returns UTF8_ACCEPT when enough octets have been consumed, in which case self.codepoint contains the decoded Unicode code point.
Returns UTF8_REJECT when invalid UTF-8 was encountered.
Returns some other positive integer when more octets need to be eaten.
-
validate
(ba)[source]¶ Incrementally validate a chunk of bytes provided as bytearray.
Will return a quad (valid?, endsOnCodePoint?, currentIndex, totalIndex).
As soon as an octet is encountered which renders the octet sequence invalid, a quad with valid? == False is returned. currentIndex returns the index within the currently consumed chunk, and totalIndex the index within the total consumed sequence that was the point of bail out. When valid? == True, currentIndex will be len(ba) and totalIndex the total amount of consumed bytes.
-
websocket
Module¶
-
class
ws4py.websocket.
Heartbeat
(websocket, frequency=2.0)[source]¶ Bases:
threading.Thread
Runs at a periodic interval specified by frequency by sending an unsolicitated pong message to the connected peer.
If the message fails to be sent and a socket error is raised, we close the websocket socket automatically, triggering the closed handler.
-
run
()[source]¶ Method representing the thread’s activity.
You may override this method in a subclass. The standard run() method invokes the callable object passed to the object’s constructor as the target argument, if any, with sequential and keyword arguments taken from the args and kwargs arguments, respectively.
-
-
class
ws4py.websocket.
WebSocket
(sock, protocols=None, extensions=None, environ=None, heartbeat_freq=None)[source]¶ Bases:
object
The
sock
is an opened connection resulting from the websocket handshake.If
protocols
is provided, it is a list of protocols negotiated during the handshake as isextensions
.If
environ
is provided, it is a copy of the WSGI environ dictionnary from the underlying WSGI server.-
stream
= None¶ Underlying websocket stream that performs the websocket parsing to high level objects. By default this stream never masks its messages. Clients using this class should set the
stream.always_mask
fields toTrue
andstream.expect_masking
fields toFalse
.
-
protocols
= None¶ List of protocols supported by this endpoint. Unused for now.
-
extensions
= None¶ List of extensions supported by this endpoint. Unused for now.
-
sock
= None¶ Underlying connection.
-
client_terminated
= None¶ Indicates if the client has been marked as terminated.
-
server_terminated
= None¶ Indicates if the server has been marked as terminated.
-
reading_buffer_size
= None¶ Current connection reading buffer size.
-
environ
= None¶ WSGI environ dictionary.
-
heartbeat_freq
= None¶ At which interval the heartbeat will be running. Set this to 0 or None to disable it entirely.
-
local_address
¶ Local endpoint address as a tuple
-
peer_address
¶ Peer endpoint address as a tuple
-
close
(code=1000, reason='')[source]¶ Call this method to initiate the websocket connection closing by sending a close frame to the connected peer. The
code
is the status code representing the termination’s reason.Once this method is called, the
server_terminated
attribute is set. Calling this method several times is safe as the closing frame will be sent only the first time.See also
Defined Status Codes http://tools.ietf.org/html/rfc6455#section-7.4.1
-
closed
(code, reason=None)[source]¶ Called when the websocket stream and connection are finally closed. The provided
code
is status set by the other point andreason
is a human readable message.See also
Defined Status Codes http://tools.ietf.org/html/rfc6455#section-7.4.1
-
terminated
¶ Returns
True
if both the client and server have been marked as terminated.
-
connection
¶
-
ping
(message)[source]¶ Send a ping message to the remote peer. The given message must be a unicode string.
-
ponged
(pong)[source]¶ Pong message, as a
messaging.PongControlMessage
instance, received on the stream.
-
received_message
(message)[source]¶ Called whenever a complete
message
, binary or text, is received and ready for application’s processing.The passed message is an instance of
messaging.TextMessage
ormessaging.BinaryMessage
.Note
You should override this method in your subclass.
-
unhandled_error
(error)[source]¶ Called whenever a socket, or an OS, error is trapped by ws4py but not managed by it. The given error is an instance of socket.error or OSError.
Note however that application exceptions will not go through this handler. Instead, do make sure you protect your code appropriately in received_message or send.
The default behaviour of this handler is to log the error with a message.
-
send
(payload, binary=False)[source]¶ Sends the given
payload
out.If
payload
is some bytes or a bytearray, then it is sent as a single message not fragmented.If
payload
is a generator, each chunk is sent as part of fragmented message.If
binary
is set, handles the payload as a binary message.
-
once
()[source]¶ Performs the operation of reading from the underlying connection in order to feed the stream of bytes.
Because this needs to support SSL sockets, we must always read as much as might be in the socket at any given time, however process expects to have itself called with only a certain number of bytes at a time. That number is found in self.reading_buffer_size, so we read everything into our own buffer, and then from there feed self.process.
Then the stream indicates whatever size must be read from the connection since it knows the frame payload length.
It returns False if an error occurred at the socket level or during the bytes processing. Otherwise, it returns True.
-
terminate
()[source]¶ Completes the websocket by calling the closed method either using the received closing code and reason, or when none was received, using the special 1006 code.
Finally close the underlying connection for good and cleanup resources by unsetting the environ and stream attributes.
-
process
(bytes)[source]¶ Takes some bytes and process them through the internal stream’s parser. If a message of any kind is found, performs one of these actions:
- A closing message will initiate the closing handshake
- Errors will initiate a closing handshake
- A message will be passed to the
received_message
method - Pings will see pongs be sent automatically
- Pongs will be passed to the
ponged
method
The process should be terminated when this method returns
False
.
-
run
()[source]¶ Performs the operation of reading from the underlying connection in order to feed the stream of bytes.
We start with a small size of two bytes to be read from the connection so that we can quickly parse an incoming frame header. Then the stream indicates whatever size must be read from the connection since it knows the frame payload length.
Note that we perform some automatic opererations:
- On a closing message, we respond with a closing message and finally close the connection
- We respond to pings with pong messages.
- Whenever an error is raised by the stream parsing, we initiate the closing of the connection with the appropiate error code.
This method is blocking and should likely be run in a thread.
-
-
class
ws4py.websocket.
EchoWebSocket
(sock, protocols=None, extensions=None, environ=None, heartbeat_freq=None)[source]¶ Bases:
ws4py.websocket.WebSocket
The
sock
is an opened connection resulting from the websocket handshake.If
protocols
is provided, it is a list of protocols negotiated during the handshake as isextensions
.If
environ
is provided, it is a copy of the WSGI environ dictionnary from the underlying WSGI server.
Subpackages¶
client Package¶
client
Package¶
-
class
ws4py.client.
WebSocketBaseClient
(url, protocols=None, extensions=None, heartbeat_freq=None, ssl_options=None, headers=None, exclude_headers=None)[source]¶ Bases:
ws4py.websocket.WebSocket
A websocket client that implements RFC 6455 and provides a simple interface to communicate with a websocket server.
This class works on its own but will block if not run in its own thread.
When an instance of this class is created, a
socket
is created. If the connection is a TCP socket, the nagle’s algorithm is disabled.The address of the server will be extracted from the given websocket url.
The websocket key is randomly generated, reset the key attribute if you want to provide yours.
For instance to create a TCP client:
>>> from ws4py.client import WebSocketBaseClient >>> ws = WebSocketBaseClient('ws://localhost/ws')
Here is an example for a TCP client over SSL:
>>> from ws4py.client import WebSocketBaseClient >>> ws = WebSocketBaseClient('wss://localhost/ws')
Finally an example of a Unix-domain connection:
>>> from ws4py.client import WebSocketBaseClient >>> ws = WebSocketBaseClient('ws+unix:///tmp/my.sock')
Note that in this case, the initial Upgrade request will be sent to
/
. You may need to change this by setting the resource explicitely before connecting:>>> from ws4py.client import WebSocketBaseClient >>> ws = WebSocketBaseClient('ws+unix:///tmp/my.sock') >>> ws.resource = '/ws' >>> ws.connect()
You may provide extra headers by passing a list of tuples which must be unicode objects.
-
bind_addr
¶ Returns the Unix socket path if or a tuple
(host, port)
depending on the initial URL’s scheme.
-
connect
()[source]¶ Connects this websocket and starts the upgrade handshake with the remote endpoint.
-
handshake_headers
¶ List of headers appropriate for the upgrade handshake.
-
handshake_request
¶ Prepare the request to be sent for the upgrade handshake.
-
process_response_line
(response_line)[source]¶ Ensure that we received a HTTP 101 status code in response to our request and if not raises
HandshakeError
.
-
geventclient
Module¶
threadedclient
Module¶
-
class
ws4py.client.threadedclient.
WebSocketClient
(url, protocols=None, extensions=None, heartbeat_freq=None, ssl_options=None, headers=None, exclude_headers=None)[source]¶ Bases:
ws4py.client.WebSocketBaseClient
from ws4py.client.threadedclient import WebSocketClient class EchoClient(WebSocketClient): def opened(self): for i in range(0, 200, 25): self.send("*" * i) def closed(self, code, reason): print(("Closed down", code, reason)) def received_message(self, m): print("=> %d %s" % (len(m), str(m))) try: ws = EchoClient('ws://localhost:9000/echo', protocols=['http-only', 'chat']) ws.connect() except KeyboardInterrupt: ws.close()
-
daemon
¶ True if the client’s thread is set to be a daemon thread.
-
tornadoclient
Module¶
server Package¶
cherrypyserver
Module¶
WebSocket within CherryPy is a tricky bit since CherryPy is a threaded server which would choke quickly if each thread of the server were kept attached to a long living connection that WebSocket expects.
In order to work around this constraint, we take some advantage of some internals of CherryPy as well as the introspection Python provides.
Basically, when the WebSocket handshake is complete, we take over the socket and let CherryPy take back the thread that was associated with the upgrade request.
These operations require a bit of work at various levels of the CherryPy framework but this module takes care of them and from your application’s perspective, this is abstracted.
Here are the various utilities provided by this module:
- WebSocketTool: The tool is in charge to perform the
- HTTP upgrade and detach the socket from CherryPy. It runs at various hook points of the request’s processing. Enable that tool at any path you wish to handle as a WebSocket handler.
- WebSocketPlugin: The plugin tracks the instanciated web socket handlers.
- It also cleans out websocket handler which connection have been closed down. The websocket connection then runs in its own thread that this plugin manages.
Simple usage example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import cherrypy
from ws4py.server.cherrypyserver import WebSocketPlugin, WebSocketTool
from ws4py.websocket import EchoWebSocket
cherrypy.config.update({'server.socket_port': 9000})
WebSocketPlugin(cherrypy.engine).subscribe()
cherrypy.tools.websocket = WebSocketTool()
class Root(object):
@cherrypy.expose
def index(self):
return 'some HTML with a websocket javascript connection'
@cherrypy.expose
def ws(self):
pass
cherrypy.quickstart(Root(), '/', config={'/ws': {'tools.websocket.on': True,
'tools.websocket.handler_cls': EchoWebSocket}})
|
Note that you can set the handler class on per-path basis, meaning you could also dynamically change the class based on other envrionmental settings (is the user authenticated for ex).
-
class
ws4py.server.cherrypyserver.
WebSocketTool
[source]¶ Bases:
cherrypy._cptools.Tool
-
upgrade
(protocols=None, extensions=None, version=(8, 13), handler_cls=<class 'ws4py.websocket.WebSocket'>, heartbeat_freq=None)[source]¶ Performs the upgrade of the connection to the WebSocket protocol.
The provided protocols may be a list of WebSocket protocols supported by the instance of the tool.
When no list is provided and no protocol is either during the upgrade, then the protocol parameter is not taken into account. On the other hand, if the protocol from the handshake isn’t part of the provided list, the upgrade fails immediatly.
-
geventserver
Module¶
wsgirefserver
Module¶
Add WebSocket support to the built-in WSGI server
provided by the wsgiref
. This is clearly not
meant to be a production server so please consider this
only for testing purpose.
Mostly, this module overrides bits and pieces of the built-in classes so that it supports the WebSocket workflow.
from wsgiref.simple_server import make_server
from ws4py.websocket import EchoWebSocket
from ws4py.server.wsgirefserver import WSGIServer, WebSocketWSGIRequestHandler
from ws4py.server.wsgiutils import WebSocketWSGIApplication
server = make_server('', 9000, server_class=WSGIServer,
handler_class=WebSocketWSGIRequestHandler,
app=WebSocketWSGIApplication(handler_cls=EchoWebSocket))
server.initialize_websockets_manager()
server.serve_forever()
Note
For some reason this server may fail against autobahntestsuite.
-
class
ws4py.server.wsgirefserver.
WebSocketWSGIHandler
(stdin, stdout, stderr, environ, multithread=True, multiprocess=False)[source]¶
-
class
ws4py.server.wsgirefserver.
WebSocketWSGIRequestHandler
(request, client_address, server)[source]¶ Bases:
wsgiref.simple_server.WSGIRequestHandler
-
class
WebSocketWSGIHandler
(stdin, stdout, stderr, environ, multithread=True, multiprocess=False)¶ Bases:
wsgiref.handlers.SimpleHandler
-
finish_response
()¶ Completes the response and performs the following tasks:
- Remove the ‘ws4py.socket’ and ‘ws4py.websocket’ environ keys.
- Attach the returned websocket, if any, to the WSGI server
using its
link_websocket_to_server
method.
-
setup_environ
()¶ Setup the environ dictionary and add the ‘ws4py.socket’ key. Its associated value is the real socket underlying socket.
-
-
class
-
class
ws4py.server.wsgirefserver.
WSGIServer
(server_address, RequestHandlerClass, bind_and_activate=True)[source]¶ Bases:
wsgiref.simple_server.WSGIServer
Constructor. May be extended, do not override.
-
initialize_websockets_manager
()[source]¶ Call thos to start the underlying websockets manager. Make sure to call it once your server is created.
-
wsgitutils
Module¶
This module provides a WSGI application suitable for a WSGI server such as gevent or wsgiref for instance.
PEP 333 couldn’t foresee a protocol such as WebSockets but luckily the way the initial protocol upgrade was designed means that we can fit the handshake in a WSGI flow.
The handshake validates the request against some internal or user-provided values and fails the request if the validation doesn’t complete.
On success, the provided WebSocket subclass is instanciated and stored into the ‘ws4py.websocket’ environ key so that the WSGI server can handle it.
The WSGI application returns an empty iterable since there is little value to return some content within the response to the handshake.
A server wishing to support WebSocket via ws4py should:
- Provide the real socket object to ws4py through the ‘ws4py.socket’ environ key. We can’t use ‘wsgi.input’ as it may be wrapper to the socket we wouldn’t know how to extract the socket from.
- Look for the ‘ws4py.websocket’ key in the environ
when the application has returned and probably attach
it to a
ws4py.manager.WebSocketManager
instance so that the websocket runs its life. - Remove the ‘ws4py.websocket’ and ‘ws4py.socket’ environ keys once the application has returned. No need for these keys to persist.
- Not close the underlying socket otherwise, well, your websocket will also shutdown.
Warning
The WSGI application sets the ‘Upgrade’ header response as specified by RFC 6455. This is not tolerated by PEP 333 since it’s a hop-by-hop header. We expect most servers won’t mind.
-
class
ws4py.server.wsgiutils.
WebSocketWSGIApplication
(protocols=None, extensions=None, handler_cls=<class 'ws4py.websocket.WebSocket'>)[source]¶ Bases:
object
WSGI application usable to complete the upgrade handshake by validating the requested protocols and extensions as well as the websocket version.
If the upgrade validates, the handler_cls class is instanciated and stored inside the WSGI environ under the ‘ws4py.websocket’ key to make it available to the WSGI handler.