X-Git-Url: https://review.fuel-infra.org/gitweb?a=blobdiff_plain;ds=sidebyside;f=eventlet%2Ftests%2Fwsgi_test.py;fp=eventlet%2Ftests%2Fwsgi_test.py;h=0000000000000000000000000000000000000000;hb=358bd9258c2b6d2ee74de4dfd07a5123107abad4;hp=179881df4f4c545fbf14aa3046abb2b705adf768;hpb=376ff3bfe7071cc0793184a378c4e74508fb0d97;p=packages%2Ftrusty%2Fpython-eventlet.git diff --git a/eventlet/tests/wsgi_test.py b/eventlet/tests/wsgi_test.py deleted file mode 100644 index 179881d..0000000 --- a/eventlet/tests/wsgi_test.py +++ /dev/null @@ -1,1755 +0,0 @@ -import cgi -import collections -import errno -import os -import signal -import socket -import sys -import traceback -import unittest - -import eventlet -from eventlet import debug -from eventlet import event -from eventlet.green import socket as greensocket -from eventlet.green import ssl -from eventlet.green import subprocess -from eventlet import greenio -from eventlet import greenthread -from eventlet import support -from eventlet.support import bytes_to_str, capture_stderr, six -from eventlet import tpool -from eventlet import wsgi - -import tests - - -certificate_file = os.path.join(os.path.dirname(__file__), 'test_server.crt') -private_key_file = os.path.join(os.path.dirname(__file__), 'test_server.key') - - -HttpReadResult = collections.namedtuple( - 'HttpReadResult', - 'status headers_lower body headers_original') - - -def hello_world(env, start_response): - if env['PATH_INFO'] == 'notexist': - start_response('404 Not Found', [('Content-type', 'text/plain')]) - return [b"not found"] - - start_response('200 OK', [('Content-type', 'text/plain')]) - return [b"hello world"] - - -def chunked_app(env, start_response): - start_response('200 OK', [('Content-type', 'text/plain')]) - yield b"this" - yield b"is" - yield b"chunked" - - -def chunked_fail_app(environ, start_response): - """http://rhodesmill.org/brandon/2013/chunked-wsgi/ - """ - headers = [('Content-Type', 'text/plain')] - start_response('200 OK', headers) - - # We start streaming data just fine. - yield b"The dwarves of yore made mighty spells," - yield b"While hammers fell like ringing bells" - - # Then the back-end fails! - try: - 1 / 0 - except Exception: - start_response('500 Error', headers, sys.exc_info()) - return - - # So rest of the response data is not available. - yield b"In places deep, where dark things sleep," - yield b"In hollow halls beneath the fells." - - -def big_chunks(env, start_response): - start_response('200 OK', [('Content-type', 'text/plain')]) - line = b'a' * 8192 - for x in range(10): - yield line - - -def use_write(env, start_response): - if env['PATH_INFO'] == '/a': - write = start_response('200 OK', [('Content-type', 'text/plain'), - ('Content-Length', '5')]) - write(b'abcde') - if env['PATH_INFO'] == '/b': - write = start_response('200 OK', [('Content-type', 'text/plain')]) - write(b'abcde') - return [] - - -def chunked_post(env, start_response): - start_response('200 OK', [('Content-type', 'text/plain')]) - if env['PATH_INFO'] == '/a': - return [env['wsgi.input'].read()] - elif env['PATH_INFO'] == '/b': - return [x for x in iter(lambda: env['wsgi.input'].read(4096), b'')] - elif env['PATH_INFO'] == '/c': - return [x for x in iter(lambda: env['wsgi.input'].read(1), b'')] - - -def already_handled(env, start_response): - start_response('200 OK', [('Content-type', 'text/plain')]) - return wsgi.ALREADY_HANDLED - - -class Site(object): - def __init__(self): - self.application = hello_world - - def __call__(self, env, start_response): - return self.application(env, start_response) - - -class IterableApp(object): - - def __init__(self, send_start_response=False, return_val=wsgi.ALREADY_HANDLED): - self.send_start_response = send_start_response - self.return_val = return_val - self.env = {} - - def __call__(self, env, start_response): - self.env = env - if self.send_start_response: - start_response('200 OK', [('Content-type', 'text/plain')]) - return self.return_val - - -class IterableSite(Site): - def __call__(self, env, start_response): - it = self.application(env, start_response) - for i in it: - yield i - - -CONTENT_LENGTH = 'content-length' - - -""" -HTTP/1.1 200 OK -Date: foo -Content-length: 11 - -hello world -""" - - -def recvall(socket_): - result = b'' - while True: - chunk = socket_.recv() - result += chunk - if chunk == b'': - break - - return result - - -class ConnectionClosed(Exception): - pass - - -def send_expect_close(sock, buf): - # Some tests will induce behavior that causes the remote end to - # close the connection before all of the data has been written. - # With small kernel buffer sizes, this can cause an EPIPE error. - # Since the test expects an early close, this can be ignored. - try: - sock.sendall(buf) - except socket.error as exc: - if support.get_errno(exc) != errno.EPIPE: - raise - - -def read_http(sock): - fd = sock.makefile('rb') - try: - response_line = bytes_to_str(fd.readline().rstrip(b'\r\n')) - except socket.error as exc: - # TODO find out whether 54 is ok here or not, I see it when running tests - # on Python 3 - if support.get_errno(exc) in (10053, 54): - raise ConnectionClosed - raise - if not response_line: - raise ConnectionClosed(response_line) - - header_lines = [] - while True: - line = fd.readline() - if line == b'\r\n': - break - else: - header_lines.append(line) - - headers_original = {} - headers_lower = {} - for x in header_lines: - x = x.strip() - if not x: - continue - key, value = bytes_to_str(x).split(':', 1) - key = key.rstrip() - value = value.lstrip() - key_lower = key.lower() - # FIXME: Duplicate headers are allowed as per HTTP RFC standard, - # the client and/or intermediate proxies are supposed to treat them - # as a single header with values concatenated using space (' ') delimiter. - assert key_lower not in headers_lower, "header duplicated: {0}".format(key) - headers_original[key] = value - headers_lower[key_lower] = value - - content_length_str = headers_lower.get(CONTENT_LENGTH.lower(), '') - if content_length_str: - num = int(content_length_str) - body = fd.read(num) - else: - # read until EOF - body = fd.read() - - result = HttpReadResult( - status=response_line, - headers_lower=headers_lower, - body=body, - headers_original=headers_original) - return result - - -class _TestBase(tests.LimitedTestCase): - def setUp(self): - super(_TestBase, self).setUp() - self.logfile = six.StringIO() - self.site = Site() - self.killer = None - self.set_site() - self.spawn_server() - - def tearDown(self): - greenthread.kill(self.killer) - eventlet.sleep(0) - super(_TestBase, self).tearDown() - - def spawn_server(self, **kwargs): - """Spawns a new wsgi server with the given arguments using - :meth:`spawn_thread`. - - Sets self.port to the port of the server - """ - new_kwargs = dict(max_size=128, - log=self.logfile, - site=self.site) - new_kwargs.update(kwargs) - - if 'sock' not in new_kwargs: - new_kwargs['sock'] = eventlet.listen(('localhost', 0)) - - self.port = new_kwargs['sock'].getsockname()[1] - self.spawn_thread(wsgi.server, **new_kwargs) - - def spawn_thread(self, target, **kwargs): - """Spawns a new greenthread using specified target and arguments. - - Kills any previously-running server and sets self.killer to the - greenthread running the target. - """ - eventlet.sleep(0) # give previous server a chance to start - if self.killer: - greenthread.kill(self.killer) - - self.killer = eventlet.spawn_n(target, **kwargs) - - def set_site(self): - raise NotImplementedError - - -class TestHttpd(_TestBase): - def set_site(self): - self.site = Site() - - def test_001_server(self): - sock = eventlet.connect( - ('localhost', self.port)) - - fd = sock.makefile('rwb') - fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n') - fd.flush() - result = fd.read() - fd.close() - # The server responds with the maximum version it supports - assert result.startswith(b'HTTP'), result - assert result.endswith(b'hello world'), result - - def test_002_keepalive(self): - sock = eventlet.connect( - ('localhost', self.port)) - - fd = sock.makefile('wb') - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - fd.flush() - read_http(sock) - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - fd.flush() - read_http(sock) - fd.close() - sock.close() - - def test_003_passing_non_int_to_read(self): - # This should go in greenio_test - sock = eventlet.connect( - ('localhost', self.port)) - - fd = sock.makefile('rwb') - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - fd.flush() - cancel = eventlet.Timeout(1, RuntimeError) - self.assertRaises(TypeError, fd.read, "This shouldn't work") - cancel.cancel() - fd.close() - - def test_004_close_keepalive(self): - sock = eventlet.connect( - ('localhost', self.port)) - - fd = sock.makefile('wb') - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - fd.flush() - read_http(sock) - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') - fd.flush() - read_http(sock) - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - fd.flush() - self.assertRaises(ConnectionClosed, read_http, sock) - fd.close() - - @tests.skipped - def test_005_run_apachebench(self): - url = 'http://localhost:12346/' - # ab is apachebench - subprocess.call( - [tests.find_command('ab'), '-c', '64', '-n', '1024', '-k', url], - stdout=subprocess.PIPE) - - def test_006_reject_long_urls(self): - sock = eventlet.connect( - ('localhost', self.port)) - path_parts = [] - for ii in range(3000): - path_parts.append('path') - path = '/'.join(path_parts) - request = 'GET /%s HTTP/1.0\r\nHost: localhost\r\n\r\n' % path - send_expect_close(sock, request.encode()) - fd = sock.makefile('rb') - result = fd.readline() - if result: - # windows closes the socket before the data is flushed, - # so we never get anything back - status = result.split(b' ')[1] - self.assertEqual(status, b'414') - fd.close() - - def test_007_get_arg(self): - # define a new handler that does a get_arg as well as a read_body - def new_app(env, start_response): - body = bytes_to_str(env['wsgi.input'].read()) - a = cgi.parse_qs(body).get('a', [1])[0] - start_response('200 OK', [('Content-type', 'text/plain')]) - return [six.b('a is %s, body is %s' % (a, body))] - - self.site.application = new_app - sock = eventlet.connect( - ('localhost', self.port)) - request = '\r\n'.join(( - 'POST / HTTP/1.0', - 'Host: localhost', - 'Content-Length: 3', - '', - 'a=a')) - fd = sock.makefile('wb') - fd.write(request.encode()) - fd.flush() - - # send some junk after the actual request - fd.write(b'01234567890123456789') - result = read_http(sock) - self.assertEqual(result.body, b'a is a, body is a=a') - fd.close() - - def test_008_correctresponse(self): - sock = eventlet.connect(('localhost', self.port)) - - fd = sock.makefile('wb') - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - fd.flush() - result_200 = read_http(sock) - fd.write(b'GET /notexist HTTP/1.1\r\nHost: localhost\r\n\r\n') - fd.flush() - read_http(sock) - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - fd.flush() - result_test = read_http(sock) - self.assertEqual(result_200.status, result_test.status) - fd.close() - sock.close() - - def test_009_chunked_response(self): - self.site.application = chunked_app - sock = eventlet.connect( - ('localhost', self.port)) - - fd = sock.makefile('rwb') - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') - fd.flush() - assert b'Transfer-Encoding: chunked' in fd.read() - - def test_010_no_chunked_http_1_0(self): - self.site.application = chunked_app - sock = eventlet.connect( - ('localhost', self.port)) - - fd = sock.makefile('rwb') - fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n') - fd.flush() - assert b'Transfer-Encoding: chunked' not in fd.read() - - def test_011_multiple_chunks(self): - self.site.application = big_chunks - sock = eventlet.connect( - ('localhost', self.port)) - - fd = sock.makefile('rwb') - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') - fd.flush() - headers = b'' - while True: - line = fd.readline() - if line == b'\r\n': - break - else: - headers += line - assert b'Transfer-Encoding: chunked' in headers - chunks = 0 - chunklen = int(fd.readline(), 16) - while chunklen: - chunks += 1 - fd.read(chunklen) - fd.readline() # CRLF - chunklen = int(fd.readline(), 16) - assert chunks > 1 - response = fd.read() - # Require a CRLF to close the message body - self.assertEqual(response, b'\r\n') - - @tests.skip_if_no_ssl - def test_012_ssl_server(self): - def wsgi_app(environ, start_response): - start_response('200 OK', {}) - return [environ['wsgi.input'].read()] - - certificate_file = os.path.join(os.path.dirname(__file__), 'test_server.crt') - private_key_file = os.path.join(os.path.dirname(__file__), 'test_server.key') - - server_sock = eventlet.wrap_ssl(eventlet.listen(('localhost', 0)), - certfile=certificate_file, - keyfile=private_key_file, - server_side=True) - self.spawn_server(sock=server_sock, site=wsgi_app) - - sock = eventlet.connect(('localhost', self.port)) - sock = eventlet.wrap_ssl(sock) - sock.write( - b'POST /foo HTTP/1.1\r\nHost: localhost\r\n' - b'Connection: close\r\nContent-length:3\r\n\r\nabc') - result = recvall(sock) - assert result.endswith(b'abc') - - @tests.skip_if_no_ssl - def test_013_empty_return(self): - def wsgi_app(environ, start_response): - start_response("200 OK", []) - return [b""] - - certificate_file = os.path.join(os.path.dirname(__file__), 'test_server.crt') - private_key_file = os.path.join(os.path.dirname(__file__), 'test_server.key') - server_sock = eventlet.wrap_ssl(eventlet.listen(('localhost', 0)), - certfile=certificate_file, - keyfile=private_key_file, - server_side=True) - self.spawn_server(sock=server_sock, site=wsgi_app) - - sock = eventlet.connect(('localhost', server_sock.getsockname()[1])) - sock = eventlet.wrap_ssl(sock) - sock.write(b'GET /foo HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') - result = recvall(sock) - assert result[-4:] == b'\r\n\r\n' - - def test_014_chunked_post(self): - self.site.application = chunked_post - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('rwb') - fd.write('PUT /a HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n' - 'Transfer-Encoding: chunked\r\n\r\n' - '2\r\noh\r\n4\r\n hai\r\n0\r\n\r\n'.encode()) - fd.flush() - while True: - if fd.readline() == b'\r\n': - break - response = fd.read() - assert response == b'oh hai', 'invalid response %s' % response - - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('rwb') - fd.write('PUT /b HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n' - 'Transfer-Encoding: chunked\r\n\r\n' - '2\r\noh\r\n4\r\n hai\r\n0\r\n\r\n'.encode()) - fd.flush() - while True: - if fd.readline() == b'\r\n': - break - response = fd.read() - assert response == b'oh hai', 'invalid response %s' % response - - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('rwb') - fd.write('PUT /c HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n' - 'Transfer-Encoding: chunked\r\n\r\n' - '2\r\noh\r\n4\r\n hai\r\n0\r\n\r\n'.encode()) - fd.flush() - while True: - if fd.readline() == b'\r\n': - break - response = fd.read(8192) - assert response == b'oh hai', 'invalid response %s' % response - - def test_015_write(self): - self.site.application = use_write - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('wb') - fd.write(b'GET /a HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') - fd.flush() - result1 = read_http(sock) - assert 'content-length' in result1.headers_lower - - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('wb') - fd.write(b'GET /b HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') - fd.flush() - result2 = read_http(sock) - assert 'transfer-encoding' in result2.headers_lower - assert result2.headers_lower['transfer-encoding'] == 'chunked' - - def test_016_repeated_content_length(self): - """content-length header was being doubled up if it was set in - start_response and could also be inferred from the iterator - """ - def wsgi_app(environ, start_response): - start_response('200 OK', [('Content-Length', '7')]) - return [b'testing'] - self.site.application = wsgi_app - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('rwb') - fd.write(b'GET /a HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') - fd.flush() - header_lines = [] - while True: - line = fd.readline() - if line == b'\r\n': - break - else: - header_lines.append(line) - self.assertEqual(1, len( - [l for l in header_lines if l.lower().startswith(b'content-length')])) - - @tests.skip_if_no_ssl - def test_017_ssl_zeroreturnerror(self): - - def server(sock, site, log): - try: - serv = wsgi.Server(sock, sock.getsockname(), site, log) - client_socket = sock.accept() - serv.process_request(client_socket) - return True - except Exception: - traceback.print_exc() - return False - - def wsgi_app(environ, start_response): - start_response('200 OK', []) - return [environ['wsgi.input'].read()] - - certificate_file = os.path.join(os.path.dirname(__file__), 'test_server.crt') - private_key_file = os.path.join(os.path.dirname(__file__), 'test_server.key') - - sock = eventlet.wrap_ssl( - eventlet.listen(('localhost', 0)), - certfile=certificate_file, keyfile=private_key_file, - server_side=True) - server_coro = eventlet.spawn(server, sock, wsgi_app, self.logfile) - - client = eventlet.connect(('localhost', sock.getsockname()[1])) - client = eventlet.wrap_ssl(client) - client.write(b'X') # non-empty payload so that SSL handshake occurs - greenio.shutdown_safe(client) - client.close() - - success = server_coro.wait() - assert success - - def test_018_http_10_keepalive(self): - # verify that if an http/1.0 client sends connection: keep-alive - # that we don't close the connection - sock = eventlet.connect( - ('localhost', self.port)) - - fd = sock.makefile('wb') - fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n') - fd.flush() - - result1 = read_http(sock) - assert 'connection' in result1.headers_lower - self.assertEqual('keep-alive', result1.headers_lower['connection']) - # repeat request to verify connection is actually still open - fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n') - fd.flush() - result2 = read_http(sock) - assert 'connection' in result2.headers_lower - self.assertEqual('keep-alive', result2.headers_lower['connection']) - sock.close() - - def test_019_fieldstorage_compat(self): - def use_fieldstorage(environ, start_response): - cgi.FieldStorage(fp=environ['wsgi.input'], environ=environ) - start_response('200 OK', [('Content-type', 'text/plain')]) - return [b'hello!'] - - self.site.application = use_fieldstorage - sock = eventlet.connect( - ('localhost', self.port)) - - fd = sock.makefile('rwb') - fd.write('POST / HTTP/1.1\r\n' - 'Host: localhost\r\n' - 'Connection: close\r\n' - 'Transfer-Encoding: chunked\r\n\r\n' - '2\r\noh\r\n' - '4\r\n hai\r\n0\r\n\r\n'.encode()) - fd.flush() - assert b'hello!' in fd.read() - - def test_020_x_forwarded_for(self): - request_bytes = ( - b'GET / HTTP/1.1\r\nHost: localhost\r\n' - + b'X-Forwarded-For: 1.2.3.4, 5.6.7.8\r\n\r\n' - ) - - sock = eventlet.connect(('localhost', self.port)) - sock.sendall(request_bytes) - sock.recv(1024) - sock.close() - assert '1.2.3.4,5.6.7.8,127.0.0.1' in self.logfile.getvalue() - - # turning off the option should work too - self.logfile = six.StringIO() - self.spawn_server(log_x_forwarded_for=False) - - sock = eventlet.connect(('localhost', self.port)) - sock.sendall(request_bytes) - sock.recv(1024) - sock.close() - assert '1.2.3.4' not in self.logfile.getvalue() - assert '5.6.7.8' not in self.logfile.getvalue() - assert '127.0.0.1' in self.logfile.getvalue() - - def test_socket_remains_open(self): - greenthread.kill(self.killer) - server_sock = eventlet.listen(('localhost', 0)) - server_sock_2 = server_sock.dup() - self.spawn_server(sock=server_sock_2) - # do a single req/response to verify it's up - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('rwb') - fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n') - fd.flush() - result = fd.read(1024) - fd.close() - assert result.startswith(b'HTTP'), result - assert result.endswith(b'hello world'), result - - # shut down the server and verify the server_socket fd is still open, - # but the actual socketobject passed in to wsgi.server is closed - greenthread.kill(self.killer) - eventlet.sleep(0) # make the kill go through - try: - server_sock_2.accept() - # shouldn't be able to use this one anymore - except socket.error as exc: - self.assertEqual(support.get_errno(exc), errno.EBADF) - self.spawn_server(sock=server_sock) - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('rwb') - fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n') - fd.flush() - result = fd.read(1024) - fd.close() - assert result.startswith(b'HTTP'), result - assert result.endswith(b'hello world'), result - - def test_021_environ_clobbering(self): - def clobberin_time(environ, start_response): - for environ_var in [ - 'wsgi.version', 'wsgi.url_scheme', - 'wsgi.input', 'wsgi.errors', 'wsgi.multithread', - 'wsgi.multiprocess', 'wsgi.run_once', 'REQUEST_METHOD', - 'SCRIPT_NAME', 'RAW_PATH_INFO', 'PATH_INFO', 'QUERY_STRING', - 'CONTENT_TYPE', 'CONTENT_LENGTH', 'SERVER_NAME', 'SERVER_PORT', - 'SERVER_PROTOCOL']: - environ[environ_var] = None - start_response('200 OK', [('Content-type', 'text/plain')]) - return [] - self.site.application = clobberin_time - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('rwb') - fd.write('GET / HTTP/1.1\r\n' - 'Host: localhost\r\n' - 'Connection: close\r\n' - '\r\n\r\n'.encode()) - fd.flush() - assert b'200 OK' in fd.read() - - def test_022_custom_pool(self): - # just test that it accepts the parameter for now - # TODO(waitall): test that it uses the pool and that you can waitall() to - # ensure that all clients finished - p = eventlet.GreenPool(5) - self.spawn_server(custom_pool=p) - - # this stuff is copied from test_001_server, could be better factored - sock = eventlet.connect( - ('localhost', self.port)) - fd = sock.makefile('rwb') - fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n') - fd.flush() - result = fd.read() - fd.close() - assert result.startswith(b'HTTP'), result - assert result.endswith(b'hello world'), result - - def test_023_bad_content_length(self): - sock = eventlet.connect( - ('localhost', self.port)) - fd = sock.makefile('rwb') - fd.write(b'GET / HTTP/1.0\r\nHost: localhost\r\nContent-length: argh\r\n\r\n') - fd.flush() - result = fd.read() - fd.close() - assert result.startswith(b'HTTP'), result - assert b'400 Bad Request' in result, result - assert b'500' not in result, result - - def test_024_expect_100_continue(self): - def wsgi_app(environ, start_response): - if int(environ['CONTENT_LENGTH']) > 1024: - start_response('417 Expectation Failed', [('Content-Length', '7')]) - return [b'failure'] - else: - text = environ['wsgi.input'].read() - start_response('200 OK', [('Content-Length', str(len(text)))]) - return [text] - self.site.application = wsgi_app - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('rwb') - fd.write(b'PUT / HTTP/1.1\r\nHost: localhost\r\nContent-length: 1025\r\n' - b'Expect: 100-continue\r\n\r\n') - fd.flush() - result = read_http(sock) - self.assertEqual(result.status, 'HTTP/1.1 417 Expectation Failed') - self.assertEqual(result.body, b'failure') - fd.write( - b'PUT / HTTP/1.1\r\nHost: localhost\r\nContent-length: 7\r\n' - b'Expect: 100-continue\r\n\r\ntesting') - fd.flush() - header_lines = [] - while True: - line = fd.readline() - if line == b'\r\n': - break - else: - header_lines.append(line) - assert header_lines[0].startswith(b'HTTP/1.1 100 Continue') - header_lines = [] - while True: - line = fd.readline() - if line == b'\r\n': - break - else: - header_lines.append(line) - assert header_lines[0].startswith(b'HTTP/1.1 200 OK') - assert fd.read(7) == b'testing' - fd.close() - sock.close() - - def test_024a_expect_100_continue_with_headers(self): - def wsgi_app(environ, start_response): - if int(environ['CONTENT_LENGTH']) > 1024: - start_response('417 Expectation Failed', [('Content-Length', '7')]) - return [b'failure'] - else: - environ['wsgi.input'].set_hundred_continue_response_headers( - [('Hundred-Continue-Header-1', 'H1'), - ('Hundred-Continue-Header-2', 'H2'), - ('Hundred-Continue-Header-k', 'Hk')]) - text = environ['wsgi.input'].read() - start_response('200 OK', [('Content-Length', str(len(text)))]) - return [text] - self.site.application = wsgi_app - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('rwb') - fd.write(b'PUT / HTTP/1.1\r\nHost: localhost\r\nContent-length: 1025\r\n' - b'Expect: 100-continue\r\n\r\n') - fd.flush() - result = read_http(sock) - self.assertEqual(result.status, 'HTTP/1.1 417 Expectation Failed') - self.assertEqual(result.body, b'failure') - fd.write( - b'PUT / HTTP/1.1\r\nHost: localhost\r\nContent-length: 7\r\n' - b'Expect: 100-continue\r\n\r\ntesting') - fd.flush() - header_lines = [] - while True: - line = fd.readline() - if line == b'\r\n': - break - else: - header_lines.append(line.strip()) - assert header_lines[0].startswith(b'HTTP/1.1 100 Continue') - headers = dict((k, v) for k, v in (h.split(b': ', 1) for h in header_lines[1:])) - assert b'Hundred-Continue-Header-1' in headers - assert b'Hundred-Continue-Header-2' in headers - assert b'Hundred-Continue-Header-K' in headers - self.assertEqual(b'H1', headers[b'Hundred-Continue-Header-1']) - self.assertEqual(b'H2', headers[b'Hundred-Continue-Header-2']) - self.assertEqual(b'Hk', headers[b'Hundred-Continue-Header-K']) - header_lines = [] - while True: - line = fd.readline() - if line == b'\r\n': - break - else: - header_lines.append(line) - assert header_lines[0].startswith(b'HTTP/1.1 200 OK') - self.assertEqual(fd.read(7), b'testing') - fd.close() - sock.close() - - def test_024b_expect_100_continue_with_headers_multiple_chunked(self): - def wsgi_app(environ, start_response): - environ['wsgi.input'].set_hundred_continue_response_headers( - [('Hundred-Continue-Header-1', 'H1'), - ('Hundred-Continue-Header-2', 'H2')]) - text = environ['wsgi.input'].read() - - environ['wsgi.input'].set_hundred_continue_response_headers( - [('Hundred-Continue-Header-3', 'H3')]) - environ['wsgi.input'].send_hundred_continue_response() - - text += environ['wsgi.input'].read() - - start_response('200 OK', [('Content-Length', str(len(text)))]) - return [text] - self.site.application = wsgi_app - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('rwb') - fd.write(b'PUT /a HTTP/1.1\r\n' - b'Host: localhost\r\nConnection: close\r\n' - b'Transfer-Encoding: chunked\r\n' - b'Expect: 100-continue\r\n\r\n') - fd.flush() - - # Expect 1st 100-continue response - header_lines = [] - while True: - line = fd.readline() - if line == b'\r\n': - break - else: - header_lines.append(line.strip()) - assert header_lines[0].startswith(b'HTTP/1.1 100 Continue') - headers = dict((k, v) for k, v in (h.split(b': ', 1) - for h in header_lines[1:])) - assert b'Hundred-Continue-Header-1' in headers - assert b'Hundred-Continue-Header-2' in headers - self.assertEqual(b'H1', headers[b'Hundred-Continue-Header-1']) - self.assertEqual(b'H2', headers[b'Hundred-Continue-Header-2']) - - # Send message 1 - fd.write(b'5\r\nfirst\r\n8\r\n message\r\n0\r\n\r\n') - fd.flush() - - # Expect a 2nd 100-continue response - header_lines = [] - while True: - line = fd.readline() - if line == b'\r\n': - break - else: - header_lines.append(line.strip()) - assert header_lines[0].startswith(b'HTTP/1.1 100 Continue') - headers = dict((k, v) for k, v in (h.split(b': ', 1) - for h in header_lines[1:])) - assert b'Hundred-Continue-Header-3' in headers - self.assertEqual(b'H3', headers[b'Hundred-Continue-Header-3']) - - # Send message 2 - fd.write(b'8\r\n, second\r\n8\r\n message\r\n0\r\n\r\n') - fd.flush() - - # Expect final 200-OK - header_lines = [] - while True: - line = fd.readline() - if line == b'\r\n': - break - else: - header_lines.append(line.strip()) - assert header_lines[0].startswith(b'HTTP/1.1 200 OK') - - self.assertEqual(fd.read(29), b'first message, second message') - fd.close() - sock.close() - - def test_024c_expect_100_continue_with_headers_multiple_nonchunked(self): - def wsgi_app(environ, start_response): - - environ['wsgi.input'].set_hundred_continue_response_headers( - [('Hundred-Continue-Header-1', 'H1'), - ('Hundred-Continue-Header-2', 'H2')]) - text = environ['wsgi.input'].read(13) - - environ['wsgi.input'].set_hundred_continue_response_headers( - [('Hundred-Continue-Header-3', 'H3')]) - environ['wsgi.input'].send_hundred_continue_response() - - text += environ['wsgi.input'].read(16) - - start_response('200 OK', [('Content-Length', str(len(text)))]) - return [text] - - self.site.application = wsgi_app - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('rwb') - fd.write(b'PUT /a HTTP/1.1\r\n' - b'Host: localhost\r\nConnection: close\r\n' - b'Content-Length: 29\r\n' - b'Expect: 100-continue\r\n\r\n') - fd.flush() - - # Expect 1st 100-continue response - header_lines = [] - while True: - line = fd.readline() - if line == b'\r\n': - break - else: - header_lines.append(line.strip()) - assert header_lines[0].startswith(b'HTTP/1.1 100 Continue') - headers = dict((k, v) for k, v in (h.split(b': ', 1) - for h in header_lines[1:])) - assert b'Hundred-Continue-Header-1' in headers - assert b'Hundred-Continue-Header-2' in headers - self.assertEqual(b'H1', headers[b'Hundred-Continue-Header-1']) - self.assertEqual(b'H2', headers[b'Hundred-Continue-Header-2']) - - # Send message 1 - fd.write(b'first message') - fd.flush() - - # Expect a 2nd 100-continue response - header_lines = [] - while True: - line = fd.readline() - if line == b'\r\n': - break - else: - header_lines.append(line.strip()) - assert header_lines[0].startswith(b'HTTP/1.1 100 Continue') - headers = dict((k, v) for k, v in (h.split(b': ', 1) - for h in header_lines[1:])) - assert b'Hundred-Continue-Header-3' in headers - self.assertEqual(b'H3', headers[b'Hundred-Continue-Header-3']) - - # Send message 2 - fd.write(b', second message\r\n') - fd.flush() - - # Expect final 200-OK - header_lines = [] - while True: - line = fd.readline() - if line == b'\r\n': - break - else: - header_lines.append(line.strip()) - assert header_lines[0].startswith(b'HTTP/1.1 200 OK') - - self.assertEqual(fd.read(29), b'first message, second message') - fd.close() - sock.close() - - def test_025_accept_errors(self): - debug.hub_exceptions(True) - listener = greensocket.socket() - listener.bind(('localhost', 0)) - # NOT calling listen, to trigger the error - with capture_stderr() as log: - self.spawn_server(sock=listener) - eventlet.sleep(0) # need to enter server loop - try: - eventlet.connect(('localhost', self.port)) - self.fail("Didn't expect to connect") - except socket.error as exc: - self.assertEqual(support.get_errno(exc), errno.ECONNREFUSED) - - log_content = log.getvalue() - assert 'Invalid argument' in log_content, log_content - debug.hub_exceptions(False) - - def test_026_log_format(self): - self.spawn_server(log_format="HI %(request_line)s HI") - sock = eventlet.connect(('localhost', self.port)) - sock.sendall(b'GET /yo! HTTP/1.1\r\nHost: localhost\r\n\r\n') - sock.recv(1024) - sock.close() - assert '\nHI GET /yo! HTTP/1.1 HI\n' in self.logfile.getvalue(), self.logfile.getvalue() - - def test_close_chunked_with_1_0_client(self): - # verify that if we return a generator from our app - # and we're not speaking with a 1.1 client, that we - # close the connection - self.site.application = chunked_app - sock = eventlet.connect(('localhost', self.port)) - - sock.sendall(b'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n') - - result = read_http(sock) - self.assertEqual(result.headers_lower['connection'], 'close') - self.assertNotEqual(result.headers_lower.get('transfer-encoding'), 'chunked') - self.assertEqual(result.body, b"thisischunked") - - def test_minimum_chunk_size_parameter_leaves_httpprotocol_class_member_intact(self): - start_size = wsgi.HttpProtocol.minimum_chunk_size - - self.spawn_server(minimum_chunk_size=start_size * 2) - sock = eventlet.connect(('localhost', self.port)) - sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - read_http(sock) - - self.assertEqual(wsgi.HttpProtocol.minimum_chunk_size, start_size) - sock.close() - - def test_error_in_chunked_closes_connection(self): - # From http://rhodesmill.org/brandon/2013/chunked-wsgi/ - self.spawn_server(minimum_chunk_size=1) - - self.site.application = chunked_fail_app - sock = eventlet.connect(('localhost', self.port)) - - sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - - result = read_http(sock) - self.assertEqual(result.status, 'HTTP/1.1 200 OK') - self.assertEqual(result.headers_lower.get('transfer-encoding'), 'chunked') - expected_body = ( - b'27\r\nThe dwarves of yore made mighty spells,\r\n' - b'25\r\nWhile hammers fell like ringing bells\r\n') - self.assertEqual(result.body, expected_body) - - # verify that socket is closed by server - self.assertEqual(sock.recv(1), b'') - - def test_026_http_10_nokeepalive(self): - # verify that if an http/1.0 client sends connection: keep-alive - # and the server doesn't accept keep-alives, we close the connection - self.spawn_server(keepalive=False) - sock = eventlet.connect( - ('localhost', self.port)) - - sock.sendall(b'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n') - result = read_http(sock) - self.assertEqual(result.headers_lower['connection'], 'close') - - def test_027_keepalive_chunked(self): - self.site.application = chunked_post - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('wb') - common_suffix = ( - b'Host: localhost\r\nTransfer-Encoding: chunked\r\n\r\n' + - b'10\r\n0123456789abcdef\r\n0\r\n\r\n') - fd.write(b'PUT /a HTTP/1.1\r\n' + common_suffix) - fd.flush() - read_http(sock) - fd.write(b'PUT /b HTTP/1.1\r\n' + common_suffix) - fd.flush() - read_http(sock) - fd.write(b'PUT /c HTTP/1.1\r\n' + common_suffix) - fd.flush() - read_http(sock) - fd.write(b'PUT /a HTTP/1.1\r\n' + common_suffix) - fd.flush() - read_http(sock) - sock.close() - - @tests.skip_if_no_ssl - def test_028_ssl_handshake_errors(self): - errored = [False] - - def server(sock): - try: - wsgi.server(sock=sock, site=hello_world, log=self.logfile) - errored[0] = 'SSL handshake error caused wsgi.server to exit.' - except greenthread.greenlet.GreenletExit: - pass - except Exception as e: - errored[0] = 'SSL handshake error raised exception %s.' % e - raise - for data in ('', 'GET /non-ssl-request HTTP/1.0\r\n\r\n'): - srv_sock = eventlet.wrap_ssl( - eventlet.listen(('localhost', 0)), - certfile=certificate_file, keyfile=private_key_file, - server_side=True) - port = srv_sock.getsockname()[1] - g = eventlet.spawn_n(server, srv_sock) - client = eventlet.connect(('localhost', port)) - if data: # send non-ssl request - client.sendall(data.encode()) - else: # close sock prematurely - client.close() - eventlet.sleep(0) # let context switch back to server - assert not errored[0], errored[0] - # make another request to ensure the server's still alive - try: - client = ssl.wrap_socket(eventlet.connect(('localhost', port))) - client.write(b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n') - result = recvall(client) - assert result.startswith(b'HTTP'), result - assert result.endswith(b'hello world') - except ImportError: - pass # TODO(openssl): should test with OpenSSL - greenthread.kill(g) - - def test_029_posthooks(self): - posthook1_count = [0] - posthook2_count = [0] - - def posthook1(env, value, multiplier=1): - self.assertEqual(env['local.test'], 'test_029_posthooks') - posthook1_count[0] += value * multiplier - - def posthook2(env, value, divisor=1): - self.assertEqual(env['local.test'], 'test_029_posthooks') - posthook2_count[0] += value / divisor - - def one_posthook_app(env, start_response): - env['local.test'] = 'test_029_posthooks' - if 'eventlet.posthooks' not in env: - start_response('500 eventlet.posthooks not supported', - [('Content-Type', 'text/plain')]) - else: - env['eventlet.posthooks'].append( - (posthook1, (2,), {'multiplier': 3})) - start_response('200 OK', [('Content-Type', 'text/plain')]) - yield b'' - self.site.application = one_posthook_app - sock = eventlet.connect(('localhost', self.port)) - fp = sock.makefile('rwb') - fp.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - fp.flush() - self.assertEqual(fp.readline(), b'HTTP/1.1 200 OK\r\n') - fp.close() - sock.close() - self.assertEqual(posthook1_count[0], 6) - self.assertEqual(posthook2_count[0], 0) - - def two_posthook_app(env, start_response): - env['local.test'] = 'test_029_posthooks' - if 'eventlet.posthooks' not in env: - start_response('500 eventlet.posthooks not supported', - [('Content-Type', 'text/plain')]) - else: - env['eventlet.posthooks'].append( - (posthook1, (4,), {'multiplier': 5})) - env['eventlet.posthooks'].append( - (posthook2, (100,), {'divisor': 4})) - start_response('200 OK', [('Content-Type', 'text/plain')]) - yield b'' - self.site.application = two_posthook_app - sock = eventlet.connect(('localhost', self.port)) - fp = sock.makefile('rwb') - fp.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - fp.flush() - self.assertEqual(fp.readline(), b'HTTP/1.1 200 OK\r\n') - fp.close() - sock.close() - self.assertEqual(posthook1_count[0], 26) - self.assertEqual(posthook2_count[0], 25) - - def test_030_reject_long_header_lines(self): - sock = eventlet.connect(('localhost', self.port)) - request = 'GET / HTTP/1.0\r\nHost: localhost\r\nLong: %s\r\n\r\n' % \ - ('a' * 10000) - send_expect_close(sock, request.encode()) - result = read_http(sock) - self.assertEqual(result.status, 'HTTP/1.0 400 Header Line Too Long') - - def test_031_reject_large_headers(self): - sock = eventlet.connect(('localhost', self.port)) - headers = ('Name: %s\r\n' % ('a' * 7000,)) * 20 - request = 'GET / HTTP/1.0\r\nHost: localhost\r\n%s\r\n\r\n' % headers - send_expect_close(sock, request.encode()) - result = read_http(sock) - self.assertEqual(result.status, 'HTTP/1.0 400 Headers Too Large') - - def test_032_wsgi_input_as_iterable(self): - # https://bitbucket.org/eventlet/eventlet/issue/150 - # env['wsgi.input'] returns a single byte at a time - # when used as an iterator - g = [0] - - def echo_by_iterating(env, start_response): - start_response('200 OK', [('Content-type', 'text/plain')]) - for chunk in env['wsgi.input']: - g[0] += 1 - yield chunk - - self.site.application = echo_by_iterating - upload_data = b'123456789abcdef' * 100 - request = ( - 'POST / HTTP/1.0\r\n' - 'Host: localhost\r\n' - 'Content-Length: %i\r\n\r\n%s' - ) % (len(upload_data), bytes_to_str(upload_data)) - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('rwb') - fd.write(request.encode()) - fd.flush() - result = read_http(sock) - self.assertEqual(result.body, upload_data) - fd.close() - self.assertEqual(g[0], 1) - - def test_zero_length_chunked_response(self): - def zero_chunked_app(env, start_response): - start_response('200 OK', [('Content-type', 'text/plain')]) - yield b"" - - self.site.application = zero_chunked_app - sock = eventlet.connect( - ('localhost', self.port)) - - fd = sock.makefile('rwb') - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') - fd.flush() - response = fd.read().split(b'\r\n') - headers = [] - while True: - h = response.pop(0) - headers.append(h) - if h == b'': - break - assert b'Transfer-Encoding: chunked' in b''.join(headers), headers - # should only be one chunk of zero size with two blank lines - # (one terminates the chunk, one terminates the body) - self.assertEqual(response, [b'0', b'', b'']) - - def test_configurable_url_length_limit(self): - self.spawn_server(url_length_limit=20000) - sock = eventlet.connect( - ('localhost', self.port)) - path = 'x' * 15000 - request = 'GET /%s HTTP/1.0\r\nHost: localhost\r\n\r\n' % path - fd = sock.makefile('rwb') - fd.write(request.encode()) - fd.flush() - result = fd.readline() - if result: - # windows closes the socket before the data is flushed, - # so we never get anything back - status = result.split(b' ')[1] - self.assertEqual(status, b'200') - fd.close() - - def test_aborted_chunked_post(self): - read_content = event.Event() - blew_up = [False] - - def chunk_reader(env, start_response): - try: - content = env['wsgi.input'].read(1024) - except IOError: - blew_up[0] = True - content = b'ok' - read_content.send(content) - start_response('200 OK', [('Content-Type', 'text/plain')]) - return [content] - self.site.application = chunk_reader - expected_body = 'a bunch of stuff' - data = "\r\n".join(['PUT /somefile HTTP/1.0', - 'Transfer-Encoding: chunked', - '', - 'def', - expected_body]) - # start PUT-ing some chunked data but close prematurely - sock = eventlet.connect(('127.0.0.1', self.port)) - sock.sendall(data.encode()) - sock.close() - # the test passes if we successfully get here, and read all the data - # in spite of the early close - self.assertEqual(read_content.wait(), b'ok') - assert blew_up[0] - - def test_exceptions_close_connection(self): - def wsgi_app(environ, start_response): - raise RuntimeError("intentional error") - self.site.application = wsgi_app - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('rwb') - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - fd.flush() - result = read_http(sock) - self.assertEqual(result.status, 'HTTP/1.1 500 Internal Server Error') - self.assertEqual(result.headers_lower['connection'], 'close') - assert 'transfer-encoding' not in result.headers_lower - - def test_unicode_raises_error(self): - def wsgi_app(environ, start_response): - start_response("200 OK", []) - yield u"oh hai" - yield u"non-encodable unicode: \u0230" - self.site.application = wsgi_app - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('rwb') - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') - fd.flush() - result = read_http(sock) - self.assertEqual(result.status, 'HTTP/1.1 500 Internal Server Error') - self.assertEqual(result.headers_lower['connection'], 'close') - assert b'unicode' in result.body - - def test_path_info_decoding(self): - def wsgi_app(environ, start_response): - start_response("200 OK", []) - yield six.b("decoded: %s" % environ['PATH_INFO']) - yield six.b("raw: %s" % environ['RAW_PATH_INFO']) - self.site.application = wsgi_app - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('rwb') - fd.write(b'GET /a*b@%40%233 HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') - fd.flush() - result = read_http(sock) - self.assertEqual(result.status, 'HTTP/1.1 200 OK') - assert b'decoded: /a*b@@#3' in result.body - assert b'raw: /a*b@%40%233' in result.body - - def test_ipv6(self): - try: - sock = eventlet.listen(('::1', 0), family=socket.AF_INET6) - except (socket.gaierror, socket.error): # probably no ipv6 - return - log = six.StringIO() - # first thing the server does is try to log the IP it's bound to - - def run_server(): - try: - wsgi.server(sock=sock, log=log, site=Site()) - except ValueError: - log.write(b'broken') - - self.spawn_thread(run_server) - - logval = log.getvalue() - while not logval: - eventlet.sleep(0.0) - logval = log.getvalue() - if 'broked' in logval: - self.fail('WSGI server raised exception with ipv6 socket') - - def test_debug(self): - self.spawn_server(debug=False) - - def crasher(env, start_response): - raise RuntimeError("intentional crash") - self.site.application = crasher - - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('wb') - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - fd.flush() - result1 = read_http(sock) - self.assertEqual(result1.status, 'HTTP/1.1 500 Internal Server Error') - self.assertEqual(result1.body, b'') - self.assertEqual(result1.headers_lower['connection'], 'close') - assert 'transfer-encoding' not in result1.headers_lower - - # verify traceback when debugging enabled - self.spawn_server(debug=True) - self.site.application = crasher - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('wb') - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - fd.flush() - result2 = read_http(sock) - self.assertEqual(result2.status, 'HTTP/1.1 500 Internal Server Error') - assert b'intentional crash' in result2.body, result2.body - assert b'RuntimeError' in result2.body, result2.body - assert b'Traceback' in result2.body, result2.body - self.assertEqual(result2.headers_lower['connection'], 'close') - assert 'transfer-encoding' not in result2.headers_lower - - def test_client_disconnect(self): - """Issue #95 Server must handle disconnect from client in the middle of response - """ - def long_response(environ, start_response): - start_response('200 OK', [('Content-Length', '9876')]) - yield b'a' * 9876 - - server_sock = eventlet.listen(('localhost', 0)) - self.port = server_sock.getsockname()[1] - server = wsgi.Server(server_sock, server_sock.getsockname(), long_response, - log=self.logfile) - - def make_request(): - sock = eventlet.connect(server_sock.getsockname()) - sock.send(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - sock.close() - - request_thread = eventlet.spawn(make_request) - server_conn = server_sock.accept() - # Next line must not raise IOError -32 Broken pipe - server.process_request(server_conn) - request_thread.wait() - server_sock.close() - - def test_server_connection_timeout_exception(self): - # Handle connection socket timeouts - # https://bitbucket.org/eventlet/eventlet/issue/143/ - # Runs tests.wsgi_test_conntimeout in a separate process. - testcode_path = os.path.join( - os.path.dirname(os.path.abspath(__file__)), - 'wsgi_test_conntimeout.py') - output = tests.run_python(testcode_path) - sections = output.split(b"SEPERATOR_SENTINEL") - # first section is empty - self.assertEqual(3, len(sections), output) - # if the "BOOM" check fails, it's because our timeout didn't happen - # (if eventlet stops using file.readline() to read HTTP headers, - # for instance) - for runlog in sections[1:]: - debug = False if "debug set to: False" in runlog else True - if debug: - self.assertTrue("timed out" in runlog) - self.assertTrue("BOOM" in runlog) - self.assertFalse("Traceback" in runlog) - - def test_server_socket_timeout(self): - self.spawn_server(socket_timeout=0.1) - sock = eventlet.connect(('localhost', self.port)) - sock.send(b'GET / HTTP/1.1\r\n') - eventlet.sleep(0.1) - try: - read_http(sock) - assert False, 'Expected ConnectionClosed exception' - except ConnectionClosed: - pass - - def test_disable_header_name_capitalization(self): - # Disable HTTP header name capitalization - # - # https://github.com/eventlet/eventlet/issues/80 - random_case_header = ('eTAg', 'TAg-VAluE') - - def wsgi_app(environ, start_response): - start_response('200 oK', [random_case_header]) - return [b''] - - self.spawn_server(site=wsgi_app, capitalize_response_headers=False) - - sock = eventlet.connect(('localhost', self.port)) - sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - result = read_http(sock) - sock.close() - self.assertEqual(result.status, 'HTTP/1.1 200 oK') - self.assertEqual(result.headers_lower[random_case_header[0].lower()], random_case_header[1]) - self.assertEqual(result.headers_original[random_case_header[0]], random_case_header[1]) - - -def read_headers(sock): - fd = sock.makefile('rb') - try: - response_line = fd.readline() - except socket.error as exc: - if support.get_errno(exc) == 10053: - raise ConnectionClosed - raise - if not response_line: - raise ConnectionClosed - - header_lines = [] - while True: - line = fd.readline() - if line == b'\r\n': - break - else: - header_lines.append(line) - headers = dict() - for x in header_lines: - x = x.strip() - if not x: - continue - key, value = x.split(b': ', 1) - assert key.lower() not in headers, "%s header duplicated" % key - headers[bytes_to_str(key.lower())] = bytes_to_str(value) - return bytes_to_str(response_line), headers - - -class IterableAlreadyHandledTest(_TestBase): - def set_site(self): - self.site = IterableSite() - - def get_app(self): - return IterableApp(True) - - def test_iterable_app_keeps_socket_open_unless_connection_close_sent(self): - self.site.application = self.get_app() - sock = eventlet.connect( - ('localhost', self.port)) - - fd = sock.makefile('rwb') - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - - fd.flush() - response_line, headers = read_headers(sock) - self.assertEqual(response_line, 'HTTP/1.1 200 OK\r\n') - assert 'connection' not in headers - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') - fd.flush() - result = read_http(sock) - self.assertEqual(result.status, 'HTTP/1.1 200 OK') - self.assertEqual(result.headers_lower.get('transfer-encoding'), 'chunked') - self.assertEqual(result.body, b'0\r\n\r\n') # Still coming back chunked - - -class ProxiedIterableAlreadyHandledTest(IterableAlreadyHandledTest): - # same thing as the previous test but ensuring that it works with tpooled - # results as well as regular ones - @tests.skip_with_pyevent - def get_app(self): - return tpool.Proxy(super(ProxiedIterableAlreadyHandledTest, self).get_app()) - - def tearDown(self): - tpool.killall() - super(ProxiedIterableAlreadyHandledTest, self).tearDown() - - -class TestChunkedInput(_TestBase): - dirt = "" - validator = None - - def application(self, env, start_response): - input = env['wsgi.input'] - response = [] - - pi = env["PATH_INFO"] - - if pi == "/short-read": - d = input.read(10) - response = [d] - elif pi == "/lines": - for x in input: - response.append(x) - elif pi == "/ping": - input.read() - response.append(b"pong") - elif pi.startswith("/yield_spaces"): - if pi.endswith('override_min'): - env['eventlet.minimum_write_chunk_size'] = 1 - self.yield_next_space = False - - def response_iter(): - yield b' ' - num_sleeps = 0 - while not self.yield_next_space and num_sleeps < 200: - eventlet.sleep(.01) - num_sleeps += 1 - - yield b' ' - - start_response('200 OK', - [('Content-Type', 'text/plain'), - ('Content-Length', '2')]) - return response_iter() - else: - raise RuntimeError("bad path") - - start_response('200 OK', [('Content-Type', 'text/plain')]) - return response - - def connect(self): - return eventlet.connect(('localhost', self.port)) - - def set_site(self): - self.site = Site() - self.site.application = self.application - - def chunk_encode(self, chunks, dirt=None): - if dirt is None: - dirt = self.dirt - - b = "" - for c in chunks: - b += "%x%s\r\n%s\r\n" % (len(c), dirt, c) - return b - - def body(self, dirt=None): - return self.chunk_encode(["this", " is ", "chunked", "\nline", - " 2", "\n", "line3", ""], dirt=dirt) - - def ping(self, fd): - fd.sendall(b"GET /ping HTTP/1.1\r\n\r\n") - self.assertEqual(read_http(fd).body, b"pong") - - def test_short_read_with_content_length(self): - body = self.body() - req = "POST /short-read HTTP/1.1\r\ntransfer-encoding: Chunked\r\n" \ - "Content-Length:1000\r\n\r\n" + body - - fd = self.connect() - fd.sendall(req.encode()) - self.assertEqual(read_http(fd).body, b"this is ch") - - self.ping(fd) - fd.close() - - def test_short_read_with_zero_content_length(self): - body = self.body() - req = "POST /short-read HTTP/1.1\r\ntransfer-encoding: Chunked\r\n" \ - "Content-Length:0\r\n\r\n" + body - fd = self.connect() - fd.sendall(req.encode()) - self.assertEqual(read_http(fd).body, b"this is ch") - - self.ping(fd) - fd.close() - - def test_short_read(self): - body = self.body() - req = "POST /short-read HTTP/1.1\r\ntransfer-encoding: Chunked\r\n\r\n" + body - - fd = self.connect() - fd.sendall(req.encode()) - self.assertEqual(read_http(fd).body, b"this is ch") - - self.ping(fd) - fd.close() - - def test_dirt(self): - body = self.body(dirt="; here is dirt\0bla") - req = "POST /ping HTTP/1.1\r\ntransfer-encoding: Chunked\r\n\r\n" + body - - fd = self.connect() - fd.sendall(req.encode()) - self.assertEqual(read_http(fd).body, b"pong") - - self.ping(fd) - fd.close() - - def test_chunked_readline(self): - body = self.body() - req = "POST /lines HTTP/1.1\r\nContent-Length: %s\r\n" \ - "transfer-encoding: Chunked\r\n\r\n%s" % (len(body), body) - - fd = self.connect() - fd.sendall(req.encode()) - self.assertEqual(read_http(fd).body, b'this is chunked\nline 2\nline3') - fd.close() - - def test_chunked_readline_wsgi_override_minimum_chunk_size(self): - - fd = self.connect() - fd.sendall(b"POST /yield_spaces/override_min HTTP/1.1\r\nContent-Length: 0\r\n\r\n") - - resp_so_far = b'' - with eventlet.Timeout(.1): - while True: - one_byte = fd.recv(1) - resp_so_far += one_byte - if resp_so_far.endswith(b'\r\n\r\n'): - break - self.assertEqual(fd.recv(1), b' ') - try: - with eventlet.Timeout(.1): - fd.recv(1) - except eventlet.Timeout: - pass - else: - assert False - self.yield_next_space = True - - with eventlet.Timeout(.1): - self.assertEqual(fd.recv(1), b' ') - - def test_chunked_readline_wsgi_not_override_minimum_chunk_size(self): - - fd = self.connect() - fd.sendall(b"POST /yield_spaces HTTP/1.1\r\nContent-Length: 0\r\n\r\n") - - resp_so_far = b'' - try: - with eventlet.Timeout(.1): - while True: - one_byte = fd.recv(1) - resp_so_far += one_byte - if resp_so_far.endswith(b'\r\n\r\n'): - break - self.assertEqual(fd.recv(1), b' ') - except eventlet.Timeout: - pass - else: - assert False - - def test_close_before_finished(self): - got_signal = [] - - def handler(*args): - got_signal.append(1) - raise KeyboardInterrupt() - - signal.signal(signal.SIGALRM, handler) - signal.alarm(1) - - try: - body = '4\r\nthi' - req = "POST /short-read HTTP/1.1\r\ntransfer-encoding: Chunked\r\n\r\n" + body - - fd = self.connect() - fd.sendall(req.encode()) - fd.close() - eventlet.sleep(0.0) - finally: - signal.alarm(0) - signal.signal(signal.SIGALRM, signal.SIG_DFL) - - assert not got_signal, "caught alarm signal. infinite loop detected." - - -if __name__ == '__main__': - unittest.main()