websocket.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  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()
  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. trusted_origins=[], location='/', 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. `trusted_origins` (for servere sockets) is a list of expected values
  39. for the "Origin" header sent by a client. If the received Origin header
  40. has value not in this list, a HandshakeError is raised. If the list is
  41. empty (default), all origins are excepted.
  42. `location` is optional, used for the HTTP handshake. In a URL, this
  43. would show as ws://host[:port]/path.
  44. `auth` is optional, used for HTTP Basic or Digest authentication during
  45. the handshake. It must be specified as a (username, password) tuple.
  46. `sfamily` and `sproto` are used for the regular socket constructor.
  47. """
  48. self.protocols = protocols
  49. self.extensions = extensions
  50. self.origin = origin
  51. self.trusted_origins = trusted_origins
  52. self.location = location
  53. self.auth = auth
  54. self.secure = False
  55. self.handshake_sent = False
  56. self.hooks_send = []
  57. self.hooks_recv = []
  58. self.sock = sock or socket.socket(sfamily, socket.SOCK_STREAM, sproto)
  59. def bind(self, address):
  60. self.sock.bind(address)
  61. def listen(self, backlog):
  62. self.sock.listen(backlog)
  63. def accept(self):
  64. """
  65. Equivalent to socket.accept(), but transforms the socket into a
  66. websocket instance and sends a server handshake (after receiving a
  67. client handshake). Note that the handshake may raise a HandshakeError
  68. exception.
  69. """
  70. sock, address = self.sock.accept()
  71. wsock = websocket(sock)
  72. ServerHandshake(wsock).perform()
  73. wsock.handshake_sent = True
  74. return wsock, address
  75. def connect(self, address):
  76. """
  77. Equivalent to socket.connect(), but sends an client handshake request
  78. after connecting.
  79. `address` is a (host, port) tuple of the server to connect to.
  80. """
  81. self.sock.connect(address)
  82. ClientHandshake(self).perform()
  83. self.handshake_sent = True
  84. def send(self, *args):
  85. """
  86. Send a number of frames.
  87. """
  88. for frame in args:
  89. for hook in self.hooks_send:
  90. frame = hook(frame)
  91. #print 'send frame:', frame, 'to %s:%d' % self.sock.getpeername()
  92. self.sock.sendall(frame.pack())
  93. def recv(self):
  94. """
  95. Receive a single frames. This can be either a data frame or a control
  96. frame.
  97. """
  98. frame = receive_frame(self.sock)
  99. for hook in self.hooks_recv:
  100. frame = hook(frame)
  101. #print 'receive frame:', frame, 'from %s:%d' % self.sock.getpeername()
  102. return frame
  103. def recvn(self, n):
  104. """
  105. Receive exactly `n` frames. These can be either data frames or control
  106. frames, or a combination of both.
  107. """
  108. return [self.recv() for i in xrange(n)]
  109. def getpeername(self):
  110. return self.sock.getpeername()
  111. def getsockname(self):
  112. return self.sock.getsockname()
  113. def setsockopt(self, level, optname, value):
  114. self.sock.setsockopt(level, optname, value)
  115. def getsockopt(self, level, optname):
  116. return self.sock.getsockopt(level, optname)
  117. def close(self):
  118. self.sock.close()
  119. def enable_ssl(self, *args, **kwargs):
  120. """
  121. Transforms the regular socket.socket to an ssl.SSLSocket for secure
  122. connections. Any arguments are passed to ssl.wrap_socket:
  123. http://docs.python.org/dev/library/ssl.html#ssl.wrap_socket
  124. """
  125. if self.handshake_sent:
  126. raise SSLError('can only enable SSL before handshake')
  127. self.secure = True
  128. self.sock = ssl.wrap_socket(self.sock, *args, **kwargs)
  129. def add_hook(self, send=None, recv=None, prepend=False):
  130. """
  131. Add a pair of send and receive hooks that are called for each frame
  132. that is sent or received. A hook is a function that receives a single
  133. argument - a Frame instance - and returns a `Frame` instance as well.
  134. `prepend` is a flag indicating whether the send hook is prepended to
  135. the other send hooks. This is expecially useful when a program uses
  136. extensions such as the built-in `DeflateFrame` extension. These
  137. extensions are installed using these hooks as well.
  138. For example, the following code creates a `Frame` instance for data
  139. being sent and removes the instance for received data. This way, data
  140. can be sent and received as if on a regular socket.
  141. >>> import twspy
  142. >>> sock.add_hook(lambda data: tswpy.Frame(tswpy.OPCODE_TEXT, data),
  143. >>> lambda frame: frame.payload)
  144. To add base64 encoding to the example above:
  145. >>> import base64
  146. >>> sock.add_hook(base64.encodestring, base64.decodestring, True)
  147. Note that here `prepend=True`, so that data passed to `send()` is first
  148. encoded and then packed into a frame. Of course, one could also decide
  149. to add the base64 hook first, or to return a new `Frame` instance with
  150. base64-encoded data.
  151. """
  152. if send:
  153. self.hooks_send.insert(0 if prepend else -1, send)
  154. if recv:
  155. self.hooks_recv.insert(-1 if prepend else 0, recv)