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
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
92
122
125
128
130 if cherrypy.server.protocol_version == "HTTP/1.1":
131 self.PROTOCOL = "HTTP/1.1"
132
133 self.persistent = True
134
135
136 self.getPage("/")
137 self.assertStatus('200 OK')
138 self.assertBody(pov)
139 self.assertNoHeader("Connection")
140
141
142 if set_cl:
143
144
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
154
155
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
173 self.assertRaises(httplib.NotConnected, self.getPage, "/")
174
175
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
186 self.getPage("/", headers=[("Connection", "Keep-Alive")])
187 self.assertStatus('200 OK')
188 self.assertBody(pov)
189 self.assertHeader("Connection", "Keep-Alive")
190
191
192 if set_cl:
193
194
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
204
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
214 self.assertRaises(httplib.NotConnected, self.getPage, "/")
215
217 if cherrypy.server.protocol_version != "HTTP/1.1":
218 print "skipped ",
219 return
220
221 self.PROTOCOL = "HTTP/1.1"
222
223
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
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
246 time.sleep(timeout * 2)
247
248
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
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
281
282 conn.send('GET /hello HTTP/1.1')
283
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
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
304 if cherrypy.server.protocol_version != "HTTP/1.1":
305 print "skipped ",
306 return
307
308 self.PROTOCOL = "HTTP/1.1"
309
310
311 self.persistent = True
312 conn = self.HTTP_CONN
313
314
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
321 conn._output('GET /hello HTTP/1.1')
322 conn._output("Host: %s" % self.HOST)
323 conn._send_output()
324
325
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
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
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
352
353
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
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
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
384 conn.send("I am a small file")
385
386
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
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
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
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
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
431 conn.send("x" * 1000)
432
433
434 response.begin()
435 self.status, self.headers, self.body = webtest.shb(response)
436 self.assertStatus(500)
437
438
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
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
456 conn.send("I am a small file")
457
458
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
466 if cherrypy.server.protocol_version != "HTTP/1.1":
467 print "skipped ",
468 return
469
470 self.PROTOCOL = "HTTP/1.1"
471
472
473 self.persistent = True
474
475
476 self.getPage("/")
477 self.assertStatus('200 OK')
478 self.assertBody(pov)
479 self.assertNoHeader("Connection")
480
481
482 self.getPage("/custom/204")
483 self.assertStatus(204)
484 self.assertNoHeader("Content-Length")
485 self.assertBody("")
486 self.assertNoHeader("Connection")
487
488
489 self.getPage("/custom/304")
490 self.assertStatus(304)
491 self.assertNoHeader("Content-Length")
492 self.assertBody("")
493 self.assertNoHeader("Connection")
494
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
503 print "skipped ",
504 return
505
506 self.PROTOCOL = "HTTP/1.1"
507
508
509 self.persistent = True
510 conn = self.HTTP_CONN
511
512
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
520
521
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
531
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
538
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
548
549
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
589
590
591
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