websocket.py 7.3 KB

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