blob: 08f8d9a604384e317e52e3dffa49d95d6ab889eb [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
Antoine Pitrou8f5731b2009-11-03 17:20:10 +000012import os
13import re
14import sys
Thomas Wouters0e3f5912006-08-11 14:57:12 +000015
Benjamin Petersonee8712c2008-05-20 21:35:26 +000016from test import support
Thomas Wouters0e3f5912006-08-11 14:57:12 +000017
18class MockServer(WSGIServer):
19 """Non-socket HTTP server"""
20
21 def __init__(self, server_address, RequestHandlerClass):
22 BaseServer.__init__(self, server_address, RequestHandlerClass)
23 self.server_bind()
24
25 def server_bind(self):
26 host, port = self.server_address
27 self.server_name = host
28 self.server_port = port
29 self.setup_environ()
30
31
32class MockHandler(WSGIRequestHandler):
33 """Non-socket HTTP handler"""
34 def setup(self):
35 self.connection = self.request
36 self.rfile, self.wfile = self.connection
37
38 def finish(self):
39 pass
40
41
Thomas Wouters0e3f5912006-08-11 14:57:12 +000042def hello_app(environ,start_response):
43 start_response("200 OK", [
44 ('Content-Type','text/plain'),
45 ('Date','Mon, 05 Jun 2006 18:49:54 GMT')
46 ])
Phillip J. Ebye1594222010-11-02 22:28:59 +000047 return [b"Hello, world!"]
Thomas Wouters0e3f5912006-08-11 14:57:12 +000048
Guido van Rossum6a10e022007-08-08 17:01:45 +000049def run_amock(app=hello_app, data=b"GET / HTTP/1.0\n\n"):
Thomas Wouters0e3f5912006-08-11 14:57:12 +000050 server = make_server("", 80, app, MockServer, MockHandler)
Jeremy Hyltone6b59c52007-08-10 19:13:33 +000051 inp = BufferedReader(BytesIO(data))
Antoine Pitrou38a66ad2009-01-03 18:41:49 +000052 out = BytesIO()
Jeremy Hyltone6b59c52007-08-10 19:13:33 +000053 olderr = sys.stderr
54 err = sys.stderr = StringIO()
Thomas Wouters0e3f5912006-08-11 14:57:12 +000055
56 try:
Jeremy Hyltone6b59c52007-08-10 19:13:33 +000057 server.finish_request((inp, out), ("127.0.0.1",8888))
Thomas Wouters0e3f5912006-08-11 14:57:12 +000058 finally:
59 sys.stderr = olderr
60
61 return out.getvalue(), err.getvalue()
62
Thomas Wouters0e3f5912006-08-11 14:57:12 +000063def compare_generic_iter(make_it,match):
64 """Utility to compare a generic 2.1/2.2+ iterator with an iterable
65
66 If running under Python 2.2+, this tests the iterator using iter()/next(),
67 as well as __getitem__. 'make_it' must be a function returning a fresh
68 iterator to be tested (since this may test the iterator twice)."""
69
70 it = make_it()
71 n = 0
72 for item in match:
73 if not it[n]==item: raise AssertionError
74 n+=1
75 try:
76 it[n]
77 except IndexError:
78 pass
79 else:
80 raise AssertionError("Too many items from __getitem__",it)
81
82 try:
83 iter, StopIteration
84 except NameError:
85 pass
86 else:
87 # Only test iter mode under 2.2+
88 it = make_it()
89 if not iter(it) is it: raise AssertionError
90 for item in match:
Georg Brandla18af4e2007-04-21 15:47:16 +000091 if not next(it) == item: raise AssertionError
Thomas Wouters0e3f5912006-08-11 14:57:12 +000092 try:
Georg Brandla18af4e2007-04-21 15:47:16 +000093 next(it)
Thomas Wouters0e3f5912006-08-11 14:57:12 +000094 except StopIteration:
95 pass
96 else:
Georg Brandla18af4e2007-04-21 15:47:16 +000097 raise AssertionError("Too many items from .__next__()", it)
Thomas Wouters0e3f5912006-08-11 14:57:12 +000098
99
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000100class IntegrationTests(TestCase):
101
102 def check_hello(self, out, has_length=True):
103 self.assertEqual(out,
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000104 ("HTTP/1.0 200 OK\r\n"
Phillip J. Ebyb6d4a8e2010-11-03 22:39:01 +0000105 "Server: WSGIServer/0.2 Python/"+sys.version.split()[0]+"\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000106 "Content-Type: text/plain\r\n"
107 "Date: Mon, 05 Jun 2006 18:49:54 GMT\r\n" +
108 (has_length and "Content-Length: 13\r\n" or "") +
109 "\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000110 "Hello, world!").encode("iso-8859-1")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000111 )
112
113 def test_plain_hello(self):
114 out, err = run_amock()
115 self.check_hello(out)
116
117 def test_validated_hello(self):
118 out, err = run_amock(validator(hello_app))
119 # the middleware doesn't support len(), so content-length isn't there
120 self.check_hello(out, has_length=False)
121
122 def test_simple_validation_error(self):
123 def bad_app(environ,start_response):
124 start_response("200 OK", ('Content-Type','text/plain'))
125 return ["Hello, world!"]
126 out, err = run_amock(validator(bad_app))
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000127 self.assertTrue(out.endswith(
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000128 b"A server error occurred. Please contact the administrator."
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000129 ))
130 self.assertEqual(
131 err.splitlines()[-2],
132 "AssertionError: Headers (('Content-Type', 'text/plain')) must"
Martin v. Löwis250ad612008-04-07 05:43:42 +0000133 " be of type list: <class 'tuple'>"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000134 )
135
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000136 def test_wsgi_input(self):
137 def bad_app(e,s):
138 e["wsgi.input"].read()
Phillip J. Ebye1594222010-11-02 22:28:59 +0000139 s("200 OK", [("Content-Type", "text/plain; charset=utf-8")])
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000140 return [b"data"]
141 out, err = run_amock(validator(bad_app))
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000142 self.assertTrue(out.endswith(
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000143 b"A server error occurred. Please contact the administrator."
144 ))
145 self.assertEqual(
146 err.splitlines()[-2], "AssertionError"
147 )
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000148
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000149 def test_bytes_validation(self):
150 def app(e, s):
Phillip J. Ebye1594222010-11-02 22:28:59 +0000151 s("200 OK", [
152 ("Content-Type", "text/plain; charset=utf-8"),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000153 ("Date", "Wed, 24 Dec 2008 13:29:32 GMT"),
154 ])
155 return [b"data"]
156 out, err = run_amock(validator(app))
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000157 self.assertTrue(err.endswith('"GET / HTTP/1.0" 200 4\n'))
Antoine Pitrou5f817412009-01-03 18:49:41 +0000158 ver = sys.version.split()[0].encode('ascii')
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000159 self.assertEqual(
160 b"HTTP/1.0 200 OK\r\n"
Phillip J. Ebyb6d4a8e2010-11-03 22:39:01 +0000161 b"Server: WSGIServer/0.2 Python/" + ver + b"\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000162 b"Content-Type: text/plain; charset=utf-8\r\n"
163 b"Date: Wed, 24 Dec 2008 13:29:32 GMT\r\n"
164 b"\r\n"
165 b"data",
166 out)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000167
168
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000169class UtilityTests(TestCase):
170
171 def checkShift(self,sn_in,pi_in,part,sn_out,pi_out):
172 env = {'SCRIPT_NAME':sn_in,'PATH_INFO':pi_in}
173 util.setup_testing_defaults(env)
174 self.assertEqual(util.shift_path_info(env),part)
175 self.assertEqual(env['PATH_INFO'],pi_out)
176 self.assertEqual(env['SCRIPT_NAME'],sn_out)
177 return env
178
179 def checkDefault(self, key, value, alt=None):
180 # Check defaulting when empty
181 env = {}
182 util.setup_testing_defaults(env)
Ezio Melottie9615932010-01-24 19:26:24 +0000183 if isinstance(value, StringIO):
184 self.assertIsInstance(env[key], StringIO)
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000185 elif isinstance(value,BytesIO):
Ezio Melottie9615932010-01-24 19:26:24 +0000186 self.assertIsInstance(env[key],BytesIO)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000187 else:
Ezio Melottie9615932010-01-24 19:26:24 +0000188 self.assertEqual(env[key], value)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000189
190 # Check existing value
191 env = {key:alt}
192 util.setup_testing_defaults(env)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000193 self.assertTrue(env[key] is alt)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000194
195 def checkCrossDefault(self,key,value,**kw):
196 util.setup_testing_defaults(kw)
197 self.assertEqual(kw[key],value)
198
199 def checkAppURI(self,uri,**kw):
200 util.setup_testing_defaults(kw)
201 self.assertEqual(util.application_uri(kw),uri)
202
203 def checkReqURI(self,uri,query=1,**kw):
204 util.setup_testing_defaults(kw)
205 self.assertEqual(util.request_uri(kw,query),uri)
206
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000207 def checkFW(self,text,size,match):
208
209 def make_it(text=text,size=size):
210 return util.FileWrapper(StringIO(text),size)
211
212 compare_generic_iter(make_it,match)
213
214 it = make_it()
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000215 self.assertFalse(it.filelike.closed)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000216
217 for item in it:
218 pass
219
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000220 self.assertFalse(it.filelike.closed)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000221
222 it.close()
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000223 self.assertTrue(it.filelike.closed)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000224
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000225 def testSimpleShifts(self):
226 self.checkShift('','/', '', '/', '')
227 self.checkShift('','/x', 'x', '/x', '')
228 self.checkShift('/','', None, '/', '')
229 self.checkShift('/a','/x/y', 'x', '/a/x', '/y')
230 self.checkShift('/a','/x/', 'x', '/a/x', '/')
231
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000232 def testNormalizedShifts(self):
233 self.checkShift('/a/b', '/../y', '..', '/a', '/y')
234 self.checkShift('', '/../y', '..', '', '/y')
235 self.checkShift('/a/b', '//y', 'y', '/a/b/y', '')
236 self.checkShift('/a/b', '//y/', 'y', '/a/b/y', '/')
237 self.checkShift('/a/b', '/./y', 'y', '/a/b/y', '')
238 self.checkShift('/a/b', '/./y/', 'y', '/a/b/y', '/')
239 self.checkShift('/a/b', '///./..//y/.//', '..', '/a', '/y/')
240 self.checkShift('/a/b', '///', '', '/a/b/', '')
241 self.checkShift('/a/b', '/.//', '', '/a/b/', '')
242 self.checkShift('/a/b', '/x//', 'x', '/a/b/x', '/')
243 self.checkShift('/a/b', '/.', None, '/a/b', '')
244
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000245 def testDefaults(self):
246 for key, value in [
247 ('SERVER_NAME','127.0.0.1'),
248 ('SERVER_PORT', '80'),
249 ('SERVER_PROTOCOL','HTTP/1.0'),
250 ('HTTP_HOST','127.0.0.1'),
251 ('REQUEST_METHOD','GET'),
252 ('SCRIPT_NAME',''),
253 ('PATH_INFO','/'),
254 ('wsgi.version', (1,0)),
255 ('wsgi.run_once', 0),
256 ('wsgi.multithread', 0),
257 ('wsgi.multiprocess', 0),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000258 ('wsgi.input', BytesIO()),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000259 ('wsgi.errors', StringIO()),
260 ('wsgi.url_scheme','http'),
261 ]:
262 self.checkDefault(key,value)
263
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000264 def testCrossDefaults(self):
265 self.checkCrossDefault('HTTP_HOST',"foo.bar",SERVER_NAME="foo.bar")
266 self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="on")
267 self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="1")
268 self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="yes")
269 self.checkCrossDefault('wsgi.url_scheme',"http",HTTPS="foo")
270 self.checkCrossDefault('SERVER_PORT',"80",HTTPS="foo")
271 self.checkCrossDefault('SERVER_PORT',"443",HTTPS="on")
272
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000273 def testGuessScheme(self):
274 self.assertEqual(util.guess_scheme({}), "http")
275 self.assertEqual(util.guess_scheme({'HTTPS':"foo"}), "http")
276 self.assertEqual(util.guess_scheme({'HTTPS':"on"}), "https")
277 self.assertEqual(util.guess_scheme({'HTTPS':"yes"}), "https")
278 self.assertEqual(util.guess_scheme({'HTTPS':"1"}), "https")
279
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000280 def testAppURIs(self):
281 self.checkAppURI("http://127.0.0.1/")
282 self.checkAppURI("http://127.0.0.1/spam", SCRIPT_NAME="/spam")
Guido van Rossum52dbbb92008-08-18 21:44:30 +0000283 self.checkAppURI("http://127.0.0.1/sp%C3%A4m", SCRIPT_NAME="/späm")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000284 self.checkAppURI("http://spam.example.com:2071/",
285 HTTP_HOST="spam.example.com:2071", SERVER_PORT="2071")
286 self.checkAppURI("http://spam.example.com/",
287 SERVER_NAME="spam.example.com")
288 self.checkAppURI("http://127.0.0.1/",
289 HTTP_HOST="127.0.0.1", SERVER_NAME="spam.example.com")
290 self.checkAppURI("https://127.0.0.1/", HTTPS="on")
291 self.checkAppURI("http://127.0.0.1:8000/", SERVER_PORT="8000",
292 HTTP_HOST=None)
293
294 def testReqURIs(self):
295 self.checkReqURI("http://127.0.0.1/")
296 self.checkReqURI("http://127.0.0.1/spam", SCRIPT_NAME="/spam")
Guido van Rossum52dbbb92008-08-18 21:44:30 +0000297 self.checkReqURI("http://127.0.0.1/sp%C3%A4m", SCRIPT_NAME="/späm")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000298 self.checkReqURI("http://127.0.0.1/spammity/spam",
299 SCRIPT_NAME="/spammity", PATH_INFO="/spam")
Senthil Kumaran299fa4c2010-12-29 06:25:42 +0000300 self.checkReqURI("http://127.0.0.1/spammity/spam;ham",
301 SCRIPT_NAME="/spammity", PATH_INFO="/spam;ham")
302 self.checkReqURI("http://127.0.0.1/spammity/spam;cookie=1234,5678",
303 SCRIPT_NAME="/spammity", PATH_INFO="/spam;cookie=1234,5678")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000304 self.checkReqURI("http://127.0.0.1/spammity/spam?say=ni",
305 SCRIPT_NAME="/spammity", PATH_INFO="/spam",QUERY_STRING="say=ni")
306 self.checkReqURI("http://127.0.0.1/spammity/spam", 0,
307 SCRIPT_NAME="/spammity", PATH_INFO="/spam",QUERY_STRING="say=ni")
308
309 def testFileWrapper(self):
310 self.checkFW("xyz"*50, 120, ["xyz"*40,"xyz"*10])
311
312 def testHopByHop(self):
313 for hop in (
314 "Connection Keep-Alive Proxy-Authenticate Proxy-Authorization "
315 "TE Trailers Transfer-Encoding Upgrade"
316 ).split():
317 for alt in hop, hop.title(), hop.upper(), hop.lower():
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000318 self.assertTrue(util.is_hop_by_hop(alt))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000319
320 # Not comprehensive, just a few random header names
321 for hop in (
322 "Accept Cache-Control Date Pragma Trailer Via Warning"
323 ).split():
324 for alt in hop, hop.title(), hop.upper(), hop.lower():
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000325 self.assertFalse(util.is_hop_by_hop(alt))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000326
327class HeaderTests(TestCase):
328
329 def testMappingInterface(self):
330 test = [('x','y')]
331 self.assertEqual(len(Headers([])),0)
332 self.assertEqual(len(Headers(test[:])),1)
333 self.assertEqual(Headers(test[:]).keys(), ['x'])
334 self.assertEqual(Headers(test[:]).values(), ['y'])
335 self.assertEqual(Headers(test[:]).items(), test)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000336 self.assertFalse(Headers(test).items() is test) # must be copy!
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000337
338 h=Headers([])
339 del h['foo'] # should not raise an error
340
341 h['Foo'] = 'bar'
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000342 for m in h.__contains__, h.get, h.get_all, h.__getitem__:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000343 self.assertTrue(m('foo'))
344 self.assertTrue(m('Foo'))
345 self.assertTrue(m('FOO'))
346 self.assertFalse(m('bar'))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000347
348 self.assertEqual(h['foo'],'bar')
349 h['foo'] = 'baz'
350 self.assertEqual(h['FOO'],'baz')
351 self.assertEqual(h.get_all('foo'),['baz'])
352
353 self.assertEqual(h.get("foo","whee"), "baz")
354 self.assertEqual(h.get("zoo","whee"), "whee")
355 self.assertEqual(h.setdefault("foo","whee"), "baz")
356 self.assertEqual(h.setdefault("zoo","whee"), "whee")
357 self.assertEqual(h["foo"],"baz")
358 self.assertEqual(h["zoo"],"whee")
359
360 def testRequireList(self):
361 self.assertRaises(TypeError, Headers, "foo")
362
363
364 def testExtras(self):
365 h = Headers([])
366 self.assertEqual(str(h),'\r\n')
367
368 h.add_header('foo','bar',baz="spam")
369 self.assertEqual(h['foo'], 'bar; baz="spam"')
370 self.assertEqual(str(h),'foo: bar; baz="spam"\r\n\r\n')
371
372 h.add_header('Foo','bar',cheese=None)
373 self.assertEqual(h.get_all('foo'),
374 ['bar; baz="spam"', 'bar; cheese'])
375
376 self.assertEqual(str(h),
377 'foo: bar; baz="spam"\r\n'
378 'Foo: bar; cheese\r\n'
379 '\r\n'
380 )
381
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000382class ErrorHandler(BaseCGIHandler):
383 """Simple handler subclass for testing BaseHandler"""
384
Antoine Pitrou8f5731b2009-11-03 17:20:10 +0000385 # BaseHandler records the OS environment at import time, but envvars
386 # might have been changed later by other tests, which trips up
387 # HandlerTests.testEnviron().
388 os_environ = dict(os.environ.items())
389
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000390 def __init__(self,**kw):
391 setup_testing_defaults(kw)
392 BaseCGIHandler.__init__(
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000393 self, BytesIO(), BytesIO(), StringIO(), kw,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000394 multithread=True, multiprocess=True
395 )
396
397class TestHandler(ErrorHandler):
398 """Simple handler subclass for testing BaseHandler, w/error passthru"""
399
400 def handle_error(self):
401 raise # for testing, we want to see what's happening
402
403
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000404class HandlerTests(TestCase):
405
406 def checkEnvironAttrs(self, handler):
407 env = handler.environ
408 for attr in [
409 'version','multithread','multiprocess','run_once','file_wrapper'
410 ]:
411 if attr=='file_wrapper' and handler.wsgi_file_wrapper is None:
412 continue
413 self.assertEqual(getattr(handler,'wsgi_'+attr),env['wsgi.'+attr])
414
415 def checkOSEnviron(self,handler):
416 empty = {}; setup_testing_defaults(empty)
417 env = handler.environ
418 from os import environ
419 for k,v in environ.items():
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000420 if k not in empty:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000421 self.assertEqual(env[k],v)
422 for k,v in empty.items():
Benjamin Peterson577473f2010-01-19 00:09:57 +0000423 self.assertIn(k, env)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000424
425 def testEnviron(self):
426 h = TestHandler(X="Y")
427 h.setup_environ()
428 self.checkEnvironAttrs(h)
429 self.checkOSEnviron(h)
430 self.assertEqual(h.environ["X"],"Y")
431
432 def testCGIEnviron(self):
433 h = BaseCGIHandler(None,None,None,{})
434 h.setup_environ()
435 for key in 'wsgi.url_scheme', 'wsgi.input', 'wsgi.errors':
Benjamin Peterson577473f2010-01-19 00:09:57 +0000436 self.assertIn(key, h.environ)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000437
438 def testScheme(self):
439 h=TestHandler(HTTPS="on"); h.setup_environ()
440 self.assertEqual(h.environ['wsgi.url_scheme'],'https')
441 h=TestHandler(); h.setup_environ()
442 self.assertEqual(h.environ['wsgi.url_scheme'],'http')
443
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000444 def testAbstractMethods(self):
445 h = BaseHandler()
446 for name in [
447 '_flush','get_stdin','get_stderr','add_cgi_vars'
448 ]:
449 self.assertRaises(NotImplementedError, getattr(h,name))
450 self.assertRaises(NotImplementedError, h._write, "test")
451
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000452 def testContentLength(self):
453 # Demo one reason iteration is better than write()... ;)
454
455 def trivial_app1(e,s):
456 s('200 OK',[])
Phillip J. Ebye1594222010-11-02 22:28:59 +0000457 return [e['wsgi.url_scheme'].encode('iso-8859-1')]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000458
459 def trivial_app2(e,s):
Phillip J. Ebye1594222010-11-02 22:28:59 +0000460 s('200 OK',[])(e['wsgi.url_scheme'].encode('iso-8859-1'))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000461 return []
462
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000463 def trivial_app3(e,s):
464 s('200 OK',[])
465 return ['\u0442\u0435\u0441\u0442'.encode("utf-8")]
466
Antoine Pitroub715fac2011-01-06 17:17:04 +0000467 def trivial_app4(e,s):
468 # Simulate a response to a HEAD request
469 s('200 OK',[('Content-Length', '12345')])
470 return []
471
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000472 h = TestHandler()
473 h.run(trivial_app1)
474 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000475 ("Status: 200 OK\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000476 "Content-Length: 4\r\n"
477 "\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000478 "http").encode("iso-8859-1"))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000479
480 h = TestHandler()
481 h.run(trivial_app2)
482 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000483 ("Status: 200 OK\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000484 "\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000485 "http").encode("iso-8859-1"))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000486
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000487 h = TestHandler()
488 h.run(trivial_app3)
489 self.assertEqual(h.stdout.getvalue(),
490 b'Status: 200 OK\r\n'
491 b'Content-Length: 8\r\n'
492 b'\r\n'
493 b'\xd1\x82\xd0\xb5\xd1\x81\xd1\x82')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000494
Antoine Pitroub715fac2011-01-06 17:17:04 +0000495 h = TestHandler()
496 h.run(trivial_app4)
497 self.assertEqual(h.stdout.getvalue(),
498 b'Status: 200 OK\r\n'
499 b'Content-Length: 12345\r\n'
500 b'\r\n')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000501
502 def testBasicErrorOutput(self):
503
504 def non_error_app(e,s):
505 s('200 OK',[])
506 return []
507
508 def error_app(e,s):
509 raise AssertionError("This should be caught by handler")
510
511 h = ErrorHandler()
512 h.run(non_error_app)
513 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000514 ("Status: 200 OK\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000515 "Content-Length: 0\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000516 "\r\n").encode("iso-8859-1"))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000517 self.assertEqual(h.stderr.getvalue(),"")
518
519 h = ErrorHandler()
520 h.run(error_app)
521 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000522 ("Status: %s\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000523 "Content-Type: text/plain\r\n"
524 "Content-Length: %d\r\n"
Phillip J. Ebye1594222010-11-02 22:28:59 +0000525 "\r\n" % (h.error_status,len(h.error_body))).encode('iso-8859-1')
526 + h.error_body)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000527
Benjamin Peterson577473f2010-01-19 00:09:57 +0000528 self.assertIn("AssertionError", h.stderr.getvalue())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000529
530 def testErrorAfterOutput(self):
Phillip J. Ebye1594222010-11-02 22:28:59 +0000531 MSG = b"Some output has been sent"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000532 def error_app(e,s):
533 s("200 OK",[])(MSG)
534 raise AssertionError("This should be caught by handler")
535
536 h = ErrorHandler()
537 h.run(error_app)
538 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000539 ("Status: 200 OK\r\n"
Phillip J. Ebye1594222010-11-02 22:28:59 +0000540 "\r\n".encode("iso-8859-1")+MSG))
Benjamin Peterson577473f2010-01-19 00:09:57 +0000541 self.assertIn("AssertionError", h.stderr.getvalue())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000542
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000543 def testHeaderFormats(self):
544
545 def non_error_app(e,s):
546 s('200 OK',[])
547 return []
548
549 stdpat = (
550 r"HTTP/%s 200 OK\r\n"
551 r"Date: \w{3}, [ 0123]\d \w{3} \d{4} \d\d:\d\d:\d\d GMT\r\n"
552 r"%s" r"Content-Length: 0\r\n" r"\r\n"
553 )
554 shortpat = (
555 "Status: 200 OK\r\n" "Content-Length: 0\r\n" "\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000556 ).encode("iso-8859-1")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000557
558 for ssw in "FooBar/1.0", None:
559 sw = ssw and "Server: %s\r\n" % ssw or ""
560
561 for version in "1.0", "1.1":
562 for proto in "HTTP/0.9", "HTTP/1.0", "HTTP/1.1":
563
564 h = TestHandler(SERVER_PROTOCOL=proto)
565 h.origin_server = False
566 h.http_version = version
567 h.server_software = ssw
568 h.run(non_error_app)
569 self.assertEqual(shortpat,h.stdout.getvalue())
570
571 h = TestHandler(SERVER_PROTOCOL=proto)
572 h.origin_server = True
573 h.http_version = version
574 h.server_software = ssw
575 h.run(non_error_app)
576 if proto=="HTTP/0.9":
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000577 self.assertEqual(h.stdout.getvalue(),b"")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000578 else:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000579 self.assertTrue(
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000580 re.match((stdpat%(version,sw)).encode("iso-8859-1"),
581 h.stdout.getvalue()),
582 ((stdpat%(version,sw)).encode("iso-8859-1"),
583 h.stdout.getvalue())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000584 )
585
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000586 def testBytesData(self):
587 def app(e, s):
Phillip J. Ebye1594222010-11-02 22:28:59 +0000588 s("200 OK", [
589 ("Content-Type", "text/plain; charset=utf-8"),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000590 ])
591 return [b"data"]
592
593 h = TestHandler()
594 h.run(app)
595 self.assertEqual(b"Status: 200 OK\r\n"
596 b"Content-Type: text/plain; charset=utf-8\r\n"
597 b"Content-Length: 4\r\n"
598 b"\r\n"
599 b"data",
600 h.stdout.getvalue())
601
Antoine Pitrouae247a52012-10-21 14:09:05 +0200602 def testCloseOnError(self):
603 side_effects = {'close_called': False}
604 MSG = b"Some output has been sent"
605 def error_app(e,s):
606 s("200 OK",[])(MSG)
607 class CrashyIterable(object):
608 def __iter__(self):
609 while True:
610 yield b'blah'
611 raise AssertionError("This should be caught by handler")
612 def close(self):
613 side_effects['close_called'] = True
614 return CrashyIterable()
615
616 h = ErrorHandler()
617 h.run(error_app)
618 self.assertEqual(side_effects['close_called'], True)
619
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000620
621def test_main():
Benjamin Petersonee8712c2008-05-20 21:35:26 +0000622 support.run_unittest(__name__)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000623
624if __name__ == "__main__":
625 test_main()