1ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
2ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
3ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh# Also licenced under the Apache License, 2.0: http://opensource.org/licenses/apache2.0.php
4ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh# Licensed to PSF under a Contributor Agreement
5ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh"""
6ffab958fd8d42ed7227d83007350e61555a1fa36Andrew HsiehMiddleware to check for obedience to the WSGI specification.
7ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
8ffab958fd8d42ed7227d83007350e61555a1fa36Andrew HsiehSome of the things this checks:
9ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
10ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh* Signature of the application and start_response (including that
11ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  keyword arguments are not used).
12ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
13ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh* Environment checks:
14ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
15ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - Environment is a dictionary (and not a subclass).
16ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
17ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - That all the required keys are in the environment: REQUEST_METHOD,
18ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    SERVER_NAME, SERVER_PORT, wsgi.version, wsgi.input, wsgi.errors,
19ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    wsgi.multithread, wsgi.multiprocess, wsgi.run_once
20ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
21ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - That HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH are not in the
22ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    environment (these headers should appear as CONTENT_LENGTH and
23ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    CONTENT_TYPE).
24ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
25ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - Warns if QUERY_STRING is missing, as the cgi module acts
26ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    unpredictably in that case.
27ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
28ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - That CGI-style variables (that don't contain a .) have
29ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    (non-unicode) string values
30ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
31ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - That wsgi.version is a tuple
32ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
33ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - That wsgi.url_scheme is 'http' or 'https' (@@: is this too
34ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    restrictive?)
35ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
36ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - Warns if the REQUEST_METHOD is not known (@@: probably too
37ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    restrictive).
38ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
39ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - That SCRIPT_NAME and PATH_INFO are empty or start with /
40ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
41ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - That at least one of SCRIPT_NAME or PATH_INFO are set.
42ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
43ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - That CONTENT_LENGTH is a positive integer.
44ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
45ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - That SCRIPT_NAME is not '/' (it should be '', and PATH_INFO should
46ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    be '/').
47ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
48ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - That wsgi.input has the methods read, readline, readlines, and
49ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    __iter__
50ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
51ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - That wsgi.errors has the methods flush, write, writelines
52ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
53ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh* The status is a string, contains a space, starts with an integer,
54ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  and that integer is in range (> 100).
55ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
56ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh* That the headers is a list (not a subclass, not another kind of
57ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  sequence).
58ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
59ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh* That the items of the headers are tuples of strings.
60ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
61ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh* That there is no 'status' header (that is used in CGI, but not in
62ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  WSGI).
63ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
64ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh* That the headers don't contain newlines or colons, end in _ or -, or
65ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  contain characters codes below 037.
66ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
67ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh* That Content-Type is given if there is content (CGI often has a
68ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  default content type, but WSGI does not).
69ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
70ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh* That no Content-Type is given when there is no content (@@: is this
71ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  too restrictive?)
72ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
73ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh* That the exc_info argument to start_response is a tuple or None.
74ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
75ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh* That all calls to the writer are with strings, and no other methods
76ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  on the writer are accessed.
77ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
78ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh* That wsgi.input is used properly:
79ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
80ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - .read() is called with zero or one argument
81ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
82ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - That it returns a string
83ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
84ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - That readline, readlines, and __iter__ return strings
85ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
86ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - That .close() is not called
87ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
88ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - No other methods are provided
89ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
90ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh* That wsgi.errors is used properly:
91ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
92ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - .write() and .writelines() is called with a string
93ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
94ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - That .close() is not called, and no other methods are provided.
95ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
96ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh* The response iterator:
97ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
98ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - That it is not a string (it should be a list of a single string; a
99ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    string will work, but perform horribly).
100ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
101ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - That .next() returns a string
102ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
103ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - That the iterator is not iterated over until start_response has
104ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    been called (that can signal either a server or application
105ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    error).
106ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
107ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh  - That .close() is called (doesn't raise exception, only prints to
108ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    sys.stderr, because we only know it isn't called when the object
109ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    is garbage collected).
110ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh"""
111ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh__all__ = ['validator']
112ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
113ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
114ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehimport re
115ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehimport sys
116ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehfrom types import DictType, StringType, TupleType, ListType
117ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehimport warnings
118ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
119ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehheader_re = re.compile(r'^[a-zA-Z][a-zA-Z0-9\-_]*$')
120ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehbad_header_value_re = re.compile(r'[\000-\037]')
121ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
122ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehclass WSGIWarning(Warning):
123ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    """
124ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    Raised in response to WSGI-spec-related warnings
125ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    """
126ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
127ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehdef assert_(cond, *args):
128ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    if not cond:
129ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        raise AssertionError(*args)
130ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
131ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehdef validator(application):
132ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
133ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    """
134ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    When applied between a WSGI server and a WSGI application, this
135ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    middleware will check for WSGI compliancy on a number of levels.
136ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    This middleware does not modify the request or response in any
137ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    way, but will raise an AssertionError if anything seems off
138ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    (except for a failure to close the application iterator, which
139ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    will be printed to stderr -- there's no way to raise an exception
140ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    at that point).
141ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    """
142ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
143ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def lint_app(*args, **kw):
144ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(len(args) == 2, "Two arguments required")
145ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(not kw, "No keyword arguments allowed")
146ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        environ, start_response = args
147ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
148ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        check_environ(environ)
149ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
150ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        # We use this to check if the application returns without
151ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        # calling start_response:
152ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        start_response_started = []
153ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
154ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        def start_response_wrapper(*args, **kw):
155ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            assert_(len(args) == 2 or len(args) == 3, (
156ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                "Invalid number of arguments: %s" % (args,)))
157ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            assert_(not kw, "No keyword arguments allowed")
158ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            status = args[0]
159ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            headers = args[1]
160ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            if len(args) == 3:
161ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                exc_info = args[2]
162ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            else:
163ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                exc_info = None
164ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
165ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            check_status(status)
166ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            check_headers(headers)
167ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            check_content_type(status, headers)
168ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            check_exc_info(exc_info)
169ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
170ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            start_response_started.append(None)
171ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            return WriteWrapper(start_response(*args))
172ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
173ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        environ['wsgi.input'] = InputWrapper(environ['wsgi.input'])
174ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        environ['wsgi.errors'] = ErrorWrapper(environ['wsgi.errors'])
175ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
176ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        iterator = application(environ, start_response_wrapper)
177ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(iterator is not None and iterator != False,
178ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "The application must return an iterator, if only an empty list")
179ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
180ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        check_iterator(iterator)
181ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
182ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        return IteratorWrapper(iterator, start_response_started)
183ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
184ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    return lint_app
185ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
186ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehclass InputWrapper:
187ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
188ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def __init__(self, wsgi_input):
189ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.input = wsgi_input
190ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
191ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def read(self, *args):
192ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(len(args) <= 1)
193ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        v = self.input.read(*args)
194ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(type(v) is type(""))
195ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        return v
196ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
197ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def readline(self):
198ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        v = self.input.readline()
199ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(type(v) is type(""))
200ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        return v
201ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
202ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def readlines(self, *args):
203ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(len(args) <= 1)
204ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        lines = self.input.readlines(*args)
205ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(type(lines) is type([]))
206ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        for line in lines:
207ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            assert_(type(line) is type(""))
208ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        return lines
209ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
210ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def __iter__(self):
211ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        while 1:
212ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            line = self.readline()
213ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            if not line:
214ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                return
215ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            yield line
216ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
217ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def close(self):
218ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(0, "input.close() must not be called")
219ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
220ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehclass ErrorWrapper:
221ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
222ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def __init__(self, wsgi_errors):
223ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.errors = wsgi_errors
224ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
225ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def write(self, s):
226ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(type(s) is type(""))
227ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.errors.write(s)
228ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
229ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def flush(self):
230ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.errors.flush()
231ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
232ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def writelines(self, seq):
233ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        for line in seq:
234ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            self.write(line)
235ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
236ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def close(self):
237ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(0, "errors.close() must not be called")
238ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
239ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehclass WriteWrapper:
240ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
241ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def __init__(self, wsgi_writer):
242ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.writer = wsgi_writer
243ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
244ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def __call__(self, s):
245ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(type(s) is type(""))
246ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.writer(s)
247ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
248ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehclass PartialIteratorWrapper:
249ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
250ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def __init__(self, wsgi_iterator):
251ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.iterator = wsgi_iterator
252ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
253ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def __iter__(self):
254ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        # We want to make sure __iter__ is called
255ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        return IteratorWrapper(self.iterator, None)
256ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
257ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehclass IteratorWrapper:
258ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
259ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def __init__(self, wsgi_iterator, check_start_response):
260ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.original_iterator = wsgi_iterator
261ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.iterator = iter(wsgi_iterator)
262ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.closed = False
263ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.check_start_response = check_start_response
264ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
265ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def __iter__(self):
266ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        return self
267ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
268ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def next(self):
269ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(not self.closed,
270ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "Iterator read after closed")
271ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        v = self.iterator.next()
272ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        if self.check_start_response is not None:
273ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            assert_(self.check_start_response,
274ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                "The application returns and we started iterating over its body, but start_response has not yet been called")
275ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            self.check_start_response = None
276ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        return v
277ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
278ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def close(self):
279ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        self.closed = True
280ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        if hasattr(self.original_iterator, 'close'):
281ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            self.original_iterator.close()
282ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
283ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    def __del__(self):
284ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        if not self.closed:
285ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            sys.stderr.write(
286ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                "Iterator garbage collected without being closed")
287ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(self.closed,
288ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "Iterator garbage collected without being closed")
289ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
290ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehdef check_environ(environ):
291ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    assert_(type(environ) is DictType,
292ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        "Environment is not of the right type: %r (environment: %r)"
293ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        % (type(environ), environ))
294ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
295ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    for key in ['REQUEST_METHOD', 'SERVER_NAME', 'SERVER_PORT',
296ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                'wsgi.version', 'wsgi.input', 'wsgi.errors',
297ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                'wsgi.multithread', 'wsgi.multiprocess',
298ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                'wsgi.run_once']:
299ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(key in environ,
300ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "Environment missing required key: %r" % (key,))
301ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
302ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    for key in ['HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH']:
303ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(key not in environ,
304ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "Environment should not have the key: %s "
305ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "(use %s instead)" % (key, key[5:]))
306ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
307ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    if 'QUERY_STRING' not in environ:
308ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        warnings.warn(
309ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            'QUERY_STRING is not in the WSGI environment; the cgi '
310ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            'module will use sys.argv when this variable is missing, '
311ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            'so application errors are more likely',
312ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            WSGIWarning)
313ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
314ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    for key in environ.keys():
315ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        if '.' in key:
316ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            # Extension, we don't care about its type
317ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            continue
318ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(type(environ[key]) is StringType,
319ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "Environmental variable %s is not a string: %r (value: %r)"
320ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            % (key, type(environ[key]), environ[key]))
321ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
322ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    assert_(type(environ['wsgi.version']) is TupleType,
323ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        "wsgi.version should be a tuple (%r)" % (environ['wsgi.version'],))
324ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    assert_(environ['wsgi.url_scheme'] in ('http', 'https'),
325ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        "wsgi.url_scheme unknown: %r" % environ['wsgi.url_scheme'])
326ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
327ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    check_input(environ['wsgi.input'])
328ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    check_errors(environ['wsgi.errors'])
329ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
330ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    # @@: these need filling out:
331ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    if environ['REQUEST_METHOD'] not in (
332ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        'GET', 'HEAD', 'POST', 'OPTIONS','PUT','DELETE','TRACE'):
333ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        warnings.warn(
334ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "Unknown REQUEST_METHOD: %r" % environ['REQUEST_METHOD'],
335ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            WSGIWarning)
336ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
337ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    assert_(not environ.get('SCRIPT_NAME')
338ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            or environ['SCRIPT_NAME'].startswith('/'),
339ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        "SCRIPT_NAME doesn't start with /: %r" % environ['SCRIPT_NAME'])
340ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    assert_(not environ.get('PATH_INFO')
341ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            or environ['PATH_INFO'].startswith('/'),
342ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        "PATH_INFO doesn't start with /: %r" % environ['PATH_INFO'])
343ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    if environ.get('CONTENT_LENGTH'):
344ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(int(environ['CONTENT_LENGTH']) >= 0,
345ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "Invalid CONTENT_LENGTH: %r" % environ['CONTENT_LENGTH'])
346ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
347ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    if not environ.get('SCRIPT_NAME'):
348ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_('PATH_INFO' in environ,
349ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "One of SCRIPT_NAME or PATH_INFO are required (PATH_INFO "
350ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "should at least be '/' if SCRIPT_NAME is empty)")
351ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    assert_(environ.get('SCRIPT_NAME') != '/',
352ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        "SCRIPT_NAME cannot be '/'; it should instead be '', and "
353ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        "PATH_INFO should be '/'")
354ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
355ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehdef check_input(wsgi_input):
356ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    for attr in ['read', 'readline', 'readlines', '__iter__']:
357ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(hasattr(wsgi_input, attr),
358ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "wsgi.input (%r) doesn't have the attribute %s"
359ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            % (wsgi_input, attr))
360ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
361ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehdef check_errors(wsgi_errors):
362ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    for attr in ['flush', 'write', 'writelines']:
363ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(hasattr(wsgi_errors, attr),
364ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "wsgi.errors (%r) doesn't have the attribute %s"
365ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            % (wsgi_errors, attr))
366ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
367ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehdef check_status(status):
368ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    assert_(type(status) is StringType,
369ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        "Status must be a string (not %r)" % status)
370ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    # Implicitly check that we can turn it into an integer:
371ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    status_code = status.split(None, 1)[0]
372ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    assert_(len(status_code) == 3,
373ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        "Status codes must be three characters: %r" % status_code)
374ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    status_int = int(status_code)
375ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    assert_(status_int >= 100, "Status code is invalid: %r" % status_int)
376ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    if len(status) < 4 or status[3] != ' ':
377ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        warnings.warn(
378ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "The status string (%r) should be a three-digit integer "
379ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "followed by a single space and a status explanation"
380ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            % status, WSGIWarning)
381ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
382ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehdef check_headers(headers):
383ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    assert_(type(headers) is ListType,
384ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        "Headers (%r) must be of type list: %r"
385ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        % (headers, type(headers)))
386ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    header_names = {}
387ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    for item in headers:
388ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(type(item) is TupleType,
389ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "Individual headers (%r) must be of type tuple: %r"
390ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            % (item, type(item)))
391ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(len(item) == 2)
392ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        name, value = item
393ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(name.lower() != 'status',
394ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "The Status header cannot be used; it conflicts with CGI "
395ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "script, and HTTP status is not given through headers "
396ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "(value: %r)." % value)
397ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        header_names[name.lower()] = None
398ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_('\n' not in name and ':' not in name,
399ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "Header names may not contain ':' or '\\n': %r" % name)
400ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(header_re.search(name), "Bad header name: %r" % name)
401ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(not name.endswith('-') and not name.endswith('_'),
402ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            "Names may not end in '-' or '_': %r" % name)
403ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        if bad_header_value_re.search(value):
404ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            assert_(0, "Bad header value: %r (bad char: %r)"
405ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            % (value, bad_header_value_re.search(value).group(0)))
406ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
407ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehdef check_content_type(status, headers):
408ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    code = int(status.split(None, 1)[0])
409ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    # @@: need one more person to verify this interpretation of RFC 2616
410ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    #     http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
411ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    NO_MESSAGE_BODY = (204, 304)
412ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    for name, value in headers:
413ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        if name.lower() == 'content-type':
414ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            if code not in NO_MESSAGE_BODY:
415ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                return
416ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            assert_(0, ("Content-Type header found in a %s response, "
417ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                        "which must not return content.") % code)
418ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    if code not in NO_MESSAGE_BODY:
419ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        assert_(0, "No Content-Type header found in headers (%s)" % headers)
420ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
421ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehdef check_exc_info(exc_info):
422ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    assert_(exc_info is None or type(exc_info) is type(()),
423ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        "exc_info (%r) is not a tuple: %r" % (exc_info, type(exc_info)))
424ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    # More exc_info checks?
425ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
426ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehdef check_iterator(iterator):
427ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    # Technically a string is legal, which is why it's a really bad
428ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    # idea, because it may cause the response to be returned
429ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    # character-by-character
430ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    assert_(not isinstance(iterator, str),
431ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        "You should not return a string as your application iterator, "
432ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        "instead return a single-item list containing that string.")
433