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 bytes_to_str, capture_stderr, 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 [b"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 b"The dwarves of yore made mighty spells,"
60 yield b"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 b"In places deep, where dark things sleep,"
71 yield b"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), b'')]
98 elif env['PATH_INFO'] == '/c':
99 return [x for x in iter(lambda: env['wsgi.input'].read(1), b'')]
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 def recvall(socket_):
151 chunk = socket_.recv()
159 class ConnectionClosed(Exception):
163 def send_expect_close(sock, buf):
164 # Some tests will induce behavior that causes the remote end to
165 # close the connection before all of the data has been written.
166 # With small kernel buffer sizes, this can cause an EPIPE error.
167 # Since the test expects an early close, this can be ignored.
170 except socket.error as exc:
171 if support.get_errno(exc) != errno.EPIPE:
176 fd = sock.makefile('rb')
178 response_line = bytes_to_str(fd.readline().rstrip(b'\r\n'))
179 except socket.error as exc:
180 # TODO find out whether 54 is ok here or not, I see it when running tests
182 if support.get_errno(exc) in (10053, 54):
183 raise ConnectionClosed
185 if not response_line:
186 raise ConnectionClosed(response_line)
194 header_lines.append(line)
196 headers_original = {}
198 for x in header_lines:
202 key, value = bytes_to_str(x).split(':', 1)
204 value = value.lstrip()
205 key_lower = key.lower()
206 # FIXME: Duplicate headers are allowed as per HTTP RFC standard,
207 # the client and/or intermediate proxies are supposed to treat them
208 # as a single header with values concatenated using space (' ') delimiter.
209 assert key_lower not in headers_lower, "header duplicated: {0}".format(key)
210 headers_original[key] = value
211 headers_lower[key_lower] = value
213 content_length_str = headers_lower.get(CONTENT_LENGTH.lower(), '')
214 if content_length_str:
215 num = int(content_length_str)
221 result = HttpReadResult(
222 status=response_line,
223 headers_lower=headers_lower,
225 headers_original=headers_original)
229 class _TestBase(tests.LimitedTestCase):
231 super(_TestBase, self).setUp()
232 self.logfile = six.StringIO()
239 greenthread.kill(self.killer)
241 super(_TestBase, self).tearDown()
243 def spawn_server(self, **kwargs):
244 """Spawns a new wsgi server with the given arguments using
245 :meth:`spawn_thread`.
247 Sets self.port to the port of the server
249 new_kwargs = dict(max_size=128,
252 new_kwargs.update(kwargs)
254 if 'sock' not in new_kwargs:
255 new_kwargs['sock'] = eventlet.listen(('localhost', 0))
257 self.port = new_kwargs['sock'].getsockname()[1]
258 self.spawn_thread(wsgi.server, **new_kwargs)
260 def spawn_thread(self, target, **kwargs):
261 """Spawns a new greenthread using specified target and arguments.
263 Kills any previously-running server and sets self.killer to the
264 greenthread running the target.
266 eventlet.sleep(0) # give previous server a chance to start
268 greenthread.kill(self.killer)
270 self.killer = eventlet.spawn_n(target, **kwargs)
273 raise NotImplementedError
276 class TestHttpd(_TestBase):
280 def test_001_server(self):
281 sock = eventlet.connect(
282 ('localhost', self.port))
284 fd = sock.makefile('rwb')
285 fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n')
289 # The server responds with the maximum version it supports
290 assert result.startswith(b'HTTP'), result
291 assert result.endswith(b'hello world'), result
293 def test_002_keepalive(self):
294 sock = eventlet.connect(
295 ('localhost', self.port))
297 fd = sock.makefile('wb')
298 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
301 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
307 def test_003_passing_non_int_to_read(self):
308 # This should go in greenio_test
309 sock = eventlet.connect(
310 ('localhost', self.port))
312 fd = sock.makefile('rwb')
313 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
315 cancel = eventlet.Timeout(1, RuntimeError)
316 self.assertRaises(TypeError, fd.read, "This shouldn't work")
320 def test_004_close_keepalive(self):
321 sock = eventlet.connect(
322 ('localhost', self.port))
324 fd = sock.makefile('wb')
325 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
328 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
331 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
333 self.assertRaises(ConnectionClosed, read_http, sock)
337 def test_005_run_apachebench(self):
338 url = 'http://localhost:12346/'
341 [tests.find_command('ab'), '-c', '64', '-n', '1024', '-k', url],
342 stdout=subprocess.PIPE)
344 def test_006_reject_long_urls(self):
345 sock = eventlet.connect(
346 ('localhost', self.port))
348 for ii in range(3000):
349 path_parts.append('path')
350 path = '/'.join(path_parts)
351 request = 'GET /%s HTTP/1.0\r\nHost: localhost\r\n\r\n' % path
352 send_expect_close(sock, request.encode())
353 fd = sock.makefile('rb')
354 result = fd.readline()
356 # windows closes the socket before the data is flushed,
357 # so we never get anything back
358 status = result.split(b' ')[1]
359 self.assertEqual(status, b'414')
362 def test_007_get_arg(self):
363 # define a new handler that does a get_arg as well as a read_body
364 def new_app(env, start_response):
365 body = bytes_to_str(env['wsgi.input'].read())
366 a = cgi.parse_qs(body).get('a', [1])[0]
367 start_response('200 OK', [('Content-type', 'text/plain')])
368 return [six.b('a is %s, body is %s' % (a, body))]
370 self.site.application = new_app
371 sock = eventlet.connect(
372 ('localhost', self.port))
373 request = '\r\n'.join((
379 fd = sock.makefile('wb')
380 fd.write(request.encode())
383 # send some junk after the actual request
384 fd.write(b'01234567890123456789')
385 result = read_http(sock)
386 self.assertEqual(result.body, b'a is a, body is a=a')
389 def test_008_correctresponse(self):
390 sock = eventlet.connect(('localhost', self.port))
392 fd = sock.makefile('wb')
393 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
395 result_200 = read_http(sock)
396 fd.write(b'GET /notexist HTTP/1.1\r\nHost: localhost\r\n\r\n')
399 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
401 result_test = read_http(sock)
402 self.assertEqual(result_200.status, result_test.status)
406 def test_009_chunked_response(self):
407 self.site.application = chunked_app
408 sock = eventlet.connect(
409 ('localhost', self.port))
411 fd = sock.makefile('rwb')
412 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
414 assert b'Transfer-Encoding: chunked' in fd.read()
416 def test_010_no_chunked_http_1_0(self):
417 self.site.application = chunked_app
418 sock = eventlet.connect(
419 ('localhost', self.port))
421 fd = sock.makefile('rwb')
422 fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n')
424 assert b'Transfer-Encoding: chunked' not in fd.read()
426 def test_011_multiple_chunks(self):
427 self.site.application = big_chunks
428 sock = eventlet.connect(
429 ('localhost', self.port))
431 fd = sock.makefile('rwb')
432 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
441 assert b'Transfer-Encoding: chunked' in headers
443 chunklen = int(fd.readline(), 16)
448 chunklen = int(fd.readline(), 16)
451 # Require a CRLF to close the message body
452 self.assertEqual(response, b'\r\n')
454 @tests.skip_if_no_ssl
455 def test_012_ssl_server(self):
456 def wsgi_app(environ, start_response):
457 start_response('200 OK', {})
458 return [environ['wsgi.input'].read()]
460 certificate_file = os.path.join(os.path.dirname(__file__), 'test_server.crt')
461 private_key_file = os.path.join(os.path.dirname(__file__), 'test_server.key')
463 server_sock = eventlet.wrap_ssl(eventlet.listen(('localhost', 0)),
464 certfile=certificate_file,
465 keyfile=private_key_file,
467 self.spawn_server(sock=server_sock, site=wsgi_app)
469 sock = eventlet.connect(('localhost', self.port))
470 sock = eventlet.wrap_ssl(sock)
472 b'POST /foo HTTP/1.1\r\nHost: localhost\r\n'
473 b'Connection: close\r\nContent-length:3\r\n\r\nabc')
474 result = recvall(sock)
475 assert result.endswith(b'abc')
477 @tests.skip_if_no_ssl
478 def test_013_empty_return(self):
479 def wsgi_app(environ, start_response):
480 start_response("200 OK", [])
483 certificate_file = os.path.join(os.path.dirname(__file__), 'test_server.crt')
484 private_key_file = os.path.join(os.path.dirname(__file__), 'test_server.key')
485 server_sock = eventlet.wrap_ssl(eventlet.listen(('localhost', 0)),
486 certfile=certificate_file,
487 keyfile=private_key_file,
489 self.spawn_server(sock=server_sock, site=wsgi_app)
491 sock = eventlet.connect(('localhost', server_sock.getsockname()[1]))
492 sock = eventlet.wrap_ssl(sock)
493 sock.write(b'GET /foo HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
494 result = recvall(sock)
495 assert result[-4:] == b'\r\n\r\n'
497 def test_014_chunked_post(self):
498 self.site.application = chunked_post
499 sock = eventlet.connect(('localhost', self.port))
500 fd = sock.makefile('rwb')
501 fd.write('PUT /a 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() == b'\r\n':
509 assert response == b'oh hai', 'invalid response %s' % response
511 sock = eventlet.connect(('localhost', self.port))
512 fd = sock.makefile('rwb')
513 fd.write('PUT /b HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n'
514 'Transfer-Encoding: chunked\r\n\r\n'
515 '2\r\noh\r\n4\r\n hai\r\n0\r\n\r\n'.encode())
518 if fd.readline() == b'\r\n':
521 assert response == b'oh hai', 'invalid response %s' % response
523 sock = eventlet.connect(('localhost', self.port))
524 fd = sock.makefile('rwb')
525 fd.write('PUT /c HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n'
526 'Transfer-Encoding: chunked\r\n\r\n'
527 '2\r\noh\r\n4\r\n hai\r\n0\r\n\r\n'.encode())
530 if fd.readline() == b'\r\n':
532 response = fd.read(8192)
533 assert response == b'oh hai', 'invalid response %s' % response
535 def test_015_write(self):
536 self.site.application = use_write
537 sock = eventlet.connect(('localhost', self.port))
538 fd = sock.makefile('wb')
539 fd.write(b'GET /a HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
541 result1 = read_http(sock)
542 assert 'content-length' in result1.headers_lower
544 sock = eventlet.connect(('localhost', self.port))
545 fd = sock.makefile('wb')
546 fd.write(b'GET /b HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
548 result2 = read_http(sock)
549 assert 'transfer-encoding' in result2.headers_lower
550 assert result2.headers_lower['transfer-encoding'] == 'chunked'
552 def test_016_repeated_content_length(self):
553 """content-length header was being doubled up if it was set in
554 start_response and could also be inferred from the iterator
556 def wsgi_app(environ, start_response):
557 start_response('200 OK', [('Content-Length', '7')])
559 self.site.application = wsgi_app
560 sock = eventlet.connect(('localhost', self.port))
561 fd = sock.makefile('rwb')
562 fd.write(b'GET /a HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
570 header_lines.append(line)
571 self.assertEqual(1, len(
572 [l for l in header_lines if l.lower().startswith(b'content-length')]))
574 @tests.skip_if_no_ssl
575 def test_017_ssl_zeroreturnerror(self):
577 def server(sock, site, log):
579 serv = wsgi.Server(sock, sock.getsockname(), site, log)
580 client_socket = sock.accept()
581 serv.process_request(client_socket)
584 traceback.print_exc()
587 def wsgi_app(environ, start_response):
588 start_response('200 OK', [])
589 return [environ['wsgi.input'].read()]
591 certificate_file = os.path.join(os.path.dirname(__file__), 'test_server.crt')
592 private_key_file = os.path.join(os.path.dirname(__file__), 'test_server.key')
594 sock = eventlet.wrap_ssl(
595 eventlet.listen(('localhost', 0)),
596 certfile=certificate_file, keyfile=private_key_file,
598 server_coro = eventlet.spawn(server, sock, wsgi_app, self.logfile)
600 client = eventlet.connect(('localhost', sock.getsockname()[1]))
601 client = eventlet.wrap_ssl(client)
602 client.write(b'X') # non-empty payload so that SSL handshake occurs
603 greenio.shutdown_safe(client)
606 success = server_coro.wait()
609 def test_018_http_10_keepalive(self):
610 # verify that if an http/1.0 client sends connection: keep-alive
611 # that we don't close the connection
612 sock = eventlet.connect(
613 ('localhost', self.port))
615 fd = sock.makefile('wb')
616 fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n')
619 result1 = read_http(sock)
620 assert 'connection' in result1.headers_lower
621 self.assertEqual('keep-alive', result1.headers_lower['connection'])
622 # repeat request to verify connection is actually still open
623 fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n')
625 result2 = read_http(sock)
626 assert 'connection' in result2.headers_lower
627 self.assertEqual('keep-alive', result2.headers_lower['connection'])
630 def test_019_fieldstorage_compat(self):
631 def use_fieldstorage(environ, start_response):
632 cgi.FieldStorage(fp=environ['wsgi.input'], environ=environ)
633 start_response('200 OK', [('Content-type', 'text/plain')])
636 self.site.application = use_fieldstorage
637 sock = eventlet.connect(
638 ('localhost', self.port))
640 fd = sock.makefile('rwb')
641 fd.write('POST / HTTP/1.1\r\n'
642 'Host: localhost\r\n'
643 'Connection: close\r\n'
644 'Transfer-Encoding: chunked\r\n\r\n'
646 '4\r\n hai\r\n0\r\n\r\n'.encode())
648 assert b'hello!' in fd.read()
650 def test_020_x_forwarded_for(self):
652 b'GET / HTTP/1.1\r\nHost: localhost\r\n'
653 + b'X-Forwarded-For: 1.2.3.4, 5.6.7.8\r\n\r\n'
656 sock = eventlet.connect(('localhost', self.port))
657 sock.sendall(request_bytes)
660 assert '1.2.3.4,5.6.7.8,127.0.0.1' in self.logfile.getvalue()
662 # turning off the option should work too
663 self.logfile = six.StringIO()
664 self.spawn_server(log_x_forwarded_for=False)
666 sock = eventlet.connect(('localhost', self.port))
667 sock.sendall(request_bytes)
670 assert '1.2.3.4' not in self.logfile.getvalue()
671 assert '5.6.7.8' not in self.logfile.getvalue()
672 assert '127.0.0.1' in self.logfile.getvalue()
674 def test_socket_remains_open(self):
675 greenthread.kill(self.killer)
676 server_sock = eventlet.listen(('localhost', 0))
677 server_sock_2 = server_sock.dup()
678 self.spawn_server(sock=server_sock_2)
679 # do a single req/response to verify it's up
680 sock = eventlet.connect(('localhost', self.port))
681 fd = sock.makefile('rwb')
682 fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n')
684 result = fd.read(1024)
686 assert result.startswith(b'HTTP'), result
687 assert result.endswith(b'hello world'), result
689 # shut down the server and verify the server_socket fd is still open,
690 # but the actual socketobject passed in to wsgi.server is closed
691 greenthread.kill(self.killer)
692 eventlet.sleep(0) # make the kill go through
694 server_sock_2.accept()
695 # shouldn't be able to use this one anymore
696 except socket.error as exc:
697 self.assertEqual(support.get_errno(exc), errno.EBADF)
698 self.spawn_server(sock=server_sock)
699 sock = eventlet.connect(('localhost', self.port))
700 fd = sock.makefile('rwb')
701 fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n')
703 result = fd.read(1024)
705 assert result.startswith(b'HTTP'), result
706 assert result.endswith(b'hello world'), result
708 def test_021_environ_clobbering(self):
709 def clobberin_time(environ, start_response):
711 'wsgi.version', 'wsgi.url_scheme',
712 'wsgi.input', 'wsgi.errors', 'wsgi.multithread',
713 'wsgi.multiprocess', 'wsgi.run_once', 'REQUEST_METHOD',
714 'SCRIPT_NAME', 'RAW_PATH_INFO', 'PATH_INFO', 'QUERY_STRING',
715 'CONTENT_TYPE', 'CONTENT_LENGTH', 'SERVER_NAME', 'SERVER_PORT',
717 environ[environ_var] = None
718 start_response('200 OK', [('Content-type', 'text/plain')])
720 self.site.application = clobberin_time
721 sock = eventlet.connect(('localhost', self.port))
722 fd = sock.makefile('rwb')
723 fd.write('GET / HTTP/1.1\r\n'
724 'Host: localhost\r\n'
725 'Connection: close\r\n'
728 assert b'200 OK' in fd.read()
730 def test_022_custom_pool(self):
731 # just test that it accepts the parameter for now
732 # TODO(waitall): test that it uses the pool and that you can waitall() to
733 # ensure that all clients finished
734 p = eventlet.GreenPool(5)
735 self.spawn_server(custom_pool=p)
737 # this stuff is copied from test_001_server, could be better factored
738 sock = eventlet.connect(
739 ('localhost', self.port))
740 fd = sock.makefile('rwb')
741 fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n')
745 assert result.startswith(b'HTTP'), result
746 assert result.endswith(b'hello world'), result
748 def test_023_bad_content_length(self):
749 sock = eventlet.connect(
750 ('localhost', self.port))
751 fd = sock.makefile('rwb')
752 fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\nContent-length: argh\r\n\r\n')
756 assert result.startswith(b'HTTP'), result
757 assert b'400 Bad Request' in result, result
758 assert b'500' not in result, result
760 def test_024_expect_100_continue(self):
761 def wsgi_app(environ, start_response):
762 if int(environ['CONTENT_LENGTH']) > 1024:
763 start_response('417 Expectation Failed', [('Content-Length', '7')])
766 text = environ['wsgi.input'].read()
767 start_response('200 OK', [('Content-Length', str(len(text)))])
769 self.site.application = wsgi_app
770 sock = eventlet.connect(('localhost', self.port))
771 fd = sock.makefile('rwb')
772 fd.write(b'PUT / HTTP/1.1\r\nHost: localhost\r\nContent-length: 1025\r\n'
773 b'Expect: 100-continue\r\n\r\n')
775 result = read_http(sock)
776 self.assertEqual(result.status, 'HTTP/1.1 417 Expectation Failed')
777 self.assertEqual(result.body, b'failure')
779 b'PUT / HTTP/1.1\r\nHost: localhost\r\nContent-length: 7\r\n'
780 b'Expect: 100-continue\r\n\r\ntesting')
788 header_lines.append(line)
789 assert header_lines[0].startswith(b'HTTP/1.1 100 Continue')
796 header_lines.append(line)
797 assert header_lines[0].startswith(b'HTTP/1.1 200 OK')
798 assert fd.read(7) == b'testing'
802 def test_024a_expect_100_continue_with_headers(self):
803 def wsgi_app(environ, start_response):
804 if int(environ['CONTENT_LENGTH']) > 1024:
805 start_response('417 Expectation Failed', [('Content-Length', '7')])
808 environ['wsgi.input'].set_hundred_continue_response_headers(
809 [('Hundred-Continue-Header-1', 'H1'),
810 ('Hundred-Continue-Header-2', 'H2'),
811 ('Hundred-Continue-Header-k', 'Hk')])
812 text = environ['wsgi.input'].read()
813 start_response('200 OK', [('Content-Length', str(len(text)))])
815 self.site.application = wsgi_app
816 sock = eventlet.connect(('localhost', self.port))
817 fd = sock.makefile('rwb')
818 fd.write(b'PUT / HTTP/1.1\r\nHost: localhost\r\nContent-length: 1025\r\n'
819 b'Expect: 100-continue\r\n\r\n')
821 result = read_http(sock)
822 self.assertEqual(result.status, 'HTTP/1.1 417 Expectation Failed')
823 self.assertEqual(result.body, b'failure')
825 b'PUT / HTTP/1.1\r\nHost: localhost\r\nContent-length: 7\r\n'
826 b'Expect: 100-continue\r\n\r\ntesting')
834 header_lines.append(line.strip())
835 assert header_lines[0].startswith(b'HTTP/1.1 100 Continue')
836 headers = dict((k, v) for k, v in (h.split(b': ', 1) for h in header_lines[1:]))
837 assert b'Hundred-Continue-Header-1' in headers
838 assert b'Hundred-Continue-Header-2' in headers
839 assert b'Hundred-Continue-Header-K' in headers
840 self.assertEqual(b'H1', headers[b'Hundred-Continue-Header-1'])
841 self.assertEqual(b'H2', headers[b'Hundred-Continue-Header-2'])
842 self.assertEqual(b'Hk', headers[b'Hundred-Continue-Header-K'])
849 header_lines.append(line)
850 assert header_lines[0].startswith(b'HTTP/1.1 200 OK')
851 self.assertEqual(fd.read(7), b'testing')
855 def test_024b_expect_100_continue_with_headers_multiple_chunked(self):
856 def wsgi_app(environ, start_response):
857 environ['wsgi.input'].set_hundred_continue_response_headers(
858 [('Hundred-Continue-Header-1', 'H1'),
859 ('Hundred-Continue-Header-2', 'H2')])
860 text = environ['wsgi.input'].read()
862 environ['wsgi.input'].set_hundred_continue_response_headers(
863 [('Hundred-Continue-Header-3', 'H3')])
864 environ['wsgi.input'].send_hundred_continue_response()
866 text += environ['wsgi.input'].read()
868 start_response('200 OK', [('Content-Length', str(len(text)))])
870 self.site.application = wsgi_app
871 sock = eventlet.connect(('localhost', self.port))
872 fd = sock.makefile('rwb')
873 fd.write(b'PUT /a HTTP/1.1\r\n'
874 b'Host: localhost\r\nConnection: close\r\n'
875 b'Transfer-Encoding: chunked\r\n'
876 b'Expect: 100-continue\r\n\r\n')
879 # Expect 1st 100-continue response
886 header_lines.append(line.strip())
887 assert header_lines[0].startswith(b'HTTP/1.1 100 Continue')
888 headers = dict((k, v) for k, v in (h.split(b': ', 1)
889 for h in header_lines[1:]))
890 assert b'Hundred-Continue-Header-1' in headers
891 assert b'Hundred-Continue-Header-2' in headers
892 self.assertEqual(b'H1', headers[b'Hundred-Continue-Header-1'])
893 self.assertEqual(b'H2', headers[b'Hundred-Continue-Header-2'])
896 fd.write(b'5\r\nfirst\r\n8\r\n message\r\n0\r\n\r\n')
899 # Expect a 2nd 100-continue response
906 header_lines.append(line.strip())
907 assert header_lines[0].startswith(b'HTTP/1.1 100 Continue')
908 headers = dict((k, v) for k, v in (h.split(b': ', 1)
909 for h in header_lines[1:]))
910 assert b'Hundred-Continue-Header-3' in headers
911 self.assertEqual(b'H3', headers[b'Hundred-Continue-Header-3'])
914 fd.write(b'8\r\n, second\r\n8\r\n message\r\n0\r\n\r\n')
917 # Expect final 200-OK
924 header_lines.append(line.strip())
925 assert header_lines[0].startswith(b'HTTP/1.1 200 OK')
927 self.assertEqual(fd.read(29), b'first message, second message')
931 def test_024c_expect_100_continue_with_headers_multiple_nonchunked(self):
932 def wsgi_app(environ, start_response):
934 environ['wsgi.input'].set_hundred_continue_response_headers(
935 [('Hundred-Continue-Header-1', 'H1'),
936 ('Hundred-Continue-Header-2', 'H2')])
937 text = environ['wsgi.input'].read(13)
939 environ['wsgi.input'].set_hundred_continue_response_headers(
940 [('Hundred-Continue-Header-3', 'H3')])
941 environ['wsgi.input'].send_hundred_continue_response()
943 text += environ['wsgi.input'].read(16)
945 start_response('200 OK', [('Content-Length', str(len(text)))])
948 self.site.application = wsgi_app
949 sock = eventlet.connect(('localhost', self.port))
950 fd = sock.makefile('rwb')
951 fd.write(b'PUT /a HTTP/1.1\r\n'
952 b'Host: localhost\r\nConnection: close\r\n'
953 b'Content-Length: 29\r\n'
954 b'Expect: 100-continue\r\n\r\n')
957 # Expect 1st 100-continue response
964 header_lines.append(line.strip())
965 assert header_lines[0].startswith(b'HTTP/1.1 100 Continue')
966 headers = dict((k, v) for k, v in (h.split(b': ', 1)
967 for h in header_lines[1:]))
968 assert b'Hundred-Continue-Header-1' in headers
969 assert b'Hundred-Continue-Header-2' in headers
970 self.assertEqual(b'H1', headers[b'Hundred-Continue-Header-1'])
971 self.assertEqual(b'H2', headers[b'Hundred-Continue-Header-2'])
974 fd.write(b'first message')
977 # Expect a 2nd 100-continue response
984 header_lines.append(line.strip())
985 assert header_lines[0].startswith(b'HTTP/1.1 100 Continue')
986 headers = dict((k, v) for k, v in (h.split(b': ', 1)
987 for h in header_lines[1:]))
988 assert b'Hundred-Continue-Header-3' in headers
989 self.assertEqual(b'H3', headers[b'Hundred-Continue-Header-3'])
992 fd.write(b', second message\r\n')
995 # Expect final 200-OK
1002 header_lines.append(line.strip())
1003 assert header_lines[0].startswith(b'HTTP/1.1 200 OK')
1005 self.assertEqual(fd.read(29), b'first message, second message')
1009 def test_025_accept_errors(self):
1010 debug.hub_exceptions(True)
1011 listener = greensocket.socket()
1012 listener.bind(('localhost', 0))
1013 # NOT calling listen, to trigger the error
1014 with capture_stderr() as log:
1015 self.spawn_server(sock=listener)
1016 eventlet.sleep(0) # need to enter server loop
1018 eventlet.connect(('localhost', self.port))
1019 self.fail("Didn't expect to connect")
1020 except socket.error as exc:
1021 self.assertEqual(support.get_errno(exc), errno.ECONNREFUSED)
1023 log_content = log.getvalue()
1024 assert 'Invalid argument' in log_content, log_content
1025 debug.hub_exceptions(False)
1027 def test_026_log_format(self):
1028 self.spawn_server(log_format="HI %(request_line)s HI")
1029 sock = eventlet.connect(('localhost', self.port))
1030 sock.sendall(b'GET /yo! HTTP/1.1\r\nHost: localhost\r\n\r\n')
1033 assert '\nHI GET /yo! HTTP/1.1 HI\n' in self.logfile.getvalue(), self.logfile.getvalue()
1035 def test_close_chunked_with_1_0_client(self):
1036 # verify that if we return a generator from our app
1037 # and we're not speaking with a 1.1 client, that we
1038 # close the connection
1039 self.site.application = chunked_app
1040 sock = eventlet.connect(('localhost', self.port))
1042 sock.sendall(b'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n')
1044 result = read_http(sock)
1045 self.assertEqual(result.headers_lower['connection'], 'close')
1046 self.assertNotEqual(result.headers_lower.get('transfer-encoding'), 'chunked')
1047 self.assertEqual(result.body, b"thisischunked")
1049 def test_minimum_chunk_size_parameter_leaves_httpprotocol_class_member_intact(self):
1050 start_size = wsgi.HttpProtocol.minimum_chunk_size
1052 self.spawn_server(minimum_chunk_size=start_size * 2)
1053 sock = eventlet.connect(('localhost', self.port))
1054 sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
1057 self.assertEqual(wsgi.HttpProtocol.minimum_chunk_size, start_size)
1060 def test_error_in_chunked_closes_connection(self):
1061 # From http://rhodesmill.org/brandon/2013/chunked-wsgi/
1062 self.spawn_server(minimum_chunk_size=1)
1064 self.site.application = chunked_fail_app
1065 sock = eventlet.connect(('localhost', self.port))
1067 sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
1069 result = read_http(sock)
1070 self.assertEqual(result.status, 'HTTP/1.1 200 OK')
1071 self.assertEqual(result.headers_lower.get('transfer-encoding'), 'chunked')
1073 b'27\r\nThe dwarves of yore made mighty spells,\r\n'
1074 b'25\r\nWhile hammers fell like ringing bells\r\n')
1075 self.assertEqual(result.body, expected_body)
1077 # verify that socket is closed by server
1078 self.assertEqual(sock.recv(1), b'')
1080 def test_026_http_10_nokeepalive(self):
1081 # verify that if an http/1.0 client sends connection: keep-alive
1082 # and the server doesn't accept keep-alives, we close the connection
1083 self.spawn_server(keepalive=False)
1084 sock = eventlet.connect(
1085 ('localhost', self.port))
1087 sock.sendall(b'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n')
1088 result = read_http(sock)
1089 self.assertEqual(result.headers_lower['connection'], 'close')
1091 def test_027_keepalive_chunked(self):
1092 self.site.application = chunked_post
1093 sock = eventlet.connect(('localhost', self.port))
1094 fd = sock.makefile('wb')
1096 b'Host: localhost\r\nTransfer-Encoding: chunked\r\n\r\n' +
1097 b'10\r\n0123456789abcdef\r\n0\r\n\r\n')
1098 fd.write(b'PUT /a HTTP/1.1\r\n' + common_suffix)
1101 fd.write(b'PUT /b HTTP/1.1\r\n' + common_suffix)
1104 fd.write(b'PUT /c HTTP/1.1\r\n' + common_suffix)
1107 fd.write(b'PUT /a HTTP/1.1\r\n' + common_suffix)
1112 @tests.skip_if_no_ssl
1113 def test_028_ssl_handshake_errors(self):
1118 wsgi.server(sock=sock, site=hello_world, log=self.logfile)
1119 errored[0] = 'SSL handshake error caused wsgi.server to exit.'
1120 except greenthread.greenlet.GreenletExit:
1122 except Exception as e:
1123 errored[0] = 'SSL handshake error raised exception %s.' % e
1125 for data in ('', 'GET /non-ssl-request HTTP/1.0\r\n\r\n'):
1126 srv_sock = eventlet.wrap_ssl(
1127 eventlet.listen(('localhost', 0)),
1128 certfile=certificate_file, keyfile=private_key_file,
1130 port = srv_sock.getsockname()[1]
1131 g = eventlet.spawn_n(server, srv_sock)
1132 client = eventlet.connect(('localhost', port))
1133 if data: # send non-ssl request
1134 client.sendall(data.encode())
1135 else: # close sock prematurely
1137 eventlet.sleep(0) # let context switch back to server
1138 assert not errored[0], errored[0]
1139 # make another request to ensure the server's still alive
1141 client = ssl.wrap_socket(eventlet.connect(('localhost', port)))
1142 client.write(b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n')
1143 result = recvall(client)
1144 assert result.startswith(b'HTTP'), result
1145 assert result.endswith(b'hello world')
1147 pass # TODO(openssl): should test with OpenSSL
1150 def test_029_posthooks(self):
1151 posthook1_count = [0]
1152 posthook2_count = [0]
1154 def posthook1(env, value, multiplier=1):
1155 self.assertEqual(env['local.test'], 'test_029_posthooks')
1156 posthook1_count[0] += value * multiplier
1158 def posthook2(env, value, divisor=1):
1159 self.assertEqual(env['local.test'], 'test_029_posthooks')
1160 posthook2_count[0] += value / divisor
1162 def one_posthook_app(env, start_response):
1163 env['local.test'] = 'test_029_posthooks'
1164 if 'eventlet.posthooks' not in env:
1165 start_response('500 eventlet.posthooks not supported',
1166 [('Content-Type', 'text/plain')])
1168 env['eventlet.posthooks'].append(
1169 (posthook1, (2,), {'multiplier': 3}))
1170 start_response('200 OK', [('Content-Type', 'text/plain')])
1172 self.site.application = one_posthook_app
1173 sock = eventlet.connect(('localhost', self.port))
1174 fp = sock.makefile('rwb')
1175 fp.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
1177 self.assertEqual(fp.readline(), b'HTTP/1.1 200 OK\r\n')
1180 self.assertEqual(posthook1_count[0], 6)
1181 self.assertEqual(posthook2_count[0], 0)
1183 def two_posthook_app(env, start_response):
1184 env['local.test'] = 'test_029_posthooks'
1185 if 'eventlet.posthooks' not in env:
1186 start_response('500 eventlet.posthooks not supported',
1187 [('Content-Type', 'text/plain')])
1189 env['eventlet.posthooks'].append(
1190 (posthook1, (4,), {'multiplier': 5}))
1191 env['eventlet.posthooks'].append(
1192 (posthook2, (100,), {'divisor': 4}))
1193 start_response('200 OK', [('Content-Type', 'text/plain')])
1195 self.site.application = two_posthook_app
1196 sock = eventlet.connect(('localhost', self.port))
1197 fp = sock.makefile('rwb')
1198 fp.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
1200 self.assertEqual(fp.readline(), b'HTTP/1.1 200 OK\r\n')
1203 self.assertEqual(posthook1_count[0], 26)
1204 self.assertEqual(posthook2_count[0], 25)
1206 def test_030_reject_long_header_lines(self):
1207 sock = eventlet.connect(('localhost', self.port))
1208 request = 'GET / HTTP/1.0\r\nHost: localhost\r\nLong: %s\r\n\r\n' % \
1210 send_expect_close(sock, request.encode())
1211 result = read_http(sock)
1212 self.assertEqual(result.status, 'HTTP/1.0 400 Header Line Too Long')
1214 def test_031_reject_large_headers(self):
1215 sock = eventlet.connect(('localhost', self.port))
1216 headers = ('Name: %s\r\n' % ('a' * 7000,)) * 20
1217 request = 'GET / HTTP/1.0\r\nHost: localhost\r\n%s\r\n\r\n' % headers
1218 send_expect_close(sock, request.encode())
1219 result = read_http(sock)
1220 self.assertEqual(result.status, 'HTTP/1.0 400 Headers Too Large')
1222 def test_032_wsgi_input_as_iterable(self):
1223 # https://bitbucket.org/eventlet/eventlet/issue/150
1224 # env['wsgi.input'] returns a single byte at a time
1225 # when used as an iterator
1228 def echo_by_iterating(env, start_response):
1229 start_response('200 OK', [('Content-type', 'text/plain')])
1230 for chunk in env['wsgi.input']:
1234 self.site.application = echo_by_iterating
1235 upload_data = b'123456789abcdef' * 100
1237 'POST / HTTP/1.0\r\n'
1238 'Host: localhost\r\n'
1239 'Content-Length: %i\r\n\r\n%s'
1240 ) % (len(upload_data), bytes_to_str(upload_data))
1241 sock = eventlet.connect(('localhost', self.port))
1242 fd = sock.makefile('rwb')
1243 fd.write(request.encode())
1245 result = read_http(sock)
1246 self.assertEqual(result.body, upload_data)
1248 self.assertEqual(g[0], 1)
1250 def test_zero_length_chunked_response(self):
1251 def zero_chunked_app(env, start_response):
1252 start_response('200 OK', [('Content-type', 'text/plain')])
1255 self.site.application = zero_chunked_app
1256 sock = eventlet.connect(
1257 ('localhost', self.port))
1259 fd = sock.makefile('rwb')
1260 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
1262 response = fd.read().split(b'\r\n')
1269 assert b'Transfer-Encoding: chunked' in b''.join(headers), headers
1270 # should only be one chunk of zero size with two blank lines
1271 # (one terminates the chunk, one terminates the body)
1272 self.assertEqual(response, [b'0', b'', b''])
1274 def test_configurable_url_length_limit(self):
1275 self.spawn_server(url_length_limit=20000)
1276 sock = eventlet.connect(
1277 ('localhost', self.port))
1279 request = 'GET /%s HTTP/1.0\r\nHost: localhost\r\n\r\n' % path
1280 fd = sock.makefile('rwb')
1281 fd.write(request.encode())
1283 result = fd.readline()
1285 # windows closes the socket before the data is flushed,
1286 # so we never get anything back
1287 status = result.split(b' ')[1]
1288 self.assertEqual(status, b'200')
1291 def test_aborted_chunked_post(self):
1292 read_content = event.Event()
1295 def chunk_reader(env, start_response):
1297 content = env['wsgi.input'].read(1024)
1301 read_content.send(content)
1302 start_response('200 OK', [('Content-Type', 'text/plain')])
1304 self.site.application = chunk_reader
1305 expected_body = 'a bunch of stuff'
1306 data = "\r\n".join(['PUT /somefile HTTP/1.0',
1307 'Transfer-Encoding: chunked',
1311 # start PUT-ing some chunked data but close prematurely
1312 sock = eventlet.connect(('127.0.0.1', self.port))
1313 sock.sendall(data.encode())
1315 # the test passes if we successfully get here, and read all the data
1316 # in spite of the early close
1317 self.assertEqual(read_content.wait(), b'ok')
1320 def test_exceptions_close_connection(self):
1321 def wsgi_app(environ, start_response):
1322 raise RuntimeError("intentional error")
1323 self.site.application = wsgi_app
1324 sock = eventlet.connect(('localhost', self.port))
1325 fd = sock.makefile('rwb')
1326 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
1328 result = read_http(sock)
1329 self.assertEqual(result.status, 'HTTP/1.1 500 Internal Server Error')
1330 self.assertEqual(result.headers_lower['connection'], 'close')
1331 assert 'transfer-encoding' not in result.headers_lower
1333 def test_unicode_raises_error(self):
1334 def wsgi_app(environ, start_response):
1335 start_response("200 OK", [])
1337 yield u"non-encodable unicode: \u0230"
1338 self.site.application = wsgi_app
1339 sock = eventlet.connect(('localhost', self.port))
1340 fd = sock.makefile('rwb')
1341 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
1343 result = read_http(sock)
1344 self.assertEqual(result.status, 'HTTP/1.1 500 Internal Server Error')
1345 self.assertEqual(result.headers_lower['connection'], 'close')
1346 assert b'unicode' in result.body
1348 def test_path_info_decoding(self):
1349 def wsgi_app(environ, start_response):
1350 start_response("200 OK", [])
1351 yield six.b("decoded: %s" % environ['PATH_INFO'])
1352 yield six.b("raw: %s" % environ['RAW_PATH_INFO'])
1353 self.site.application = wsgi_app
1354 sock = eventlet.connect(('localhost', self.port))
1355 fd = sock.makefile('rwb')
1356 fd.write(b'GET /a*b@%40%233 HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
1358 result = read_http(sock)
1359 self.assertEqual(result.status, 'HTTP/1.1 200 OK')
1360 assert b'decoded: /a*b@@#3' in result.body
1361 assert b'raw: /a*b@%40%233' in result.body
1363 def test_ipv6(self):
1365 sock = eventlet.listen(('::1', 0), family=socket.AF_INET6)
1366 except (socket.gaierror, socket.error): # probably no ipv6
1368 log = six.StringIO()
1369 # first thing the server does is try to log the IP it's bound to
1373 wsgi.server(sock=sock, log=log, site=Site())
1375 log.write(b'broken')
1377 self.spawn_thread(run_server)
1379 logval = log.getvalue()
1382 logval = log.getvalue()
1383 if 'broked' in logval:
1384 self.fail('WSGI server raised exception with ipv6 socket')
1386 def test_debug(self):
1387 self.spawn_server(debug=False)
1389 def crasher(env, start_response):
1390 raise RuntimeError("intentional crash")
1391 self.site.application = crasher
1393 sock = eventlet.connect(('localhost', self.port))
1394 fd = sock.makefile('wb')
1395 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
1397 result1 = read_http(sock)
1398 self.assertEqual(result1.status, 'HTTP/1.1 500 Internal Server Error')
1399 self.assertEqual(result1.body, b'')
1400 self.assertEqual(result1.headers_lower['connection'], 'close')
1401 assert 'transfer-encoding' not in result1.headers_lower
1403 # verify traceback when debugging enabled
1404 self.spawn_server(debug=True)
1405 self.site.application = crasher
1406 sock = eventlet.connect(('localhost', self.port))
1407 fd = sock.makefile('wb')
1408 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
1410 result2 = read_http(sock)
1411 self.assertEqual(result2.status, 'HTTP/1.1 500 Internal Server Error')
1412 assert b'intentional crash' in result2.body, result2.body
1413 assert b'RuntimeError' in result2.body, result2.body
1414 assert b'Traceback' in result2.body, result2.body
1415 self.assertEqual(result2.headers_lower['connection'], 'close')
1416 assert 'transfer-encoding' not in result2.headers_lower
1418 def test_client_disconnect(self):
1419 """Issue #95 Server must handle disconnect from client in the middle of response
1421 def long_response(environ, start_response):
1422 start_response('200 OK', [('Content-Length', '9876')])
1425 server_sock = eventlet.listen(('localhost', 0))
1426 self.port = server_sock.getsockname()[1]
1427 server = wsgi.Server(server_sock, server_sock.getsockname(), long_response,
1431 sock = eventlet.connect(server_sock.getsockname())
1432 sock.send(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
1435 request_thread = eventlet.spawn(make_request)
1436 server_conn = server_sock.accept()
1437 # Next line must not raise IOError -32 Broken pipe
1438 server.process_request(server_conn)
1439 request_thread.wait()
1442 def test_server_connection_timeout_exception(self):
1443 # Handle connection socket timeouts
1444 # https://bitbucket.org/eventlet/eventlet/issue/143/
1445 # Runs tests.wsgi_test_conntimeout in a separate process.
1446 testcode_path = os.path.join(
1447 os.path.dirname(os.path.abspath(__file__)),
1448 'wsgi_test_conntimeout.py')
1449 output = tests.run_python(testcode_path)
1450 sections = output.split(b"SEPERATOR_SENTINEL")
1451 # first section is empty
1452 self.assertEqual(3, len(sections), output)
1453 # if the "BOOM" check fails, it's because our timeout didn't happen
1454 # (if eventlet stops using file.readline() to read HTTP headers,
1456 for runlog in sections[1:]:
1457 debug = False if "debug set to: False" in runlog else True
1459 self.assertTrue("timed out" in runlog)
1460 self.assertTrue("BOOM" in runlog)
1461 self.assertFalse("Traceback" in runlog)
1463 def test_server_socket_timeout(self):
1464 self.spawn_server(socket_timeout=0.1)
1465 sock = eventlet.connect(('localhost', self.port))
1466 sock.send(b'GET / HTTP/1.1\r\n')
1470 assert False, 'Expected ConnectionClosed exception'
1471 except ConnectionClosed:
1474 def test_disable_header_name_capitalization(self):
1475 # Disable HTTP header name capitalization
1477 # https://github.com/eventlet/eventlet/issues/80
1478 random_case_header = ('eTAg', 'TAg-VAluE')
1480 def wsgi_app(environ, start_response):
1481 start_response('200 oK', [random_case_header])
1484 self.spawn_server(site=wsgi_app, capitalize_response_headers=False)
1486 sock = eventlet.connect(('localhost', self.port))
1487 sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
1488 result = read_http(sock)
1490 self.assertEqual(result.status, 'HTTP/1.1 200 oK')
1491 self.assertEqual(result.headers_lower[random_case_header[0].lower()], random_case_header[1])
1492 self.assertEqual(result.headers_original[random_case_header[0]], random_case_header[1])
1495 def read_headers(sock):
1496 fd = sock.makefile('rb')
1498 response_line = fd.readline()
1499 except socket.error as exc:
1500 if support.get_errno(exc) == 10053:
1501 raise ConnectionClosed
1503 if not response_line:
1504 raise ConnectionClosed
1508 line = fd.readline()
1512 header_lines.append(line)
1514 for x in header_lines:
1518 key, value = x.split(b': ', 1)
1519 assert key.lower() not in headers, "%s header duplicated" % key
1520 headers[bytes_to_str(key.lower())] = bytes_to_str(value)
1521 return bytes_to_str(response_line), headers
1524 class IterableAlreadyHandledTest(_TestBase):
1526 self.site = IterableSite()
1529 return IterableApp(True)
1531 def test_iterable_app_keeps_socket_open_unless_connection_close_sent(self):
1532 self.site.application = self.get_app()
1533 sock = eventlet.connect(
1534 ('localhost', self.port))
1536 fd = sock.makefile('rwb')
1537 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
1540 response_line, headers = read_headers(sock)
1541 self.assertEqual(response_line, 'HTTP/1.1 200 OK\r\n')
1542 assert 'connection' not in headers
1543 fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
1545 result = read_http(sock)
1546 self.assertEqual(result.status, 'HTTP/1.1 200 OK')
1547 self.assertEqual(result.headers_lower.get('transfer-encoding'), 'chunked')
1548 self.assertEqual(result.body, b'0\r\n\r\n') # Still coming back chunked
1551 class ProxiedIterableAlreadyHandledTest(IterableAlreadyHandledTest):
1552 # same thing as the previous test but ensuring that it works with tpooled
1553 # results as well as regular ones
1554 @tests.skip_with_pyevent
1556 return tpool.Proxy(super(ProxiedIterableAlreadyHandledTest, self).get_app())
1560 super(ProxiedIterableAlreadyHandledTest, self).tearDown()
1563 class TestChunkedInput(_TestBase):
1567 def application(self, env, start_response):
1568 input = env['wsgi.input']
1571 pi = env["PATH_INFO"]
1573 if pi == "/short-read":
1576 elif pi == "/lines":
1581 response.append(b"pong")
1582 elif pi.startswith("/yield_spaces"):
1583 if pi.endswith('override_min'):
1584 env['eventlet.minimum_write_chunk_size'] = 1
1585 self.yield_next_space = False
1587 def response_iter():
1590 while not self.yield_next_space and num_sleeps < 200:
1596 start_response('200 OK',
1597 [('Content-Type', 'text/plain'),
1598 ('Content-Length', '2')])
1599 return response_iter()
1601 raise RuntimeError("bad path")
1603 start_response('200 OK', [('Content-Type', 'text/plain')])
1607 return eventlet.connect(('localhost', self.port))
1611 self.site.application = self.application
1613 def chunk_encode(self, chunks, dirt=None):
1619 b += "%x%s\r\n%s\r\n" % (len(c), dirt, c)
1622 def body(self, dirt=None):
1623 return self.chunk_encode(["this", " is ", "chunked", "\nline",
1624 " 2", "\n", "line3", ""], dirt=dirt)
1627 fd.sendall(b"GET /ping HTTP/1.1\r\n\r\n")
1628 self.assertEqual(read_http(fd).body, b"pong")
1630 def test_short_read_with_content_length(self):
1632 req = "POST /short-read HTTP/1.1\r\ntransfer-encoding: Chunked\r\n" \
1633 "Content-Length:1000\r\n\r\n" + body
1636 fd.sendall(req.encode())
1637 self.assertEqual(read_http(fd).body, b"this is ch")
1642 def test_short_read_with_zero_content_length(self):
1644 req = "POST /short-read HTTP/1.1\r\ntransfer-encoding: Chunked\r\n" \
1645 "Content-Length:0\r\n\r\n" + body
1647 fd.sendall(req.encode())
1648 self.assertEqual(read_http(fd).body, b"this is ch")
1653 def test_short_read(self):
1655 req = "POST /short-read HTTP/1.1\r\ntransfer-encoding: Chunked\r\n\r\n" + body
1658 fd.sendall(req.encode())
1659 self.assertEqual(read_http(fd).body, b"this is ch")
1664 def test_dirt(self):
1665 body = self.body(dirt="; here is dirt\0bla")
1666 req = "POST /ping HTTP/1.1\r\ntransfer-encoding: Chunked\r\n\r\n" + body
1669 fd.sendall(req.encode())
1670 self.assertEqual(read_http(fd).body, b"pong")
1675 def test_chunked_readline(self):
1677 req = "POST /lines HTTP/1.1\r\nContent-Length: %s\r\n" \
1678 "transfer-encoding: Chunked\r\n\r\n%s" % (len(body), body)
1681 fd.sendall(req.encode())
1682 self.assertEqual(read_http(fd).body, b'this is chunked\nline 2\nline3')
1685 def test_chunked_readline_wsgi_override_minimum_chunk_size(self):
1688 fd.sendall(b"POST /yield_spaces/override_min HTTP/1.1\r\nContent-Length: 0\r\n\r\n")
1691 with eventlet.Timeout(.1):
1693 one_byte = fd.recv(1)
1694 resp_so_far += one_byte
1695 if resp_so_far.endswith(b'\r\n\r\n'):
1697 self.assertEqual(fd.recv(1), b' ')
1699 with eventlet.Timeout(.1):
1701 except eventlet.Timeout:
1705 self.yield_next_space = True
1707 with eventlet.Timeout(.1):
1708 self.assertEqual(fd.recv(1), b' ')
1710 def test_chunked_readline_wsgi_not_override_minimum_chunk_size(self):
1713 fd.sendall(b"POST /yield_spaces HTTP/1.1\r\nContent-Length: 0\r\n\r\n")
1717 with eventlet.Timeout(.1):
1719 one_byte = fd.recv(1)
1720 resp_so_far += one_byte
1721 if resp_so_far.endswith(b'\r\n\r\n'):
1723 self.assertEqual(fd.recv(1), b' ')
1724 except eventlet.Timeout:
1729 def test_close_before_finished(self):
1733 got_signal.append(1)
1734 raise KeyboardInterrupt()
1736 signal.signal(signal.SIGALRM, handler)
1741 req = "POST /short-read HTTP/1.1\r\ntransfer-encoding: Chunked\r\n\r\n" + body
1744 fd.sendall(req.encode())
1749 signal.signal(signal.SIGALRM, signal.SIG_DFL)
1751 assert not got_signal, "caught alarm signal. infinite loop detected."
1754 if __name__ == '__main__':