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, 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
137
140
142 if cherrypy.server.protocol_version == "HTTP/1.1":
143 self.PROTOCOL = "HTTP/1.1"
144
145 self.persistent = True
146
147
148 self.getPage("/")
149 self.assertStatus('200 OK')
150 self.assertBody(pov)
151 self.assertNoHeader("Connection")
152
153
154 if set_cl:
155
156
157 self.getPage("/stream?set_cl=Yes")
158 self.assertHeader("Content-Length")
159 self.assertNoHeader("Connection", "close")
160 self.assertNoHeader("Transfer-Encoding")
161
162 self.assertStatus('200 OK')
163 self.assertBody('0123456789')
164 else:
165
166
167
168 self.getPage("/stream")
169 self.assertNoHeader("Content-Length")
170 self.assertStatus('200 OK')
171 self.assertBody('0123456789')
172
173 chunked_response = False
174 for k, v in self.headers:
175 if k.lower() == "transfer-encoding":
176 if str(v) == "chunked":
177 chunked_response = True
178
179 if chunked_response:
180 self.assertNoHeader("Connection", "close")
181 else:
182 self.assertHeader("Connection", "close")
183
184
185
186 self.assertRaises(NotConnected, self.getPage, "/")
187
188
189
190 self.getPage("/stream", method='HEAD')
191 self.assertStatus('200 OK')
192 self.assertBody('')
193 self.assertNoHeader("Transfer-Encoding")
194 else:
195 self.PROTOCOL = "HTTP/1.0"
196
197 self.persistent = True
198
199
200 self.getPage("/", headers=[("Connection", "Keep-Alive")])
201 self.assertStatus('200 OK')
202 self.assertBody(pov)
203 self.assertHeader("Connection", "Keep-Alive")
204
205
206 if set_cl:
207
208
209 self.getPage("/stream?set_cl=Yes",
210 headers=[("Connection", "Keep-Alive")])
211 self.assertHeader("Content-Length")
212 self.assertHeader("Connection", "Keep-Alive")
213 self.assertNoHeader("Transfer-Encoding")
214 self.assertStatus('200 OK')
215 self.assertBody('0123456789')
216 else:
217
218
219 self.getPage("/stream", headers=[("Connection", "Keep-Alive")])
220 self.assertStatus('200 OK')
221 self.assertBody('0123456789')
222
223 self.assertNoHeader("Content-Length")
224 self.assertNoHeader("Connection", "Keep-Alive")
225 self.assertNoHeader("Transfer-Encoding")
226
227
228
229 self.assertRaises(NotConnected, self.getPage, "/")
230
257
258
259
260
262 setup_server = staticmethod(setup_server)
263
303
305
306
307 if cherrypy.server.protocol_version != "HTTP/1.1":
308 return self.skip()
309
310 self.PROTOCOL = "HTTP/1.1"
311
312
313 self.persistent = True
314 conn = self.HTTP_CONN
315 conn.putrequest("GET", "/timeout?t=%s" % timeout, skip_host=True)
316 conn.putheader("Host", self.HOST)
317 conn.endheaders()
318 response = conn.response_class(conn.sock, method="GET")
319 response.begin()
320 self.assertEqual(response.status, 200)
321 self.body = response.read()
322 self.assertBody(str(timeout))
323
324
325 conn._output(ntob('GET /hello HTTP/1.1'))
326 conn._output(ntob("Host: %s" % self.HOST, 'ascii'))
327 conn._send_output()
328 response = conn.response_class(conn.sock, method="GET")
329 response.begin()
330 self.assertEqual(response.status, 200)
331 self.body = response.read()
332 self.assertBody("Hello, world!")
333
334
335 time.sleep(timeout * 2)
336
337
338 conn._output(ntob('GET /hello HTTP/1.1'))
339 conn._output(ntob("Host: %s" % self.HOST, 'ascii'))
340 conn._send_output()
341 response = conn.response_class(conn.sock, method="GET")
342 try:
343 response.begin()
344 except:
345 if not isinstance(sys.exc_info()[1],
346 (socket.error, BadStatusLine)):
347 self.fail("Writing to timed out socket didn't fail"
348 " as it should have: %s" % sys.exc_info()[1])
349 else:
350 if response.status != 408:
351 self.fail("Writing to timed out socket didn't fail"
352 " as it should have: %s" %
353 response.read())
354
355 conn.close()
356
357
358 self.persistent = True
359 conn = self.HTTP_CONN
360 conn.putrequest("GET", "/", skip_host=True)
361 conn.putheader("Host", self.HOST)
362 conn.endheaders()
363 response = conn.response_class(conn.sock, method="GET")
364 response.begin()
365 self.assertEqual(response.status, 200)
366 self.body = response.read()
367 self.assertBody(pov)
368
369
370
371 conn.send(ntob('GET /hello HTTP/1.1'))
372
373 time.sleep(timeout * 2)
374 response = conn.response_class(conn.sock, method="GET")
375 try:
376 response.begin()
377 except:
378 if not isinstance(sys.exc_info()[1],
379 (socket.error, BadStatusLine)):
380 self.fail("Writing to timed out socket didn't fail"
381 " as it should have: %s" % sys.exc_info()[1])
382 else:
383 self.fail("Writing to timed out socket didn't fail"
384 " as it should have: %s" %
385 response.read())
386
387 conn.close()
388
389
390 self.persistent = True
391 conn = self.HTTP_CONN
392 conn.putrequest("GET", "/", skip_host=True)
393 conn.putheader("Host", self.HOST)
394 conn.endheaders()
395 response = conn.response_class(conn.sock, method="GET")
396 response.begin()
397 self.assertEqual(response.status, 200)
398 self.body = response.read()
399 self.assertBody(pov)
400 conn.close()
401
403 if cherrypy.server.protocol_version != "HTTP/1.1":
404 return self.skip()
405
406 self.PROTOCOL = "HTTP/1.1"
407
408
409 self.persistent = True
410 conn = self.HTTP_CONN
411
412
413 conn.putrequest("GET", "/hello", skip_host=True)
414 conn.putheader("Host", self.HOST)
415 conn.endheaders()
416
417 for trial in range(5):
418
419 conn._output(ntob('GET /hello HTTP/1.1'))
420 conn._output(ntob("Host: %s" % self.HOST, 'ascii'))
421 conn._send_output()
422
423
424 response = conn.response_class(conn.sock, method="GET")
425 response.begin()
426 body = response.read(13)
427 self.assertEqual(response.status, 200)
428 self.assertEqual(body, ntob("Hello, world!"))
429
430
431 response = conn.response_class(conn.sock, method="GET")
432 response.begin()
433 body = response.read()
434 self.assertEqual(response.status, 200)
435 self.assertEqual(body, ntob("Hello, world!"))
436
437 conn.close()
438
440 if cherrypy.server.protocol_version != "HTTP/1.1":
441 return self.skip()
442
443 self.PROTOCOL = "HTTP/1.1"
444
445 self.persistent = True
446 conn = self.HTTP_CONN
447
448
449
450
451 conn.putrequest("POST", "/upload", skip_host=True)
452 conn.putheader("Host", self.HOST)
453 conn.putheader("Content-Type", "text/plain")
454 conn.putheader("Content-Length", "4")
455 conn.endheaders()
456 conn.send(ntob("d'oh"))
457 response = conn.response_class(conn.sock, method="POST")
458 version, status, reason = response._read_status()
459 self.assertNotEqual(status, 100)
460 conn.close()
461
462
463 conn.connect()
464 conn.putrequest("POST", "/upload", skip_host=True)
465 conn.putheader("Host", self.HOST)
466 conn.putheader("Content-Type", "text/plain")
467 conn.putheader("Content-Length", "17")
468 conn.putheader("Expect", "100-continue")
469 conn.endheaders()
470 response = conn.response_class(conn.sock, method="POST")
471
472
473 version, status, reason = response._read_status()
474 self.assertEqual(status, 100)
475 while True:
476 line = response.fp.readline().strip()
477 if line:
478 self.fail(
479 "100 Continue should not output any headers. Got %r" %
480 line)
481 else:
482 break
483
484
485 body = ntob("I am a small file")
486 conn.send(body)
487
488
489 response.begin()
490 self.status, self.headers, self.body = webtest.shb(response)
491 self.assertStatus(200)
492 self.assertBody("thanks for '%s'" % body)
493 conn.close()
494
495
497 setup_server = staticmethod(setup_server)
498
500 if cherrypy.server.protocol_version != "HTTP/1.1":
501 return self.skip()
502
503 self.PROTOCOL = "HTTP/1.1"
504
505 if self.scheme == "https":
506 self.HTTP_CONN = HTTPSConnection
507 else:
508 self.HTTP_CONN = HTTPConnection
509
510
511 old_max = cherrypy.server.max_request_body_size
512 for new_max in (0, old_max):
513 cherrypy.server.max_request_body_size = new_max
514
515 self.persistent = True
516 conn = self.HTTP_CONN
517
518
519 conn.putrequest("POST", "/err_before_read", skip_host=True)
520 conn.putheader("Host", self.HOST)
521 conn.putheader("Content-Type", "text/plain")
522 conn.putheader("Content-Length", "1000")
523 conn.putheader("Expect", "100-continue")
524 conn.endheaders()
525 response = conn.response_class(conn.sock, method="POST")
526
527
528 version, status, reason = response._read_status()
529 self.assertEqual(status, 100)
530 while True:
531 skip = response.fp.readline().strip()
532 if not skip:
533 break
534
535
536 conn.send(ntob("x" * 1000))
537
538
539 response.begin()
540 self.status, self.headers, self.body = webtest.shb(response)
541 self.assertStatus(500)
542
543
544 conn._output(ntob('POST /upload HTTP/1.1'))
545 conn._output(ntob("Host: %s" % self.HOST, 'ascii'))
546 conn._output(ntob("Content-Type: text/plain"))
547 conn._output(ntob("Content-Length: 17"))
548 conn._output(ntob("Expect: 100-continue"))
549 conn._send_output()
550 response = conn.response_class(conn.sock, method="POST")
551
552
553 version, status, reason = response._read_status()
554 self.assertEqual(status, 100)
555 while True:
556 skip = response.fp.readline().strip()
557 if not skip:
558 break
559
560
561 body = ntob("I am a small file")
562 conn.send(body)
563
564
565 response.begin()
566 self.status, self.headers, self.body = webtest.shb(response)
567 self.assertStatus(200)
568 self.assertBody("thanks for '%s'" % body)
569 conn.close()
570
572 if cherrypy.server.protocol_version != "HTTP/1.1":
573 return self.skip()
574
575 self.PROTOCOL = "HTTP/1.1"
576
577
578 self.persistent = True
579
580
581 self.getPage("/")
582 self.assertStatus('200 OK')
583 self.assertBody(pov)
584 self.assertNoHeader("Connection")
585
586
587 self.getPage("/custom/204")
588 self.assertStatus(204)
589 self.assertNoHeader("Content-Length")
590 self.assertBody("")
591 self.assertNoHeader("Connection")
592
593
594 self.getPage("/custom/304")
595 self.assertStatus(304)
596 self.assertNoHeader("Content-Length")
597 self.assertBody("")
598 self.assertNoHeader("Connection")
599
601 if cherrypy.server.protocol_version != "HTTP/1.1":
602 return self.skip()
603
604 if (hasattr(self, 'harness') and
605 "modpython" in self.harness.__class__.__name__.lower()):
606
607 return self.skip()
608
609 self.PROTOCOL = "HTTP/1.1"
610
611
612 self.persistent = True
613 conn = self.HTTP_CONN
614
615
616 body = ntob("8;key=value\r\nxx\r\nxxxx\r\n5\r\nyyyyy\r\n0\r\n"
617 "Content-Type: application/json\r\n"
618 "\r\n")
619 conn.putrequest("POST", "/upload", skip_host=True)
620 conn.putheader("Host", self.HOST)
621 conn.putheader("Transfer-Encoding", "chunked")
622 conn.putheader("Trailer", "Content-Type")
623
624
625
626 conn.putheader("Content-Length", "3")
627 conn.endheaders()
628 conn.send(body)
629 response = conn.getresponse()
630 self.status, self.headers, self.body = webtest.shb(response)
631 self.assertStatus('200 OK')
632 self.assertBody("thanks for '%s'" % ntob('xx\r\nxxxxyyyyy'))
633
634
635
636 body = ntob("3e3\r\n" + ("x" * 995) + "\r\n0\r\n\r\n")
637 conn.putrequest("POST", "/upload", skip_host=True)
638 conn.putheader("Host", self.HOST)
639 conn.putheader("Transfer-Encoding", "chunked")
640 conn.putheader("Content-Type", "text/plain")
641
642
643 conn.endheaders()
644 conn.send(body)
645 response = conn.getresponse()
646 self.status, self.headers, self.body = webtest.shb(response)
647 self.assertStatus(413)
648 conn.close()
649
651
652
653 self.persistent = True
654 conn = self.HTTP_CONN
655 conn.putrequest("POST", "/upload", skip_host=True)
656 conn.putheader("Host", self.HOST)
657 conn.putheader("Content-Type", "text/plain")
658 conn.putheader("Content-Length", "9999")
659 conn.endheaders()
660 response = conn.getresponse()
661 self.status, self.headers, self.body = webtest.shb(response)
662 self.assertStatus(413)
663 self.assertBody("The entity sent with the request exceeds "
664 "the maximum allowed bytes.")
665 conn.close()
666
668
669
670 self.persistent = True
671 conn = self.HTTP_CONN
672 conn.putrequest("GET", "/custom_cl?body=I+have+too+many+bytes&cl=5",
673 skip_host=True)
674 conn.putheader("Host", self.HOST)
675 conn.endheaders()
676 response = conn.getresponse()
677 self.status, self.headers, self.body = webtest.shb(response)
678 self.assertStatus(500)
679 self.assertBody(
680 "The requested resource returned more bytes than the "
681 "declared Content-Length.")
682 conn.close()
683
685
686
687 self.persistent = True
688 conn = self.HTTP_CONN
689 conn.putrequest(
690 "GET", "/custom_cl?body=I+too&body=+have+too+many&cl=5",
691 skip_host=True)
692 conn.putheader("Host", self.HOST)
693 conn.endheaders()
694 response = conn.getresponse()
695 self.status, self.headers, self.body = webtest.shb(response)
696 self.assertStatus(200)
697 self.assertBody("I too")
698 conn.close()
699
701 remote_data_conn = urlopen('%s://%s:%s/one_megabyte_of_a/' %
702 (self.scheme, self.HOST, self.PORT,))
703 buf = remote_data_conn.read(512)
704 time.sleep(timeout * 0.6)
705 remaining = (1024 * 1024) - 512
706 while remaining:
707 data = remote_data_conn.read(remaining)
708 if not data:
709 break
710 else:
711 buf += data
712 remaining -= len(data)
713
714 self.assertEqual(len(buf), 1024 * 1024)
715 self.assertEqual(buf, ntob("a" * 1024 * 1024))
716 self.assertEqual(remaining, 0)
717 remote_data_conn.close()
718
719
741