Removed spurious static_path.
[smonitor.git] / monitor / cherrypy / test / test_conn.py
blob1346f593104c3de1b5f203dddfdb5ddd034385e5
1 """Tests for TCP connection handling, including proper and timely close."""
3 import socket
4 import sys
5 import time
6 timeout = 1
9 import cherrypy
10 from cherrypy._cpcompat import HTTPConnection, HTTPSConnection, NotConnected, BadStatusLine
11 from cherrypy._cpcompat import ntob, urlopen, unicodestr
12 from cherrypy.test import webtest
13 from cherrypy import _cperror
16 pov = 'pPeErRsSiIsStTeEnNcCeE oOfF vViIsSiIoOnN'
18 def setup_server():
20 def raise500():
21 raise cherrypy.HTTPError(500)
23 class Root:
25 def index(self):
26 return pov
27 index.exposed = True
28 page1 = index
29 page2 = index
30 page3 = index
32 def hello(self):
33 return "Hello, world!"
34 hello.exposed = True
36 def timeout(self, t):
37 return str(cherrypy.server.httpserver.timeout)
38 timeout.exposed = True
40 def stream(self, set_cl=False):
41 if set_cl:
42 cherrypy.response.headers['Content-Length'] = 10
44 def content():
45 for x in range(10):
46 yield str(x)
48 return content()
49 stream.exposed = True
50 stream._cp_config = {'response.stream': True}
52 def error(self, code=500):
53 raise cherrypy.HTTPError(code)
54 error.exposed = True
56 def upload(self):
57 if not cherrypy.request.method == 'POST':
58 raise AssertionError("'POST' != request.method %r" %
59 cherrypy.request.method)
60 return "thanks for '%s'" % cherrypy.request.body.read()
61 upload.exposed = True
63 def custom(self, response_code):
64 cherrypy.response.status = response_code
65 return "Code = %s" % response_code
66 custom.exposed = True
68 def err_before_read(self):
69 return "ok"
70 err_before_read.exposed = True
71 err_before_read._cp_config = {'hooks.on_start_resource': raise500}
73 def one_megabyte_of_a(self):
74 return ["a" * 1024] * 1024
75 one_megabyte_of_a.exposed = True
77 def custom_cl(self, body, cl):
78 cherrypy.response.headers['Content-Length'] = cl
79 if not isinstance(body, list):
80 body = [body]
81 newbody = []
82 for chunk in body:
83 if isinstance(chunk, unicodestr):
84 chunk = chunk.encode('ISO-8859-1')
85 newbody.append(chunk)
86 return newbody
87 custom_cl.exposed = True
88 # Turn off the encoding tool so it doens't collapse
89 # our response body and reclaculate the Content-Length.
90 custom_cl._cp_config = {'tools.encode.on': False}
92 cherrypy.tree.mount(Root())
93 cherrypy.config.update({
94 'server.max_request_body_size': 1001,
95 'server.socket_timeout': timeout,
99 from cherrypy.test import helper
101 class ConnectionCloseTests(helper.CPWebCase):
102 setup_server = staticmethod(setup_server)
104 def test_HTTP11(self):
105 if cherrypy.server.protocol_version != "HTTP/1.1":
106 return self.skip()
108 self.PROTOCOL = "HTTP/1.1"
110 self.persistent = True
112 # Make the first request and assert there's no "Connection: close".
113 self.getPage("/")
114 self.assertStatus('200 OK')
115 self.assertBody(pov)
116 self.assertNoHeader("Connection")
118 # Make another request on the same connection.
119 self.getPage("/page1")
120 self.assertStatus('200 OK')
121 self.assertBody(pov)
122 self.assertNoHeader("Connection")
124 # Test client-side close.
125 self.getPage("/page2", headers=[("Connection", "close")])
126 self.assertStatus('200 OK')
127 self.assertBody(pov)
128 self.assertHeader("Connection", "close")
130 # Make another request on the same connection, which should error.
131 self.assertRaises(NotConnected, self.getPage, "/")
133 def test_Streaming_no_len(self):
134 self._streaming(set_cl=False)
136 def test_Streaming_with_len(self):
137 self._streaming(set_cl=True)
139 def _streaming(self, set_cl):
140 if cherrypy.server.protocol_version == "HTTP/1.1":
141 self.PROTOCOL = "HTTP/1.1"
143 self.persistent = True
145 # Make the first request and assert there's no "Connection: close".
146 self.getPage("/")
147 self.assertStatus('200 OK')
148 self.assertBody(pov)
149 self.assertNoHeader("Connection")
151 # Make another, streamed request on the same connection.
152 if set_cl:
153 # When a Content-Length is provided, the content should stream
154 # without closing the connection.
155 self.getPage("/stream?set_cl=Yes")
156 self.assertHeader("Content-Length")
157 self.assertNoHeader("Connection", "close")
158 self.assertNoHeader("Transfer-Encoding")
160 self.assertStatus('200 OK')
161 self.assertBody('0123456789')
162 else:
163 # When no Content-Length response header is provided,
164 # streamed output will either close the connection, or use
165 # chunked encoding, to determine transfer-length.
166 self.getPage("/stream")
167 self.assertNoHeader("Content-Length")
168 self.assertStatus('200 OK')
169 self.assertBody('0123456789')
171 chunked_response = False
172 for k, v in self.headers:
173 if k.lower() == "transfer-encoding":
174 if str(v) == "chunked":
175 chunked_response = True
177 if chunked_response:
178 self.assertNoHeader("Connection", "close")
179 else:
180 self.assertHeader("Connection", "close")
182 # Make another request on the same connection, which should error.
183 self.assertRaises(NotConnected, self.getPage, "/")
185 # Try HEAD. See http://www.cherrypy.org/ticket/864.
186 self.getPage("/stream", method='HEAD')
187 self.assertStatus('200 OK')
188 self.assertBody('')
189 self.assertNoHeader("Transfer-Encoding")
190 else:
191 self.PROTOCOL = "HTTP/1.0"
193 self.persistent = True
195 # Make the first request and assert Keep-Alive.
196 self.getPage("/", headers=[("Connection", "Keep-Alive")])
197 self.assertStatus('200 OK')
198 self.assertBody(pov)
199 self.assertHeader("Connection", "Keep-Alive")
201 # Make another, streamed request on the same connection.
202 if set_cl:
203 # When a Content-Length is provided, the content should
204 # stream without closing the connection.
205 self.getPage("/stream?set_cl=Yes",
206 headers=[("Connection", "Keep-Alive")])
207 self.assertHeader("Content-Length")
208 self.assertHeader("Connection", "Keep-Alive")
209 self.assertNoHeader("Transfer-Encoding")
210 self.assertStatus('200 OK')
211 self.assertBody('0123456789')
212 else:
213 # When a Content-Length is not provided,
214 # the server should close the connection.
215 self.getPage("/stream", headers=[("Connection", "Keep-Alive")])
216 self.assertStatus('200 OK')
217 self.assertBody('0123456789')
219 self.assertNoHeader("Content-Length")
220 self.assertNoHeader("Connection", "Keep-Alive")
221 self.assertNoHeader("Transfer-Encoding")
223 # Make another request on the same connection, which should error.
224 self.assertRaises(NotConnected, self.getPage, "/")
226 def test_HTTP10_KeepAlive(self):
227 self.PROTOCOL = "HTTP/1.0"
228 if self.scheme == "https":
229 self.HTTP_CONN = HTTPSConnection
230 else:
231 self.HTTP_CONN = HTTPConnection
233 # Test a normal HTTP/1.0 request.
234 self.getPage("/page2")
235 self.assertStatus('200 OK')
236 self.assertBody(pov)
237 # Apache, for example, may emit a Connection header even for HTTP/1.0
238 ## self.assertNoHeader("Connection")
240 # Test a keep-alive HTTP/1.0 request.
241 self.persistent = True
243 self.getPage("/page3", headers=[("Connection", "Keep-Alive")])
244 self.assertStatus('200 OK')
245 self.assertBody(pov)
246 self.assertHeader("Connection", "Keep-Alive")
248 # Remove the keep-alive header again.
249 self.getPage("/page3")
250 self.assertStatus('200 OK')
251 self.assertBody(pov)
252 # Apache, for example, may emit a Connection header even for HTTP/1.0
253 ## self.assertNoHeader("Connection")
256 class PipelineTests(helper.CPWebCase):
257 setup_server = staticmethod(setup_server)
259 def test_HTTP11_Timeout(self):
260 # If we timeout without sending any data,
261 # the server will close the conn with a 408.
262 if cherrypy.server.protocol_version != "HTTP/1.1":
263 return self.skip()
265 self.PROTOCOL = "HTTP/1.1"
267 # Connect but send nothing.
268 self.persistent = True
269 conn = self.HTTP_CONN
270 conn.auto_open = False
271 conn.connect()
273 # Wait for our socket timeout
274 time.sleep(timeout * 2)
276 # The request should have returned 408 already.
277 response = conn.response_class(conn.sock, method="GET")
278 response.begin()
279 self.assertEqual(response.status, 408)
280 conn.close()
282 # Connect but send half the headers only.
283 self.persistent = True
284 conn = self.HTTP_CONN
285 conn.auto_open = False
286 conn.connect()
287 conn.send(ntob('GET /hello HTTP/1.1'))
288 conn.send(("Host: %s" % self.HOST).encode('ascii'))
290 # Wait for our socket timeout
291 time.sleep(timeout * 2)
293 # The conn should have already sent 408.
294 response = conn.response_class(conn.sock, method="GET")
295 response.begin()
296 self.assertEqual(response.status, 408)
297 conn.close()
299 def test_HTTP11_Timeout_after_request(self):
300 # If we timeout after at least one request has succeeded,
301 # the server will close the conn without 408.
302 if cherrypy.server.protocol_version != "HTTP/1.1":
303 return self.skip()
305 self.PROTOCOL = "HTTP/1.1"
307 # Make an initial request
308 self.persistent = True
309 conn = self.HTTP_CONN
310 conn.putrequest("GET", "/timeout?t=%s" % timeout, skip_host=True)
311 conn.putheader("Host", self.HOST)
312 conn.endheaders()
313 response = conn.response_class(conn.sock, method="GET")
314 response.begin()
315 self.assertEqual(response.status, 200)
316 self.body = response.read()
317 self.assertBody(str(timeout))
319 # Make a second request on the same socket
320 conn._output(ntob('GET /hello HTTP/1.1'))
321 conn._output(ntob("Host: %s" % self.HOST, 'ascii'))
322 conn._send_output()
323 response = conn.response_class(conn.sock, method="GET")
324 response.begin()
325 self.assertEqual(response.status, 200)
326 self.body = response.read()
327 self.assertBody("Hello, world!")
329 # Wait for our socket timeout
330 time.sleep(timeout * 2)
332 # Make another request on the same socket, which should error
333 conn._output(ntob('GET /hello HTTP/1.1'))
334 conn._output(ntob("Host: %s" % self.HOST, 'ascii'))
335 conn._send_output()
336 response = conn.response_class(conn.sock, method="GET")
337 try:
338 response.begin()
339 except:
340 if not isinstance(sys.exc_info()[1],
341 (socket.error, BadStatusLine)):
342 self.fail("Writing to timed out socket didn't fail"
343 " as it should have: %s" % sys.exc_info()[1])
344 else:
345 if response.status != 408:
346 self.fail("Writing to timed out socket didn't fail"
347 " as it should have: %s" %
348 response.read())
350 conn.close()
352 # Make another request on a new socket, which should work
353 self.persistent = True
354 conn = self.HTTP_CONN
355 conn.putrequest("GET", "/", skip_host=True)
356 conn.putheader("Host", self.HOST)
357 conn.endheaders()
358 response = conn.response_class(conn.sock, method="GET")
359 response.begin()
360 self.assertEqual(response.status, 200)
361 self.body = response.read()
362 self.assertBody(pov)
365 # Make another request on the same socket,
366 # but timeout on the headers
367 conn.send(ntob('GET /hello HTTP/1.1'))
368 # Wait for our socket timeout
369 time.sleep(timeout * 2)
370 response = conn.response_class(conn.sock, method="GET")
371 try:
372 response.begin()
373 except:
374 if not isinstance(sys.exc_info()[1],
375 (socket.error, BadStatusLine)):
376 self.fail("Writing to timed out socket didn't fail"
377 " as it should have: %s" % sys.exc_info()[1])
378 else:
379 self.fail("Writing to timed out socket didn't fail"
380 " as it should have: %s" %
381 response.read())
383 conn.close()
385 # Retry the request on a new connection, which should work
386 self.persistent = True
387 conn = self.HTTP_CONN
388 conn.putrequest("GET", "/", skip_host=True)
389 conn.putheader("Host", self.HOST)
390 conn.endheaders()
391 response = conn.response_class(conn.sock, method="GET")
392 response.begin()
393 self.assertEqual(response.status, 200)
394 self.body = response.read()
395 self.assertBody(pov)
396 conn.close()
398 def test_HTTP11_pipelining(self):
399 if cherrypy.server.protocol_version != "HTTP/1.1":
400 return self.skip()
402 self.PROTOCOL = "HTTP/1.1"
404 # Test pipelining. httplib doesn't support this directly.
405 self.persistent = True
406 conn = self.HTTP_CONN
408 # Put request 1
409 conn.putrequest("GET", "/hello", skip_host=True)
410 conn.putheader("Host", self.HOST)
411 conn.endheaders()
413 for trial in range(5):
414 # Put next request
415 conn._output(ntob('GET /hello HTTP/1.1'))
416 conn._output(ntob("Host: %s" % self.HOST, 'ascii'))
417 conn._send_output()
419 # Retrieve previous response
420 response = conn.response_class(conn.sock, method="GET")
421 response.begin()
422 body = response.read(13)
423 self.assertEqual(response.status, 200)
424 self.assertEqual(body, ntob("Hello, world!"))
426 # Retrieve final response
427 response = conn.response_class(conn.sock, method="GET")
428 response.begin()
429 body = response.read()
430 self.assertEqual(response.status, 200)
431 self.assertEqual(body, ntob("Hello, world!"))
433 conn.close()
435 def test_100_Continue(self):
436 if cherrypy.server.protocol_version != "HTTP/1.1":
437 return self.skip()
439 self.PROTOCOL = "HTTP/1.1"
441 self.persistent = True
442 conn = self.HTTP_CONN
444 # Try a page without an Expect request header first.
445 # Note that httplib's response.begin automatically ignores
446 # 100 Continue responses, so we must manually check for it.
447 conn.putrequest("POST", "/upload", skip_host=True)
448 conn.putheader("Host", self.HOST)
449 conn.putheader("Content-Type", "text/plain")
450 conn.putheader("Content-Length", "4")
451 conn.endheaders()
452 conn.send(ntob("d'oh"))
453 response = conn.response_class(conn.sock, method="POST")
454 version, status, reason = response._read_status()
455 self.assertNotEqual(status, 100)
456 conn.close()
458 # Now try a page with an Expect header...
459 conn.connect()
460 conn.putrequest("POST", "/upload", skip_host=True)
461 conn.putheader("Host", self.HOST)
462 conn.putheader("Content-Type", "text/plain")
463 conn.putheader("Content-Length", "17")
464 conn.putheader("Expect", "100-continue")
465 conn.endheaders()
466 response = conn.response_class(conn.sock, method="POST")
468 # ...assert and then skip the 100 response
469 version, status, reason = response._read_status()
470 self.assertEqual(status, 100)
471 while True:
472 line = response.fp.readline().strip()
473 if line:
474 self.fail("100 Continue should not output any headers. Got %r" % line)
475 else:
476 break
478 # ...send the body
479 body = ntob("I am a small file")
480 conn.send(body)
482 # ...get the final response
483 response.begin()
484 self.status, self.headers, self.body = webtest.shb(response)
485 self.assertStatus(200)
486 self.assertBody("thanks for '%s'" % body)
487 conn.close()
490 class ConnectionTests(helper.CPWebCase):
491 setup_server = staticmethod(setup_server)
493 def test_readall_or_close(self):
494 if cherrypy.server.protocol_version != "HTTP/1.1":
495 return self.skip()
497 self.PROTOCOL = "HTTP/1.1"
499 if self.scheme == "https":
500 self.HTTP_CONN = HTTPSConnection
501 else:
502 self.HTTP_CONN = HTTPConnection
504 # Test a max of 0 (the default) and then reset to what it was above.
505 old_max = cherrypy.server.max_request_body_size
506 for new_max in (0, old_max):
507 cherrypy.server.max_request_body_size = new_max
509 self.persistent = True
510 conn = self.HTTP_CONN
512 # Get a POST page with an error
513 conn.putrequest("POST", "/err_before_read", skip_host=True)
514 conn.putheader("Host", self.HOST)
515 conn.putheader("Content-Type", "text/plain")
516 conn.putheader("Content-Length", "1000")
517 conn.putheader("Expect", "100-continue")
518 conn.endheaders()
519 response = conn.response_class(conn.sock, method="POST")
521 # ...assert and then skip the 100 response
522 version, status, reason = response._read_status()
523 self.assertEqual(status, 100)
524 while True:
525 skip = response.fp.readline().strip()
526 if not skip:
527 break
529 # ...send the body
530 conn.send(ntob("x" * 1000))
532 # ...get the final response
533 response.begin()
534 self.status, self.headers, self.body = webtest.shb(response)
535 self.assertStatus(500)
537 # Now try a working page with an Expect header...
538 conn._output(ntob('POST /upload HTTP/1.1'))
539 conn._output(ntob("Host: %s" % self.HOST, 'ascii'))
540 conn._output(ntob("Content-Type: text/plain"))
541 conn._output(ntob("Content-Length: 17"))
542 conn._output(ntob("Expect: 100-continue"))
543 conn._send_output()
544 response = conn.response_class(conn.sock, method="POST")
546 # ...assert and then skip the 100 response
547 version, status, reason = response._read_status()
548 self.assertEqual(status, 100)
549 while True:
550 skip = response.fp.readline().strip()
551 if not skip:
552 break
554 # ...send the body
555 body = ntob("I am a small file")
556 conn.send(body)
558 # ...get the final response
559 response.begin()
560 self.status, self.headers, self.body = webtest.shb(response)
561 self.assertStatus(200)
562 self.assertBody("thanks for '%s'" % body)
563 conn.close()
565 def test_No_Message_Body(self):
566 if cherrypy.server.protocol_version != "HTTP/1.1":
567 return self.skip()
569 self.PROTOCOL = "HTTP/1.1"
571 # Set our HTTP_CONN to an instance so it persists between requests.
572 self.persistent = True
574 # Make the first request and assert there's no "Connection: close".
575 self.getPage("/")
576 self.assertStatus('200 OK')
577 self.assertBody(pov)
578 self.assertNoHeader("Connection")
580 # Make a 204 request on the same connection.
581 self.getPage("/custom/204")
582 self.assertStatus(204)
583 self.assertNoHeader("Content-Length")
584 self.assertBody("")
585 self.assertNoHeader("Connection")
587 # Make a 304 request on the same connection.
588 self.getPage("/custom/304")
589 self.assertStatus(304)
590 self.assertNoHeader("Content-Length")
591 self.assertBody("")
592 self.assertNoHeader("Connection")
594 def test_Chunked_Encoding(self):
595 if cherrypy.server.protocol_version != "HTTP/1.1":
596 return self.skip()
598 if (hasattr(self, 'harness') and
599 "modpython" in self.harness.__class__.__name__.lower()):
600 # mod_python forbids chunked encoding
601 return self.skip()
603 self.PROTOCOL = "HTTP/1.1"
605 # Set our HTTP_CONN to an instance so it persists between requests.
606 self.persistent = True
607 conn = self.HTTP_CONN
609 # Try a normal chunked request (with extensions)
610 body = ntob("8;key=value\r\nxx\r\nxxxx\r\n5\r\nyyyyy\r\n0\r\n"
611 "Content-Type: application/json\r\n"
612 "\r\n")
613 conn.putrequest("POST", "/upload", skip_host=True)
614 conn.putheader("Host", self.HOST)
615 conn.putheader("Transfer-Encoding", "chunked")
616 conn.putheader("Trailer", "Content-Type")
617 # Note that this is somewhat malformed:
618 # we shouldn't be sending Content-Length.
619 # RFC 2616 says the server should ignore it.
620 conn.putheader("Content-Length", "3")
621 conn.endheaders()
622 conn.send(body)
623 response = conn.getresponse()
624 self.status, self.headers, self.body = webtest.shb(response)
625 self.assertStatus('200 OK')
626 self.assertBody("thanks for '%s'" % ntob('xx\r\nxxxxyyyyy'))
628 # Try a chunked request that exceeds server.max_request_body_size.
629 # Note that the delimiters and trailer are included.
630 body = ntob("3e3\r\n" + ("x" * 995) + "\r\n0\r\n\r\n")
631 conn.putrequest("POST", "/upload", skip_host=True)
632 conn.putheader("Host", self.HOST)
633 conn.putheader("Transfer-Encoding", "chunked")
634 conn.putheader("Content-Type", "text/plain")
635 # Chunked requests don't need a content-length
636 ## conn.putheader("Content-Length", len(body))
637 conn.endheaders()
638 conn.send(body)
639 response = conn.getresponse()
640 self.status, self.headers, self.body = webtest.shb(response)
641 self.assertStatus(413)
642 conn.close()
644 def test_Content_Length_in(self):
645 # Try a non-chunked request where Content-Length exceeds
646 # server.max_request_body_size. Assert error before body send.
647 self.persistent = True
648 conn = self.HTTP_CONN
649 conn.putrequest("POST", "/upload", skip_host=True)
650 conn.putheader("Host", self.HOST)
651 conn.putheader("Content-Type", "text/plain")
652 conn.putheader("Content-Length", "9999")
653 conn.endheaders()
654 response = conn.getresponse()
655 self.status, self.headers, self.body = webtest.shb(response)
656 self.assertStatus(413)
657 self.assertBody("The entity sent with the request exceeds "
658 "the maximum allowed bytes.")
659 conn.close()
661 def test_Content_Length_out_preheaders(self):
662 # Try a non-chunked response where Content-Length is less than
663 # the actual bytes in the response body.
664 self.persistent = True
665 conn = self.HTTP_CONN
666 conn.putrequest("GET", "/custom_cl?body=I+have+too+many+bytes&cl=5",
667 skip_host=True)
668 conn.putheader("Host", self.HOST)
669 conn.endheaders()
670 response = conn.getresponse()
671 self.status, self.headers, self.body = webtest.shb(response)
672 self.assertStatus(500)
673 self.assertBody(
674 "The requested resource returned more bytes than the "
675 "declared Content-Length.")
676 conn.close()
678 def test_Content_Length_out_postheaders(self):
679 # Try a non-chunked response where Content-Length is less than
680 # the actual bytes in the response body.
681 self.persistent = True
682 conn = self.HTTP_CONN
683 conn.putrequest("GET", "/custom_cl?body=I+too&body=+have+too+many&cl=5",
684 skip_host=True)
685 conn.putheader("Host", self.HOST)
686 conn.endheaders()
687 response = conn.getresponse()
688 self.status, self.headers, self.body = webtest.shb(response)
689 self.assertStatus(200)
690 self.assertBody("I too")
691 conn.close()
693 def test_598(self):
694 remote_data_conn = urlopen('%s://%s:%s/one_megabyte_of_a/' %
695 (self.scheme, self.HOST, self.PORT,))
696 buf = remote_data_conn.read(512)
697 time.sleep(timeout * 0.6)
698 remaining = (1024 * 1024) - 512
699 while remaining:
700 data = remote_data_conn.read(remaining)
701 if not data:
702 break
703 else:
704 buf += data
705 remaining -= len(data)
707 self.assertEqual(len(buf), 1024 * 1024)
708 self.assertEqual(buf, ntob("a" * 1024 * 1024))
709 self.assertEqual(remaining, 0)
710 remote_data_conn.close()
713 class BadRequestTests(helper.CPWebCase):
714 setup_server = staticmethod(setup_server)
716 def test_No_CRLF(self):
717 self.persistent = True
719 conn = self.HTTP_CONN
720 conn.send(ntob('GET /hello HTTP/1.1\n\n'))
721 response = conn.response_class(conn.sock, method="GET")
722 response.begin()
723 self.body = response.read()
724 self.assertBody("HTTP requires CRLF terminators")
725 conn.close()
727 conn.connect()
728 conn.send(ntob('GET /hello HTTP/1.1\r\n\n'))
729 response = conn.response_class(conn.sock, method="GET")
730 response.begin()
731 self.body = response.read()
732 self.assertBody("HTTP requires CRLF terminators")
733 conn.close()