Package cherrypy :: Package test :: Module test_conn
[hide private]
[frames] | no frames]

Source Code for Module cherrypy.test.test_conn

  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, BadStatusLine 
 11  from cherrypy._cpcompat import ntob, urlopen, unicodestr 
 12  from cherrypy.test import webtest 
 13  from cherrypy import _cperror 
 14   
 15   
 16  pov = 'pPeErRsSiIsStTeEnNcCeE oOfF vViIsSiIoOnN' 
 17   
18 -def setup_server():
19 20 def raise500(): 21 raise cherrypy.HTTPError(500)
22 23 class Root: 24 25 def index(self): 26 return pov 27 index.exposed = True 28 page1 = index 29 page2 = index 30 page3 = index 31 32 def hello(self): 33 return "Hello, world!" 34 hello.exposed = True 35 36 def timeout(self, t): 37 return str(cherrypy.server.httpserver.timeout) 38 timeout.exposed = True 39 40 def stream(self, set_cl=False): 41 if set_cl: 42 cherrypy.response.headers['Content-Length'] = 10 43 44 def content(): 45 for x in range(10): 46 yield str(x) 47 48 return content() 49 stream.exposed = True 50 stream._cp_config = {'response.stream': True} 51 52 def error(self, code=500): 53 raise cherrypy.HTTPError(code) 54 error.exposed = True 55 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 62 63 def custom(self, response_code): 64 cherrypy.response.status = response_code 65 return "Code = %s" % response_code 66 custom.exposed = True 67 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} 72 73 def one_megabyte_of_a(self): 74 return ["a" * 1024] * 1024 75 one_megabyte_of_a.exposed = True 76 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} 91 92 cherrypy.tree.mount(Root()) 93 cherrypy.config.update({ 94 'server.max_request_body_size': 1001, 95 'server.socket_timeout': timeout, 96 }) 97 98 99 from cherrypy.test import helper 100
101 -class ConnectionCloseTests(helper.CPWebCase):
102 setup_server = staticmethod(setup_server) 103
104 - def test_HTTP11(self):
105 if cherrypy.server.protocol_version != "HTTP/1.1": 106 return self.skip() 107 108 self.PROTOCOL = "HTTP/1.1" 109 110 self.persistent = True 111 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") 117 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") 123 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") 129 130 # Make another request on the same connection, which should error. 131 self.assertRaises(NotConnected, self.getPage, "/")
132
133 - def test_Streaming_no_len(self):
134 self._streaming(set_cl=False)
135
136 - def test_Streaming_with_len(self):
137 self._streaming(set_cl=True)
138
139 - def _streaming(self, set_cl):
140 if cherrypy.server.protocol_version == "HTTP/1.1": 141 self.PROTOCOL = "HTTP/1.1" 142 143 self.persistent = True 144 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") 150 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") 159 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') 170 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 176 177 if chunked_response: 178 self.assertNoHeader("Connection", "close") 179 else: 180 self.assertHeader("Connection", "close") 181 182 # Make another request on the same connection, which should error. 183 self.assertRaises(NotConnected, self.getPage, "/") 184 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" 192 193 self.persistent = True 194 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") 200 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') 218 219 self.assertNoHeader("Content-Length") 220 self.assertNoHeader("Connection", "Keep-Alive") 221 self.assertNoHeader("Transfer-Encoding") 222 223 # Make another request on the same connection, which should error. 224 self.assertRaises(NotConnected, self.getPage, "/")
225
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 232 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") 239 240 # Test a keep-alive HTTP/1.0 request. 241 self.persistent = True 242 243 self.getPage("/page3", headers=[("Connection", "Keep-Alive")]) 244 self.assertStatus('200 OK') 245 self.assertBody(pov) 246 self.assertHeader("Connection", "Keep-Alive") 247 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") 254 255
256 -class PipelineTests(helper.CPWebCase):
257 setup_server = staticmethod(setup_server) 258
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() 264 265 self.PROTOCOL = "HTTP/1.1" 266 267 # Connect but send nothing. 268 self.persistent = True 269 conn = self.HTTP_CONN 270 conn.auto_open = False 271 conn.connect() 272 273 # Wait for our socket timeout 274 time.sleep(timeout * 2) 275 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() 281 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')) 289 290 # Wait for our socket timeout 291 time.sleep(timeout * 2) 292 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()
298
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() 304 305 self.PROTOCOL = "HTTP/1.1" 306 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)) 318 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!") 328 329 # Wait for our socket timeout 330 time.sleep(timeout * 2) 331 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()) 349 350 conn.close() 351 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) 363 364 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()) 382 383 conn.close() 384 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()
397
398 - def test_HTTP11_pipelining(self):
399 if cherrypy.server.protocol_version != "HTTP/1.1": 400 return self.skip() 401 402 self.PROTOCOL = "HTTP/1.1" 403 404 # Test pipelining. httplib doesn't support this directly. 405 self.persistent = True 406 conn = self.HTTP_CONN 407 408 # Put request 1 409 conn.putrequest("GET", "/hello", skip_host=True) 410 conn.putheader("Host", self.HOST) 411 conn.endheaders() 412 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() 418 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!")) 425 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!")) 432 433 conn.close()
434
435 - def test_100_Continue(self):
436 if cherrypy.server.protocol_version != "HTTP/1.1": 437 return self.skip() 438 439 self.PROTOCOL = "HTTP/1.1" 440 441 self.persistent = True 442 conn = self.HTTP_CONN 443 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() 457 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") 467 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 477 478 # ...send the body 479 body = ntob("I am a small file") 480 conn.send(body) 481 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()
488 489
490 -class ConnectionTests(helper.CPWebCase):
491 setup_server = staticmethod(setup_server) 492
493 - def test_readall_or_close(self):
494 if cherrypy.server.protocol_version != "HTTP/1.1": 495 return self.skip() 496 497 self.PROTOCOL = "HTTP/1.1" 498 499 if self.scheme == "https": 500 self.HTTP_CONN = HTTPSConnection 501 else: 502 self.HTTP_CONN = HTTPConnection 503 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 508 509 self.persistent = True 510 conn = self.HTTP_CONN 511 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") 520 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 528 529 # ...send the body 530 conn.send(ntob("x" * 1000)) 531 532 # ...get the final response 533 response.begin() 534 self.status, self.headers, self.body = webtest.shb(response) 535 self.assertStatus(500) 536 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") 545 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 553 554 # ...send the body 555 body = ntob("I am a small file") 556 conn.send(body) 557 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()
564
565 - def test_No_Message_Body(self):
566 if cherrypy.server.protocol_version != "HTTP/1.1": 567 return self.skip() 568 569 self.PROTOCOL = "HTTP/1.1" 570 571 # Set our HTTP_CONN to an instance so it persists between requests. 572 self.persistent = True 573 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") 579 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") 586 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")
593
594 - def test_Chunked_Encoding(self):
595 if cherrypy.server.protocol_version != "HTTP/1.1": 596 return self.skip() 597 598 if (hasattr(self, 'harness') and 599 "modpython" in self.harness.__class__.__name__.lower()): 600 # mod_python forbids chunked encoding 601 return self.skip() 602 603 self.PROTOCOL = "HTTP/1.1" 604 605 # Set our HTTP_CONN to an instance so it persists between requests. 606 self.persistent = True 607 conn = self.HTTP_CONN 608 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')) 627 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()
643
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()
660
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()
677
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()
692
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) 706 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()
711 712
713 -class BadRequestTests(helper.CPWebCase):
714 setup_server = staticmethod(setup_server) 715
716 - def test_No_CRLF(self):
717 self.persistent = True 718 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() 726 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()
734