Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

root/trunk/cherrypy/test/test_conn.py

Revision 2002 (checked in by fumanchu, 3 months ago)

WOOHOO. Fixed test_conn on nix.

  • Property svn:eol-style set to native
Line 
1 """Tests for TCP connection handling, including proper and timely close."""
2
3 from cherrypy.test import test
4 test.prefer_parent_path()
5
6 import httplib
7 import urllib
8 import socket
9 import sys
10 import time
11 timeout = 1
12
13
14 import cherrypy
15 from cherrypy.test import webtest
16 from cherrypy import _cperror
17
18
19 pov = 'pPeErRsSiIsStTeEnNcCeE oOfF vViIsSiIoOnN'
20
21 def setup_server():
22    
23     def raise500():
24         raise cherrypy.HTTPError(500)
25    
26     class Root:
27        
28         def index(self):
29             return pov
30         index.exposed = True
31         page1 = index
32         page2 = index
33         page3 = index
34        
35         def hello(self):
36             return "Hello, world!"
37         hello.exposed = True
38        
39         def timeout(self, t):
40             return str(cherrypy.server.httpserver.timeout)
41         timeout.exposed = True
42        
43         def stream(self, set_cl=False):
44             if set_cl:
45                 cherrypy.response.headers['Content-Length'] = 10
46            
47             def content():
48                 for x in xrange(10):
49                     yield str(x)
50            
51             return content()
52         stream.exposed = True
53         stream._cp_config = {'response.stream': True}
54        
55         def error(self, code=500):
56             raise cherrypy.HTTPError(code)
57         error.exposed = True
58        
59         def upload(self):
60             if not cherrypy.request.method == 'POST':
61                 raise AssertionError("'POST' != request.method %r" %
62                                      cherrypy.request.method)
63             return ("thanks for '%s' (%s)" %
64                     (cherrypy.request.body.read(),
65                      cherrypy.request.headers['Content-Type']))
66         upload.exposed = True
67        
68         def custom(self, response_code):
69             cherrypy.response.status = response_code
70             return "Code = %s" % response_code
71         custom.exposed = True
72        
73         def err_before_read(self):
74             return "ok"
75         err_before_read.exposed = True
76         err_before_read._cp_config = {'hooks.on_start_resource': raise500}
77
78         def one_megabyte_of_a(self):
79             return ["a" * 1024] * 1024
80         one_megabyte_of_a.exposed = True
81    
82     cherrypy.tree.mount(Root())
83     cherrypy.config.update({
84         'server.max_request_body_size': 1001,
85         'environment': 'test_suite',
86         })
87
88
89 from cherrypy.test import helper
90
91 class ConnectionTests(helper.CPWebCase):
92    
93     def test_HTTP11(self):
94         if cherrypy.server.protocol_version != "HTTP/1.1":
95             print "skipped ",
96             return
97        
98         self.PROTOCOL = "HTTP/1.1"
99        
100         self.persistent = True
101        
102         # Make the first request and assert there's no "Connection: close".
103         self.getPage("/")
104         self.assertStatus('200 OK')
105         self.assertBody(pov)
106         self.assertNoHeader("Connection")
107        
108         # Make another request on the same connection.
109         self.getPage("/page1")
110         self.assertStatus('200 OK')
111         self.assertBody(pov)
112         self.assertNoHeader("Connection")
113        
114         # Test client-side close.
115         self.getPage("/page2", headers=[("Connection", "close")])
116         self.assertStatus('200 OK')
117         self.assertBody(pov)
118         self.assertHeader("Connection", "close")
119        
120         # Make another request on the same connection, which should error.
121         self.assertRaises(httplib.NotConnected, self.getPage, "/")
122    
123     def test_Streaming_no_len(self):
124         self._streaming(set_cl=False)
125    
126     def test_Streaming_with_len(self):
127         self._streaming(set_cl=True)
128    
129     def _streaming(self, set_cl):
130         if cherrypy.server.protocol_version == "HTTP/1.1":
131             self.PROTOCOL = "HTTP/1.1"
132            
133             self.persistent = True
134            
135             # Make the first request and assert there's no "Connection: close".
136             self.getPage("/")
137             self.assertStatus('200 OK')
138             self.assertBody(pov)
139             self.assertNoHeader("Connection")
140            
141             # Make another, streamed request on the same connection.
142             if set_cl:
143                 # When a Content-Length is provided, the content should stream
144                 # without closing the connection.
145                 self.getPage("/stream?set_cl=Yes")
146                 self.assertHeader("Content-Length")
147                 self.assertNoHeader("Connection", "close")
148                 self.assertNoHeader("Transfer-Encoding")
149                
150                 self.assertStatus('200 OK')
151                 self.assertBody('0123456789')
152             else:
153                 # When no Content-Length response header is provided,
154                 # streamed output will either close the connection, or use
155                 # chunked encoding, to determine transfer-length.
156                 self.getPage("/stream")
157                 self.assertNoHeader("Content-Length")
158                 self.assertStatus('200 OK')
159                 self.assertBody('0123456789')
160                
161                 chunked_response = False
162                 for k, v in self.headers:
163                     if k.lower() == "transfer-encoding":
164                         if str(v) == "chunked":
165                             chunked_response = True
166                
167                 if chunked_response:
168                     self.assertNoHeader("Connection", "close")
169                 else:
170                     self.assertHeader("Connection", "close")
171                    
172                     # Make another request on the same connection, which should error.
173                     self.assertRaises(httplib.NotConnected, self.getPage, "/")
174         else:
175             self.PROTOCOL = "HTTP/1.0"
176            
177             self.persistent = True
178            
179             # Make the first request and assert Keep-Alive.
180             self.getPage("/", headers=[("Connection", "Keep-Alive")])
181             self.assertStatus('200 OK')
182             self.assertBody(pov)
183             self.assertHeader("Connection", "Keep-Alive")
184            
185             # Make another, streamed request on the same connection.
186             if set_cl:
187                 # When a Content-Length is provided, the content should
188                 # stream without closing the connection.
189                 self.getPage("/stream?set_cl=Yes",
190                              headers=[("Connection", "Keep-Alive")])
191                 self.assertHeader("Content-Length")
192                 self.assertHeader("Connection", "Keep-Alive")
193                 self.assertNoHeader("Transfer-Encoding")
194                 self.assertStatus('200 OK')
195                 self.assertBody('0123456789')
196             else:
197                 # When a Content-Length is not provided,
198                 # the server should close the connection.
199                 self.getPage("/stream", headers=[("Connection", "Keep-Alive")])
200                 self.assertStatus('200 OK')
201                 self.assertBody('0123456789')
202                
203                 self.assertNoHeader("Content-Length")
204                 self.assertNoHeader("Connection", "Keep-Alive")
205                 self.assertNoHeader("Transfer-Encoding")
206                
207                 # Make another request on the same connection, which should error.
208                 self.assertRaises(httplib.NotConnected, self.getPage, "/")
209    
210     def test_HTTP11_Timeout(self):
211         if cherrypy.server.protocol_version != "HTTP/1.1":
212             print "skipped ",
213             return
214        
215         self.PROTOCOL = "HTTP/1.1"
216        
217         # Make an initial request
218         self.persistent = True
219         conn = self.HTTP_CONN
220         conn.putrequest("GET", "/timeout?t=%s" % timeout, skip_host=True)
221         conn.putheader("Host", self.HOST)
222         conn.endheaders()
223         response = conn.response_class(conn.sock, method="GET")
224         response.begin()
225         self.assertEqual(response.status, 200)
226         self.body = response.read()
227         self.assertBody(str(timeout))
228        
229         # Make a second request on the same socket
230         conn._output('GET /hello HTTP/1.1')
231         conn._output("Host: %s" % self.HOST)
232         conn._send_output()
233         response = conn.response_class(conn.sock, method="GET")
234         response.begin()
235         self.assertEqual(response.status, 200)
236         self.body = response.read()
237         self.assertBody("Hello, world!")
238        
239         # Wait for our socket timeout
240         time.sleep(timeout * 2)
241        
242         # Make another request on the same socket, which should error
243         conn._output('GET /hello HTTP/1.1')
244         conn._output("Host: %s" % self.HOST)
245         conn._send_output()
246         response = conn.response_class(conn.sock, method="GET")
247         try:
248             response.begin()
249         except:
250             if not isinstance(sys.exc_info()[1],
251                               (socket.error, httplib.BadStatusLine)):
252                 self.fail("Writing to timed out socket didn't fail"
253                           " as it should have: %s" % sys.exc_info()[1])
254         else:
255             if response.status != 408:
256                 self.fail("Writing to timed out socket didn't fail"
257                           " as it should have: %s" %
258                           response.read())
259        
260         conn.close()
261        
262         # Make another request on a new socket, which should work
263         self.persistent = True
264         conn = self.HTTP_CONN
265         conn.putrequest("GET", "/", skip_host=True)
266         conn.putheader("Host", self.HOST)
267         conn.endheaders()
268         response = conn.response_class(conn.sock, method="GET")
269         response.begin()
270         self.assertEqual(response.status, 200)
271         self.body = response.read()
272         self.assertBody(pov)
273        
274         # Make another request on the same socket,
275         # but timeout on the headers
276         conn.send('GET /hello HTTP/1.1')
277         # Wait for our socket timeout
278         time.sleep(timeout * 2)
279         response = conn.response_class(conn.sock, method="GET")
280         response.begin()
281         self.assertEqual(response.status, 408)
282         conn.close()
283        
284         # Retry the request on a new connection, which should work
285         self.persistent = True
286         conn = self.HTTP_CONN
287         conn.putrequest("GET", "/", skip_host=True)
288         conn.putheader("Host", self.HOST)
289         conn.endheaders()
290         response = conn.response_class(conn.sock, method="GET")
291         response.begin()
292         self.assertEqual(response.status, 200)
293         self.body = response.read()
294         self.assertBody(pov)
295         conn.close()
296    
297     def test_HTTP11_pipelining(self):
298         if cherrypy.server.protocol_version != "HTTP/1.1":
299             print "skipped ",
300             return
301        
302         self.PROTOCOL = "HTTP/1.1"
303        
304         # Test pipelining. httplib doesn't support this directly.
305         self.persistent = True
306         conn = self.HTTP_CONN
307        
308         # Put request 1
309         conn.putrequest("GET", "/hello", skip_host=True)
310         conn.putheader("Host", self.HOST)
311         conn.endheaders()
312        
313         for trial in xrange(5):
314             # Put next request
315             conn._output('GET /hello HTTP/1.1')
316             conn._output("Host: %s" % self.HOST)
317             conn._send_output()
318            
319             # Retrieve previous response
320             response = conn.response_class(conn.sock, method="GET")
321             response.begin()
322             body = response.read()
323             self.assertEqual(response.status, 200)
324             self.assertEqual(body, "Hello, world!")
325        
326         # Retrieve final response
327         response = conn.response_class(conn.sock, method="GET")
328         response.begin()
329         body = response.read()
330         self.assertEqual(response.status, 200)
331         self.assertEqual(body, "Hello, world!")
332        
333         conn.close()
334    
335     def test_100_Continue(self):
336         if cherrypy.server.protocol_version != "HTTP/1.1":
337             print "skipped ",
338             return
339        
340         self.PROTOCOL = "HTTP/1.1"
341        
342         self.persistent = True
343         conn = self.HTTP_CONN
344        
345         # Try a page without an Expect request header first.
346         # Note that httplib's response.begin automatically ignores
347         # 100 Continue responses, so we must manually check for it.
348         conn.putrequest("POST", "/upload", skip_host=True)
349         conn.putheader("Host", self.HOST)
350         conn.putheader("Content-Type", "text/plain")
351         conn.putheader("Content-Length", "4")
352         conn.endheaders()
353         conn.send("d'oh")
354         response = conn.response_class(conn.sock, method="POST")
355         version, status, reason = response._read_status()
356         self.assertNotEqual(status, 100)
357         conn.close()
358        
359         # Now try a page with an Expect header...
360         conn.connect()
361         conn.putrequest("POST", "/upload", skip_host=True)
362         conn.putheader("Host", self.HOST)
363         conn.putheader("Content-Type", "text/plain")
364         conn.putheader("Content-Length", "17")
365         conn.putheader("Expect", "100-continue")
366         conn.endheaders()
367         response = conn.response_class(conn.sock, method="POST")
368        
369         # ...assert and then skip the 100 response
370         version, status, reason = response._read_status()
371         self.assertEqual(status, 100)
372         while True:
373             skip = response.fp.readline().strip()
374             if not skip:
375                 break
376        
377         # ...send the body
378         conn.send("I am a small file")
379        
380         # ...get the final response
381         response.begin()
382         self.status, self.headers, self.body = webtest.shb(response)
383         self.assertStatus(200)
384         self.assertBody("thanks for 'I am a small file' (text/plain)")
385         conn.close()
386    
387     def test_readall_or_close(self):
388         if cherrypy.server.protocol_version != "HTTP/1.1":
389             print "skipped ",
390             return
391        
392         self.PROTOCOL = "HTTP/1.1"
393        
394         if self.scheme == "https":
395             self.HTTP_CONN = httplib.HTTPSConnection
396         else:
397             self.HTTP_CONN = httplib.HTTPConnection
398        
399         # Test a max of 0 (the default) and then reset to what it was above.
400         old_max = cherrypy.server.max_request_body_size
401         for new_max in (0, old_max):
402             cherrypy.server.max_request_body_size = new_max
403            
404             self.persistent = True
405             conn = self.HTTP_CONN
406            
407             # Get a POST page with an error
408             conn.putrequest("POST", "/err_before_read", skip_host=True)
409             conn.putheader("Host", self.HOST)
410             conn.putheader("Content-Type", "text/plain")
411             conn.putheader("Content-Length", "1000")
412             conn.putheader("Expect", "100-continue")
413             conn.endheaders()
414             response = conn.response_class(conn.sock, method="POST")
415            
416             # ...assert and then skip the 100 response
417             version, status, reason = response._read_status()
418             self.assertEqual(status, 100)
419             while True:
420                 skip = response.fp.readline().strip()
421                 if not skip:
422                     break
423            
424             # ...send the body
425             conn.send("x" * 1000)
426            
427             # ...get the final response
428             response.begin()
429             self.status, self.headers, self.body = webtest.shb(response)
430             self.assertStatus(500)
431            
432             # Now try a working page with an Expect header...
433             conn._output('POST /upload HTTP/1.1')
434             conn._output("Host: %s" % self.HOST)
435             conn._output("Content-Type: text/plain")
436             conn._output("Content-Length: 17")
437             conn._output("Expect: 100-continue")
438             conn._send_output()
439             response = conn.response_class(conn.sock, method="POST")
440            
441             # ...assert and then skip the 100 response
442             version, status, reason = response._read_status()
443             self.assertEqual(status, 100)
444             while True:
445                 skip = response.fp.readline().strip()
446                 if not skip:
447                     break
448            
449             # ...send the body
450             conn.send("I am a small file")
451            
452             # ...get the final response
453             response.begin()
454             self.status, self.headers, self.body = webtest.shb(response)
455             self.assertStatus(200)
456             self.assertBody("thanks for 'I am a small file' (text/plain)")
457             conn.close()
458    
459     def test_No_Message_Body(self):
460         if cherrypy.server.protocol_version != "HTTP/1.1":
461             print "skipped ",
462             return
463        
464         self.PROTOCOL = "HTTP/1.1"
465        
466         # Set our HTTP_CONN to an instance so it persists between requests.