/ws4py/server/wsgi/middleware.py

https://github.com/swax/WebSocket-for-Python
Python | 136 lines | 100 code | 24 blank | 12 comment | 25 complexity | 82e5666ed9db6afd285f10c53841fbb7 MD5 | raw file
  1. import copy
  2. import base64
  3. from hashlib import sha1
  4. import types
  5. import socket
  6. import gevent
  7. from gevent.coros import Semaphore as Lock
  8. from gevent.queue import Queue
  9. from ws4py import WS_KEY
  10. from ws4py.exc import HandshakeError, StreamClosed
  11. from ws4py.streaming import Stream
  12. from ws4py.server.handler.threadedhandler import WebSocketHandler as ThreadedHandler
  13. WS_VERSION = 8
  14. class WebSocketHandler(ThreadedHandler):
  15. """WebSocket API for handlers
  16. This provides a socket-like interface similar to the browser
  17. WebSocket API for managing a WebSocket connection.
  18. """
  19. def __init__(self, sock, protocols, extensions, environ):
  20. ThreadedHandler.__init__(self, sock, protocols, extensions)
  21. self.environ = environ
  22. self._messages = Queue()
  23. self._lock = Lock()
  24. self._th = gevent.spawn(self._receive)
  25. def closed(self, code, reason=None):
  26. self._messages.put(StreamClosed(code, reason))
  27. def received_message(self, m):
  28. self._messages.put(copy.deepcopy(m))
  29. def receive(self, msg_obj=False):
  30. msg = self._messages.get()
  31. if isinstance(msg, StreamClosed):
  32. # Maybe we'll do something better
  33. return None
  34. if msg_obj:
  35. return msg
  36. else:
  37. return msg.data
  38. class WebSocketUpgradeMiddleware(object):
  39. """WSGI middleware for handling WebSocket upgrades"""
  40. def __init__(self, handle, fallback_app=None, protocols=None, extensions=None,
  41. websocket_class=WebSocketHandler):
  42. self.handle = handle
  43. self.fallback_app = fallback_app
  44. self.protocols = protocols
  45. self.extensions = extensions
  46. self.websocket_class = websocket_class
  47. def __call__(self, environ, start_response):
  48. # Initial handshake validation
  49. try:
  50. if 'websocket' not in environ.get('upgrade.protocol', ''):
  51. raise HandshakeError("Upgrade protocol is not websocket")
  52. if environ.get('REQUEST_METHOD') != 'GET':
  53. raise HandshakeError('Method is not GET')
  54. key = environ.get('HTTP_SEC_WEBSOCKET_KEY')
  55. if key:
  56. ws_key = base64.b64decode(key)
  57. if len(ws_key) != 16:
  58. raise HandshakeError("WebSocket key's length is invalid")
  59. else:
  60. raise HandshakeError("Not a valid HyBi WebSocket request")
  61. version = environ.get('HTTP_SEC_WEBSOCKET_VERSION')
  62. if version:
  63. if version != str(WS_VERSION):
  64. raise HandshakeError('Unsupported WebSocket version')
  65. environ['websocket.version'] = str(WS_VERSION)
  66. else:
  67. raise HandshakeError('WebSocket version required')
  68. except HandshakeError, e:
  69. if self.fallback_app:
  70. return self.fallback_app(environ, start_response)
  71. else:
  72. start_response("400 Bad Handshake", [])
  73. return [str(e)]
  74. # Collect supported subprotocols
  75. protocols = self.protocols or []
  76. subprotocols = environ.get('HTTP_SEC_WEBSOCKET_PROTOCOL')
  77. ws_protocols = []
  78. if subprotocols:
  79. for s in subprotocols.split(','):
  80. s = s.strip()
  81. if s in protocols:
  82. ws_protocols.append(s)
  83. # Collect supported extensions
  84. exts = self.extensions or []
  85. ws_extensions = []
  86. extensions = environ.get('HTTP_SEC_WEBSOCKET_EXTENSIONS')
  87. if extensions:
  88. for ext in extensions.split(','):
  89. ext = ext.strip()
  90. if ext in exts:
  91. ws_extensions.append(ext)
  92. # Build and start the HTTP response
  93. headers = [
  94. ('Upgrade', 'websocket'),
  95. ('Connection', 'Upgrade'),
  96. ('Sec-WebSocket-Version', environ['websocket.version']),
  97. ('Sec-WebSocket-Accept', base64.b64encode(sha1(key + WS_KEY).digest())),
  98. ]
  99. if ws_protocols:
  100. headers.append(('Sec-WebSocket-Protocol', ', '.join(ws_protocols)))
  101. if ws_extensions:
  102. headers.append(('Sec-WebSocket-Extensions', ','.join(ws_extensions)))
  103. start_response("101 Web Socket Hybi Handshake", headers)
  104. # Build a websocket object and pass it to the handler
  105. self.handle(
  106. self.websocket_class(
  107. environ.get('upgrade.socket'),
  108. ws_protocols,
  109. ws_extensions,
  110. environ),
  111. environ)