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  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 175 # Try HEAD. See http://www.cherrypy.org/ticket/864. 176 self.getPage("/stream", method='HEAD') 177 self.assertStatus('200 OK') 178 self.assertBody('') 179 self.assertNoHeader("Transfer-Encoding") 180 else: 181 self.PROTOCOL = "HTTP/1.0" 182 183 self.persistent = True 184 185 # Make the first request and assert Keep-Alive. 186 self.getPage("/", headers=[("Connection", "Keep-Alive")]) 187 self.assertStatus('200 OK') 188 self.assertBody(pov) 189 self.assertHeader("Connection", "Keep-Alive") 190 191 # Make another, streamed request on the same connection. 192 if set_cl: 193 # When a Content-Length is provided, the content should 194 # stream without closing the connection. 195 self.getPage("/stream?set_cl=Yes", 196 headers=[("Connection", "Keep-Alive")]) 197 self.assertHeader("Content-Length") 198 self.assertHeader("Connection", "Keep-Alive") 199 self.assertNoHeader("Transfer-Encoding") 200 self.assertStatus('200 OK') 201 self.assertBody('0123456789') 202 else: 203 # When a Content-Length is not provided, 204 # the server should close the connection. 205 self.getPage("/stream", headers=[("Connection", "Keep-Alive")]) 206 self.assertStatus('200 OK') 207 self.assertBody('0123456789') 208 209 self.assertNoHeader("Content-Length") 210 self.assertNoHeader("Connection", "Keep-Alive") 211 self.assertNoHeader("Transfer-Encoding") 212 213 # Make another request on the same connection, which should error. 214 self.assertRaises(httplib.NotConnected, self.getPage, "/")
215
216 - def test_HTTP11_Timeout(self):
217 if cherrypy.server.protocol_version != "HTTP/1.1": 218 print "skipped ", 219 return 220 221 self.PROTOCOL = "HTTP/1.1" 222 223 # Make an initial request 224 self.persistent = True 225 conn = self.HTTP_CONN 226 conn.putrequest("GET", "/timeout?t=%s" % timeout, skip_host=True) 227 conn.putheader("Host", self.HOST) 228 conn.endheaders() 229 response = conn.response_class(conn.sock, method="GET") 230 response.begin() 231 self.assertEqual(response.status, 200) 232 self.body = response.read() 233 self.assertBody(str(timeout)) 234 235 # Make a second request on the same socket 236 conn._output('GET /hello HTTP/1.1') 237 conn._output("Host: %s" % self.HOST) 238 conn._send_output() 239 response = conn.response_class(conn.sock, method="GET") 240 response.begin() 241 self.assertEqual(response.status, 200) 242 self.body = response.read() 243 self.assertBody("Hello, world!") 244 245 # Wait for our socket timeout 246 time.sleep(timeout * 2) 247 248 # Make another request on the same socket, which should error 249 conn._output('GET /hello HTTP/1.1') 250 conn._output("Host: %s" % self.HOST) 251 conn._send_output() 252 response = conn.response_class(conn.sock, method="GET") 253 try: 254 response.begin() 255 except: 256 if not isinstance(sys.exc_info()[1], 257 (socket.error, httplib.BadStatusLine)): 258 self.fail("Writing to timed out socket didn't fail" 259 " as it should have: %s" % sys.exc_info()[1]) 260 else: 261 if response.status != 408: 262 self.fail("Writing to timed out socket didn't fail" 263 " as it should have: %s" % 264 response.read()) 265 266 conn.close() 267 268 # Make another request on a new socket, which should work 269 self.persistent = True 270 conn = self.HTTP_CONN 271 conn.putrequest("GET", "/", skip_host=True) 272 conn.putheader("Host", self.HOST) 273 conn.endheaders() 274 response = conn.response_class(conn.sock, method="GET") 275 response.begin() 276 self.assertEqual(response.status, 200) 277 self.body = response.read() 278 self.assertBody(pov) 279 280 # Make another request on the same socket, 281 # but timeout on the headers 282 conn.send('GET /hello HTTP/1.1') 283 # Wait for our socket timeout 284 time.sleep(timeout * 2) 285 response = conn.response_class(conn.sock, method="GET") 286 response.begin() 287 self.assertEqual(response.status, 408) 288 conn.close() 289 290 # Retry the request on a new connection, which should work 291 self.persistent = True 292 conn = self.HTTP_CONN 293 conn.putrequest("GET", "/", skip_host=True) 294 conn.putheader("Host", self.HOST) 295 conn.endheaders() 296 response = conn.response_class(conn.sock, method="GET") 297 response.begin() 298 self.assertEqual(response.status, 200) 299 self.body = response.read() 300 self.assertBody(pov) 301 conn.close()
302
303 - def test_HTTP11_pipelining(self):
304 if cherrypy.server.protocol_version != "HTTP/1.1": 305 print "skipped ", 306 return 307 308 self.PROTOCOL = "HTTP/1.1" 309 310 # Test pipelining. httplib doesn't support this directly. 311 self.persistent = True 312 conn = self.HTTP_CONN 313 314 # Put request 1 315 conn.putrequest("GET", "/hello", skip_host=True) 316 conn.putheader("Host", self.HOST) 317 conn.endheaders() 318 319 for trial in xrange(5): 320 # Put next request 321 conn._output('GET /hello HTTP/1.1') 322 conn._output("Host: %s" % self.HOST) 323 conn._send_output() 324 325 # Retrieve previous response 326 response = conn.response_class(conn.sock, method="GET") 327 response.begin() 328 body = response.read() 329 self.assertEqual(response.status, 200) 330 self.assertEqual(body, "Hello, world!") 331 332 # Retrieve final response 333 response = conn.response_class(conn.sock, method="GET") 334 response.begin() 335 body = response.read() 336 self.assertEqual(response.status, 200) 337 self.assertEqual(body, "Hello, world!") 338 339 conn.close()
340
341 - def test_100_Continue(self):
342 if cherrypy.server.protocol_version != "HTTP/1.1": 343 print "skipped ", 344 return 345 346 self.PROTOCOL = "HTTP/1.1" 347 348 self.persistent = True 349 conn = self.HTTP_CONN 350 351 # Try a page without an Expect request header first. 352 # Note that httplib's response.begin automatically ignores 353 # 100 Continue responses, so we must manually check for it. 354 conn.putrequest("POST", "/upload", skip_host=True) 355 conn.putheader("Host", self.HOST) 356 conn.putheader("Content-Type", "text/plain") 357 conn.putheader("Content-Length", "4") 358 conn.endheaders() 359 conn.send("d'oh") 360 response = conn.response_class(conn.sock, method="POST") 361 version, status, reason = response._read_status() 362 self.assertNotEqual(status, 100) 363 conn.close() 364 365 # Now try a page with an Expect header... 366 conn.connect() 367 conn.putrequest("POST", "/upload", skip_host=True) 368 conn.putheader("Host", self.HOST) 369 conn.putheader("Content-Type", "text/plain") 370 conn.putheader("Content-Length", "17") 371 conn.putheader("Expect", "100-continue") 372 conn.endheaders() 373 response = conn.response_class(conn.sock, method="POST") 374 375 # ...assert and then skip the 100 response 376 version, status, reason = response._read_status() 377 self.assertEqual(status, 100) 378 while True: 379 skip = response.fp.readline().strip() 380 if not skip: 381 break 382 383 # ...send the body 384 conn.send("I am a small file") 385 386 # ...get the final response 387 response.begin() 388 self.status, self.headers, self.body = webtest.shb(response) 389 self.assertStatus(200) 390 self.assertBody("thanks for 'I am a small file' (text/plain)") 391 conn.close()
392
393 - def test_readall_or_close(self):
394 if cherrypy.server.protocol_version != "HTTP/1.1": 395 print "skipped ", 396 return 397 398 self.PROTOCOL = "HTTP/1.1" 399 400 if self.scheme == "https": 401 self.HTTP_CONN = httplib.HTTPSConnection 402 else: 403 self.HTTP_CONN = httplib.HTTPConnection 404 405 # Test a max of 0 (the default) and then reset to what it was above. 406 old_max = cherrypy.server.max_request_body_size 407 for new_max in (0, old_max): 408 cherrypy.server.max_request_body_size = new_max 409 410 self.persistent = True 411 conn = self.HTTP_CONN 412 413 # Get a POST page with an error 414 conn.putrequest("POST", "/err_before_read", skip_host=True) 415 conn.putheader("Host", self.HOST) 416 conn.putheader("Content-Type", "text/plain") 417 conn.putheader("Content-Length", "1000") 418 conn.putheader("Expect", "100-continue") 419 conn.endheaders() 420 response = conn.response_class(conn.sock, method="POST") 421 422 # ...assert and then skip the 100 response 423 version, status, reason = response._read_status() 424 self.assertEqual(status, 100) 425 while True: 426 skip = response.fp.readline().strip() 427 if not skip: 428 break 429 430 # ...send the body 431 conn.send("x" * 1000) 432 433 # ...get the final response 434 response.begin() 435 self.status, self.headers, self.body = webtest.shb(response) 436 self.assertStatus(500) 437 438 # Now try a working page with an Expect header... 439 conn._output('POST /upload HTTP/1.1') 440 conn._output("Host: %s" % self.HOST) 441 conn._output("Content-Type: text/plain") 442 conn._output("Content-Length: 17") 443 conn._output("Expect: 100-continue") 444 conn._send_output() 445 response = conn.response_class(conn.sock, method="POST") 446 447 # ...assert and then skip the 100 response 448 version, status, reason = response._read_status() 449 self.assertEqual(status, 100) 450 while True: 451 skip = response.fp.readline().strip() 452 if not skip: 453 break 454 455 # ...send the body 456 conn.send("I am a small file") 457 458 # ...get the final response 459 response.begin() 460 self.status, self.headers, self.body = webtest.shb(response) 461 self.assertStatus(200) 462 self.assertBody("thanks for 'I am a small file' (text/plain)") 463 conn.close()
464
465 - def test_No_Message_Body(self):
466 if cherrypy.server.protocol_version != "HTTP/1.1": 467 print "skipped ", 468 return 469 470 self.PROTOCOL = "HTTP/1.1" 471 472 # Set our HTTP_CONN to an instance so it persists between requests. 473 self.persistent = True 474 475 # Make the first request and assert there's no "Connection: close". 476 self.getPage("/") 477 self.assertStatus('200 OK') 478 self.assertBody(pov) 479 self.assertNoHeader("Connection") 480 481 # Make a 204 request on the same connection. 482 self.getPage("/custom/204") 483 self.assertStatus(204) 484 self.assertNoHeader("Content-Length") 485 self.assertBody("") 486 self.assertNoHeader("Connection") 487 488 # Make a 304 request on the same connection. 489 self.getPage("/custom/304") 490 self.assertStatus(304) 491 self.assertNoHeader("Content-Length") 492 self.assertBody("") 493 self.assertNoHeader("Connection")
494
495 - def test_Chunked_Encoding(self):
496 if cherrypy.server.protocol_version != "HTTP/1.1": 497 print "skipped ", 498 return 499 500 if (hasattr(self, 'harness') and 501 "modpython" in self.harness.__class__.__name__.lower()): 502 # mod_python forbids chunked encoding 503 print "skipped ", 504 return 505 506 self.PROTOCOL = "HTTP/1.1" 507 508 # Set our HTTP_CONN to an instance so it persists between requests. 509 self.persistent = True 510 conn = self.HTTP_CONN 511 512 # Try a normal chunked request (with extensions) 513 body = ("8;key=value\r\nxx\r\nxxxx\r\n5\r\nyyyyy\r\n0\r\n" 514 "Content-Type: application/x-json\r\n\r\n") 515 conn.putrequest("POST", "/upload", skip_host=True) 516 conn.putheader("Host", self.HOST) 517 conn.putheader("Transfer-Encoding", "chunked") 518 conn.putheader("Trailer", "Content-Type") 519 # Note that this is somewhat malformed: 520 # we shouldn't be sending Content-Length. 521 # RFC 2616 says the server should ignore it. 522 conn.putheader("Content-Length", len(body)) 523 conn.endheaders() 524 conn.send(body) 525 response = conn.getresponse() 526 self.status, self.headers, self.body = webtest.shb(response) 527 self.assertStatus('200 OK') 528 self.assertBody("thanks for 'xx\r\nxxxxyyyyy' (application/x-json)") 529 530 # Try a chunked request that exceeds server.max_request_body_size. 531 # Note that the delimiters and trailer are included. 532 body = "3e3\r\n" + ("x" * 995) + "\r\n0\r\n\r\n" 533 conn.putrequest("POST", "/upload", skip_host=True) 534 conn.putheader("Host", self.HOST) 535 conn.putheader("Transfer-Encoding", "chunked") 536 conn.putheader("Content-Type", "text/plain") 537 # Chunked requests don't need a content-length 538 ## conn.putheader("Content-Length", len(body)) 539 conn.endheaders() 540 conn.send(body) 541 response = conn.getresponse() 542 self.status, self.headers, self.body = webtest.shb(response) 543 self.assertStatus(413) 544 self.assertBody("") 545 conn.close()
546
547 - def test_Content_Length(self):
548 # Try a non-chunked request where Content-Length exceeds 549 # server.max_request_body_size. Assert error before body send. 550 self.persistent = True 551 conn = self.HTTP_CONN 552 conn.putrequest("POST", "/upload", skip_host=True) 553 conn.putheader("Host", self.HOST) 554 conn.putheader("Content-Type", "text/plain") 555 conn.putheader("Content-Length", 9999) 556 conn.endheaders() 557 response = conn.getresponse() 558 self.status, self.headers, self.body = webtest.shb(response) 559 self.assertStatus(413) 560 self.assertBody("") 561 conn.close()
562
563 - def test_HTTP10(self):
564 self.PROTOCOL = "HTTP/1.0" 565 if self.scheme == "https": 566 self.HTTP_CONN = httplib.HTTPSConnection 567 else: 568 self.HTTP_CONN = httplib.HTTPConnection 569 570 # Test a normal HTTP/1.0 request. 571 self.getPage("/page2") 572 self.assertStatus('200 OK') 573 self.assertBody(pov) 574 # Apache, for example, may emit a Connection header even for HTTP/1.0 575 ## self.assertNoHeader("Connection") 576 577 # Test a keep-alive HTTP/1.0 request. 578 self.persistent = True 579 580 self.getPage("/page3", headers=[("Connection", "Keep-Alive")]) 581 self.assertStatus('200 OK') 582 self.assertBody(pov) 583 self.assertHeader("Connection", "Keep-Alive") 584 585 # Remove the keep-alive header again. 586 self.getPage("/page3") 587 self.assertStatus('200 OK') 588 self.assertBody(pov)
589 # Apache, for example, may emit a Connection header even for HTTP/1.0 590 ## self.assertNoHeader("Connection") 591
592 - def test_598(self):
593 remote_data_conn = urllib.urlopen('%s://%s:%s/one_megabyte_of_a/' % 594 (self.scheme, self.HOST, self.PORT,)) 595 buf = remote_data_conn.read(512) 596 time.sleep(timeout * 0.6) 597 remaining = (1024 * 1024) - 512 598 while remaining: 599 data = remote_data_conn.read(remaining) 600 if not data: 601 break 602 else: 603 buf += data 604 remaining -= len(data) 605 606 self.assertEqual(len(buf), 1024 * 1024) 607 self.assertEqual(buf, "a" * 1024 * 1024) 608 self.assertEqual(remaining, 0) 609 remote_data_conn.close()
610 611 612 if __name__ == "__main__": 613 setup_server() 614 helper.testmain({'server.socket_timeout': timeout, 615 'server.socket_host': '127.0.0.1'}) 616