websocket.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. import socket
  2. import ssl
  3. from frame import receive_frame, pop_frame, contains_frame
  4. from handshake import ServerHandshake, ClientHandshake
  5. from errors import SSLError
  6. INHERITED_ATTRS = ['bind', 'close', 'listen', 'fileno', 'getpeername',
  7. 'getsockname', 'getsockopt', 'setsockopt', 'setblocking',
  8. 'settimeout', 'gettimeout', 'shutdown', 'family', 'type',
  9. 'proto']
  10. STATE_INIT = 0
  11. STATE_READ = 1
  12. STATE_WRITE = 2
  13. STATE_CLOSE = 4
  14. class websocket(object):
  15. """
  16. Implementation of web socket, upgrades a regular TCP socket to a websocket
  17. using the HTTP handshakes and frame (un)packing, as specified by RFC 6455.
  18. The API of a websocket is identical to that of a regular socket, as
  19. illustrated by the examples below.
  20. Server example:
  21. >>> import wspy, socket
  22. >>> sock = wspy.websocket()
  23. >>> sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  24. >>> sock.bind(('', 8000))
  25. >>> sock.listen(5)
  26. >>> client = sock.accept()
  27. >>> client.send(wspy.Frame(wspy.OPCODE_TEXT, 'Hello, Client!'))
  28. >>> frame = client.recv()
  29. Client example:
  30. >>> import wspy
  31. >>> sock = wspy.websocket(location='/my/path')
  32. >>> sock.connect(('', 8000))
  33. >>> sock.send(wspy.Frame(wspy.OPCODE_TEXT, 'Hello, Server!'))
  34. """
  35. def __init__(self, sock=None, protocols=[], extensions=[], origin=None,
  36. location='/', trusted_origins=[], locations=[], auth=None,
  37. sfamily=socket.AF_INET, sproto=0):
  38. """
  39. Create a regular TCP socket of family `family` and protocol
  40. `sock` is an optional regular TCP socket to be used for sending binary
  41. data. If not specified, a new socket is created.
  42. `protocols` is a list of supported protocol names.
  43. `extensions` is a list of supported extensions (`Extension` instances).
  44. `origin` (for client sockets) is the value for the "Origin" header sent
  45. in a client handshake .
  46. `location` (for client sockets) is optional, used to request a
  47. particular resource in the HTTP handshake. In a URL, this would show as
  48. ws://host[:port]/<location>. Use this when the server serves multiple
  49. resources (see `locations`).
  50. `trusted_origins` (for server sockets) is a list of expected values
  51. for the "Origin" header sent by a client. If the received Origin header
  52. has value not in this list, a HandshakeError is raised. If the list is
  53. empty (default), all origins are excepted.
  54. `locations` (for server sockets) is an optional list of resources
  55. serverd by this server. If specified (without trailing slashes), these
  56. are used to verify the resource location requested by a client. The
  57. requested location may be used to distinquish different services in a
  58. server implementation.
  59. `auth` is optional, used for HTTP Basic or Digest authentication during
  60. the handshake. It must be specified as a (username, password) tuple.
  61. `sfamily` and `sproto` are used for the regular socket constructor.
  62. """
  63. self.protocols = protocols
  64. self.extensions = extensions
  65. self.origin = origin
  66. self.location = location
  67. self.trusted_origins = trusted_origins
  68. self.locations = locations
  69. self.auth = auth
  70. self.secure = False
  71. self.handshake_sent = False
  72. self.hooks_send = []
  73. self.hooks_recv = []
  74. self.state = STATE_INIT
  75. self.sendbuf = ''
  76. self.recvbuf = ''
  77. self.recv_callbacks = []
  78. self.sock = sock or socket.socket(sfamily, socket.SOCK_STREAM, sproto)
  79. def __getattr__(self, name):
  80. if name in INHERITED_ATTRS:
  81. return getattr(self.sock, name)
  82. raise AttributeError("'%s' has no attribute '%s'"
  83. % (self.__class__.__name__, name))
  84. def accept(self):
  85. """
  86. Equivalent to socket.accept(), but transforms the socket into a
  87. websocket instance and sends a server handshake (after receiving a
  88. client handshake). Note that the handshake may raise a HandshakeError
  89. exception.
  90. """
  91. sock, address = self.sock.accept()
  92. wsock = websocket(sock)
  93. wsock.secure = self.secure
  94. ServerHandshake(wsock).perform(self)
  95. wsock.handshake_sent = True
  96. return wsock, address
  97. def connect(self, address):
  98. """
  99. Equivalent to socket.connect(), but sends an client handshake request
  100. after connecting.
  101. `address` is a (host, port) tuple of the server to connect to.
  102. """
  103. self.sock.connect(address)
  104. ClientHandshake(self).perform()
  105. self.handshake_sent = True
  106. def send(self, *args):
  107. """
  108. Send a number of frames.
  109. """
  110. for frame in args:
  111. for hook in self.hooks_send:
  112. frame = hook(frame)
  113. #print 'send frame:', frame, 'to %s:%d' % self.sock.getpeername()
  114. self.sock.sendall(frame.pack())
  115. def recv(self):
  116. """
  117. Receive a single frames. This can be either a data frame or a control
  118. frame.
  119. """
  120. frame = receive_frame(self.sock)
  121. for hook in self.hooks_recv:
  122. frame = hook(frame)
  123. #print 'receive frame:', frame, 'from %s:%d' % self.sock.getpeername()
  124. return frame
  125. def recvn(self, n):
  126. """
  127. Receive exactly `n` frames. These can be either data frames or control
  128. frames, or a combination of both.
  129. """
  130. return [self.recv() for i in xrange(n)]
  131. def queue_send(self, frame):
  132. """
  133. Enqueue `frame` to the send buffer so that it is send on the next
  134. `do_async_send`.
  135. """
  136. for hook in self.hooks_send:
  137. frame = hook(frame)
  138. self.sendbuf += frame.pack()
  139. self.state |= STATE_WRITE
  140. def queue_recv(self, callback):
  141. """
  142. Enqueue `callback` to be called when the next frame is recieved by
  143. `do_async_recv`.
  144. """
  145. self.recv_callbacks.push(callback)
  146. self.state |= STATE_READ
  147. def queue_close(self):
  148. self.state |= STATE_CLOSE
  149. def do_async_send(self):
  150. """
  151. Send any queued data. If all data is sent, STATE_WRITE is removed from
  152. the state mask.
  153. """
  154. assert self.state & STATE_WRITE
  155. assert len(self.sendbuf)
  156. nwritten = self.sock.send(self.sendbuf)
  157. self.sendbuf = self.sendbuf[nwritten:]
  158. if len(self.sendbuf) == 0:
  159. self.state ^= STATE_WRITE
  160. def do_async_recv(self, bufsize):
  161. """
  162. """
  163. assert self.state & STATE_READ
  164. self.recvbuf += self.sock.recv(bufsize)
  165. while contains_frame(self.recvbuf):
  166. frame, self.recvbuf = pop_frame(self.recvbuf)
  167. if len(self.recv_callbacks) == 0:
  168. raise IndexError('no callback installed for received frame %s'
  169. % frame)
  170. cb = self.recv_callbacks.pop(0)
  171. cb(frame)
  172. if len(self.recvbuf) == 0:
  173. self.state ^= STATE_READ
  174. def enable_ssl(self, *args, **kwargs):
  175. """
  176. Transforms the regular socket.socket to an ssl.SSLSocket for secure
  177. connections. Any arguments are passed to ssl.wrap_socket:
  178. http://docs.python.org/dev/library/ssl.html#ssl.wrap_socket
  179. """
  180. if self.handshake_sent:
  181. raise SSLError('can only enable SSL before handshake')
  182. self.secure = True
  183. self.sock = ssl.wrap_socket(self.sock, *args, **kwargs)
  184. def add_hook(self, send=None, recv=None, prepend=False):
  185. """
  186. Add a pair of send and receive hooks that are called for each frame
  187. that is sent or received. A hook is a function that receives a single
  188. argument - a Frame instance - and returns a `Frame` instance as well.
  189. `prepend` is a flag indicating whether the send hook is prepended to
  190. the other send hooks. This is expecially useful when a program uses
  191. extensions such as the built-in `DeflateFrame` extension. These
  192. extensions are installed using these hooks as well.
  193. For example, the following code creates a `Frame` instance for data
  194. being sent and removes the instance for received data. This way, data
  195. can be sent and received as if on a regular socket.
  196. >>> import wspy
  197. >>> sock = wspy.websocket()
  198. >>> sock.add_hook(lambda data: tswpy.Frame(tswpy.OPCODE_TEXT, data),
  199. >>> lambda frame: frame.payload)
  200. To add base64 encoding to the example above:
  201. >>> import base64
  202. >>> sock.add_hook(base64.encodestring, base64.decodestring, True)
  203. Note that here `prepend=True`, so that data passed to `send()` is first
  204. encoded and then packed into a frame. Of course, one could also decide
  205. to add the base64 hook first, or to return a new `Frame` instance with
  206. base64-encoded data.
  207. """
  208. if send:
  209. self.hooks_send.insert(0 if prepend else -1, send)
  210. if recv:
  211. self.hooks_recv.insert(-1 if prepend else 0, recv)