| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- import socket
- import ssl
- from frame import receive_frame, pop_frame, contains_frame
- from handshake import ServerHandshake, ClientHandshake
- from errors import SSLError
- INHERITED_ATTRS = ['bind', 'close', 'listen', 'fileno', 'getpeername',
- 'getsockname', 'getsockopt', 'setsockopt', 'setblocking',
- 'settimeout', 'gettimeout', 'shutdown', 'family', 'type',
- 'proto']
- class websocket(object):
- """
- Implementation of web socket, upgrades a regular TCP socket to a websocket
- using the HTTP handshakes and frame (un)packing, as specified by RFC 6455.
- The API of a websocket is identical to that of a regular socket, as
- illustrated by the examples below.
- 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!'))
- """
- def __init__(self, sock=None, protocols=[], extensions=[], origin=None,
- location='/', trusted_origins=[], locations=[], auth=None,
- recv_callback=None, sfamily=socket.AF_INET, sproto=0):
- """
- Create a regular TCP socket of family `family` and protocol
- `sock` is an optional regular TCP socket to be used for sending binary
- data. If not specified, a new socket is created.
- `protocols` is a list of supported protocol names.
- `extensions` is a list of supported extensions (`Extension` instances).
- `origin` (for client sockets) is the value for the "Origin" header sent
- in a client handshake .
- `location` (for client sockets) is optional, used to request a
- particular resource in the HTTP handshake. In a URL, this would show as
- ws://host[:port]/<location>. Use this when the server serves multiple
- resources (see `locations`).
- `trusted_origins` (for server sockets) is a list of expected values
- for the "Origin" header sent by a client. If the received Origin header
- has value not in this list, a HandshakeError is raised. If the list is
- empty (default), all origins are excepted.
- `locations` (for server sockets) is an optional list of resources
- serverd by this server. If specified (without trailing slashes), these
- are used to verify the resource location requested by a client. The
- requested location may be used to distinquish different services in a
- server implementation.
- `auth` is optional, used for HTTP Basic or Digest authentication during
- the handshake. It must be specified as a (username, password) tuple.
- `recv_callback` is the callback for received frames in asynchronous
- sockets. Use in conjunction with setblocking(0). The callback itself
- may for example change the recv_callback attribute to change the
- behaviour for the next received message. Can be set when calling
- `queue_send`.
- `sfamily` and `sproto` are used for the regular socket constructor.
- """
- self.protocols = protocols
- self.extensions = extensions
- self.origin = origin
- self.location = location
- self.trusted_origins = trusted_origins
- self.locations = locations
- self.auth = auth
- self.secure = False
- self.handshake_sent = False
- self.hooks_send = []
- self.hooks_recv = []
- self.sendbuf_frames = []
- self.sendbuf = ''
- self.recvbuf = ''
- self.recv_callback = recv_callback
- self.sock = sock or socket.socket(sfamily, socket.SOCK_STREAM, sproto)
- def __getattr__(self, name):
- if name in INHERITED_ATTRS:
- return getattr(self.sock, name)
- raise AttributeError("'%s' has no attribute '%s'"
- % (self.__class__.__name__, name))
- def accept(self):
- """
- Equivalent to socket.accept(), but transforms the socket into a
- websocket instance and sends a server handshake (after receiving a
- client handshake). Note that the handshake may raise a HandshakeError
- exception.
- """
- sock, address = self.sock.accept()
- wsock = websocket(sock)
- wsock.secure = self.secure
- ServerHandshake(wsock).perform(self)
- wsock.handshake_sent = True
- return wsock, address
- def connect(self, address):
- """
- Equivalent to socket.connect(), but sends an client handshake request
- after connecting.
- `address` is a (host, port) tuple of the server to connect to.
- """
- self.sock.connect(address)
- ClientHandshake(self).perform()
- self.handshake_sent = True
- def send(self, *args):
- """
- Send a number of frames.
- """
- for frame in args:
- for hook in self.hooks_send:
- frame = hook(frame)
- #print 'send frame:', frame, 'to %s:%d' % self.sock.getpeername()
- self.sock.sendall(frame.pack())
- def recv(self):
- """
- Receive a single frames. This can be either a data frame or a control
- frame.
- """
- frame = receive_frame(self.sock)
- for hook in self.hooks_recv:
- frame = hook(frame)
- #print 'receive frame:', frame, 'from %s:%d' % self.sock.getpeername()
- return frame
- def recvn(self, n):
- """
- Receive exactly `n` frames. These can be either data frames or control
- frames, or a combination of both.
- """
- return [self.recv() for i in xrange(n)]
- def queue_send(self, frame, callback=None, recv_callback=None):
- """
- Enqueue `frame` to the send buffer so that it is send on the next
- `do_async_send`. `callback` is an optional callable to call when the
- frame has been fully written. `recv_callback` is an optional callable
- to quickly set the `recv_callback` attribute to.
- """
- for hook in self.hooks_send:
- frame = hook(frame)
- self.sendbuf += frame.pack()
- self.sendbuf_frames.append([frame, len(self.sendbuf), callback])
- if recv_callback:
- self.recv_callback = recv_callback
- def do_async_send(self):
- """
- Send any queued data.
- """
- assert len(self.sendbuf)
- nwritten = self.sock.send(self.sendbuf)
- nframes = 0
- for entry in self.sendbuf_frames:
- frame, offset, callback = entry
- if offset <= nwritten:
- nframes += 1
- if callback:
- print 'write cb'
- callback()
- else:
- entry[1] -= nwritten
- self.sendbuf = self.sendbuf[nwritten:]
- self.sendbuf_frames = self.sendbuf_frames[nframes:]
- def do_async_recv(self, bufsize):
- """
- """
- data = self.sock.recv(bufsize)
- if len(data) == 0:
- raise socket.error('no data to receive')
- self.recvbuf += data
- while contains_frame(self.recvbuf):
- frame, self.recvbuf = pop_frame(self.recvbuf)
- if not self.recv_callback:
- raise ValueError('no callback installed for %s' % frame)
- self.recv_callback(frame)
- def can_send(self):
- return len(self.sendbuf) > 0
- def can_recv(self):
- return self.recv_callback is not None
- def enable_ssl(self, *args, **kwargs):
- """
- Transforms the regular socket.socket to an ssl.SSLSocket for secure
- connections. Any arguments are passed to ssl.wrap_socket:
- http://docs.python.org/dev/library/ssl.html#ssl.wrap_socket
- """
- if self.handshake_sent:
- raise SSLError('can only enable SSL before handshake')
- self.secure = True
- self.sock = ssl.wrap_socket(self.sock, *args, **kwargs)
- def add_hook(self, send=None, recv=None, prepend=False):
- """
- Add a pair of send and receive hooks that are called for each frame
- that is sent or received. A hook is a function that receives a single
- argument - a Frame instance - and returns a `Frame` instance as well.
- `prepend` is a flag indicating whether the send hook is prepended to
- the other send hooks. This is expecially useful when a program uses
- extensions such as the built-in `DeflateFrame` extension. These
- extensions are installed using these hooks as well.
- For example, the following code creates a `Frame` instance for data
- being sent and removes the instance for received data. This way, data
- can be sent and received as if on a regular socket.
- >>> import wspy
- >>> sock = wspy.websocket()
- >>> sock.add_hook(lambda data: tswpy.Frame(tswpy.OPCODE_TEXT, data),
- >>> lambda frame: frame.payload)
- To add base64 encoding to the example above:
- >>> import base64
- >>> sock.add_hook(base64.encodestring, base64.decodestring, True)
- Note that here `prepend=True`, so that data passed to `send()` is first
- encoded and then packed into a frame. Of course, one could also decide
- to add the base64 hook first, or to return a new `Frame` instance with
- base64-encoded data.
- """
- if send:
- self.hooks_send.insert(0 if prepend else -1, send)
- if recv:
- self.hooks_recv.insert(-1 if prepend else 0, recv)
|