### Navigation

- index
- modules |
- ws4py 0.3.2 documentation »
- Module code »

# Source code for ws4py.server.wsgiutils

    # -*- coding: utf-8 -*-
    __doc__ = """
    This module provides a WSGI application suitable
    for a WSGI server such as gevent or wsgiref for instance.

    :pep:`333` couldn't foresee a protocol such as
    WebSockets but luckily the way the initial
    protocol upgrade was designed means that we can
    fit the handshake in a WSGI flow.

    The handshake validates the request against
    some internal or user-provided values and
    fails the request if the validation doesn't
    complete.

    On success, the provided WebSocket subclass
    is instanciated and stored into the
    `'ws4py.websocket'` environ key so that
    the WSGI server can handle it.

    The WSGI application returns an empty iterable
    since there is little value to return some
    content within the response to the handshake.

    A server wishing to support WebSocket via ws4py
    should:

    - Provide the real socket object to ws4py through the
      `'ws4py.socket'` environ key. We can't use `'wsgi.input'`
      as it may be wrapper to the socket we wouldn't know
      how to extract the socket from.
    - Look for the `'ws4py.websocket'` key in the environ
      when the application has returned and probably attach
      it to a :class:`ws4py.manager.WebSocketManager` instance
      so that the websocket runs its life.
    - Remove the `'ws4py.websocket'` and `'ws4py.socket'`
      environ keys once the application has returned.
      No need for these keys to persist.
    - Not close the underlying socket otherwise, well,
      your websocket will also shutdown.

    .. warning::

      The WSGI application sets the `'Upgrade'` header response
      as specified by :rfc:`6455`. This is not tolerated by
      :pep:`333` since it's a hop-by-hop header.
      We expect most servers won't mind.
    """
    import base64
    from hashlib import sha1
    import logging
    import sys

    from ws4py.websocket import WebSocket
    from ws4py.exc import HandshakeError
    from ws4py.compat import unicode, py3k
    from ws4py import WS_VERSION, WS_KEY, format_addresses

    logger = logging.getLogger('ws4py')

    __all__ = ['WebSocketWSGIApplication']

    class WebSocketWSGIApplication(object):
        def __init__(self, protocols=None, extensions=None, handler_cls=WebSocket):
            """
            WSGI application usable to complete the upgrade handshake
            by validating the requested protocols and extensions as
            well as the websocket version.

            If the upgrade validates, the `handler_cls` class
            is instanciated and stored inside the WSGI `environ`
            under the `'ws4py.websocket'` key to make it
            available to the WSGI handler.
            """
            self.protocols = protocols
            self.extensions = extensions
            self.handler_cls = handler_cls

        def make_websocket(self, sock, protocols, extensions, environ):
            """
            Initialize the `handler_cls` instance with the given
            negociated sets of protocols and extensions as well as
            the `environ` and `sock`.

            Stores then the instance in the `environ` dict
            under the `'ws4py.websocket'` key.
            """
            websocket = self.handler_cls(sock, protocols, extensions,
                                         environ.copy())
            environ['ws4py.websocket'] = websocket
            return websocket

        def __call__(self, environ, start_response):
            if environ.get('REQUEST_METHOD') != 'GET':
                raise HandshakeError('HTTP method must be a GET')

            for key, expected_value in [('HTTP_UPGRADE', 'websocket'),
                                        ('HTTP_CONNECTION', 'upgrade')]:
                actual_value = environ.get(key, '').lower()
                if not actual_value:
                    raise HandshakeError('Header %s is not defined' % key)
                if expected_value not in actual_value:
                    raise HandshakeError('Illegal value for header %s: %s' %
                                         (key, actual_value))

            key = environ.get('HTTP_SEC_WEBSOCKET_KEY')
            if key:
                ws_key = base64.b64decode(key.encode('utf-8'))
                if len(ws_key) != 16:
                    raise HandshakeError("WebSocket key's length is invalid")

            version = environ.get('HTTP_SEC_WEBSOCKET_VERSION')
            supported_versions = b', '.join([unicode(v).encode('utf-8') for v in WS_VERSION])
            version_is_valid = False
            if version:
                try: version = int(version)
                except: pass
                else: version_is_valid = version in WS_VERSION

            if not version_is_valid:
                environ['websocket.version'] = unicode(version).encode('utf-8')
                raise HandshakeError('Unhandled or missing WebSocket version')

            ws_protocols = []
            protocols = self.protocols or []
            subprotocols = environ.get('HTTP_SEC_WEBSOCKET_PROTOCOL')
            if subprotocols:
                for s in subprotocols.split(','):
                    s = s.strip()
                    if s in protocols:
                        ws_protocols.append(s)

            ws_extensions = []
            exts = self.extensions or []
            extensions = environ.get('HTTP_SEC_WEBSOCKET_EXTENSIONS')
            if extensions:
                for ext in extensions.split(','):
                    ext = ext.strip()
                    if ext in exts:
                        ws_extensions.append(ext)

            accept_value = base64.b64encode(sha1(key.encode('utf-8') + WS_KEY).digest())
            if py3k: accept_value = accept_value.decode('utf-8')
            upgrade_headers = [
                ('Upgrade', 'websocket'),
                ('Connection', 'Upgrade'),
                ('Sec-WebSocket-Version', '%s' % version),
                ('Sec-WebSocket-Accept', accept_value),
                ]
            if ws_protocols:
                upgrade_headers.append(('Sec-WebSocket-Protocol', ', '.join(ws_protocols)))
            if ws_extensions:
                upgrade_headers.append(('Sec-WebSocket-Extensions', ','.join(ws_extensions)))

            start_response("101 Switching Protocols", upgrade_headers)

            self.make_websocket(environ['ws4py.socket'],
                                ws_protocols,
                                ws_extensions,
                                environ)

            return []

### Navigation

- index
- modules |
- ws4py 0.3.2 documentation »
- Module code »