Implementation of web sockets for Python, upgrades a regular socket to a web socket

Taddeus Kroes 5ed32daf31 Added permessage-deflate + lots of general debug and cleanup 11 years ago
test 9232e5d4b4 Rewrote extensions API + reimplemented deflate-frame 11 years ago
.gitignore 6d2cf25d8d Added SSL support (wss://...), updated some docs 12 years ago
LICENSE a784b98f4c Updated organization name in license 12 years ago
Makefile 4d9fbb0cda Makefile cleanup 11 years ago
README.md 5ed32daf31 Added permessage-deflate + lots of general debug and cleanup 11 years ago
TODO 7d246d5dba Implemented extensions in handshakes and installed extension hooks in websocket 12 years ago
__init__.py 5ed32daf31 Added permessage-deflate + lots of general debug and cleanup 11 years ago
async.py 5af22f0670 Debugged AsyncServer 11 years ago
connection.py 5ed32daf31 Added permessage-deflate + lots of general debug and cleanup 11 years ago
deflate_frame.py 5ed32daf31 Added permessage-deflate + lots of general debug and cleanup 11 years ago
deflate_message.py 5ed32daf31 Added permessage-deflate + lots of general debug and cleanup 11 years ago
errors.py a55b2fad33 Rewrote websocket + connection to add support for asynchronous sockets, added asynchrounous connecten & server 12 years ago
extension.py 5ed32daf31 Added permessage-deflate + lots of general debug and cleanup 11 years ago
frame.py 5ed32daf31 Added permessage-deflate + lots of general debug and cleanup 11 years ago
handshake.py 5ed32daf31 Added permessage-deflate + lots of general debug and cleanup 11 years ago
message.py b7c8c29fdd Changed type of payload of TextMessage from str to unicode 12 years ago
multiplex.py 7c99e4fe3a Split extension.py into base file and extension implementation files 12 years ago
python_digest.py 0cb48c8849 Changed package name to 'wspy' 12 years ago
server.py 5af22f0670 Debugged AsyncServer 11 years ago
setup.py 0cb48c8849 Changed package name to 'wspy' 12 years ago
websocket.py 5ed32daf31 Added permessage-deflate + lots of general debug and cleanup 11 years ago

README.md

About

wspy is a standalone implementation of web sockets for Python, defined by RFC 6455. The incentive for creating this library is the absence of a layered implementation of web sockets outside the scope of web servers such as Apache or Nginx. wspy does not require any third-party programs or libraries outside Python's standard library. It provides low-level access to sockets, as well as high-level functionalities to easily set up a web server. Thus, it is both suited for quick server programming, as well as for more demanding applications that require low-level control over each frame being sent/received.

Her is a quick overview of the features in this library:

  • Upgrading regular sockets to web sockets.
  • Building custom frames.
  • Messages, which are higher-level than frames (see "Basic usage").
  • Connections, which hide the handling of control frames and automatically concatenate fragmented messages to individual payloads.
  • HTTP authentication during handshake.
  • An extendible server implementation.
  • Secure sockets using SSL certificates (for 'wss://...' URLs).
  • An API for implementing WebSocket extensions. Included implementations are deflate-frame and permessage-deflate.
  • Asynchronous sockets with an EPOLL-based server.

Installation

Use Python's package manager:

easy_install wspy
pip install wspy

Basic usage

  • The websocket class upgrades a regular socket to a web socket. A websocket instance is a single end point of a connection. A websocket instance sends and receives frames (Frame instances) as opposed to bytes (which are sent/received in a regular socket).

Server example:

    import wspy, socket
    sock = wspy.websocket()
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('', 8000))
    sock.listen(5)

    client = sock.accept()
    client.send(wspy.Frame(wspy.OPCODE_TEXT, 'Hello, Client!'))
    frame = client.recv()

Client example:

    import wspy
    sock = wspy.websocket(location='/my/path')
    sock.connect(('', 8000))
    sock.send(wspy.Frame(wspy.OPCODE_TEXT, 'Hello, Server!'))
  • A Connection instance represents a connection between two end points, based on a websocket instance. A connection handles control frames properly, and sends/receives messages (Message instances, which are higher-level than frames). Messages are automatically converted to frames, and received frames are converted to messages. Fragmented messages (messages consisting of multiple frames) are also supported.

Example of an echo server (sends back what it receives):

    import socket
    import wspy

    class EchoConnection(wspy.Connection):
        def onopen(self):
            print 'Connection opened at %s:%d' % self.sock.getpeername()

        def onmessage(self, message):
            print 'Received message "%s"' % message.payload
            self.send(wspy.TextMessage(message.payload))

        def onclose(self, code, reason):
            print 'Connection closed'

    server = wspy.websocket()
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(('', 8000))
    server.listen(5)

    while True:
        client, addr = server.accept()
        EchoConnection(client).receive_forever()

There are two types of messages: TextMessages and BinaryMessages. A TextMessage uses frames with opcode OPCODE_TEXT, and encodes its payload using UTF-8 encoding. A BinaryMessage just sends its payload as raw data. I recommend using TextMessage by default, and BinaryMessage only when necessary.

Note: For browser clients, you will probably want to use JSON encoding. This could, for example, be implemented as follows:

    import wspy, json

    def msg(**data):
        return wspy.TextMessage(json.dumps(data))

    # create some connection `conn`...

    conn.send(msg(foo='Hello, World!'))

Built-in servers

Threaded

The Server class is very basic. It starts a new thread with a Connection.receive_forever() loop for each client that connects. It also handles client crashes properly. By default, a Server instance only logs every event using Python's logging module. To create a custom server, The Server class should be extended and its event handlers overwritten. The event handlers are named identically to the Connection event handlers, but they also receive an additional client argument. The client argumetn is a modified Connection instance, so you can invoke send() and recv().

For example, the EchoConnection example above can be rewritten to:

  import wspy

  class EchoServer(wspy.Server):
      def onopen(self, client):
          print 'Client %s connected' % client

      def onmessage(self, client, message):
          print 'Received message "%s"' % message.payload
          client.send(wspy.TextMessage(message.payload))

      def onclose(self, client, code, reason):
          print 'Client %s disconnected' % client

  EchoServer(('', 8000)).run()

The server can be stopped by typing CTRL-C in the command line. The KeyboardInterrupt raised when this happens is caught by the server.

Asynchronous

The AsyncServer class has the same API as Server, but uses EPOLL instead of threads. This means that when you send a message, it is put into a queue to be sent later when the socket is ready. The client argument is againa modified Connection instance, with a non-blocking send() method (recv is still blocking, use the server's onmessage callback instead).

Extensions

TODO