blob: e213d778f1759e5b007398756e2f300ed76f8246 [file] [log] [blame]
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001from __future__ import nested_scopes # Backward compat for 2.1
Guido van Rossumd8faa362007-04-27 19:54:29 +00002from unittest import TestCase
Thomas Wouters0e3f5912006-08-11 14:57:12 +00003from wsgiref.util import setup_testing_defaults
4from wsgiref.headers import Headers
5from wsgiref.handlers import BaseHandler, BaseCGIHandler
6from wsgiref import util
7from wsgiref.validate import validator
8from wsgiref.simple_server import WSGIServer, WSGIRequestHandler, demo_app
9from wsgiref.simple_server import make_server
Jeremy Hyltone6b59c52007-08-10 19:13:33 +000010from io import StringIO, BytesIO, BufferedReader
Alexandre Vassalottice261952008-05-12 02:31:37 +000011from socketserver import BaseServer
Senthil Kumarana5e0eaf2012-07-07 14:29:58 -070012from platform import python_implementation
13
Antoine Pitrou8f5731b2009-11-03 17:20:10 +000014import os
15import re
16import sys
Thomas Wouters0e3f5912006-08-11 14:57:12 +000017
Benjamin Petersonee8712c2008-05-20 21:35:26 +000018from test import support
Thomas Wouters0e3f5912006-08-11 14:57:12 +000019
20class MockServer(WSGIServer):
21 """Non-socket HTTP server"""
22
23 def __init__(self, server_address, RequestHandlerClass):
24 BaseServer.__init__(self, server_address, RequestHandlerClass)
25 self.server_bind()
26
27 def server_bind(self):
28 host, port = self.server_address
29 self.server_name = host
30 self.server_port = port
31 self.setup_environ()
32
33
34class MockHandler(WSGIRequestHandler):
35 """Non-socket HTTP handler"""
36 def setup(self):
37 self.connection = self.request
38 self.rfile, self.wfile = self.connection
39
40 def finish(self):
41 pass
42
43
Thomas Wouters0e3f5912006-08-11 14:57:12 +000044def hello_app(environ,start_response):
45 start_response("200 OK", [
46 ('Content-Type','text/plain'),
47 ('Date','Mon, 05 Jun 2006 18:49:54 GMT')
48 ])
Phillip J. Ebye1594222010-11-02 22:28:59 +000049 return [b"Hello, world!"]
Thomas Wouters0e3f5912006-08-11 14:57:12 +000050
Guido van Rossum6a10e022007-08-08 17:01:45 +000051def run_amock(app=hello_app, data=b"GET / HTTP/1.0\n\n"):
Thomas Wouters0e3f5912006-08-11 14:57:12 +000052 server = make_server("", 80, app, MockServer, MockHandler)
Jeremy Hyltone6b59c52007-08-10 19:13:33 +000053 inp = BufferedReader(BytesIO(data))
Antoine Pitrou38a66ad2009-01-03 18:41:49 +000054 out = BytesIO()
Jeremy Hyltone6b59c52007-08-10 19:13:33 +000055 olderr = sys.stderr
56 err = sys.stderr = StringIO()
Thomas Wouters0e3f5912006-08-11 14:57:12 +000057
58 try:
Jeremy Hyltone6b59c52007-08-10 19:13:33 +000059 server.finish_request((inp, out), ("127.0.0.1",8888))
Thomas Wouters0e3f5912006-08-11 14:57:12 +000060 finally:
61 sys.stderr = olderr
62
63 return out.getvalue(), err.getvalue()
64
Thomas Wouters0e3f5912006-08-11 14:57:12 +000065def compare_generic_iter(make_it,match):
66 """Utility to compare a generic 2.1/2.2+ iterator with an iterable
67
68 If running under Python 2.2+, this tests the iterator using iter()/next(),
69 as well as __getitem__. 'make_it' must be a function returning a fresh
70 iterator to be tested (since this may test the iterator twice)."""
71
72 it = make_it()
73 n = 0
74 for item in match:
75 if not it[n]==item: raise AssertionError
76 n+=1
77 try:
78 it[n]
79 except IndexError:
80 pass
81 else:
82 raise AssertionError("Too many items from __getitem__",it)
83
84 try:
85 iter, StopIteration
86 except NameError:
87 pass
88 else:
89 # Only test iter mode under 2.2+
90 it = make_it()
91 if not iter(it) is it: raise AssertionError
92 for item in match:
Georg Brandla18af4e2007-04-21 15:47:16 +000093 if not next(it) == item: raise AssertionError
Thomas Wouters0e3f5912006-08-11 14:57:12 +000094 try:
Georg Brandla18af4e2007-04-21 15:47:16 +000095 next(it)
Thomas Wouters0e3f5912006-08-11 14:57:12 +000096 except StopIteration:
97 pass
98 else:
Georg Brandla18af4e2007-04-21 15:47:16 +000099 raise AssertionError("Too many items from .__next__()", it)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000100
101
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000102class IntegrationTests(TestCase):
103
104 def check_hello(self, out, has_length=True):
Senthil Kumarana5e0eaf2012-07-07 14:29:58 -0700105 pyver = (python_implementation() + "/" +
106 sys.version.split()[0])
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000107 self.assertEqual(out,
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000108 ("HTTP/1.0 200 OK\r\n"
Senthil Kumarana5e0eaf2012-07-07 14:29:58 -0700109 "Server: WSGIServer/0.2 " + pyver +"\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000110 "Content-Type: text/plain\r\n"
111 "Date: Mon, 05 Jun 2006 18:49:54 GMT\r\n" +
112 (has_length and "Content-Length: 13\r\n" or "") +
113 "\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000114 "Hello, world!").encode("iso-8859-1")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000115 )
116
117 def test_plain_hello(self):
118 out, err = run_amock()
119 self.check_hello(out)
120
Senthil Kumarane025b522014-09-17 16:29:29 +0800121 def test_request_length(self):
122 out, err = run_amock(data=b"GET " + (b"x" * 65537) + b" HTTP/1.0\n\n")
123 self.assertEqual(out.splitlines()[0],
124 b"HTTP/1.0 414 Request-URI Too Long")
125
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000126 def test_validated_hello(self):
127 out, err = run_amock(validator(hello_app))
128 # the middleware doesn't support len(), so content-length isn't there
129 self.check_hello(out, has_length=False)
130
131 def test_simple_validation_error(self):
132 def bad_app(environ,start_response):
133 start_response("200 OK", ('Content-Type','text/plain'))
134 return ["Hello, world!"]
135 out, err = run_amock(validator(bad_app))
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000136 self.assertTrue(out.endswith(
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000137 b"A server error occurred. Please contact the administrator."
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000138 ))
139 self.assertEqual(
140 err.splitlines()[-2],
141 "AssertionError: Headers (('Content-Type', 'text/plain')) must"
Martin v. Löwis250ad612008-04-07 05:43:42 +0000142 " be of type list: <class 'tuple'>"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000143 )
144
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000145 def test_wsgi_input(self):
146 def bad_app(e,s):
147 e["wsgi.input"].read()
Phillip J. Ebye1594222010-11-02 22:28:59 +0000148 s("200 OK", [("Content-Type", "text/plain; charset=utf-8")])
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000149 return [b"data"]
150 out, err = run_amock(validator(bad_app))
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000151 self.assertTrue(out.endswith(
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000152 b"A server error occurred. Please contact the administrator."
153 ))
154 self.assertEqual(
155 err.splitlines()[-2], "AssertionError"
156 )
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000157
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000158 def test_bytes_validation(self):
159 def app(e, s):
Phillip J. Ebye1594222010-11-02 22:28:59 +0000160 s("200 OK", [
161 ("Content-Type", "text/plain; charset=utf-8"),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000162 ("Date", "Wed, 24 Dec 2008 13:29:32 GMT"),
163 ])
164 return [b"data"]
165 out, err = run_amock(validator(app))
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000166 self.assertTrue(err.endswith('"GET / HTTP/1.0" 200 4\n'))
Antoine Pitrou5f817412009-01-03 18:49:41 +0000167 ver = sys.version.split()[0].encode('ascii')
Senthil Kumarana5e0eaf2012-07-07 14:29:58 -0700168 py = python_implementation().encode('ascii')
169 pyver = py + b"/" + ver
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000170 self.assertEqual(
171 b"HTTP/1.0 200 OK\r\n"
Senthil Kumarana5e0eaf2012-07-07 14:29:58 -0700172 b"Server: WSGIServer/0.2 "+ pyver + b"\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000173 b"Content-Type: text/plain; charset=utf-8\r\n"
174 b"Date: Wed, 24 Dec 2008 13:29:32 GMT\r\n"
175 b"\r\n"
176 b"data",
177 out)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000178
179
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000180class UtilityTests(TestCase):
181
182 def checkShift(self,sn_in,pi_in,part,sn_out,pi_out):
183 env = {'SCRIPT_NAME':sn_in,'PATH_INFO':pi_in}
184 util.setup_testing_defaults(env)
185 self.assertEqual(util.shift_path_info(env),part)
186 self.assertEqual(env['PATH_INFO'],pi_out)
187 self.assertEqual(env['SCRIPT_NAME'],sn_out)
188 return env
189
190 def checkDefault(self, key, value, alt=None):
191 # Check defaulting when empty
192 env = {}
193 util.setup_testing_defaults(env)
Ezio Melottie9615932010-01-24 19:26:24 +0000194 if isinstance(value, StringIO):
195 self.assertIsInstance(env[key], StringIO)
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000196 elif isinstance(value,BytesIO):
Ezio Melottie9615932010-01-24 19:26:24 +0000197 self.assertIsInstance(env[key],BytesIO)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000198 else:
Ezio Melottie9615932010-01-24 19:26:24 +0000199 self.assertEqual(env[key], value)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000200
201 # Check existing value
202 env = {key:alt}
203 util.setup_testing_defaults(env)
Serhiy Storchaka25d8aea2014-02-08 14:50:08 +0200204 self.assertIs(env[key], alt)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000205
206 def checkCrossDefault(self,key,value,**kw):
207 util.setup_testing_defaults(kw)
208 self.assertEqual(kw[key],value)
209
210 def checkAppURI(self,uri,**kw):
211 util.setup_testing_defaults(kw)
212 self.assertEqual(util.application_uri(kw),uri)
213
214 def checkReqURI(self,uri,query=1,**kw):
215 util.setup_testing_defaults(kw)
216 self.assertEqual(util.request_uri(kw,query),uri)
217
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000218 def checkFW(self,text,size,match):
219
220 def make_it(text=text,size=size):
221 return util.FileWrapper(StringIO(text),size)
222
223 compare_generic_iter(make_it,match)
224
225 it = make_it()
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000226 self.assertFalse(it.filelike.closed)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000227
228 for item in it:
229 pass
230
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000231 self.assertFalse(it.filelike.closed)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000232
233 it.close()
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000234 self.assertTrue(it.filelike.closed)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000235
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000236 def testSimpleShifts(self):
237 self.checkShift('','/', '', '/', '')
238 self.checkShift('','/x', 'x', '/x', '')
239 self.checkShift('/','', None, '/', '')
240 self.checkShift('/a','/x/y', 'x', '/a/x', '/y')
241 self.checkShift('/a','/x/', 'x', '/a/x', '/')
242
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000243 def testNormalizedShifts(self):
244 self.checkShift('/a/b', '/../y', '..', '/a', '/y')
245 self.checkShift('', '/../y', '..', '', '/y')
246 self.checkShift('/a/b', '//y', 'y', '/a/b/y', '')
247 self.checkShift('/a/b', '//y/', 'y', '/a/b/y', '/')
248 self.checkShift('/a/b', '/./y', 'y', '/a/b/y', '')
249 self.checkShift('/a/b', '/./y/', 'y', '/a/b/y', '/')
250 self.checkShift('/a/b', '///./..//y/.//', '..', '/a', '/y/')
251 self.checkShift('/a/b', '///', '', '/a/b/', '')
252 self.checkShift('/a/b', '/.//', '', '/a/b/', '')
253 self.checkShift('/a/b', '/x//', 'x', '/a/b/x', '/')
254 self.checkShift('/a/b', '/.', None, '/a/b', '')
255
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000256 def testDefaults(self):
257 for key, value in [
258 ('SERVER_NAME','127.0.0.1'),
259 ('SERVER_PORT', '80'),
260 ('SERVER_PROTOCOL','HTTP/1.0'),
261 ('HTTP_HOST','127.0.0.1'),
262 ('REQUEST_METHOD','GET'),
263 ('SCRIPT_NAME',''),
264 ('PATH_INFO','/'),
265 ('wsgi.version', (1,0)),
266 ('wsgi.run_once', 0),
267 ('wsgi.multithread', 0),
268 ('wsgi.multiprocess', 0),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000269 ('wsgi.input', BytesIO()),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000270 ('wsgi.errors', StringIO()),
271 ('wsgi.url_scheme','http'),
272 ]:
273 self.checkDefault(key,value)
274
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000275 def testCrossDefaults(self):
276 self.checkCrossDefault('HTTP_HOST',"foo.bar",SERVER_NAME="foo.bar")
277 self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="on")
278 self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="1")
279 self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="yes")
280 self.checkCrossDefault('wsgi.url_scheme',"http",HTTPS="foo")
281 self.checkCrossDefault('SERVER_PORT',"80",HTTPS="foo")
282 self.checkCrossDefault('SERVER_PORT',"443",HTTPS="on")
283
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000284 def testGuessScheme(self):
285 self.assertEqual(util.guess_scheme({}), "http")
286 self.assertEqual(util.guess_scheme({'HTTPS':"foo"}), "http")
287 self.assertEqual(util.guess_scheme({'HTTPS':"on"}), "https")
288 self.assertEqual(util.guess_scheme({'HTTPS':"yes"}), "https")
289 self.assertEqual(util.guess_scheme({'HTTPS':"1"}), "https")
290
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000291 def testAppURIs(self):
292 self.checkAppURI("http://127.0.0.1/")
293 self.checkAppURI("http://127.0.0.1/spam", SCRIPT_NAME="/spam")
Serhiy Storchaka0abbe8c2014-01-12 12:08:11 +0200294 self.checkAppURI("http://127.0.0.1/sp%E4m", SCRIPT_NAME="/sp\xe4m")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000295 self.checkAppURI("http://spam.example.com:2071/",
296 HTTP_HOST="spam.example.com:2071", SERVER_PORT="2071")
297 self.checkAppURI("http://spam.example.com/",
298 SERVER_NAME="spam.example.com")
299 self.checkAppURI("http://127.0.0.1/",
300 HTTP_HOST="127.0.0.1", SERVER_NAME="spam.example.com")
301 self.checkAppURI("https://127.0.0.1/", HTTPS="on")
302 self.checkAppURI("http://127.0.0.1:8000/", SERVER_PORT="8000",
303 HTTP_HOST=None)
304
305 def testReqURIs(self):
306 self.checkReqURI("http://127.0.0.1/")
307 self.checkReqURI("http://127.0.0.1/spam", SCRIPT_NAME="/spam")
Serhiy Storchaka0abbe8c2014-01-12 12:08:11 +0200308 self.checkReqURI("http://127.0.0.1/sp%E4m", SCRIPT_NAME="/sp\xe4m")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000309 self.checkReqURI("http://127.0.0.1/spammity/spam",
310 SCRIPT_NAME="/spammity", PATH_INFO="/spam")
Serhiy Storchaka0abbe8c2014-01-12 12:08:11 +0200311 self.checkReqURI("http://127.0.0.1/spammity/sp%E4m",
312 SCRIPT_NAME="/spammity", PATH_INFO="/sp\xe4m")
Senthil Kumaran299fa4c2010-12-29 06:25:42 +0000313 self.checkReqURI("http://127.0.0.1/spammity/spam;ham",
314 SCRIPT_NAME="/spammity", PATH_INFO="/spam;ham")
315 self.checkReqURI("http://127.0.0.1/spammity/spam;cookie=1234,5678",
316 SCRIPT_NAME="/spammity", PATH_INFO="/spam;cookie=1234,5678")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000317 self.checkReqURI("http://127.0.0.1/spammity/spam?say=ni",
318 SCRIPT_NAME="/spammity", PATH_INFO="/spam",QUERY_STRING="say=ni")
Serhiy Storchaka0abbe8c2014-01-12 12:08:11 +0200319 self.checkReqURI("http://127.0.0.1/spammity/spam?s%E4y=ni",
320 SCRIPT_NAME="/spammity", PATH_INFO="/spam",QUERY_STRING="s%E4y=ni")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000321 self.checkReqURI("http://127.0.0.1/spammity/spam", 0,
322 SCRIPT_NAME="/spammity", PATH_INFO="/spam",QUERY_STRING="say=ni")
323
324 def testFileWrapper(self):
325 self.checkFW("xyz"*50, 120, ["xyz"*40,"xyz"*10])
326
327 def testHopByHop(self):
328 for hop in (
329 "Connection Keep-Alive Proxy-Authenticate Proxy-Authorization "
330 "TE Trailers Transfer-Encoding Upgrade"
331 ).split():
332 for alt in hop, hop.title(), hop.upper(), hop.lower():
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000333 self.assertTrue(util.is_hop_by_hop(alt))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000334
335 # Not comprehensive, just a few random header names
336 for hop in (
337 "Accept Cache-Control Date Pragma Trailer Via Warning"
338 ).split():
339 for alt in hop, hop.title(), hop.upper(), hop.lower():
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000340 self.assertFalse(util.is_hop_by_hop(alt))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000341
342class HeaderTests(TestCase):
343
344 def testMappingInterface(self):
345 test = [('x','y')]
346 self.assertEqual(len(Headers([])),0)
347 self.assertEqual(len(Headers(test[:])),1)
348 self.assertEqual(Headers(test[:]).keys(), ['x'])
349 self.assertEqual(Headers(test[:]).values(), ['y'])
350 self.assertEqual(Headers(test[:]).items(), test)
Serhiy Storchaka25d8aea2014-02-08 14:50:08 +0200351 self.assertIsNot(Headers(test).items(), test) # must be copy!
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000352
353 h=Headers([])
354 del h['foo'] # should not raise an error
355
356 h['Foo'] = 'bar'
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000357 for m in h.__contains__, h.get, h.get_all, h.__getitem__:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000358 self.assertTrue(m('foo'))
359 self.assertTrue(m('Foo'))
360 self.assertTrue(m('FOO'))
361 self.assertFalse(m('bar'))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000362
363 self.assertEqual(h['foo'],'bar')
364 h['foo'] = 'baz'
365 self.assertEqual(h['FOO'],'baz')
366 self.assertEqual(h.get_all('foo'),['baz'])
367
368 self.assertEqual(h.get("foo","whee"), "baz")
369 self.assertEqual(h.get("zoo","whee"), "whee")
370 self.assertEqual(h.setdefault("foo","whee"), "baz")
371 self.assertEqual(h.setdefault("zoo","whee"), "whee")
372 self.assertEqual(h["foo"],"baz")
373 self.assertEqual(h["zoo"],"whee")
374
375 def testRequireList(self):
376 self.assertRaises(TypeError, Headers, "foo")
377
378
379 def testExtras(self):
380 h = Headers([])
381 self.assertEqual(str(h),'\r\n')
382
383 h.add_header('foo','bar',baz="spam")
384 self.assertEqual(h['foo'], 'bar; baz="spam"')
385 self.assertEqual(str(h),'foo: bar; baz="spam"\r\n\r\n')
386
387 h.add_header('Foo','bar',cheese=None)
388 self.assertEqual(h.get_all('foo'),
389 ['bar; baz="spam"', 'bar; cheese'])
390
391 self.assertEqual(str(h),
392 'foo: bar; baz="spam"\r\n'
393 'Foo: bar; cheese\r\n'
394 '\r\n'
395 )
396
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000397class ErrorHandler(BaseCGIHandler):
398 """Simple handler subclass for testing BaseHandler"""
399
Antoine Pitrou8f5731b2009-11-03 17:20:10 +0000400 # BaseHandler records the OS environment at import time, but envvars
401 # might have been changed later by other tests, which trips up
402 # HandlerTests.testEnviron().
403 os_environ = dict(os.environ.items())
404
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000405 def __init__(self,**kw):
406 setup_testing_defaults(kw)
407 BaseCGIHandler.__init__(
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000408 self, BytesIO(), BytesIO(), StringIO(), kw,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000409 multithread=True, multiprocess=True
410 )
411
412class TestHandler(ErrorHandler):
413 """Simple handler subclass for testing BaseHandler, w/error passthru"""
414
415 def handle_error(self):
416 raise # for testing, we want to see what's happening
417
418
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000419class HandlerTests(TestCase):
420
421 def checkEnvironAttrs(self, handler):
422 env = handler.environ
423 for attr in [
424 'version','multithread','multiprocess','run_once','file_wrapper'
425 ]:
426 if attr=='file_wrapper' and handler.wsgi_file_wrapper is None:
427 continue
428 self.assertEqual(getattr(handler,'wsgi_'+attr),env['wsgi.'+attr])
429
430 def checkOSEnviron(self,handler):
431 empty = {}; setup_testing_defaults(empty)
432 env = handler.environ
433 from os import environ
434 for k,v in environ.items():
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000435 if k not in empty:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000436 self.assertEqual(env[k],v)
437 for k,v in empty.items():
Benjamin Peterson577473f2010-01-19 00:09:57 +0000438 self.assertIn(k, env)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000439
440 def testEnviron(self):
441 h = TestHandler(X="Y")
442 h.setup_environ()
443 self.checkEnvironAttrs(h)
444 self.checkOSEnviron(h)
445 self.assertEqual(h.environ["X"],"Y")
446
447 def testCGIEnviron(self):
448 h = BaseCGIHandler(None,None,None,{})
449 h.setup_environ()
450 for key in 'wsgi.url_scheme', 'wsgi.input', 'wsgi.errors':
Benjamin Peterson577473f2010-01-19 00:09:57 +0000451 self.assertIn(key, h.environ)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000452
453 def testScheme(self):
454 h=TestHandler(HTTPS="on"); h.setup_environ()
455 self.assertEqual(h.environ['wsgi.url_scheme'],'https')
456 h=TestHandler(); h.setup_environ()
457 self.assertEqual(h.environ['wsgi.url_scheme'],'http')
458
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000459 def testAbstractMethods(self):
460 h = BaseHandler()
461 for name in [
462 '_flush','get_stdin','get_stderr','add_cgi_vars'
463 ]:
464 self.assertRaises(NotImplementedError, getattr(h,name))
465 self.assertRaises(NotImplementedError, h._write, "test")
466
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000467 def testContentLength(self):
468 # Demo one reason iteration is better than write()... ;)
469
470 def trivial_app1(e,s):
471 s('200 OK',[])
Phillip J. Ebye1594222010-11-02 22:28:59 +0000472 return [e['wsgi.url_scheme'].encode('iso-8859-1')]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000473
474 def trivial_app2(e,s):
Phillip J. Ebye1594222010-11-02 22:28:59 +0000475 s('200 OK',[])(e['wsgi.url_scheme'].encode('iso-8859-1'))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000476 return []
477
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000478 def trivial_app3(e,s):
479 s('200 OK',[])
480 return ['\u0442\u0435\u0441\u0442'.encode("utf-8")]
481
Antoine Pitroub715fac2011-01-06 17:17:04 +0000482 def trivial_app4(e,s):
483 # Simulate a response to a HEAD request
484 s('200 OK',[('Content-Length', '12345')])
485 return []
486
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000487 h = TestHandler()
488 h.run(trivial_app1)
489 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000490 ("Status: 200 OK\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000491 "Content-Length: 4\r\n"
492 "\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000493 "http").encode("iso-8859-1"))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000494
495 h = TestHandler()
496 h.run(trivial_app2)
497 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000498 ("Status: 200 OK\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000499 "\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000500 "http").encode("iso-8859-1"))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000501
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000502 h = TestHandler()
503 h.run(trivial_app3)
504 self.assertEqual(h.stdout.getvalue(),
505 b'Status: 200 OK\r\n'
506 b'Content-Length: 8\r\n'
507 b'\r\n'
508 b'\xd1\x82\xd0\xb5\xd1\x81\xd1\x82')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000509
Antoine Pitroub715fac2011-01-06 17:17:04 +0000510 h = TestHandler()
511 h.run(trivial_app4)
512 self.assertEqual(h.stdout.getvalue(),
513 b'Status: 200 OK\r\n'
514 b'Content-Length: 12345\r\n'
515 b'\r\n')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000516
517 def testBasicErrorOutput(self):
518
519 def non_error_app(e,s):
520 s('200 OK',[])
521 return []
522
523 def error_app(e,s):
524 raise AssertionError("This should be caught by handler")
525
526 h = ErrorHandler()
527 h.run(non_error_app)
528 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000529 ("Status: 200 OK\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000530 "Content-Length: 0\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000531 "\r\n").encode("iso-8859-1"))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000532 self.assertEqual(h.stderr.getvalue(),"")
533
534 h = ErrorHandler()
535 h.run(error_app)
536 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000537 ("Status: %s\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000538 "Content-Type: text/plain\r\n"
539 "Content-Length: %d\r\n"
Phillip J. Ebye1594222010-11-02 22:28:59 +0000540 "\r\n" % (h.error_status,len(h.error_body))).encode('iso-8859-1')
541 + h.error_body)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000542
Benjamin Peterson577473f2010-01-19 00:09:57 +0000543 self.assertIn("AssertionError", h.stderr.getvalue())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000544
545 def testErrorAfterOutput(self):
Phillip J. Ebye1594222010-11-02 22:28:59 +0000546 MSG = b"Some output has been sent"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000547 def error_app(e,s):
548 s("200 OK",[])(MSG)
549 raise AssertionError("This should be caught by handler")
550
551 h = ErrorHandler()
552 h.run(error_app)
553 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000554 ("Status: 200 OK\r\n"
Phillip J. Ebye1594222010-11-02 22:28:59 +0000555 "\r\n".encode("iso-8859-1")+MSG))
Benjamin Peterson577473f2010-01-19 00:09:57 +0000556 self.assertIn("AssertionError", h.stderr.getvalue())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000557
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000558 def testHeaderFormats(self):
559
560 def non_error_app(e,s):
561 s('200 OK',[])
562 return []
563
564 stdpat = (
565 r"HTTP/%s 200 OK\r\n"
566 r"Date: \w{3}, [ 0123]\d \w{3} \d{4} \d\d:\d\d:\d\d GMT\r\n"
567 r"%s" r"Content-Length: 0\r\n" r"\r\n"
568 )
569 shortpat = (
570 "Status: 200 OK\r\n" "Content-Length: 0\r\n" "\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000571 ).encode("iso-8859-1")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000572
573 for ssw in "FooBar/1.0", None:
574 sw = ssw and "Server: %s\r\n" % ssw or ""
575
576 for version in "1.0", "1.1":
577 for proto in "HTTP/0.9", "HTTP/1.0", "HTTP/1.1":
578
579 h = TestHandler(SERVER_PROTOCOL=proto)
580 h.origin_server = False
581 h.http_version = version
582 h.server_software = ssw
583 h.run(non_error_app)
584 self.assertEqual(shortpat,h.stdout.getvalue())
585
586 h = TestHandler(SERVER_PROTOCOL=proto)
587 h.origin_server = True
588 h.http_version = version
589 h.server_software = ssw
590 h.run(non_error_app)
591 if proto=="HTTP/0.9":
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000592 self.assertEqual(h.stdout.getvalue(),b"")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000593 else:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000594 self.assertTrue(
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000595 re.match((stdpat%(version,sw)).encode("iso-8859-1"),
596 h.stdout.getvalue()),
597 ((stdpat%(version,sw)).encode("iso-8859-1"),
598 h.stdout.getvalue())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000599 )
600
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000601 def testBytesData(self):
602 def app(e, s):
Phillip J. Ebye1594222010-11-02 22:28:59 +0000603 s("200 OK", [
604 ("Content-Type", "text/plain; charset=utf-8"),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000605 ])
606 return [b"data"]
607
608 h = TestHandler()
609 h.run(app)
610 self.assertEqual(b"Status: 200 OK\r\n"
611 b"Content-Type: text/plain; charset=utf-8\r\n"
612 b"Content-Length: 4\r\n"
613 b"\r\n"
614 b"data",
615 h.stdout.getvalue())
616
Antoine Pitrouae247a52012-10-21 14:09:05 +0200617 def testCloseOnError(self):
618 side_effects = {'close_called': False}
619 MSG = b"Some output has been sent"
620 def error_app(e,s):
621 s("200 OK",[])(MSG)
622 class CrashyIterable(object):
623 def __iter__(self):
624 while True:
625 yield b'blah'
626 raise AssertionError("This should be caught by handler")
627 def close(self):
628 side_effects['close_called'] = True
629 return CrashyIterable()
630
631 h = ErrorHandler()
632 h.run(error_app)
633 self.assertEqual(side_effects['close_called'], True)
634
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000635
636def test_main():
Benjamin Petersonee8712c2008-05-20 21:35:26 +0000637 support.run_unittest(__name__)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000638
639if __name__ == "__main__":
640 test_main()