You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Enso-Bot/venv/Lib/site-packages/aiohttp/wsgi.py

236 lines
7.4 KiB
Python

"""wsgi server.
TODO:
* proxy protocol
* x-forward security
* wsgi file support (os.sendfile)
"""
import asyncio
import inspect
import io
import os
import socket
import sys
from urllib.parse import urlsplit
import aiohttp
from aiohttp import hdrs, server
__all__ = ('WSGIServerHttpProtocol',)
class WSGIServerHttpProtocol(server.ServerHttpProtocol):
"""HTTP Server that implements the Python WSGI protocol.
It uses 'wsgi.async' of 'True'. 'wsgi.input' can behave differently
depends on 'readpayload' constructor parameter. If readpayload is set to
True, wsgi server reads all incoming data into BytesIO object and
sends it as 'wsgi.input' environ var. If readpayload is set to false
'wsgi.input' is a StreamReader and application should read incoming
data with "yield from environ['wsgi.input'].read()". It defaults to False.
"""
SCRIPT_NAME = os.environ.get('SCRIPT_NAME', '')
def __init__(self, app, readpayload=False, is_ssl=False, *args, **kw):
super().__init__(*args, **kw)
self.wsgi = app
self.is_ssl = is_ssl
self.readpayload = readpayload
def create_wsgi_response(self, message):
return WsgiResponse(self.writer, message)
def create_wsgi_environ(self, message, payload):
uri_parts = urlsplit(message.path)
environ = {
'wsgi.input': payload,
'wsgi.errors': sys.stderr,
'wsgi.version': (1, 0),
'wsgi.async': True,
'wsgi.multithread': False,
'wsgi.multiprocess': False,
'wsgi.run_once': False,
'wsgi.file_wrapper': FileWrapper,
'SERVER_SOFTWARE': aiohttp.HttpMessage.SERVER_SOFTWARE,
'REQUEST_METHOD': message.method,
'QUERY_STRING': uri_parts.query or '',
'RAW_URI': message.path,
'SERVER_PROTOCOL': 'HTTP/%s.%s' % message.version
}
script_name = self.SCRIPT_NAME
for hdr_name, hdr_value in message.headers.items():
hdr_name = hdr_name.upper()
if hdr_name == 'SCRIPT_NAME':
script_name = hdr_value
elif 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
url_scheme = environ.get('HTTP_X_FORWARDED_PROTO')
if url_scheme is None:
url_scheme = 'https' if self.is_ssl else 'http'
environ['wsgi.url_scheme'] = url_scheme
# authors should be aware that REMOTE_HOST and REMOTE_ADDR
# may not qualify the remote addr
# also SERVER_PORT variable MUST be set to the TCP/IP port number on
# which this request is received from the client.
# http://www.ietf.org/rfc/rfc3875
family = self.transport.get_extra_info('socket').family
if family in (socket.AF_INET, socket.AF_INET6):
peername = self.transport.get_extra_info('peername')
environ['REMOTE_ADDR'] = peername[0]
environ['REMOTE_PORT'] = str(peername[1])
http_host = message.headers.get("HOST", None)
if http_host:
hostport = http_host.split(":")
environ['SERVER_NAME'] = hostport[0]
if len(hostport) > 1:
environ['SERVER_PORT'] = str(hostport[1])
else:
environ['SERVER_PORT'] = '80'
else:
# SERVER_NAME should be set to value of Host header, but this
# header is not required. In this case we shoud set it to local
# address of socket
sockname = self.transport.get_extra_info('sockname')
environ['SERVER_NAME'] = sockname[0]
environ['SERVER_PORT'] = str(sockname[1])
else:
# We are behind reverse proxy, so get all vars from headers
for header in ('REMOTE_ADDR', 'REMOTE_PORT',
'SERVER_NAME', 'SERVER_PORT'):
environ[header] = message.headers.get(header, '')
path_info = uri_parts.path
if script_name:
path_info = path_info.split(script_name, 1)[-1]
environ['PATH_INFO'] = path_info
environ['SCRIPT_NAME'] = script_name
environ['async.reader'] = self.reader
environ['async.writer'] = self.writer
return environ
@asyncio.coroutine
def handle_request(self, message, payload):
"""Handle a single HTTP request"""
now = self._loop.time()
if self.readpayload:
wsgiinput = io.BytesIO()
wsgiinput.write((yield from payload.read()))
wsgiinput.seek(0)
payload = wsgiinput
environ = self.create_wsgi_environ(message, payload)
response = self.create_wsgi_response(message)
riter = self.wsgi(environ, response.start_response)
if isinstance(riter, asyncio.Future) or inspect.isgenerator(riter):
riter = yield from riter
resp = response.response
try:
for item in riter:
if isinstance(item, asyncio.Future):
item = yield from item
yield from resp.write(item)
yield from resp.write_eof()
finally:
if hasattr(riter, 'close'):
riter.close()
if resp.keep_alive():
self.keep_alive(True)
self.log_access(
message, environ, response.response, self._loop.time() - now)
class FileWrapper:
"""Custom file wrapper."""
def __init__(self, fobj, chunk_size=8192):
self.fobj = fobj
self.chunk_size = chunk_size
if hasattr(fobj, 'close'):
self.close = fobj.close
def __iter__(self):
return self
def __next__(self):
data = self.fobj.read(self.chunk_size)
if data:
return data
raise StopIteration
class WsgiResponse:
"""Implementation of start_response() callable as specified by PEP 3333"""
status = None
HOP_HEADERS = {
hdrs.CONNECTION,
hdrs.KEEP_ALIVE,
hdrs.PROXY_AUTHENTICATE,
hdrs.PROXY_AUTHORIZATION,
hdrs.TE,
hdrs.TRAILER,
hdrs.TRANSFER_ENCODING,
hdrs.UPGRADE,
}
def __init__(self, writer, message):
self.writer = writer
self.message = message
def start_response(self, status, headers, exc_info=None):
if exc_info:
try:
if self.status:
raise exc_info[1]
finally:
exc_info = None
status_code = int(status.split(' ', 1)[0])
self.status = status
resp = self.response = aiohttp.Response(
self.writer, status_code,
self.message.version, self.message.should_close)
resp.HOP_HEADERS = self.HOP_HEADERS
for name, value in headers:
resp.add_header(name, value)
if resp.has_chunked_hdr:
resp.enable_chunked_encoding()
# send headers immediately for websocket connection
if status_code == 101 and resp.upgrade and resp.websocket:
resp.send_headers()
else:
resp._send_headers = True
return self.response.write