Source code for aiohttp.wsgi

"""wsgi server.

TODO:
  * proxy protocol
  * x-forward security
  * wsgi file support (os.sendfile)
"""

__all__ = ['WSGIServerHttpProtocol']

import asyncio
import inspect
import io
import os
import sys
import time
from urllib.parse import urlsplit

import aiohttp
from aiohttp import server, helpers, hdrs


[docs]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
[docs] def create_wsgi_response(self, message): return WsgiResponse(self.writer, message)
[docs] def create_wsgi_environ(self, message, payload): uri_parts = urlsplit(message.path) url_scheme = 'https' if self.is_ssl else 'http' 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, 'wsgi.url_scheme': url_scheme, '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 } # authors should be aware that REMOTE_HOST and REMOTE_ADDR # may not qualify the remote addr: # http://www.ietf.org/rfc/rfc3875 forward = self.transport.get_extra_info('addr', '127.0.0.1') script_name = self.SCRIPT_NAME server = forward for hdr_name, hdr_value in message.headers.items(): if hdr_name == 'HOST': server = hdr_value elif 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 remote = helpers.parse_remote_addr(forward) environ['REMOTE_ADDR'] = remote[0] environ['REMOTE_PORT'] = remote[1] if isinstance(server, str): server = server.split(':') if len(server) == 1: server.append('80' if url_scheme == 'http' else '443') environ['SERVER_NAME'] = server[0] environ['SERVER_PORT'] = str(server[1]) 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
[docs] def handle_request(self, message, payload): """Handle a single HTTP request""" now = time.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, time.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 resp.add_headers(*headers) 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

http client/server for asyncio

https://secure.travis-ci.org/KeepSafe/aiohttp.png?branch=master

Navigation

Fork me on GitHub