1 """Tests for TCP connection handling, including proper and timely close."""
2
3 import socket
4 import sys
5 import time
6 timeout = 1
7
8
9 import cherrypy
10 from cherrypy._cpcompat import HTTPConnection, HTTPSConnection, NotConnected
11 from cherrypy._cpcompat import BadStatusLine, ntob, tonative, urlopen, unicodestr
12 from cherrypy.test import webtest
13 from cherrypy import _cperror
14
15
16 pov = 'pPeErRsSiIsStTeEnNcCeE oOfF vViIsSiIoOnN'
17
18
23
24 class Root:
25
26 def index(self):
27 return pov
28 index.exposed = True
29 page1 = index
30 page2 = index
31 page3 = index
32
33 def hello(self):
34 return "Hello, world!"
35 hello.exposed = True
36
37 def timeout(self, t):
38 return str(cherrypy.server.httpserver.timeout)
39 timeout.exposed = True
40
41 def stream(self, set_cl=False):
42 if set_cl:
43 cherrypy.response.headers['Content-Length'] = 10
44
45 def content():
46 for x in range(10):
47 yield str(x)
48
49 return content()
50 stream.exposed = True
51 stream._cp_config = {'response.stream': True}
52
53 def error(self, code=500):
54 raise cherrypy.HTTPError(code)
55 error.exposed = True
56
57 def upload(self):
58 if not cherrypy.request.method == 'POST':
59 raise AssertionError("'POST' != request.method %r" %
60 cherrypy.request.method)
61 return "thanks for '%s'" % cherrypy.request.body.read()
62 upload.exposed = True
63
64 def custom(self, response_code):
65 cherrypy.response.status = response_code
66 return "Code = %s" % response_code
67 custom.exposed = True
68
69 def err_before_read(self):
70 return "ok"
71 err_before_read.exposed = True
72 err_before_read._cp_config = {'hooks.on_start_resource': raise500}
73
74 def one_megabyte_of_a(self):
75 return ["a" * 1024] * 1024
76 one_megabyte_of_a.exposed = True
77
78 def custom_cl(self, body, cl):
79 cherrypy.response.headers['Content-Length'] = cl
80 if not isinstance(body, list):
81 body = [body]
82 newbody = []
83 for chunk in body:
84 if isinstance(chunk, unicodestr):
85 chunk = chunk.encode('ISO-8859-1')
86 newbody.append(chunk)
87 return newbody
88 custom_cl.exposed = True
89
90
91 custom_cl._cp_config = {'tools.encode.on': False}
92
93 cherrypy.tree.mount(Root())
94 cherrypy.config.update({
95 'server.max_request_body_size': 1001,
96 'server.socket_timeout': timeout,
97 })
98
99
100 from cherrypy.test import helper
101
102
104 setup_server = staticmethod(setup_server)
105
134
136 try:
137 self._streaming(set_cl=False)
138 finally:
139 try:
140 self.HTTP_CONN.close()
141 except (TypeError, AttributeError):
142 pass
143
145 try:
146 self._streaming(set_cl=True)
147 finally:
148 try:
149 self.HTTP_CONN.close()
150 except (TypeError, AttributeError):
151 pass
152
154 if cherrypy.server.protocol_version == "HTTP/1.1":
155 self.PROTOCOL = "HTTP/1.1"
156
157 self.persistent = True
158
159
160 self.getPage("/")
161 self.assertStatus('200 OK')
162 self.assertBody(pov)
163 self.assertNoHeader("Connection")
164
165
166 if set_cl:
167
168
169 self.getPage("/stream?set_cl=Yes")
170 self.assertHeader("Content-Length")
171 self.assertNoHeader("Connection", "close")
172 self.assertNoHeader("Transfer-Encoding")
173
174 self.assertStatus('200 OK')
175 self.assertBody('0123456789')
176 else:
177
178
179
180 self.getPage("/stream")
181 self.assertNoHeader("Content-Length")
182 self.assertStatus('200 OK')
183 self.assertBody('0123456789')
184
185 chunked_response = False
186 for k, v in self.headers:
187 if k.lower() == "transfer-encoding":
188 if str(v) == "chunked":
189 chunked_response = True
190
191 if chunked_response:
192 self.assertNoHeader("Connection", "close")
193 else:
194 self.assertHeader("Connection", "close")
195
196
197
198 self.assertRaises(NotConnected, self.getPage, "/")
199
200
201
202 self.getPage("/stream", method='HEAD')
203 self.assertStatus('200 OK')
204 self.assertBody('')
205 self.assertNoHeader("Transfer-Encoding")
206 else:
207 self.PROTOCOL = "HTTP/1.0"
208
209 self.persistent = True
210
211
212 self.getPage("/", headers=[("Connection", "Keep-Alive")])
213 self.assertStatus('200 OK')
214 self.assertBody(pov)
215 self.assertHeader("Connection", "Keep-Alive")
216
217
218 if set_cl:
219
220
221 self.getPage("/stream?set_cl=Yes",
222 headers=[("Connection", "Keep-Alive")])
223 self.assertHeader("Content-Length")
224 self.assertHeader("Connection", "Keep-Alive")
225 self.assertNoHeader("Transfer-Encoding")
226 self.assertStatus('200 OK')
227 self.assertBody('0123456789')
228 else:
229
230
231 self.getPage("/stream", headers=[("Connection", "Keep-Alive")])
232 self.assertStatus('200 OK')
233 self.assertBody('0123456789')
234
235 self.assertNoHeader("Content-Length")
236 self.assertNoHeader("Connection", "Keep-Alive")
237 self.assertNoHeader("Transfer-Encoding")
238
239
240
241 self.assertRaises(NotConnected, self.getPage, "/")
242
269
270
271
272
274 setup_server = staticmethod(setup_server)
275
315
317
318
319 if cherrypy.server.protocol_version != "HTTP/1.1":
320 return self.skip()
321
322 self.PROTOCOL = "HTTP/1.1"
323
324
325 self.persistent = True
326 conn = self.HTTP_CONN
327 conn.putrequest("GET", "/timeout?t=%s" % timeout, skip_host=True)
328 conn.putheader("Host", self.HOST)
329 conn.endheaders()
330 response = conn.response_class(conn.sock, method="GET")
331 response.begin()
332 self.assertEqual(response.status, 200)
333 self.body = response.read()
334 self.assertBody(str(timeout))
335
336
337 conn._output(ntob('GET /hello HTTP/1.1'))
338 conn._output(ntob("Host: %s" % self.HOST, 'ascii'))
339 conn._send_output()
340 response = conn.response_class(conn.sock, method="GET")
341 response.begin()
342 self.assertEqual(response.status, 200)
343 self.body = response.read()
344 self.assertBody("Hello, world!")
345
346
347 time.sleep(timeout * 2)
348
349
350 conn._output(ntob('GET /hello HTTP/1.1'))
351 conn._output(ntob("Host: %s" % self.HOST, 'ascii'))
352 conn._send_output()
353 response = conn.response_class(conn.sock, method="GET")
354 try:
355 response.begin()
356 except:
357 if not isinstance(sys.exc_info()[1],
358 (socket.error, BadStatusLine)):
359 self.fail("Writing to timed out socket didn't fail"
360 " as it should have: %s" % sys.exc_info()[1])
361 else:
362 if response.status != 408:
363 self.fail("Writing to timed out socket didn't fail"
364 " as it should have: %s" %
365 response.read())
366
367 conn.close()
368
369
370 self.persistent = True
371 conn = self.HTTP_CONN
372 conn.putrequest("GET", "/", skip_host=True)
373 conn.putheader("Host", self.HOST)
374 conn.endheaders()
375 response = conn.response_class(conn.sock, method="GET")
376 response.begin()
377 self.assertEqual(response.status, 200)
378 self.body = response.read()
379 self.assertBody(pov)
380
381
382
383 conn.send(ntob('GET /hello HTTP/1.1'))
384
385 time.sleep(timeout * 2)
386 response = conn.response_class(conn.sock, method="GET")
387 try:
388 response.begin()
389 except:
390 if not isinstance(sys.exc_info()[1],
391 (socket.error, BadStatusLine)):
392 self.fail("Writing to timed out socket didn't fail"
393 " as it should have: %s" % sys.exc_info()[1])
394 else:
395 self.fail("Writing to timed out socket didn't fail"
396 " as it should have: %s" %
397 response.read())
398
399 conn.close()
400
401
402 self.persistent = True
403 conn = self.HTTP_CONN
404 conn.putrequest("GET", "/", skip_host=True)
405 conn.putheader("Host", self.HOST)
406 conn.endheaders()
407 response = conn.response_class(conn.sock, method="GET")
408 response.begin()
409 self.assertEqual(response.status, 200)
410 self.body = response.read()
411 self.assertBody(pov)
412 conn.close()
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
453 if cherrypy.server.protocol_version != "HTTP/1.1":
454 return self.skip()
455
456 self.PROTOCOL = "HTTP/1.1"
457
458 self.persistent = True
459 conn = self.HTTP_CONN
460
461
462
463
464 try:
465 conn.putrequest("POST", "/upload", skip_host=True)
466 conn.putheader("Host", self.HOST)
467 conn.putheader("Content-Type", "text/plain")
468 conn.putheader("Content-Length", "4")
469 conn.endheaders()
470 conn.send(ntob("d'oh"))
471 response = conn.response_class(conn.sock, method="POST")
472 version, status, reason = response._read_status()
473 self.assertNotEqual(status, 100)
474 finally:
475 conn.close()
476
477
478 try:
479 conn.connect()
480 conn.putrequest("POST", "/upload", skip_host=True)
481 conn.putheader("Host", self.HOST)
482 conn.putheader("Content-Type", "text/plain")
483 conn.putheader("Content-Length", "17")
484 conn.putheader("Expect", "100-continue")
485 conn.endheaders()
486 response = conn.response_class(conn.sock, method="POST")
487
488
489 version, status, reason = response._read_status()
490 self.assertEqual(status, 100)
491 while True:
492 line = response.fp.readline().strip()
493 if line:
494 self.fail(
495 "100 Continue should not output any headers. Got %r" %
496 line)
497 else:
498 break
499
500
501 body = ntob("I am a small file")
502 conn.send(body)
503
504
505 response.begin()
506 self.status, self.headers, self.body = webtest.shb(response)
507 self.assertStatus(200)
508 self.assertBody("thanks for '%s'" % body)
509 finally:
510 conn.close()
511
512
514 setup_server = staticmethod(setup_server)
515
517 if cherrypy.server.protocol_version != "HTTP/1.1":
518 return self.skip()
519
520 self.PROTOCOL = "HTTP/1.1"
521
522 if self.scheme == "https":
523 self.HTTP_CONN = HTTPSConnection
524 else:
525 self.HTTP_CONN = HTTPConnection
526
527
528 old_max = cherrypy.server.max_request_body_size
529 for new_max in (0, old_max):
530 cherrypy.server.max_request_body_size = new_max
531
532 self.persistent = True
533 conn = self.HTTP_CONN
534
535
536 conn.putrequest("POST", "/err_before_read", skip_host=True)
537 conn.putheader("Host", self.HOST)
538 conn.putheader("Content-Type", "text/plain")
539 conn.putheader("Content-Length", "1000")
540 conn.putheader("Expect", "100-continue")
541 conn.endheaders()
542 response = conn.response_class(conn.sock, method="POST")
543
544
545 version, status, reason = response._read_status()
546 self.assertEqual(status, 100)
547 while True:
548 skip = response.fp.readline().strip()
549 if not skip:
550 break
551
552
553 conn.send(ntob("x" * 1000))
554
555
556 response.begin()
557 self.status, self.headers, self.body = webtest.shb(response)
558 self.assertStatus(500)
559
560
561 conn._output(ntob('POST /upload HTTP/1.1'))
562 conn._output(ntob("Host: %s" % self.HOST, 'ascii'))
563 conn._output(ntob("Content-Type: text/plain"))
564 conn._output(ntob("Content-Length: 17"))
565 conn._output(ntob("Expect: 100-continue"))
566 conn._send_output()
567 response = conn.response_class(conn.sock, method="POST")
568
569
570 version, status, reason = response._read_status()
571 self.assertEqual(status, 100)
572 while True:
573 skip = response.fp.readline().strip()
574 if not skip:
575 break
576
577
578 body = ntob("I am a small file")
579 conn.send(body)
580
581
582 response.begin()
583 self.status, self.headers, self.body = webtest.shb(response)
584 self.assertStatus(200)
585 self.assertBody("thanks for '%s'" % body)
586 conn.close()
587
589 if cherrypy.server.protocol_version != "HTTP/1.1":
590 return self.skip()
591
592 self.PROTOCOL = "HTTP/1.1"
593
594
595 self.persistent = True
596
597
598 self.getPage("/")
599 self.assertStatus('200 OK')
600 self.assertBody(pov)
601 self.assertNoHeader("Connection")
602
603
604 self.getPage("/custom/204")
605 self.assertStatus(204)
606 self.assertNoHeader("Content-Length")
607 self.assertBody("")
608 self.assertNoHeader("Connection")
609
610
611 self.getPage("/custom/304")
612 self.assertStatus(304)
613 self.assertNoHeader("Content-Length")
614 self.assertBody("")
615 self.assertNoHeader("Connection")
616
618 if cherrypy.server.protocol_version != "HTTP/1.1":
619 return self.skip()
620
621 if (hasattr(self, 'harness') and
622 "modpython" in self.harness.__class__.__name__.lower()):
623
624 return self.skip()
625
626 self.PROTOCOL = "HTTP/1.1"
627
628
629 self.persistent = True
630 conn = self.HTTP_CONN
631
632
633 body = ntob("8;key=value\r\nxx\r\nxxxx\r\n5\r\nyyyyy\r\n0\r\n"
634 "Content-Type: application/json\r\n"
635 "\r\n")
636 conn.putrequest("POST", "/upload", skip_host=True)
637 conn.putheader("Host", self.HOST)
638 conn.putheader("Transfer-Encoding", "chunked")
639 conn.putheader("Trailer", "Content-Type")
640
641
642
643 conn.putheader("Content-Length", "3")
644 conn.endheaders()
645 conn.send(body)
646 response = conn.getresponse()
647 self.status, self.headers, self.body = webtest.shb(response)
648 self.assertStatus('200 OK')
649 self.assertBody("thanks for '%s'" % ntob('xx\r\nxxxxyyyyy'))
650
651
652
653 body = ntob("3e3\r\n" + ("x" * 995) + "\r\n0\r\n\r\n")
654 conn.putrequest("POST", "/upload", skip_host=True)
655 conn.putheader("Host", self.HOST)
656 conn.putheader("Transfer-Encoding", "chunked")
657 conn.putheader("Content-Type", "text/plain")
658
659
660 conn.endheaders()
661 conn.send(body)
662 response = conn.getresponse()
663 self.status, self.headers, self.body = webtest.shb(response)
664 self.assertStatus(413)
665 conn.close()
666
668
669
670 self.persistent = True
671 conn = self.HTTP_CONN
672 conn.putrequest("POST", "/upload", skip_host=True)
673 conn.putheader("Host", self.HOST)
674 conn.putheader("Content-Type", "text/plain")
675 conn.putheader("Content-Length", "9999")
676 conn.endheaders()
677 response = conn.getresponse()
678 self.status, self.headers, self.body = webtest.shb(response)
679 self.assertStatus(413)
680 self.assertBody("The entity sent with the request exceeds "
681 "the maximum allowed bytes.")
682 conn.close()
683
685
686
687 self.persistent = True
688 conn = self.HTTP_CONN
689 conn.putrequest("GET", "/custom_cl?body=I+have+too+many+bytes&cl=5",
690 skip_host=True)
691 conn.putheader("Host", self.HOST)
692 conn.endheaders()
693 response = conn.getresponse()
694 self.status, self.headers, self.body = webtest.shb(response)
695 self.assertStatus(500)
696 self.assertBody(
697 "The requested resource returned more bytes than the "
698 "declared Content-Length.")
699 conn.close()
700
702
703
704 self.persistent = True
705 conn = self.HTTP_CONN
706 conn.putrequest(
707 "GET", "/custom_cl?body=I+too&body=+have+too+many&cl=5",
708 skip_host=True)
709 conn.putheader("Host", self.HOST)
710 conn.endheaders()
711 response = conn.getresponse()
712 self.status, self.headers, self.body = webtest.shb(response)
713 self.assertStatus(200)
714 self.assertBody("I too")
715 conn.close()
716
718 remote_data_conn = urlopen('%s://%s:%s/one_megabyte_of_a/' %
719 (self.scheme, self.HOST, self.PORT,))
720 buf = remote_data_conn.read(512)
721 time.sleep(timeout * 0.6)
722 remaining = (1024 * 1024) - 512
723 while remaining:
724 data = remote_data_conn.read(remaining)
725 if not data:
726 break
727 else:
728 buf += data
729 remaining -= len(data)
730
731 self.assertEqual(len(buf), 1024 * 1024)
732 self.assertEqual(buf, ntob("a" * 1024 * 1024))
733 self.assertEqual(remaining, 0)
734 remote_data_conn.close()
735
736
745 upload.exposed = True
746
747 cherrypy.tree.mount(Root())
748 cherrypy.config.update({
749 'server.max_request_body_size': 1001,
750 'server.socket_timeout': 10,
751 'server.accepted_queue_size': 5,
752 'server.accepted_queue_timeout': 0.1,
753 })
754
755 import errno
756 socket_reset_errors = []
757
758 for _ in ("ECONNRESET", "WSAECONNRESET"):
759 if _ in dir(errno):
760 socket_reset_errors.append(getattr(errno, _))
761
763 setup_server = staticmethod(setup_upload_server)
764
766 conns = []
767 overflow_conn = None
768
769 try:
770
771
772 import time
773 for i in range(15):
774 conn = self.HTTP_CONN(self.HOST, self.PORT)
775 conn.putrequest("POST", "/upload", skip_host=True)
776 conn.putheader("Host", self.HOST)
777 conn.putheader("Content-Type", "text/plain")
778 conn.putheader("Content-Length", "4")
779 conn.endheaders()
780 conns.append(conn)
781
782
783 overflow_conn = self.HTTP_CONN(self.HOST, self.PORT)
784
785 for res in socket.getaddrinfo(self.HOST, self.PORT, 0,
786 socket.SOCK_STREAM):
787 af, socktype, proto, canonname, sa = res
788 overflow_conn.sock = socket.socket(af, socktype, proto)
789 overflow_conn.sock.settimeout(5)
790 overflow_conn.sock.connect(sa)
791 break
792
793 overflow_conn.putrequest("GET", "/", skip_host=True)
794 overflow_conn.putheader("Host", self.HOST)
795 overflow_conn.endheaders()
796 response = overflow_conn.response_class(overflow_conn.sock, method="GET")
797 try:
798 response.begin()
799 except socket.error as exc:
800 if exc.args[0] in socket_reset_errors:
801 pass
802 else:
803 raise AssertionError("Overflow conn did not get RST. "
804 "Got %s instead" % repr(exc.args))
805 else:
806 raise AssertionError("Overflow conn did not get RST ")
807 finally:
808 for conn in conns:
809 conn.send(ntob("done"))
810 response = conn.response_class(conn.sock, method="POST")
811 response.begin()
812 self.body = response.read()
813 self.assertBody("thanks for 'done'")
814 self.assertEqual(response.status, 200)
815 conn.close()
816 if overflow_conn:
817 overflow_conn.close()
818
840