This repository has been archived on 2024-12-26. You can view files and clone it, but cannot push or open issues or pull requests.
PyBitmessage-2024-12-26/mockenv/lib/python3.6/site-packages/ws4py/server/wsgiutils.py
2022-07-22 16:13:59 +05:30

163 lines
5.9 KiB
Python

# -*- 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 []