File: async_sanic.py

package info (click to toggle)
python-engineio 3.0.0%2Bdfsg-1
  • links: PTS
  • area: main
  • in suites: buster
  • size: 468 kB
  • sloc: python: 4,688; makefile: 15
file content (145 lines) | stat: -rw-r--r-- 4,406 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import sys
from urllib.parse import urlsplit

from sanic.response import HTTPResponse
try:
    from sanic.websocket import WebSocketProtocol
except ImportError:
    # the installed version of sanic does not have websocket support
    WebSocketProtocol = None
import six


def create_route(app, engineio_server, engineio_endpoint):
    """This function sets up the engine.io endpoint as a route for the
    application.

    Note that both GET and POST requests must be hooked up on the engine.io
    endpoint.
    """
    app.add_route(engineio_server.handle_request, engineio_endpoint,
                  methods=['GET', 'POST', 'OPTIONS'])
    try:
        app.enable_websocket()
    except AttributeError:
        # ignore, this version does not support websocket
        pass


def translate_request(request):
    """This function takes the arguments passed to the request handler and
    uses them to generate a WSGI compatible environ dictionary.
    """
    class AwaitablePayload(object):
        def __init__(self, payload):
            self.payload = payload or b''

        async def read(self, length=None):
            if length is None:
                r = self.payload
                self.payload = b''
            else:
                r = self.payload[:length]
                self.payload = self.payload[length:]
            return r

    uri_parts = urlsplit(request.url)
    environ = {
        'wsgi.input': AwaitablePayload(request.body),
        'wsgi.errors': sys.stderr,
        'wsgi.version': (1, 0),
        'wsgi.async': True,
        'wsgi.multithread': False,
        'wsgi.multiprocess': False,
        'wsgi.run_once': False,
        'SERVER_SOFTWARE': 'sanic',
        'REQUEST_METHOD': request.method,
        'QUERY_STRING': uri_parts.query or '',
        'RAW_URI': request.url,
        'SERVER_PROTOCOL': 'HTTP/' + request.version,
        'REMOTE_ADDR': '127.0.0.1',
        'REMOTE_PORT': '0',
        'SERVER_NAME': 'sanic',
        'SERVER_PORT': '0',
        'sanic.request': request
    }

    for hdr_name, hdr_value in request.headers.items():
        hdr_name = hdr_name.upper()
        if hdr_name == 'CONTENT-TYPE':
            environ['CONTENT_TYPE'] = hdr_value
            continue
        elif hdr_name == 'CONTENT-LENGTH':
            environ['CONTENT_LENGTH'] = hdr_value
            continue

        key = 'HTTP_%s' % hdr_name.replace('-', '_')
        if key in environ:
            hdr_value = '%s,%s' % (environ[key], hdr_value)

        environ[key] = hdr_value

    environ['wsgi.url_scheme'] = environ.get('HTTP_X_FORWARDED_PROTO', 'http')

    path_info = uri_parts.path

    environ['PATH_INFO'] = path_info
    environ['SCRIPT_NAME'] = ''

    return environ


def make_response(status, headers, payload, environ):
    """This function generates an appropriate response object for this async
    mode.
    """
    headers_dict = {}
    content_type = None
    for h in headers:
        if h[0].lower() == 'content-type':
            content_type = h[1]
        else:
            headers_dict[h[0]] = h[1]
    return HTTPResponse(body_bytes=payload, content_type=content_type,
                        status=int(status.split()[0]), headers=headers_dict)


class WebSocket(object):  # pragma: no cover
    """
    This wrapper class provides a sanic WebSocket interface that is
    somewhat compatible with eventlet's implementation.
    """
    def __init__(self, handler):
        self.handler = handler
        self._sock = None

    async def __call__(self, environ):
        request = environ['sanic.request']
        protocol = request.transport.get_protocol()
        self._sock = await protocol.websocket_handshake(request)

        self.environ = environ
        await self.handler(self)

    async def close(self):
        await self._sock.close()

    async def send(self, message):
        await self._sock.send(message)

    async def wait(self):
        data = await self._sock.recv()
        if not isinstance(data, six.binary_type) and \
                not isinstance(data, six.text_type):
            raise IOError()
        return data


_async = {
    'asyncio': True,
    'create_route': create_route,
    'translate_request': translate_request,
    'make_response': make_response,
    'websocket': sys.modules[__name__] if WebSocketProtocol else None,
    'websocket_class': 'WebSocket' if WebSocketProtocol else None
}