12 from eventlet import debug
13 from eventlet import event
14 from eventlet.green import socket as greensocket
15 from eventlet.green import ssl
16 from eventlet.green import subprocess
17 from eventlet import greenio
18 from eventlet import greenthread
19 from eventlet import support
20 from eventlet.support import six
21 from eventlet import tpool
22 from eventlet import wsgi
27 certificate_file = os.path.join(os.path.dirname(__file__), 'test_server.crt')
28 private_key_file = os.path.join(os.path.dirname(__file__), 'test_server.key')
31 HttpReadResult = collections.namedtuple(
33 'status headers_lower body headers_original')
36 def hello_world(env, start_response):
37 if env['PATH_INFO'] == 'notexist':
38 start_response('404 Not Found', [('Content-type', 'text/plain')])
41 start_response('200 OK', [('Content-type', 'text/plain')])
42 return ["hello world"]
45 def chunked_app(env, start_response):
46 start_response('200 OK', [('Content-type', 'text/plain')])
52 def chunked_fail_app(environ, start_response):
53 """http://rhodesmill.org/brandon/2013/chunked-wsgi/
55 headers = [('Content-Type', 'text/plain')]
56 start_response('200 OK', headers)
58 # We start streaming data just fine.
59 yield "The dwarves of yore made mighty spells,"
60 yield "While hammers fell like ringing bells"
62 # Then the back-end fails!
66 start_response('500 Error', headers, sys.exc_info())
69 # So rest of the response data is not available.
70 yield "In places deep, where dark things sleep,"
71 yield "In hollow halls beneath the fells."
74 def big_chunks(env, start_response):
75 start_response('200 OK', [('Content-type', 'text/plain')])
81 def use_write(env, start_response):
82 if env['PATH_INFO'] == '/a':
83 write = start_response('200 OK', [('Content-type', 'text/plain'),
84 ('Content-Length', '5')])
86 if env['PATH_INFO'] == '/b':
87 write = start_response('200 OK', [('Content-type', 'text/plain')])
92 def chunked_post(env, start_response):
93 start_response('200 OK', [('Content-type', 'text/plain')])
94 if env['PATH_INFO'] == '/a':
95 return [env['wsgi.input'].read()]
96 elif env['PATH_INFO'] == '/b':
97 return [x for x in iter(lambda: env['wsgi.input'].read(4096), '')]
98 elif env['PATH_INFO'] == '/c':
99 return [x for x in iter(lambda: env['wsgi.input'].read(1), '')]
102 def already_handled(env, start_response):
103 start_response('200 OK', [('Content-type', 'text/plain')])
104 return wsgi.ALREADY_HANDLED
109 self.application = hello_world
111 def __call__(self, env, start_response):
112 return self.application(env, start_response)
115 class IterableApp(object):
117 def __init__(self, send_start_response=False, return_val=wsgi.ALREADY_HANDLED):
118 self.send_start_response = send_start_response
119 self.return_val = return_val
122 def __call__(self, env, start_response):
124 if self.send_start_response:
125 start_response('200 OK', [('Content-type', 'text/plain')])
126 return self.return_val
129 class IterableSite(Site):
130 def __call__(self, env, start_response):
131 it = self.application(env, start_response)
136 CONTENT_LENGTH = 'content-length'
148 class ConnectionClosed(Exception):
155 response_line = fd.readline().rstrip('\r\n')
156 except socket.error as exc:
157 if support.get_errno(exc) == 10053:
158 raise ConnectionClosed
160 if not response_line:
161 raise ConnectionClosed(response_line)
169 header_lines.append(line)
171 headers_original = {}
173 for x in header_lines:
177 key, value = x.split(':', 1)
179 value = value.lstrip()
180 key_lower = key.lower()
181 # FIXME: Duplicate headers are allowed as per HTTP RFC standard,
182 # the client and/or intermediate proxies are supposed to treat them
183 # as a single header with values concatenated using space (' ') delimiter.
184 assert key_lower not in headers_lower, "header duplicated: {0}".format(key)
185 headers_original[key] = value
186 headers_lower[key_lower] = value
188 content_length_str = headers_lower.get(CONTENT_LENGTH.lower(), '')
189 if content_length_str:
190 num = int(content_length_str)
196 result = HttpReadResult(
197 status=response_line,
198 headers_lower=headers_lower,
200 headers_original=headers_original)
204 class _TestBase(tests.LimitedTestCase):
206 super(_TestBase, self).setUp()
207 self.logfile = six.StringIO()
214 greenthread.kill(self.killer)
216 super(_TestBase, self).tearDown()
218 def spawn_server(self, **kwargs):
219 """Spawns a new wsgi server with the given arguments using
220 :meth:`spawn_thread`.
222 Sets self.port to the port of the server
224 new_kwargs = dict(max_size=128,
227 new_kwargs.update(kwargs)
229 if 'sock' not in new_kwargs:
230 new_kwargs['sock'] = eventlet.listen(('localhost', 0))
232 self.port = new_kwargs['sock'].getsockname()[1]
233 self.spawn_thread(wsgi.server, **new_kwargs)
235 def spawn_thread(self, target, **kwargs):
236 """Spawns a new greenthread using specified target and arguments.
238 Kills any previously-running server and sets self.killer to the
239 greenthread running the target.
241 eventlet.sleep(0) # give previous server a chance to start
243 greenthread.kill(self.killer)
245 self.killer = eventlet.spawn_n(target, **kwargs)
248 raise NotImplementedError
251 class TestHttpd(_TestBase):
255 def test_001_server(self):
256 sock = eventlet.connect(
257 ('localhost', self.port))
259 fd = sock.makefile('rw')
260 fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n')
264 # The server responds with the maximum version it supports
265 assert result.startswith('HTTP'), result
266 assert result.endswith('hello world'), result
268 def test_002_keepalive(self):
269 sock = eventlet.connect(
270 ('localhost', self.port))
272 fd = sock.makefile('w')
273 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
276 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
282 def test_003_passing_non_int_to_read(self):
283 # This should go in greenio_test
284 sock = eventlet.connect(
285 ('localhost', self.port))
287 fd = sock.makefile('rw')
288 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
290 cancel = eventlet.Timeout(1, RuntimeError)
291 self.assertRaises(TypeError, fd.read, "This shouldn't work")
295 def test_004_close_keepalive(self):
296 sock = eventlet.connect(
297 ('localhost', self.port))
299 fd = sock.makefile('w')
300 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
303 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
306 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
308 self.assertRaises(ConnectionClosed, read_http, sock)
312 def test_005_run_apachebench(self):
313 url = 'http://localhost:12346/'
316 [tests.find_command('ab'), '-c', '64', '-n', '1024', '-k', url],
317 stdout=subprocess.PIPE)
319 def test_006_reject_long_urls(self):
320 sock = eventlet.connect(
321 ('localhost', self.port))
323 for ii in range(3000):
324 path_parts.append('path')
325 path = '/'.join(path_parts)
326 request = 'GET /%s HTTP/1.0\r\nHost: localhost\r\n\r\n' % path
327 fd = sock.makefile('rw')
330 result = fd.readline()
332 # windows closes the socket before the data is flushed,
333 # so we never get anything back
334 status = result.split(b' ')[1]
335 self.assertEqual(status, b'414')
338 def test_007_get_arg(self):
339 # define a new handler that does a get_arg as well as a read_body
340 def new_app(env, start_response):
341 body = env['wsgi.input'].read()
342 a = cgi.parse_qs(body).get('a', [1])[0]
343 start_response('200 OK', [('Content-type', 'text/plain')])
344 return [six.b('a is %s, body is %s' % (a, body))]
346 self.site.application = new_app
347 sock = eventlet.connect(
348 ('localhost', self.port))
349 request = '\r\n'.join((
355 fd = sock.makefile('w')
359 # send some junk after the actual request
360 fd.write(b'01234567890123456789')
361 result = read_http(sock)
362 self.assertEqual(result.body, 'a is a, body is a=a')
365 def test_008_correctresponse(self):
366 sock = eventlet.connect(('localhost', self.port))
368 fd = sock.makefile('w')
369 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
371 result_200 = read_http(sock)
372 fd.write(b'GET /notexist HTTP/1.1\r\nHost: localhost\r\n\r\n')
375 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
377 result_test = read_http(sock)
378 self.assertEqual(result_200.status, result_test.status)
382 def test_009_chunked_response(self):
383 self.site.application = chunked_app
384 sock = eventlet.connect(
385 ('localhost', self.port))
387 fd = sock.makefile('rw')
388 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
390 assert 'Transfer-Encoding: chunked' in fd.read()
392 def test_010_no_chunked_http_1_0(self):
393 self.site.application = chunked_app
394 sock = eventlet.connect(
395 ('localhost', self.port))
397 fd = sock.makefile('rw')
398 fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n')
400 assert 'Transfer-Encoding: chunked' not in fd.read()
402 def test_011_multiple_chunks(self):
403 self.site.application = big_chunks
404 sock = eventlet.connect(
405 ('localhost', self.port))
407 fd = sock.makefile('rw')
408 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
417 assert 'Transfer-Encoding: chunked' in headers
419 chunklen = int(fd.readline(), 16)
424 chunklen = int(fd.readline(), 16)
427 # Require a CRLF to close the message body
428 self.assertEqual(response, '\r\n')
430 @tests.skip_if_no_ssl
431 def test_012_ssl_server(self):
432 def wsgi_app(environ, start_response):
433 start_response('200 OK', {})
434 return [environ['wsgi.input'].read()]
436 certificate_file = os.path.join(os.path.dirname(__file__), 'test_server.crt')
437 private_key_file = os.path.join(os.path.dirname(__file__), 'test_server.key')
439 server_sock = eventlet.wrap_ssl(eventlet.listen(('localhost', 0)),
440 certfile=certificate_file,
441 keyfile=private_key_file,
443 self.spawn_server(sock=server_sock, site=wsgi_app)
445 sock = eventlet.connect(('localhost', self.port))
446 sock = eventlet.wrap_ssl(sock)
448 b'POST /foo HTTP/1.1\r\nHost: localhost\r\n'
449 b'Connection: close\r\nContent-length:3\r\n\r\nabc')
450 result = sock.read(8192)
451 self.assertEqual(result[-3:], 'abc')
453 @tests.skip_if_no_ssl
454 def test_013_empty_return(self):
455 def wsgi_app(environ, start_response):
456 start_response("200 OK", [])
459 certificate_file = os.path.join(os.path.dirname(__file__), 'test_server.crt')
460 private_key_file = os.path.join(os.path.dirname(__file__), 'test_server.key')
461 server_sock = eventlet.wrap_ssl(eventlet.listen(('localhost', 0)),
462 certfile=certificate_file,
463 keyfile=private_key_file,
465 self.spawn_server(sock=server_sock, site=wsgi_app)
467 sock = eventlet.connect(('localhost', server_sock.getsockname()[1]))
468 sock = eventlet.wrap_ssl(sock)
469 sock.write(b'GET /foo HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
470 result = sock.read(8192)
471 self.assertEqual(result[-4:], '\r\n\r\n')
473 def test_014_chunked_post(self):
474 self.site.application = chunked_post
475 sock = eventlet.connect(('localhost', self.port))
476 fd = sock.makefile('rw')
477 fd.write('PUT /a HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n'
478 'Transfer-Encoding: chunked\r\n\r\n'
479 '2\r\noh\r\n4\r\n hai\r\n0\r\n\r\n'.encode())
482 if fd.readline() == '\r\n':
485 assert response == 'oh hai', 'invalid response %s' % response
487 sock = eventlet.connect(('localhost', self.port))
488 fd = sock.makefile('rw')
489 fd.write('PUT /b HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n'
490 'Transfer-Encoding: chunked\r\n\r\n'
491 '2\r\noh\r\n4\r\n hai\r\n0\r\n\r\n'.encode())
494 if fd.readline() == '\r\n':
497 assert response == 'oh hai', 'invalid response %s' % response
499 sock = eventlet.connect(('localhost', self.port))
500 fd = sock.makefile('rw')
501 fd.write('PUT /c HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n'
502 'Transfer-Encoding: chunked\r\n\r\n'
503 '2\r\noh\r\n4\r\n hai\r\n0\r\n\r\n'.encode())
506 if fd.readline() == '\r\n':
508 response = fd.read(8192)
509 assert response == 'oh hai', 'invalid response %s' % response
511 def test_015_write(self):
512 self.site.application = use_write
513 sock = eventlet.connect(('localhost', self.port))
514 fd = sock.makefile('w')
515 fd.write(b'GET /a HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
517 result1 = read_http(sock)
518 assert 'content-length' in result1.headers_lower
520 sock = eventlet.connect(('localhost', self.port))
521 fd = sock.makefile('w')
522 fd.write(b'GET /b HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
524 result2 = read_http(sock)
525 assert 'transfer-encoding' in result2.headers_lower
526 assert result2.headers_lower['transfer-encoding'] == 'chunked'
528 def test_016_repeated_content_length(self):
529 """content-length header was being doubled up if it was set in
530 start_response and could also be inferred from the iterator
532 def wsgi_app(environ, start_response):
533 start_response('200 OK', [('Content-Length', '7')])
535 self.site.application = wsgi_app
536 sock = eventlet.connect(('localhost', self.port))
537 fd = sock.makefile('rw')
538 fd.write(b'GET /a HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
546 header_lines.append(line)
547 self.assertEqual(1, len(
548 [l for l in header_lines if l.lower().startswith('content-length')]))
550 @tests.skip_if_no_ssl
551 def test_017_ssl_zeroreturnerror(self):
553 def server(sock, site, log):
555 serv = wsgi.Server(sock, sock.getsockname(), site, log)
556 client_socket = sock.accept()
557 serv.process_request(client_socket)
560 traceback.print_exc()
563 def wsgi_app(environ, start_response):
564 start_response('200 OK', [])
565 return [environ['wsgi.input'].read()]
567 certificate_file = os.path.join(os.path.dirname(__file__), 'test_server.crt')
568 private_key_file = os.path.join(os.path.dirname(__file__), 'test_server.key')
570 sock = eventlet.wrap_ssl(
571 eventlet.listen(('localhost', 0)),
572 certfile=certificate_file, keyfile=private_key_file,
574 server_coro = eventlet.spawn(server, sock, wsgi_app, self.logfile)
576 client = eventlet.connect(('localhost', sock.getsockname()[1]))
577 client = eventlet.wrap_ssl(client)
578 client.write(b'X') # non-empty payload so that SSL handshake occurs
579 greenio.shutdown_safe(client)
582 success = server_coro.wait()
585 def test_018_http_10_keepalive(self):
586 # verify that if an http/1.0 client sends connection: keep-alive
587 # that we don't close the connection
588 sock = eventlet.connect(
589 ('localhost', self.port))
591 fd = sock.makefile('w')
592 fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n')
595 result1 = read_http(sock)
596 assert 'connection' in result1.headers_lower
597 self.assertEqual('keep-alive', result1.headers_lower['connection'])
598 # repeat request to verify connection is actually still open
599 fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n')
601 result2 = read_http(sock)
602 assert 'connection' in result2.headers_lower
603 self.assertEqual('keep-alive', result2.headers_lower['connection'])
606 def test_019_fieldstorage_compat(self):
607 def use_fieldstorage(environ, start_response):
608 cgi.FieldStorage(fp=environ['wsgi.input'], environ=environ)
609 start_response('200 OK', [('Content-type', 'text/plain')])
612 self.site.application = use_fieldstorage
613 sock = eventlet.connect(
614 ('localhost', self.port))
616 fd = sock.makefile('rw')
617 fd.write('POST / HTTP/1.1\r\n'
618 'Host: localhost\r\n'
619 'Connection: close\r\n'
620 'Transfer-Encoding: chunked\r\n\r\n'
622 '4\r\n hai\r\n0\r\n\r\n'.encode())
624 assert 'hello!' in fd.read()
626 def test_020_x_forwarded_for(self):
628 b'GET / HTTP/1.1\r\nHost: localhost\r\n'
629 + b'X-Forwarded-For: 1.2.3.4, 5.6.7.8\r\n\r\n'
632 sock = eventlet.connect(('localhost', self.port))
633 sock.sendall(request_bytes)
636 assert '1.2.3.4,5.6.7.8,127.0.0.1' in self.logfile.getvalue()
638 # turning off the option should work too
639 self.logfile = six.StringIO()
640 self.spawn_server(log_x_forwarded_for=False)
642 sock = eventlet.connect(('localhost', self.port))
643 sock.sendall(request_bytes)
646 assert '1.2.3.4' not in self.logfile.getvalue()
647 assert '5.6.7.8' not in self.logfile.getvalue()
648 assert '127.0.0.1' in self.logfile.getvalue()
650 def test_socket_remains_open(self):
651 greenthread.kill(self.killer)
652 server_sock = eventlet.listen(('localhost', 0))
653 server_sock_2 = server_sock.dup()
654 self.spawn_server(sock=server_sock_2)
655 # do a single req/response to verify it's up
656 sock = eventlet.connect(('localhost', self.port))
657 fd = sock.makefile('rw')
658 fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n')
660 result = fd.read(1024)
662 assert result.startswith('HTTP'), result
663 assert result.endswith('hello world')
665 # shut down the server and verify the server_socket fd is still open,
666 # but the actual socketobject passed in to wsgi.server is closed
667 greenthread.kill(self.killer)
668 eventlet.sleep(0) # make the kill go through
670 server_sock_2.accept()
671 # shouldn't be able to use this one anymore
672 except socket.error as exc:
673 self.assertEqual(support.get_errno(exc), errno.EBADF)
674 self.spawn_server(sock=server_sock)
675 sock = eventlet.connect(('localhost', self.port))
676 fd = sock.makefile('rw')
677 fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n')
679 result = fd.read(1024)
681 assert result.startswith('HTTP'), result
682 assert result.endswith('hello world')
684 def test_021_environ_clobbering(self):
685 def clobberin_time(environ, start_response):
687 'wsgi.version', 'wsgi.url_scheme',
688 'wsgi.input', 'wsgi.errors', 'wsgi.multithread',
689 'wsgi.multiprocess', 'wsgi.run_once', 'REQUEST_METHOD',
690 'SCRIPT_NAME', 'RAW_PATH_INFO', 'PATH_INFO', 'QUERY_STRING',
691 'CONTENT_TYPE', 'CONTENT_LENGTH', 'SERVER_NAME', 'SERVER_PORT',
693 environ[environ_var] = None
694 start_response('200 OK', [('Content-type', 'text/plain')])
696 self.site.application = clobberin_time
697 sock = eventlet.connect(('localhost', self.port))
698 fd = sock.makefile('rw')
699 fd.write('GET / HTTP/1.1\r\n'
700 'Host: localhost\r\n'
701 'Connection: close\r\n'
704 assert '200 OK' in fd.read()
706 def test_022_custom_pool(self):
707 # just test that it accepts the parameter for now
708 # TODO(waitall): test that it uses the pool and that you can waitall() to
709 # ensure that all clients finished
710 p = eventlet.GreenPool(5)
711 self.spawn_server(custom_pool=p)
713 # this stuff is copied from test_001_server, could be better factored
714 sock = eventlet.connect(
715 ('localhost', self.port))
716 fd = sock.makefile('rw')
717 fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n')
721 assert result.startswith('HTTP'), result
722 assert result.endswith('hello world')
724 def test_023_bad_content_length(self):
725 sock = eventlet.connect(
726 ('localhost', self.port))
727 fd = sock.makefile('rw')
728 fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\nContent-length: argh\r\n\r\n')
732 assert result.startswith('HTTP'), result
733 assert '400 Bad Request' in result
734 assert '500' not in result
736 def test_024_expect_100_continue(self):
737 def wsgi_app(environ, start_response):
738 if int(environ['CONTENT_LENGTH']) > 1024:
739 start_response('417 Expectation Failed', [('Content-Length', '7')])
742 text = environ['wsgi.input'].read()
743 start_response('200 OK', [('Content-Length', str(len(text)))])
745 self.site.application = wsgi_app
746 sock = eventlet.connect(('localhost', self.port))
747 fd = sock.makefile('rw')
748 fd.write(b'PUT / HTTP/1.1\r\nHost: localhost\r\nContent-length: 1025\r\n'
749 b'Expect: 100-continue\r\n\r\n')
751 result = read_http(sock)
752 self.assertEqual(result.status, 'HTTP/1.1 417 Expectation Failed')
753 self.assertEqual(result.body, 'failure')
755 b'PUT / HTTP/1.1\r\nHost: localhost\r\nContent-length: 7\r\n'
756 b'Expect: 100-continue\r\n\r\ntesting')
764 header_lines.append(line)
765 assert header_lines[0].startswith('HTTP/1.1 100 Continue')
772 header_lines.append(line)
773 assert header_lines[0].startswith('HTTP/1.1 200 OK')
774 self.assertEqual(fd.read(7), 'testing')
778 def test_024a_expect_100_continue_with_headers(self):
779 def wsgi_app(environ, start_response):
780 if int(environ['CONTENT_LENGTH']) > 1024:
781 start_response('417 Expectation Failed', [('Content-Length', '7')])
784 environ['wsgi.input'].set_hundred_continue_response_headers(
785 [('Hundred-Continue-Header-1', 'H1'),
786 ('Hundred-Continue-Header-2', 'H2'),
787 ('Hundred-Continue-Header-k', 'Hk')])
788 text = environ['wsgi.input'].read()
789 start_response('200 OK', [('Content-Length', str(len(text)))])
791 self.site.application = wsgi_app
792 sock = eventlet.connect(('localhost', self.port))
793 fd = sock.makefile('rw')
794 fd.write(b'PUT / HTTP/1.1\r\nHost: localhost\r\nContent-length: 1025\r\n'
795 b'Expect: 100-continue\r\n\r\n')
797 result = read_http(sock)
798 self.assertEqual(result.status, 'HTTP/1.1 417 Expectation Failed')
799 self.assertEqual(result.body, 'failure')
801 b'PUT / HTTP/1.1\r\nHost: localhost\r\nContent-length: 7\r\n'
802 b'Expect: 100-continue\r\n\r\ntesting')
810 header_lines.append(line.strip())
811 assert header_lines[0].startswith('HTTP/1.1 100 Continue')
812 headers = dict((k, v) for k, v in (h.split(': ', 1) for h in header_lines[1:]))
813 assert 'Hundred-Continue-Header-1' in headers
814 assert 'Hundred-Continue-Header-2' in headers
815 assert 'Hundred-Continue-Header-K' in headers
816 self.assertEqual('H1', headers['Hundred-Continue-Header-1'])
817 self.assertEqual('H2', headers['Hundred-Continue-Header-2'])
818 self.assertEqual('Hk', headers['Hundred-Continue-Header-K'])
825 header_lines.append(line)
826 assert header_lines[0].startswith('HTTP/1.1 200 OK')
827 self.assertEqual(fd.read(7), 'testing')
831 def test_025_accept_errors(self):
832 debug.hub_exceptions(True)
833 listener = greensocket.socket()
834 listener.bind(('localhost', 0))
835 # NOT calling listen, to trigger the error
836 self.logfile = six.StringIO()
837 self.spawn_server(sock=listener)
838 old_stderr = sys.stderr
840 sys.stderr = self.logfile
841 eventlet.sleep(0) # need to enter server loop
843 eventlet.connect(('localhost', self.port))
844 self.fail("Didn't expect to connect")
845 except socket.error as exc:
846 self.assertEqual(support.get_errno(exc), errno.ECONNREFUSED)
848 log_content = self.logfile.getvalue()
849 assert 'Invalid argument' in log_content, log_content
851 sys.stderr = old_stderr
852 debug.hub_exceptions(False)
854 def test_026_log_format(self):
855 self.spawn_server(log_format="HI %(request_line)s HI")
856 sock = eventlet.connect(('localhost', self.port))
857 sock.sendall(b'GET /yo! HTTP/1.1\r\nHost: localhost\r\n\r\n')
860 assert '\nHI GET /yo! HTTP/1.1 HI\n' in self.logfile.getvalue(), self.logfile.getvalue()
862 def test_close_chunked_with_1_0_client(self):
863 # verify that if we return a generator from our app
864 # and we're not speaking with a 1.1 client, that we
865 # close the connection
866 self.site.application = chunked_app
867 sock = eventlet.connect(('localhost', self.port))
869 sock.sendall(b'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n')
871 result = read_http(sock)
872 self.assertEqual(result.headers_lower['connection'], 'close')
873 self.assertNotEqual(result.headers_lower.get('transfer-encoding'), 'chunked')
874 self.assertEqual(result.body, "thisischunked")
876 def test_minimum_chunk_size_parameter_leaves_httpprotocol_class_member_intact(self):
877 start_size = wsgi.HttpProtocol.minimum_chunk_size
879 self.spawn_server(minimum_chunk_size=start_size * 2)
880 sock = eventlet.connect(('localhost', self.port))
881 sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
884 self.assertEqual(wsgi.HttpProtocol.minimum_chunk_size, start_size)
887 def test_error_in_chunked_closes_connection(self):
888 # From http://rhodesmill.org/brandon/2013/chunked-wsgi/
889 self.spawn_server(minimum_chunk_size=1)
891 self.site.application = chunked_fail_app
892 sock = eventlet.connect(('localhost', self.port))
894 sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
896 result = read_http(sock)
897 self.assertEqual(result.status, 'HTTP/1.1 200 OK')
898 self.assertEqual(result.headers_lower.get('transfer-encoding'), 'chunked')
900 b'27\r\nThe dwarves of yore made mighty spells,\r\n'
901 b'25\r\nWhile hammers fell like ringing bells\r\n')
902 self.assertEqual(result.body, expected_body)
904 # verify that socket is closed by server
905 self.assertEqual(sock.recv(1), '')
907 def test_026_http_10_nokeepalive(self):
908 # verify that if an http/1.0 client sends connection: keep-alive
909 # and the server doesn't accept keep-alives, we close the connection
910 self.spawn_server(keepalive=False)
911 sock = eventlet.connect(
912 ('localhost', self.port))
914 sock.sendall(b'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n')
915 result = read_http(sock)
916 self.assertEqual(result.headers_lower['connection'], 'close')
918 def test_027_keepalive_chunked(self):
919 self.site.application = chunked_post
920 sock = eventlet.connect(('localhost', self.port))
921 fd = sock.makefile('w')
923 b'Host: localhost\r\nTransfer-Encoding: chunked\r\n\r\n' +
924 b'10\r\n0123456789abcdef\r\n0\r\n\r\n')
925 fd.write(b'PUT /a HTTP/1.1\r\n' + common_suffix)
928 fd.write(b'PUT /b HTTP/1.1\r\n' + common_suffix)
931 fd.write(b'PUT /c HTTP/1.1\r\n' + common_suffix)
934 fd.write(b'PUT /a HTTP/1.1\r\n' + common_suffix)
939 @tests.skip_if_no_ssl
940 def test_028_ssl_handshake_errors(self):
945 wsgi.server(sock=sock, site=hello_world, log=self.logfile)
946 errored[0] = 'SSL handshake error caused wsgi.server to exit.'
947 except greenthread.greenlet.GreenletExit:
949 except Exception as e:
950 errored[0] = 'SSL handshake error raised exception %s.' % e
952 for data in ('', 'GET /non-ssl-request HTTP/1.0\r\n\r\n'):
953 srv_sock = eventlet.wrap_ssl(
954 eventlet.listen(('localhost', 0)),
955 certfile=certificate_file, keyfile=private_key_file,
957 port = srv_sock.getsockname()[1]
958 g = eventlet.spawn_n(server, srv_sock)
959 client = eventlet.connect(('localhost', port))
960 if data: # send non-ssl request
961 client.sendall(data.encode())
962 else: # close sock prematurely
964 eventlet.sleep(0) # let context switch back to server
965 assert not errored[0], errored[0]
966 # make another request to ensure the server's still alive
968 client = ssl.wrap_socket(eventlet.connect(('localhost', port)))
969 client.write(b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n')
970 result = client.read()
971 assert result.startswith('HTTP'), result
972 assert result.endswith('hello world')
974 pass # TODO(openssl): should test with OpenSSL
977 def test_029_posthooks(self):
978 posthook1_count = [0]
979 posthook2_count = [0]
981 def posthook1(env, value, multiplier=1):
982 self.assertEqual(env['local.test'], 'test_029_posthooks')
983 posthook1_count[0] += value * multiplier
985 def posthook2(env, value, divisor=1):
986 self.assertEqual(env['local.test'], 'test_029_posthooks')
987 posthook2_count[0] += value / divisor
989 def one_posthook_app(env, start_response):
990 env['local.test'] = 'test_029_posthooks'
991 if 'eventlet.posthooks' not in env:
992 start_response('500 eventlet.posthooks not supported',
993 [('Content-Type', 'text/plain')])
995 env['eventlet.posthooks'].append(
996 (posthook1, (2,), {'multiplier': 3}))
997 start_response('200 OK', [('Content-Type', 'text/plain')])
999 self.site.application = one_posthook_app
1000 sock = eventlet.connect(('localhost', self.port))
1001 fp = sock.makefile('rw')
1002 fp.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
1004 self.assertEqual(fp.readline(), 'HTTP/1.1 200 OK\r\n')
1007 self.assertEqual(posthook1_count[0], 6)
1008 self.assertEqual(posthook2_count[0], 0)
1010 def two_posthook_app(env, start_response):
1011 env['local.test'] = 'test_029_posthooks'
1012 if 'eventlet.posthooks' not in env:
1013 start_response('500 eventlet.posthooks not supported',
1014 [('Content-Type', 'text/plain')])
1016 env['eventlet.posthooks'].append(
1017 (posthook1, (4,), {'multiplier': 5}))
1018 env['eventlet.posthooks'].append(
1019 (posthook2, (100,), {'divisor': 4}))
1020 start_response('200 OK', [('Content-Type', 'text/plain')])
1022 self.site.application = two_posthook_app
1023 sock = eventlet.connect(('localhost', self.port))
1024 fp = sock.makefile('rw')
1025 fp.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
1027 self.assertEqual(fp.readline(), 'HTTP/1.1 200 OK\r\n')
1030 self.assertEqual(posthook1_count[0], 26)
1031 self.assertEqual(posthook2_count[0], 25)
1033 def test_030_reject_long_header_lines(self):
1034 sock = eventlet.connect(('localhost', self.port))
1035 request = 'GET / HTTP/1.0\r\nHost: localhost\r\nLong: %s\r\n\r\n' % \
1037 fd = sock.makefile('rw')
1038 fd.write(request.encode())
1040 result = read_http(sock)
1041 self.assertEqual(result.status, 'HTTP/1.0 400 Header Line Too Long')
1044 def test_031_reject_large_headers(self):
1045 sock = eventlet.connect(('localhost', self.port))
1046 headers = 'Name: Value\r\n' * 5050
1047 request = 'GET / HTTP/1.0\r\nHost: localhost\r\n%s\r\n\r\n' % headers
1048 fd = sock.makefile('rwb')
1049 fd.write(request.encode())
1051 result = read_http(sock)
1052 self.assertEqual(result.status, 'HTTP/1.0 400 Headers Too Large')
1055 def test_032_wsgi_input_as_iterable(self):
1056 # https://bitbucket.org/eventlet/eventlet/issue/150
1057 # env['wsgi.input'] returns a single byte at a time
1058 # when used as an iterator
1061 def echo_by_iterating(env, start_response):
1062 start_response('200 OK', [('Content-type', 'text/plain')])
1063 for chunk in env['wsgi.input']:
1067 self.site.application = echo_by_iterating
1068 upload_data = '123456789abcdef' * 100
1070 'POST / HTTP/1.0\r\n'
1071 'Host: localhost\r\n'
1072 'Content-Length: %i\r\n\r\n%s'
1073 ) % (len(upload_data), upload_data)
1074 sock = eventlet.connect(('localhost', self.port))
1075 fd = sock.makefile('rwb')
1076 fd.write(request.encode())
1078 result = read_http(sock)
1079 self.assertEqual(result.body, upload_data)
1081 self.assertEqual(g[0], 1)
1083 def test_zero_length_chunked_response(self):
1084 def zero_chunked_app(env, start_response):
1085 start_response('200 OK', [('Content-type', 'text/plain')])
1088 self.site.application = zero_chunked_app
1089 sock = eventlet.connect(
1090 ('localhost', self.port))
1092 fd = sock.makefile('rw')
1093 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
1095 response = fd.read().split('\r\n')
1102 assert 'Transfer-Encoding: chunked' in ''.join(headers)
1103 # should only be one chunk of zero size with two blank lines
1104 # (one terminates the chunk, one terminates the body)
1105 self.assertEqual(response, ['0', '', ''])
1107 def test_configurable_url_length_limit(self):
1108 self.spawn_server(url_length_limit=20000)
1109 sock = eventlet.connect(
1110 ('localhost', self.port))
1112 request = 'GET /%s HTTP/1.0\r\nHost: localhost\r\n\r\n' % path
1113 fd = sock.makefile('rw')
1116 result = fd.readline()
1118 # windows closes the socket before the data is flushed,
1119 # so we never get anything back
1120 status = result.split(' ')[1]
1121 self.assertEqual(status, '200')
1124 def test_aborted_chunked_post(self):
1125 read_content = event.Event()
1128 def chunk_reader(env, start_response):
1130 content = env['wsgi.input'].read(1024)
1134 read_content.send(content)
1135 start_response('200 OK', [('Content-Type', 'text/plain')])
1137 self.site.application = chunk_reader
1138 expected_body = 'a bunch of stuff'
1139 data = "\r\n".join(['PUT /somefile HTTP/1.0',
1140 'Transfer-Encoding: chunked',
1144 # start PUT-ing some chunked data but close prematurely
1145 sock = eventlet.connect(('127.0.0.1', self.port))
1146 sock.sendall(data.encode())
1148 # the test passes if we successfully get here, and read all the data
1149 # in spite of the early close
1150 self.assertEqual(read_content.wait(), 'ok')
1153 def test_exceptions_close_connection(self):
1154 def wsgi_app(environ, start_response):
1155 raise RuntimeError("intentional error")
1156 self.site.application = wsgi_app
1157 sock = eventlet.connect(('localhost', self.port))
1158 fd = sock.makefile('rw')
1159 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
1161 result = read_http(sock)
1162 self.assertEqual(result.status, 'HTTP/1.1 500 Internal Server Error')
1163 self.assertEqual(result.headers_lower['connection'], 'close')
1164 assert 'transfer-encoding' not in result.headers_lower
1166 def test_unicode_raises_error(self):
1167 def wsgi_app(environ, start_response):
1168 start_response("200 OK", [])
1170 yield u"non-encodable unicode: \u0230"
1171 self.site.application = wsgi_app
1172 sock = eventlet.connect(('localhost', self.port))
1173 fd = sock.makefile('rw')
1174 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
1176 result = read_http(sock)
1177 self.assertEqual(result.status, 'HTTP/1.1 500 Internal Server Error')
1178 self.assertEqual(result.headers_lower['connection'], 'close')
1179 assert 'unicode' in result.body
1181 def test_path_info_decoding(self):
1182 def wsgi_app(environ, start_response):
1183 start_response("200 OK", [])
1184 yield "decoded: %s" % environ['PATH_INFO']
1185 yield "raw: %s" % environ['RAW_PATH_INFO']
1186 self.site.application = wsgi_app
1187 sock = eventlet.connect(('localhost', self.port))
1188 fd = sock.makefile('rw')
1189 fd.write(b'GET /a*b@%40%233 HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
1191 result = read_http(sock)
1192 self.assertEqual(result.status, 'HTTP/1.1 200 OK')
1193 assert 'decoded: /a*b@@#3' in result.body
1194 assert 'raw: /a*b@%40%233' in result.body
1196 def test_ipv6(self):
1198 sock = eventlet.listen(('::1', 0), family=socket.AF_INET6)
1199 except (socket.gaierror, socket.error): # probably no ipv6
1201 log = six.StringIO()
1202 # first thing the server does is try to log the IP it's bound to
1206 wsgi.server(sock=sock, log=log, site=Site())
1208 log.write(b'broken')
1210 self.spawn_thread(run_server)
1212 logval = log.getvalue()
1215 logval = log.getvalue()
1216 if 'broked' in logval:
1217 self.fail('WSGI server raised exception with ipv6 socket')
1219 def test_debug(self):
1220 self.spawn_server(debug=False)
1222 def crasher(env, start_response):
1223 raise RuntimeError("intentional crash")
1224 self.site.application = crasher
1226 sock = eventlet.connect(('localhost', self.port))
1227 fd = sock.makefile('w')
1228 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
1230 result1 = read_http(sock)
1231 self.assertEqual(result1.status, 'HTTP/1.1 500 Internal Server Error')
1232 self.assertEqual(result1.body, '')
1233 self.assertEqual(result1.headers_lower['connection'], 'close')
1234 assert 'transfer-encoding' not in result1.headers_lower
1236 # verify traceback when debugging enabled
1237 self.spawn_server(debug=True)
1238 self.site.application = crasher
1239 sock = eventlet.connect(('localhost', self.port))
1240 fd = sock.makefile('w')
1241 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
1243 result2 = read_http(sock)
1244 self.assertEqual(result2.status, 'HTTP/1.1 500 Internal Server Error')
1245 assert 'intentional crash' in result2.body
1246 assert 'RuntimeError' in result2.body
1247 assert 'Traceback' in result2.body
1248 self.assertEqual(result2.headers_lower['connection'], 'close')
1249 assert 'transfer-encoding' not in result2.headers_lower
1251 def test_client_disconnect(self):
1252 """Issue #95 Server must handle disconnect from client in the middle of response
1254 def long_response(environ, start_response):
1255 start_response('200 OK', [('Content-Length', '9876')])
1258 server_sock = eventlet.listen(('localhost', 0))
1259 self.port = server_sock.getsockname()[1]
1260 server = wsgi.Server(server_sock, server_sock.getsockname(), long_response,
1264 sock = eventlet.connect(server_sock.getsockname())
1265 sock.send(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
1268 request_thread = eventlet.spawn(make_request)
1269 server_conn = server_sock.accept()
1270 # Next line must not raise IOError -32 Broken pipe
1271 server.process_request(server_conn)
1272 request_thread.wait()
1275 def test_server_connection_timeout_exception(self):
1276 # Handle connection socket timeouts
1277 # https://bitbucket.org/eventlet/eventlet/issue/143/
1278 # Runs tests.wsgi_test_conntimeout in a separate process.
1279 testcode_path = os.path.join(
1280 os.path.dirname(os.path.abspath(__file__)),
1281 'wsgi_test_conntimeout.py')
1282 output = tests.run_python(testcode_path)
1283 sections = output.split(b"SEPERATOR_SENTINEL")
1284 # first section is empty
1285 self.assertEqual(3, len(sections), output)
1286 # if the "BOOM" check fails, it's because our timeout didn't happen
1287 # (if eventlet stops using file.readline() to read HTTP headers,
1289 for runlog in sections[1:]:
1290 debug = False if "debug set to: False" in runlog else True
1292 self.assertTrue("timed out" in runlog)
1293 self.assertTrue("BOOM" in runlog)
1294 self.assertFalse("Traceback" in runlog)
1296 def test_server_socket_timeout(self):
1297 self.spawn_server(socket_timeout=0.1)
1298 sock = eventlet.connect(('localhost', self.port))
1299 sock.send(b'GET / HTTP/1.1\r\n')
1303 assert False, 'Expected ConnectionClosed exception'
1304 except ConnectionClosed:
1307 def test_disable_header_name_capitalization(self):
1308 # Disable HTTP header name capitalization
1310 # https://github.com/eventlet/eventlet/issues/80
1311 random_case_header = ('eTAg', 'TAg-VAluE')
1313 def wsgi_app(environ, start_response):
1314 start_response('200 oK', [random_case_header])
1317 self.spawn_server(site=wsgi_app, capitalize_response_headers=False)
1319 sock = eventlet.connect(('localhost', self.port))
1320 sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
1321 result = read_http(sock)
1323 self.assertEqual(result.status, 'HTTP/1.1 200 oK')
1324 self.assertEqual(result.headers_lower[random_case_header[0].lower()], random_case_header[1])
1325 self.assertEqual(result.headers_original[random_case_header[0]], random_case_header[1])
1328 def read_headers(sock):
1329 fd = sock.makefile()
1331 response_line = fd.readline()
1332 except socket.error as exc:
1333 if support.get_errno(exc) == 10053:
1334 raise ConnectionClosed
1336 if not response_line:
1337 raise ConnectionClosed
1341 line = fd.readline()
1345 header_lines.append(line)
1347 for x in header_lines:
1351 key, value = x.split(': ', 1)
1352 assert key.lower() not in headers, "%s header duplicated" % key
1353 headers[key.lower()] = value
1354 return response_line, headers
1357 class IterableAlreadyHandledTest(_TestBase):
1359 self.site = IterableSite()
1362 return IterableApp(True)
1364 def test_iterable_app_keeps_socket_open_unless_connection_close_sent(self):
1365 self.site.application = self.get_app()
1366 sock = eventlet.connect(
1367 ('localhost', self.port))
1369 fd = sock.makefile('rw')
1370 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
1373 response_line, headers = read_headers(sock)
1374 self.assertEqual(response_line, 'HTTP/1.1 200 OK\r\n')
1375 assert 'connection' not in headers
1376 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
1378 result = read_http(sock)
1379 self.assertEqual(result.status, 'HTTP/1.1 200 OK')
1380 self.assertEqual(result.headers_lower.get('transfer-encoding'), 'chunked')
1381 self.assertEqual(result.body, '0\r\n\r\n') # Still coming back chunked
1384 class ProxiedIterableAlreadyHandledTest(IterableAlreadyHandledTest):
1385 # same thing as the previous test but ensuring that it works with tpooled
1386 # results as well as regular ones
1387 @tests.skip_with_pyevent
1389 return tpool.Proxy(super(ProxiedIterableAlreadyHandledTest, self).get_app())
1393 super(ProxiedIterableAlreadyHandledTest, self).tearDown()
1396 class TestChunkedInput(_TestBase):
1400 def application(self, env, start_response):
1401 input = env['wsgi.input']
1404 pi = env["PATH_INFO"]
1406 if pi == "/short-read":
1409 elif pi == "/lines":
1414 response.append(b"pong")
1415 elif pi.startswith("/yield_spaces"):
1416 if pi.endswith('override_min'):
1417 env['eventlet.minimum_write_chunk_size'] = 1
1418 self.yield_next_space = False
1420 def response_iter():
1423 while not self.yield_next_space and num_sleeps < 200:
1429 start_response('200 OK',
1430 [('Content-Type', 'text/plain'),
1431 ('Content-Length', '2')])
1432 return response_iter()
1434 raise RuntimeError("bad path")
1436 start_response('200 OK', [('Content-Type', 'text/plain')])
1440 return eventlet.connect(('localhost', self.port))
1444 self.site.application = self.application
1446 def chunk_encode(self, chunks, dirt=None):
1452 b += "%x%s\r\n%s\r\n" % (len(c), dirt, c)
1455 def body(self, dirt=None):
1456 return self.chunk_encode(["this", " is ", "chunked", "\nline",
1457 " 2", "\n", "line3", ""], dirt=dirt)
1460 fd.sendall(b"GET /ping HTTP/1.1\r\n\r\n")
1461 self.assertEqual(read_http(fd).body, "pong")
1463 def test_short_read_with_content_length(self):
1465 req = "POST /short-read HTTP/1.1\r\ntransfer-encoding: Chunked\r\n" \
1466 "Content-Length:1000\r\n\r\n" + body
1469 fd.sendall(req.encode())
1470 self.assertEqual(read_http(fd).body, "this is ch")
1475 def test_short_read_with_zero_content_length(self):
1477 req = "POST /short-read HTTP/1.1\r\ntransfer-encoding: Chunked\r\n" \
1478 "Content-Length:0\r\n\r\n" + body
1480 fd.sendall(req.encode())
1481 self.assertEqual(read_http(fd).body, "this is ch")
1486 def test_short_read(self):
1488 req = "POST /short-read HTTP/1.1\r\ntransfer-encoding: Chunked\r\n\r\n" + body
1491 fd.sendall(req.encode())
1492 self.assertEqual(read_http(fd).body, "this is ch")
1497 def test_dirt(self):
1498 body = self.body(dirt="; here is dirt\0bla")
1499 req = "POST /ping HTTP/1.1\r\ntransfer-encoding: Chunked\r\n\r\n" + body
1502 fd.sendall(req.encode())
1503 self.assertEqual(read_http(fd).body, "pong")
1508 def test_chunked_readline(self):
1510 req = "POST /lines HTTP/1.1\r\nContent-Length: %s\r\n" \
1511 "transfer-encoding: Chunked\r\n\r\n%s" % (len(body), body)
1514 fd.sendall(req.encode())
1515 self.assertEqual(read_http(fd).body, 'this is chunked\nline 2\nline3')
1518 def test_chunked_readline_wsgi_override_minimum_chunk_size(self):
1521 fd.sendall(b"POST /yield_spaces/override_min HTTP/1.1\r\nContent-Length: 0\r\n\r\n")
1524 with eventlet.Timeout(.1):
1526 one_byte = fd.recv(1)
1527 resp_so_far += one_byte
1528 if resp_so_far.endswith('\r\n\r\n'):
1530 self.assertEqual(fd.recv(1), ' ')
1532 with eventlet.Timeout(.1):
1534 except eventlet.Timeout:
1538 self.yield_next_space = True
1540 with eventlet.Timeout(.1):
1541 self.assertEqual(fd.recv(1), ' ')
1543 def test_chunked_readline_wsgi_not_override_minimum_chunk_size(self):
1546 fd.sendall(b"POST /yield_spaces HTTP/1.1\r\nContent-Length: 0\r\n\r\n")
1550 with eventlet.Timeout(.1):
1552 one_byte = fd.recv(1)
1553 resp_so_far += one_byte
1554 if resp_so_far.endswith('\r\n\r\n'):
1556 self.assertEqual(fd.recv(1), ' ')
1557 except eventlet.Timeout:
1562 def test_close_before_finished(self):
1566 got_signal.append(1)
1567 raise KeyboardInterrupt()
1569 signal.signal(signal.SIGALRM, handler)
1574 req = "POST /short-read HTTP/1.1\r\ntransfer-encoding: Chunked\r\n\r\n" + body
1577 fd.sendall(req.encode())
1582 signal.signal(signal.SIGALRM, signal.SIG_DFL)
1584 assert not got_signal, "caught alarm signal. infinite loop detected."
1587 if __name__ == '__main__':