10a8c90248264a8b26970b4473770bcc3df8515fJosh Gao# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 20a8c90248264a8b26970b4473770bcc3df8515fJosh Gao# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 30a8c90248264a8b26970b4473770bcc3df8515fJosh Gao# Also licenced under the Apache License, 2.0: http://opensource.org/licenses/apache2.0.php 40a8c90248264a8b26970b4473770bcc3df8515fJosh Gao# Licensed to PSF under a Contributor Agreement 50a8c90248264a8b26970b4473770bcc3df8515fJosh Gao""" 60a8c90248264a8b26970b4473770bcc3df8515fJosh GaoMiddleware to check for obedience to the WSGI specification. 70a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 80a8c90248264a8b26970b4473770bcc3df8515fJosh GaoSome of the things this checks: 90a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 100a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* Signature of the application and start_response (including that 110a8c90248264a8b26970b4473770bcc3df8515fJosh Gao keyword arguments are not used). 120a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 130a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* Environment checks: 140a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 150a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - Environment is a dictionary (and not a subclass). 160a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 170a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - That all the required keys are in the environment: REQUEST_METHOD, 180a8c90248264a8b26970b4473770bcc3df8515fJosh Gao SERVER_NAME, SERVER_PORT, wsgi.version, wsgi.input, wsgi.errors, 190a8c90248264a8b26970b4473770bcc3df8515fJosh Gao wsgi.multithread, wsgi.multiprocess, wsgi.run_once 200a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 210a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - That HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH are not in the 220a8c90248264a8b26970b4473770bcc3df8515fJosh Gao environment (these headers should appear as CONTENT_LENGTH and 230a8c90248264a8b26970b4473770bcc3df8515fJosh Gao CONTENT_TYPE). 240a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 250a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - Warns if QUERY_STRING is missing, as the cgi module acts 260a8c90248264a8b26970b4473770bcc3df8515fJosh Gao unpredictably in that case. 270a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 280a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - That CGI-style variables (that don't contain a .) have 290a8c90248264a8b26970b4473770bcc3df8515fJosh Gao (non-unicode) string values 300a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 310a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - That wsgi.version is a tuple 320a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 330a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - That wsgi.url_scheme is 'http' or 'https' (@@: is this too 340a8c90248264a8b26970b4473770bcc3df8515fJosh Gao restrictive?) 350a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 360a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - Warns if the REQUEST_METHOD is not known (@@: probably too 370a8c90248264a8b26970b4473770bcc3df8515fJosh Gao restrictive). 380a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 390a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - That SCRIPT_NAME and PATH_INFO are empty or start with / 400a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 410a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - That at least one of SCRIPT_NAME or PATH_INFO are set. 420a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 430a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - That CONTENT_LENGTH is a positive integer. 440a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 450a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - That SCRIPT_NAME is not '/' (it should be '', and PATH_INFO should 460a8c90248264a8b26970b4473770bcc3df8515fJosh Gao be '/'). 470a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 480a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - That wsgi.input has the methods read, readline, readlines, and 490a8c90248264a8b26970b4473770bcc3df8515fJosh Gao __iter__ 500a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 510a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - That wsgi.errors has the methods flush, write, writelines 520a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 530a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* The status is a string, contains a space, starts with an integer, 540a8c90248264a8b26970b4473770bcc3df8515fJosh Gao and that integer is in range (> 100). 550a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 560a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* That the headers is a list (not a subclass, not another kind of 570a8c90248264a8b26970b4473770bcc3df8515fJosh Gao sequence). 580a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 590a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* That the items of the headers are tuples of strings. 600a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 610a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* That there is no 'status' header (that is used in CGI, but not in 620a8c90248264a8b26970b4473770bcc3df8515fJosh Gao WSGI). 630a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 640a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* That the headers don't contain newlines or colons, end in _ or -, or 650a8c90248264a8b26970b4473770bcc3df8515fJosh Gao contain characters codes below 037. 660a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 670a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* That Content-Type is given if there is content (CGI often has a 680a8c90248264a8b26970b4473770bcc3df8515fJosh Gao default content type, but WSGI does not). 690a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 700a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* That no Content-Type is given when there is no content (@@: is this 710a8c90248264a8b26970b4473770bcc3df8515fJosh Gao too restrictive?) 720a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 730a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* That the exc_info argument to start_response is a tuple or None. 740a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 750a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* That all calls to the writer are with strings, and no other methods 760a8c90248264a8b26970b4473770bcc3df8515fJosh Gao on the writer are accessed. 770a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 780a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* That wsgi.input is used properly: 790a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 800a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - .read() is called with zero or one argument 810a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 820a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - That it returns a string 830a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 840a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - That readline, readlines, and __iter__ return strings 850a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 860a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - That .close() is not called 870a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 880a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - No other methods are provided 890a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 900a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* That wsgi.errors is used properly: 910a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 920a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - .write() and .writelines() is called with a string 930a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 940a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - That .close() is not called, and no other methods are provided. 950a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 960a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* The response iterator: 970a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 980a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - That it is not a string (it should be a list of a single string; a 990a8c90248264a8b26970b4473770bcc3df8515fJosh Gao string will work, but perform horribly). 1000a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1010a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - That .next() returns a string 1020a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1030a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - That the iterator is not iterated over until start_response has 1040a8c90248264a8b26970b4473770bcc3df8515fJosh Gao been called (that can signal either a server or application 1050a8c90248264a8b26970b4473770bcc3df8515fJosh Gao error). 1060a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1070a8c90248264a8b26970b4473770bcc3df8515fJosh Gao - That .close() is called (doesn't raise exception, only prints to 1080a8c90248264a8b26970b4473770bcc3df8515fJosh Gao sys.stderr, because we only know it isn't called when the object 1090a8c90248264a8b26970b4473770bcc3df8515fJosh Gao is garbage collected). 1100a8c90248264a8b26970b4473770bcc3df8515fJosh Gao""" 1110a8c90248264a8b26970b4473770bcc3df8515fJosh Gao__all__ = ['validator'] 1120a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1130a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1140a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoimport re 1150a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoimport sys 1160a8c90248264a8b26970b4473770bcc3df8515fJosh Gaofrom types import DictType, StringType, TupleType, ListType 1170a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoimport warnings 1180a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1190a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoheader_re = re.compile(r'^[a-zA-Z][a-zA-Z0-9\-_]*$') 1200a8c90248264a8b26970b4473770bcc3df8515fJosh Gaobad_header_value_re = re.compile(r'[\000-\037]') 1210a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1220a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoclass WSGIWarning(Warning): 1230a8c90248264a8b26970b4473770bcc3df8515fJosh Gao """ 1240a8c90248264a8b26970b4473770bcc3df8515fJosh Gao Raised in response to WSGI-spec-related warnings 1250a8c90248264a8b26970b4473770bcc3df8515fJosh Gao """ 1260a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1270a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef assert_(cond, *args): 1280a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if not cond: 1290a8c90248264a8b26970b4473770bcc3df8515fJosh Gao raise AssertionError(*args) 1300a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1310a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef validator(application): 1320a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1330a8c90248264a8b26970b4473770bcc3df8515fJosh Gao """ 1340a8c90248264a8b26970b4473770bcc3df8515fJosh Gao When applied between a WSGI server and a WSGI application, this 1350a8c90248264a8b26970b4473770bcc3df8515fJosh Gao middleware will check for WSGI compliancy on a number of levels. 1360a8c90248264a8b26970b4473770bcc3df8515fJosh Gao This middleware does not modify the request or response in any 1370a8c90248264a8b26970b4473770bcc3df8515fJosh Gao way, but will raise an AssertionError if anything seems off 1380a8c90248264a8b26970b4473770bcc3df8515fJosh Gao (except for a failure to close the application iterator, which 1390a8c90248264a8b26970b4473770bcc3df8515fJosh Gao will be printed to stderr -- there's no way to raise an exception 1400a8c90248264a8b26970b4473770bcc3df8515fJosh Gao at that point). 1410a8c90248264a8b26970b4473770bcc3df8515fJosh Gao """ 1420a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1430a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def lint_app(*args, **kw): 1440a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(len(args) == 2, "Two arguments required") 1450a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(not kw, "No keyword arguments allowed") 1460a8c90248264a8b26970b4473770bcc3df8515fJosh Gao environ, start_response = args 1470a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1480a8c90248264a8b26970b4473770bcc3df8515fJosh Gao check_environ(environ) 1490a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1500a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # We use this to check if the application returns without 1510a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # calling start_response: 1520a8c90248264a8b26970b4473770bcc3df8515fJosh Gao start_response_started = [] 1530a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1540a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def start_response_wrapper(*args, **kw): 1550a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(len(args) == 2 or len(args) == 3, ( 1560a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "Invalid number of arguments: %s" % (args,))) 1570a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(not kw, "No keyword arguments allowed") 1580a8c90248264a8b26970b4473770bcc3df8515fJosh Gao status = args[0] 1590a8c90248264a8b26970b4473770bcc3df8515fJosh Gao headers = args[1] 1600a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if len(args) == 3: 1610a8c90248264a8b26970b4473770bcc3df8515fJosh Gao exc_info = args[2] 1620a8c90248264a8b26970b4473770bcc3df8515fJosh Gao else: 1630a8c90248264a8b26970b4473770bcc3df8515fJosh Gao exc_info = None 1640a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1650a8c90248264a8b26970b4473770bcc3df8515fJosh Gao check_status(status) 1660a8c90248264a8b26970b4473770bcc3df8515fJosh Gao check_headers(headers) 1670a8c90248264a8b26970b4473770bcc3df8515fJosh Gao check_content_type(status, headers) 1680a8c90248264a8b26970b4473770bcc3df8515fJosh Gao check_exc_info(exc_info) 1690a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1700a8c90248264a8b26970b4473770bcc3df8515fJosh Gao start_response_started.append(None) 1710a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return WriteWrapper(start_response(*args)) 1720a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1730a8c90248264a8b26970b4473770bcc3df8515fJosh Gao environ['wsgi.input'] = InputWrapper(environ['wsgi.input']) 1740a8c90248264a8b26970b4473770bcc3df8515fJosh Gao environ['wsgi.errors'] = ErrorWrapper(environ['wsgi.errors']) 1750a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1760a8c90248264a8b26970b4473770bcc3df8515fJosh Gao iterator = application(environ, start_response_wrapper) 1770a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(iterator is not None and iterator != False, 1780a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "The application must return an iterator, if only an empty list") 1790a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1800a8c90248264a8b26970b4473770bcc3df8515fJosh Gao check_iterator(iterator) 1810a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1820a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return IteratorWrapper(iterator, start_response_started) 1830a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1840a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return lint_app 1850a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1860a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoclass InputWrapper: 1870a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1880a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def __init__(self, wsgi_input): 1890a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.input = wsgi_input 1900a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1910a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def read(self, *args): 1920a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(len(args) <= 1) 1930a8c90248264a8b26970b4473770bcc3df8515fJosh Gao v = self.input.read(*args) 1940a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(type(v) is type("")) 1950a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return v 1960a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1970a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def readline(self): 1980a8c90248264a8b26970b4473770bcc3df8515fJosh Gao v = self.input.readline() 1990a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(type(v) is type("")) 2000a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return v 2010a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2020a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def readlines(self, *args): 2030a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(len(args) <= 1) 2040a8c90248264a8b26970b4473770bcc3df8515fJosh Gao lines = self.input.readlines(*args) 2050a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(type(lines) is type([])) 2060a8c90248264a8b26970b4473770bcc3df8515fJosh Gao for line in lines: 2070a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(type(line) is type("")) 2080a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return lines 2090a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2100a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def __iter__(self): 2110a8c90248264a8b26970b4473770bcc3df8515fJosh Gao while 1: 2120a8c90248264a8b26970b4473770bcc3df8515fJosh Gao line = self.readline() 2130a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if not line: 2140a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return 2150a8c90248264a8b26970b4473770bcc3df8515fJosh Gao yield line 2160a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2170a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def close(self): 2180a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(0, "input.close() must not be called") 2190a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2200a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoclass ErrorWrapper: 2210a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2220a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def __init__(self, wsgi_errors): 2230a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.errors = wsgi_errors 2240a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2250a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def write(self, s): 2260a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(type(s) is type("")) 2270a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.errors.write(s) 2280a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2290a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def flush(self): 2300a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.errors.flush() 2310a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2320a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def writelines(self, seq): 2330a8c90248264a8b26970b4473770bcc3df8515fJosh Gao for line in seq: 2340a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.write(line) 2350a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2360a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def close(self): 2370a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(0, "errors.close() must not be called") 2380a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2390a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoclass WriteWrapper: 2400a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2410a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def __init__(self, wsgi_writer): 2420a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.writer = wsgi_writer 2430a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2440a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def __call__(self, s): 2450a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(type(s) is type("")) 2460a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.writer(s) 2470a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2480a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoclass PartialIteratorWrapper: 2490a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2500a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def __init__(self, wsgi_iterator): 2510a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.iterator = wsgi_iterator 2520a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2530a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def __iter__(self): 2540a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # We want to make sure __iter__ is called 2550a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return IteratorWrapper(self.iterator, None) 2560a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2570a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoclass IteratorWrapper: 2580a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2590a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def __init__(self, wsgi_iterator, check_start_response): 2600a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.original_iterator = wsgi_iterator 2610a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.iterator = iter(wsgi_iterator) 2620a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.closed = False 2630a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.check_start_response = check_start_response 2640a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2650a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def __iter__(self): 2660a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return self 2670a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2680a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def next(self): 2690a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(not self.closed, 2700a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "Iterator read after closed") 2710a8c90248264a8b26970b4473770bcc3df8515fJosh Gao v = self.iterator.next() 2720a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if self.check_start_response is not None: 2730a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(self.check_start_response, 2740a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "The application returns and we started iterating over its body, but start_response has not yet been called") 2750a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.check_start_response = None 2760a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return v 2770a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2780a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def close(self): 2790a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.closed = True 2800a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if hasattr(self.original_iterator, 'close'): 2810a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.original_iterator.close() 2820a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2830a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def __del__(self): 2840a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if not self.closed: 2850a8c90248264a8b26970b4473770bcc3df8515fJosh Gao sys.stderr.write( 2860a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "Iterator garbage collected without being closed") 2870a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(self.closed, 2880a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "Iterator garbage collected without being closed") 2890a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2900a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef check_environ(environ): 2910a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(type(environ) is DictType, 2920a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "Environment is not of the right type: %r (environment: %r)" 2930a8c90248264a8b26970b4473770bcc3df8515fJosh Gao % (type(environ), environ)) 2940a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2950a8c90248264a8b26970b4473770bcc3df8515fJosh Gao for key in ['REQUEST_METHOD', 'SERVER_NAME', 'SERVER_PORT', 2960a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'wsgi.version', 'wsgi.input', 'wsgi.errors', 2970a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'wsgi.multithread', 'wsgi.multiprocess', 2980a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'wsgi.run_once']: 2990a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(key in environ, 3000a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "Environment missing required key: %r" % (key,)) 3010a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 3020a8c90248264a8b26970b4473770bcc3df8515fJosh Gao for key in ['HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH']: 3030a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(key not in environ, 3040a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "Environment should not have the key: %s " 3050a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "(use %s instead)" % (key, key[5:])) 3060a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 3070a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if 'QUERY_STRING' not in environ: 3080a8c90248264a8b26970b4473770bcc3df8515fJosh Gao warnings.warn( 3090a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'QUERY_STRING is not in the WSGI environment; the cgi ' 3100a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'module will use sys.argv when this variable is missing, ' 3110a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'so application errors are more likely', 3120a8c90248264a8b26970b4473770bcc3df8515fJosh Gao WSGIWarning) 3130a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 3140a8c90248264a8b26970b4473770bcc3df8515fJosh Gao for key in environ.keys(): 3150a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if '.' in key: 3160a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # Extension, we don't care about its type 3170a8c90248264a8b26970b4473770bcc3df8515fJosh Gao continue 3180a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(type(environ[key]) is StringType, 3190a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "Environmental variable %s is not a string: %r (value: %r)" 3200a8c90248264a8b26970b4473770bcc3df8515fJosh Gao % (key, type(environ[key]), environ[key])) 3210a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 3220a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(type(environ['wsgi.version']) is TupleType, 3230a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "wsgi.version should be a tuple (%r)" % (environ['wsgi.version'],)) 3240a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(environ['wsgi.url_scheme'] in ('http', 'https'), 3250a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "wsgi.url_scheme unknown: %r" % environ['wsgi.url_scheme']) 3260a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 3270a8c90248264a8b26970b4473770bcc3df8515fJosh Gao check_input(environ['wsgi.input']) 3280a8c90248264a8b26970b4473770bcc3df8515fJosh Gao check_errors(environ['wsgi.errors']) 3290a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 3300a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # @@: these need filling out: 3310a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if environ['REQUEST_METHOD'] not in ( 3320a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'GET', 'HEAD', 'POST', 'OPTIONS','PUT','DELETE','TRACE'): 3330a8c90248264a8b26970b4473770bcc3df8515fJosh Gao warnings.warn( 3340a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "Unknown REQUEST_METHOD: %r" % environ['REQUEST_METHOD'], 3350a8c90248264a8b26970b4473770bcc3df8515fJosh Gao WSGIWarning) 3360a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 3370a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(not environ.get('SCRIPT_NAME') 3380a8c90248264a8b26970b4473770bcc3df8515fJosh Gao or environ['SCRIPT_NAME'].startswith('/'), 3390a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "SCRIPT_NAME doesn't start with /: %r" % environ['SCRIPT_NAME']) 3400a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(not environ.get('PATH_INFO') 3410a8c90248264a8b26970b4473770bcc3df8515fJosh Gao or environ['PATH_INFO'].startswith('/'), 3420a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "PATH_INFO doesn't start with /: %r" % environ['PATH_INFO']) 3430a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if environ.get('CONTENT_LENGTH'): 3440a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(int(environ['CONTENT_LENGTH']) >= 0, 3450a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "Invalid CONTENT_LENGTH: %r" % environ['CONTENT_LENGTH']) 3460a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 3470a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if not environ.get('SCRIPT_NAME'): 3480a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_('PATH_INFO' in environ, 3490a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "One of SCRIPT_NAME or PATH_INFO are required (PATH_INFO " 3500a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "should at least be '/' if SCRIPT_NAME is empty)") 3510a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(environ.get('SCRIPT_NAME') != '/', 3520a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "SCRIPT_NAME cannot be '/'; it should instead be '', and " 3530a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "PATH_INFO should be '/'") 3540a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 3550a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef check_input(wsgi_input): 3560a8c90248264a8b26970b4473770bcc3df8515fJosh Gao for attr in ['read', 'readline', 'readlines', '__iter__']: 3570a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(hasattr(wsgi_input, attr), 3580a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "wsgi.input (%r) doesn't have the attribute %s" 3590a8c90248264a8b26970b4473770bcc3df8515fJosh Gao % (wsgi_input, attr)) 3600a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 3610a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef check_errors(wsgi_errors): 3620a8c90248264a8b26970b4473770bcc3df8515fJosh Gao for attr in ['flush', 'write', 'writelines']: 3630a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(hasattr(wsgi_errors, attr), 3640a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "wsgi.errors (%r) doesn't have the attribute %s" 3650a8c90248264a8b26970b4473770bcc3df8515fJosh Gao % (wsgi_errors, attr)) 3660a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 3670a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef check_status(status): 3680a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(type(status) is StringType, 3690a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "Status must be a string (not %r)" % status) 3700a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # Implicitly check that we can turn it into an integer: 3710a8c90248264a8b26970b4473770bcc3df8515fJosh Gao status_code = status.split(None, 1)[0] 3720a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(len(status_code) == 3, 3730a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "Status codes must be three characters: %r" % status_code) 3740a8c90248264a8b26970b4473770bcc3df8515fJosh Gao status_int = int(status_code) 3750a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(status_int >= 100, "Status code is invalid: %r" % status_int) 3760a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if len(status) < 4 or status[3] != ' ': 3770a8c90248264a8b26970b4473770bcc3df8515fJosh Gao warnings.warn( 3780a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "The status string (%r) should be a three-digit integer " 3790a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "followed by a single space and a status explanation" 3800a8c90248264a8b26970b4473770bcc3df8515fJosh Gao % status, WSGIWarning) 3810a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 3820a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef check_headers(headers): 3830a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(type(headers) is ListType, 3840a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "Headers (%r) must be of type list: %r" 3850a8c90248264a8b26970b4473770bcc3df8515fJosh Gao % (headers, type(headers))) 3860a8c90248264a8b26970b4473770bcc3df8515fJosh Gao header_names = {} 3870a8c90248264a8b26970b4473770bcc3df8515fJosh Gao for item in headers: 3880a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(type(item) is TupleType, 3890a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "Individual headers (%r) must be of type tuple: %r" 3900a8c90248264a8b26970b4473770bcc3df8515fJosh Gao % (item, type(item))) 3910a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(len(item) == 2) 3920a8c90248264a8b26970b4473770bcc3df8515fJosh Gao name, value = item 3930a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(name.lower() != 'status', 3940a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "The Status header cannot be used; it conflicts with CGI " 3950a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "script, and HTTP status is not given through headers " 3960a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "(value: %r)." % value) 3970a8c90248264a8b26970b4473770bcc3df8515fJosh Gao header_names[name.lower()] = None 3980a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_('\n' not in name and ':' not in name, 3990a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "Header names may not contain ':' or '\\n': %r" % name) 4000a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(header_re.search(name), "Bad header name: %r" % name) 4010a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(not name.endswith('-') and not name.endswith('_'), 4020a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "Names may not end in '-' or '_': %r" % name) 4030a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if bad_header_value_re.search(value): 4040a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(0, "Bad header value: %r (bad char: %r)" 4050a8c90248264a8b26970b4473770bcc3df8515fJosh Gao % (value, bad_header_value_re.search(value).group(0))) 4060a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 4070a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef check_content_type(status, headers): 4080a8c90248264a8b26970b4473770bcc3df8515fJosh Gao code = int(status.split(None, 1)[0]) 4090a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # @@: need one more person to verify this interpretation of RFC 2616 4100a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 4110a8c90248264a8b26970b4473770bcc3df8515fJosh Gao NO_MESSAGE_BODY = (204, 304) 4120a8c90248264a8b26970b4473770bcc3df8515fJosh Gao for name, value in headers: 4130a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if name.lower() == 'content-type': 4140a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if code not in NO_MESSAGE_BODY: 4150a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return 4160a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(0, ("Content-Type header found in a %s response, " 4170a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "which must not return content.") % code) 4180a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if code not in NO_MESSAGE_BODY: 4190a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(0, "No Content-Type header found in headers (%s)" % headers) 4200a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 4210a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef check_exc_info(exc_info): 4220a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(exc_info is None or type(exc_info) is type(()), 4230a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "exc_info (%r) is not a tuple: %r" % (exc_info, type(exc_info))) 4240a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # More exc_info checks? 4250a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 4260a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef check_iterator(iterator): 4270a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # Technically a string is legal, which is why it's a really bad 4280a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # idea, because it may cause the response to be returned 4290a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # character-by-character 4300a8c90248264a8b26970b4473770bcc3df8515fJosh Gao assert_(not isinstance(iterator, str), 4310a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "You should not return a string as your application iterator, " 4320a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "instead return a single-item list containing that string.") 433