PageRenderTime 24ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/src/lib/geventwebsocket/handler.py

https://gitlab.com/sbDevGit/ZeroNet
Python | 283 lines | 265 code | 6 blank | 12 comment | 2 complexity | 264afdd16fe2cc694b8a038dc08ee815 MD5 | raw file
  1. # Modified: Werkzeug Debugger workaround in run_websocket(self):
  2. import base64
  3. import hashlib
  4. import warnings
  5. from gevent.pywsgi import WSGIHandler
  6. from .websocket import WebSocket, Stream
  7. from .logging import create_logger
  8. class Client(object):
  9. def __init__(self, address, ws):
  10. self.address = address
  11. self.ws = ws
  12. class WebSocketHandler(WSGIHandler):
  13. """
  14. Automatically upgrades the connection to a websocket.
  15. To prevent the WebSocketHandler to call the underlying WSGI application,
  16. but only setup the WebSocket negotiations, do:
  17. mywebsockethandler.prevent_wsgi_call = True
  18. before calling run_application(). This is useful if you want to do more
  19. things before calling the app, and want to off-load the WebSocket
  20. negotiations to this library. Socket.IO needs this for example, to send
  21. the 'ack' before yielding the control to your WSGI app.
  22. """
  23. SUPPORTED_VERSIONS = ('13', '8', '7')
  24. GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
  25. def run_websocket(self):
  26. """
  27. Called when a websocket has been created successfully.
  28. """
  29. if getattr(self, 'prevent_wsgi_call', False):
  30. return
  31. # In case WebSocketServer is not used
  32. if not hasattr(self.server, 'clients'):
  33. self.server.clients = {}
  34. # Since we're now a websocket connection, we don't care what the
  35. # application actually responds with for the http response
  36. try:
  37. self.server.clients[self.client_address] = Client(
  38. self.client_address, self.websocket)
  39. if self.application.__class__.__name__ == "DebuggedApplication": # Modified: Werkzeug Debugger workaround (https://bitbucket.org/Jeffrey/gevent-websocket/issue/53/if-the-application-returns-a-generator-we)
  40. list(self.application(self.environ, lambda s, h: []))
  41. else:
  42. self.application(self.environ, lambda s, h: [])
  43. finally:
  44. del self.server.clients[self.client_address]
  45. if not self.websocket.closed:
  46. self.websocket.close()
  47. self.environ.update({
  48. 'wsgi.websocket': None
  49. })
  50. self.websocket = None
  51. def run_application(self):
  52. if (hasattr(self.server, 'pre_start_hook')
  53. and self.server.pre_start_hook):
  54. self.logger.debug("Calling pre-start hook")
  55. if self.server.pre_start_hook(self):
  56. return super(WebSocketHandler, self).run_application()
  57. self.logger.debug("Initializing WebSocket")
  58. self.result = self.upgrade_websocket()
  59. if hasattr(self, 'websocket'):
  60. if self.status and not self.headers_sent:
  61. self.write('')
  62. self.run_websocket()
  63. else:
  64. if self.status:
  65. # A status was set, likely an error so just send the response
  66. if not self.result:
  67. self.result = []
  68. self.process_result()
  69. return
  70. # This handler did not handle the request, so defer it to the
  71. # underlying application object
  72. return super(WebSocketHandler, self).run_application()
  73. def upgrade_websocket(self):
  74. """
  75. Attempt to upgrade the current environ into a websocket enabled
  76. connection. If successful, the environ dict with be updated with two
  77. new entries, `wsgi.websocket` and `wsgi.websocket_version`.
  78. :returns: Whether the upgrade was successful.
  79. """
  80. # Some basic sanity checks first
  81. self.logger.debug("Validating WebSocket request")
  82. if self.environ.get('REQUEST_METHOD', '') != 'GET':
  83. # This is not a websocket request, so we must not handle it
  84. self.logger.debug('Can only upgrade connection if using GET method.')
  85. return
  86. upgrade = self.environ.get('HTTP_UPGRADE', '').lower()
  87. if upgrade == 'websocket':
  88. connection = self.environ.get('HTTP_CONNECTION', '').lower()
  89. if 'upgrade' not in connection:
  90. # This is not a websocket request, so we must not handle it
  91. self.logger.warning("Client didn't ask for a connection "
  92. "upgrade")
  93. return
  94. else:
  95. # This is not a websocket request, so we must not handle it
  96. return
  97. if self.request_version != 'HTTP/1.1':
  98. self.start_response('402 Bad Request', [])
  99. self.logger.warning("Bad server protocol in headers: %s" % self.request_version)
  100. return ['Bad protocol version']
  101. if self.environ.get('HTTP_SEC_WEBSOCKET_VERSION'):
  102. return self.upgrade_connection()
  103. else:
  104. self.logger.warning("No protocol defined")
  105. self.start_response('426 Upgrade Required', [
  106. ('Sec-WebSocket-Version', ', '.join(self.SUPPORTED_VERSIONS))])
  107. return ['No Websocket protocol version defined']
  108. def upgrade_connection(self):
  109. """
  110. Validate and 'upgrade' the HTTP request to a WebSocket request.
  111. If an upgrade succeeded then then handler will have `start_response`
  112. with a status of `101`, the environ will also be updated with
  113. `wsgi.websocket` and `wsgi.websocket_version` keys.
  114. :param environ: The WSGI environ dict.
  115. :param start_response: The callable used to start the response.
  116. :param stream: File like object that will be read from/written to by
  117. the underlying WebSocket object, if created.
  118. :return: The WSGI response iterator is something went awry.
  119. """
  120. self.logger.debug("Attempting to upgrade connection")
  121. version = self.environ.get("HTTP_SEC_WEBSOCKET_VERSION")
  122. if version not in self.SUPPORTED_VERSIONS:
  123. msg = "Unsupported WebSocket Version: {0}".format(version)
  124. self.logger.warning(msg)
  125. self.start_response('400 Bad Request', [
  126. ('Sec-WebSocket-Version', ', '.join(self.SUPPORTED_VERSIONS))
  127. ])
  128. return [msg]
  129. key = self.environ.get("HTTP_SEC_WEBSOCKET_KEY", '').strip()
  130. if not key:
  131. # 5.2.1 (3)
  132. msg = "Sec-WebSocket-Key header is missing/empty"
  133. self.logger.warning(msg)
  134. self.start_response('400 Bad Request', [])
  135. return [msg]
  136. try:
  137. key_len = len(base64.b64decode(key))
  138. except TypeError:
  139. msg = "Invalid key: {0}".format(key)
  140. self.logger.warning(msg)
  141. self.start_response('400 Bad Request', [])
  142. return [msg]
  143. if key_len != 16:
  144. # 5.2.1 (3)
  145. msg = "Invalid key: {0}".format(key)
  146. self.logger.warning(msg)
  147. self.start_response('400 Bad Request', [])
  148. return [msg]
  149. # Check for WebSocket Protocols
  150. requested_protocols = self.environ.get(
  151. 'HTTP_SEC_WEBSOCKET_PROTOCOL', '')
  152. protocol = None
  153. if hasattr(self.application, 'app_protocol'):
  154. allowed_protocol = self.application.app_protocol(
  155. self.environ['PATH_INFO'])
  156. if allowed_protocol and allowed_protocol in requested_protocols:
  157. protocol = allowed_protocol
  158. self.logger.debug("Protocol allowed: {0}".format(protocol))
  159. self.websocket = WebSocket(self.environ, Stream(self), self)
  160. self.environ.update({
  161. 'wsgi.websocket_version': version,
  162. 'wsgi.websocket': self.websocket
  163. })
  164. headers = [
  165. ("Upgrade", "websocket"),
  166. ("Connection", "Upgrade"),
  167. ("Sec-WebSocket-Accept", base64.b64encode(
  168. hashlib.sha1(key + self.GUID).digest())),
  169. ]
  170. if protocol:
  171. headers.append(("Sec-WebSocket-Protocol", protocol))
  172. self.logger.debug("WebSocket request accepted, switching protocols")
  173. self.start_response("101 Switching Protocols", headers)
  174. @property
  175. def logger(self):
  176. if not hasattr(self.server, 'logger'):
  177. self.server.logger = create_logger(__name__)
  178. return self.server.logger
  179. def log_request(self):
  180. if '101' not in self.status:
  181. self.logger.info(self.format_request())
  182. @property
  183. def active_client(self):
  184. return self.server.clients[self.client_address]
  185. def start_response(self, status, headers, exc_info=None):
  186. """
  187. Called when the handler is ready to send a response back to the remote
  188. endpoint. A websocket connection may have not been created.
  189. """
  190. writer = super(WebSocketHandler, self).start_response(
  191. status, headers, exc_info=exc_info)
  192. self._prepare_response()
  193. return writer
  194. def _prepare_response(self):
  195. """
  196. Sets up the ``pywsgi.Handler`` to work with a websocket response.
  197. This is used by other projects that need to support WebSocket
  198. connections as part of a larger effort.
  199. """
  200. assert not self.headers_sent
  201. if not self.environ.get('wsgi.websocket'):
  202. # a WebSocket connection is not established, do nothing
  203. return
  204. # So that `finalize_headers` doesn't write a Content-Length header
  205. self.provided_content_length = False
  206. # The websocket is now controlling the response
  207. self.response_use_chunked = False
  208. # Once the request is over, the connection must be closed
  209. self.close_connection = True
  210. # Prevents the Date header from being written
  211. self.provided_date = True