X-Git-Url: https://review.fuel-infra.org/gitweb?a=blobdiff_plain;f=python-eventlet%2Ftests%2Fwsgi_test.py;fp=python-eventlet%2Ftests%2Fwsgi_test.py;h=37c425077a8101df931a479a78ab8c0d4373740d;hb=3dbfedbaa1a106967b7588f6ce50b89788837a33;hp=d5cea188f212505f28bc55b495b7a44a13b70019;hpb=358bd9258c2b6d2ee74de4dfd07a5123107abad4;p=packages%2Ftrusty%2Fpython-eventlet.git diff --git a/python-eventlet/tests/wsgi_test.py b/python-eventlet/tests/wsgi_test.py index d5cea18..37c4250 100644 --- a/python-eventlet/tests/wsgi_test.py +++ b/python-eventlet/tests/wsgi_test.py @@ -2,25 +2,25 @@ import cgi import collections import errno import os +import shutil import signal import socket import sys +import tempfile 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 - +from eventlet.green import socket as greensocket +from eventlet.green import ssl +from eventlet.support import bytes_to_str, capture_stderr, six import tests @@ -136,24 +136,13 @@ class IterableSite(Site): CONTENT_LENGTH = 'content-length' -""" -HTTP/1.1 200 OK -Date: foo -Content-length: 11 - -hello world -""" - - -def recvall(socket_): +def recvall(sock): result = b'' while True: - chunk = socket_.recv() - result += chunk + chunk = sock.recv(16 << 10) if chunk == b'': - break - - return result + return result + result += chunk class ConnectionClosed(Exception): @@ -244,7 +233,7 @@ class _TestBase(tests.LimitedTestCase): """Spawns a new wsgi server with the given arguments using :meth:`spawn_thread`. - Sets self.port to the port of the server + Sets `self.server_addr` to (host, port) tuple suitable for `socket.connect`. """ new_kwargs = dict(max_size=128, log=self.logfile, @@ -254,7 +243,7 @@ class _TestBase(tests.LimitedTestCase): if 'sock' not in new_kwargs: new_kwargs['sock'] = eventlet.listen(('localhost', 0)) - self.port = new_kwargs['sock'].getsockname()[1] + self.server_addr = new_kwargs['sock'].getsockname() self.spawn_thread(wsgi.server, **new_kwargs) def spawn_thread(self, target, **kwargs): @@ -278,72 +267,37 @@ class TestHttpd(_TestBase): self.site = Site() def test_001_server(self): - sock = eventlet.connect( - ('localhost', self.port)) + sock = eventlet.connect(self.server_addr) - 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() + sock.sendall(b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n') + result = recvall(sock) # 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)) + sock = eventlet.connect(self.server_addr) - fd = sock.makefile('wb') - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - fd.flush() + sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') read_http(sock) - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - fd.flush() + sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') 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)) + sock = eventlet.connect(self.server_addr) - 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() + sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + result1 = read_http(sock) + assert result1.status == 'HTTP/1.1 200 OK' + sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') + result2 = read_http(sock) + assert result2.status == 'HTTP/1.1 200 OK' + assert result2.headers_lower['connection'] == 'close' + sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') 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)) + sock = eventlet.connect(self.server_addr) path_parts = [] for ii in range(3000): path_parts.append('path') @@ -368,65 +322,48 @@ class TestHttpd(_TestBase): 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() + sock = eventlet.connect(self.server_addr) + request = b'\r\n'.join(( + b'POST / HTTP/1.0', + b'Host: localhost', + b'Content-Length: 3', + b'', + b'a=a')) + sock.sendall(request) # send some junk after the actual request - fd.write(b'01234567890123456789') + sock.sendall(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)) + sock = eventlet.connect(self.server_addr) - fd = sock.makefile('wb') - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - fd.flush() + sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') result_200 = read_http(sock) - fd.write(b'GET /notexist HTTP/1.1\r\nHost: localhost\r\n\r\n') - fd.flush() + sock.sendall(b'GET /notexist HTTP/1.1\r\nHost: localhost\r\n\r\n') read_http(sock) - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - fd.flush() + sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') 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)) + sock = eventlet.connect(self.server_addr) - 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() + sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') + assert b'Transfer-Encoding: chunked' in recvall(sock) def test_010_no_chunked_http_1_0(self): self.site.application = chunked_app - sock = eventlet.connect( - ('localhost', self.port)) + sock = eventlet.connect(self.server_addr) - 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() + sock.sendall(b'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n') + assert b'Transfer-Encoding: chunked' not in recvall(sock) def test_011_multiple_chunks(self): self.site.application = big_chunks - sock = eventlet.connect( - ('localhost', self.port)) + sock = eventlet.connect(self.server_addr) fd = sock.makefile('rwb') fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') @@ -451,6 +388,54 @@ class TestHttpd(_TestBase): # Require a CRLF to close the message body self.assertEqual(response, b'\r\n') + def test_partial_writes_are_handled(self): + # https://github.com/eventlet/eventlet/issues/295 + # Eventlet issue: "Python 3: wsgi doesn't handle correctly partial + # write of socket send() when using writelines()". + # + # The bug was caused by the default writelines() implementaiton + # (used by the wsgi module) which doesn't check if write() + # successfully completed sending *all* data therefore data could be + # lost and the client could be left hanging forever. + # + # Switching wsgi wfile to buffered mode fixes the issue. + # + # Related CPython issue: "Raw I/O writelines() broken", + # http://bugs.python.org/issue26292 + # + # Custom accept() and send() in order to simulate a connection that + # only sends one byte at a time so that any code that doesn't handle + # partial writes correctly has to fail. + listen_socket = eventlet.listen(('localhost', 0)) + original_accept = listen_socket.accept + + def accept(): + connection, address = original_accept() + original_send = connection.send + + def send(b, *args): + b = b[:1] + return original_send(b, *args) + + connection.send = send + return connection, address + + listen_socket.accept = accept + + def application(env, start_response): + # Sending content-length is important here so that the client knows + # exactly how many bytes does it need to wait for. + start_response('200 OK', [('Content-length', 3)]) + yield 'asd' + + self.spawn_server(sock=listen_socket) + self.site.application = application + sock = eventlet.connect(self.server_addr) + sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + # This would previously hang forever + result = read_http(sock) + assert result.body == b'asd' + @tests.skip_if_no_ssl def test_012_ssl_server(self): def wsgi_app(environ, start_response): @@ -466,7 +451,7 @@ class TestHttpd(_TestBase): server_side=True) self.spawn_server(sock=server_sock, site=wsgi_app) - sock = eventlet.connect(('localhost', self.port)) + sock = eventlet.connect(self.server_addr) sock = eventlet.wrap_ssl(sock) sock.write( b'POST /foo HTTP/1.1\r\nHost: localhost\r\n' @@ -496,7 +481,7 @@ class TestHttpd(_TestBase): def test_014_chunked_post(self): self.site.application = chunked_post - sock = eventlet.connect(('localhost', self.port)) + sock = eventlet.connect(self.server_addr) 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' @@ -508,7 +493,7 @@ class TestHttpd(_TestBase): response = fd.read() assert response == b'oh hai', 'invalid response %s' % response - sock = eventlet.connect(('localhost', self.port)) + sock = eventlet.connect(self.server_addr) 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' @@ -520,7 +505,7 @@ class TestHttpd(_TestBase): response = fd.read() assert response == b'oh hai', 'invalid response %s' % response - sock = eventlet.connect(('localhost', self.port)) + sock = eventlet.connect(self.server_addr) 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' @@ -534,17 +519,13 @@ class TestHttpd(_TestBase): 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() + sock = eventlet.connect(self.server_addr) + sock.sendall(b'GET /a HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') 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() + sock = eventlet.connect(self.server_addr) + sock.sendall(b'GET /b HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') result2 = read_http(sock) assert 'transfer-encoding' in result2.headers_lower assert result2.headers_lower['transfer-encoding'] == 'chunked' @@ -557,7 +538,7 @@ class TestHttpd(_TestBase): start_response('200 OK', [('Content-Length', '7')]) return [b'testing'] self.site.application = wsgi_app - sock = eventlet.connect(('localhost', self.port)) + sock = eventlet.connect(self.server_addr) fd = sock.makefile('rwb') fd.write(b'GET /a HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') fd.flush() @@ -609,19 +590,15 @@ class TestHttpd(_TestBase): 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() + sock = eventlet.connect(self.server_addr) + sock.sendall(b'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n') 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() + sock.sendall(b'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n') result2 = read_http(sock) assert 'connection' in result2.headers_lower self.assertEqual('keep-alive', result2.headers_lower['connection']) @@ -634,18 +611,15 @@ class TestHttpd(_TestBase): return [b'hello!'] self.site.application = use_fieldstorage - sock = eventlet.connect( - ('localhost', self.port)) + sock = eventlet.connect(self.server_addr) - 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() + sock.sendall(b'POST / HTTP/1.1\r\n' + b'Host: localhost\r\n' + b'Connection: close\r\n' + b'Transfer-Encoding: chunked\r\n\r\n' + b'2\r\noh\r\n' + b'4\r\n hai\r\n0\r\n\r\n') + assert b'hello!' in recvall(sock) def test_020_x_forwarded_for(self): request_bytes = ( @@ -653,7 +627,7 @@ class TestHttpd(_TestBase): + b'X-Forwarded-For: 1.2.3.4, 5.6.7.8\r\n\r\n' ) - sock = eventlet.connect(('localhost', self.port)) + sock = eventlet.connect(self.server_addr) sock.sendall(request_bytes) sock.recv(1024) sock.close() @@ -663,7 +637,7 @@ class TestHttpd(_TestBase): self.logfile = six.StringIO() self.spawn_server(log_x_forwarded_for=False) - sock = eventlet.connect(('localhost', self.port)) + sock = eventlet.connect(self.server_addr) sock.sendall(request_bytes) sock.recv(1024) sock.close() @@ -677,12 +651,9 @@ class TestHttpd(_TestBase): 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() + sock = eventlet.connect(server_sock.getsockname()) + sock.sendall(b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n') + result = sock.recv(1024) assert result.startswith(b'HTTP'), result assert result.endswith(b'hello world'), result @@ -696,12 +667,9 @@ class TestHttpd(_TestBase): 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() + sock = eventlet.connect(server_sock.getsockname()) + sock.sendall(b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n') + result = sock.recv(1024) assert result.startswith(b'HTTP'), result assert result.endswith(b'hello world'), result @@ -718,14 +686,12 @@ class TestHttpd(_TestBase): 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() + sock = eventlet.connect(self.server_addr) + sock.sendall(b'GET / HTTP/1.1\r\n' + b'Host: localhost\r\n' + b'Connection: close\r\n' + b'\r\n\r\n') + assert b'200 OK' in recvall(sock) def test_022_custom_pool(self): # just test that it accepts the parameter for now @@ -735,24 +701,16 @@ class TestHttpd(_TestBase): 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() + sock = eventlet.connect(self.server_addr) + sock.sendall(b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n') + result = recvall(sock) 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() + sock = eventlet.connect(self.server_addr) + sock.sendall(b'GET / HTTP/1.0\r\nHost: localhost\r\nContent-length: argh\r\n\r\n') + result = recvall(sock) assert result.startswith(b'HTTP'), result assert b'400 Bad Request' in result, result assert b'500' not in result, result @@ -767,7 +725,7 @@ class TestHttpd(_TestBase): start_response('200 OK', [('Content-Length', str(len(text)))]) return [text] self.site.application = wsgi_app - sock = eventlet.connect(('localhost', self.port)) + sock = eventlet.connect(self.server_addr) 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') @@ -813,7 +771,7 @@ class TestHttpd(_TestBase): start_response('200 OK', [('Content-Length', str(len(text)))]) return [text] self.site.application = wsgi_app - sock = eventlet.connect(('localhost', self.port)) + sock = eventlet.connect(self.server_addr) 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') @@ -868,7 +826,7 @@ class TestHttpd(_TestBase): start_response('200 OK', [('Content-Length', str(len(text)))]) return [text] self.site.application = wsgi_app - sock = eventlet.connect(('localhost', self.port)) + sock = eventlet.connect(self.server_addr) fd = sock.makefile('rwb') fd.write(b'PUT /a HTTP/1.1\r\n' b'Host: localhost\r\nConnection: close\r\n' @@ -946,7 +904,7 @@ class TestHttpd(_TestBase): return [text] self.site.application = wsgi_app - sock = eventlet.connect(('localhost', self.port)) + sock = eventlet.connect(self.server_addr) fd = sock.makefile('rwb') fd.write(b'PUT /a HTTP/1.1\r\n' b'Host: localhost\r\nConnection: close\r\n' @@ -1015,7 +973,7 @@ class TestHttpd(_TestBase): self.spawn_server(sock=listener) eventlet.sleep(0) # need to enter server loop try: - eventlet.connect(('localhost', self.port)) + eventlet.connect(self.server_addr) self.fail("Didn't expect to connect") except socket.error as exc: self.assertEqual(support.get_errno(exc), errno.ECONNREFUSED) @@ -1026,7 +984,7 @@ class TestHttpd(_TestBase): def test_026_log_format(self): self.spawn_server(log_format="HI %(request_line)s HI") - sock = eventlet.connect(('localhost', self.port)) + sock = eventlet.connect(self.server_addr) sock.sendall(b'GET /yo! HTTP/1.1\r\nHost: localhost\r\n\r\n') sock.recv(1024) sock.close() @@ -1037,7 +995,7 @@ class TestHttpd(_TestBase): # 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 = eventlet.connect(self.server_addr) sock.sendall(b'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n') @@ -1046,11 +1004,26 @@ class TestHttpd(_TestBase): self.assertNotEqual(result.headers_lower.get('transfer-encoding'), 'chunked') self.assertEqual(result.body, b"thisischunked") + def test_chunked_response_when_app_yields_empty_string(self): + def empty_string_chunked_app(env, start_response): + env['eventlet.minimum_write_chunk_size'] = 0 # no buffering + start_response('200 OK', [('Content-type', 'text/plain')]) + return iter([b"stuff", b"", b"more stuff"]) + + self.site.application = empty_string_chunked_app + sock = eventlet.connect(self.server_addr) + + sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') + + result = read_http(sock) + self.assertEqual(result.headers_lower.get('transfer-encoding'), 'chunked') + self.assertEqual(result.body, b"5\r\nstuff\r\na\r\nmore stuff\r\n0\r\n\r\n") + 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 = eventlet.connect(self.server_addr) sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') read_http(sock) @@ -1062,7 +1035,7 @@ class TestHttpd(_TestBase): self.spawn_server(minimum_chunk_size=1) self.site.application = chunked_fail_app - sock = eventlet.connect(('localhost', self.port)) + sock = eventlet.connect(self.server_addr) sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') @@ -1081,8 +1054,7 @@ class TestHttpd(_TestBase): # 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 = eventlet.connect(self.server_addr) sock.sendall(b'GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n') result = read_http(sock) @@ -1090,22 +1062,17 @@ class TestHttpd(_TestBase): def test_027_keepalive_chunked(self): self.site.application = chunked_post - sock = eventlet.connect(('localhost', self.port)) - fd = sock.makefile('wb') + sock = eventlet.connect(self.server_addr) 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() + sock.sendall(b'PUT /a HTTP/1.1\r\n' + common_suffix) read_http(sock) - fd.write(b'PUT /b HTTP/1.1\r\n' + common_suffix) - fd.flush() + sock.sendall(b'PUT /b HTTP/1.1\r\n' + common_suffix) read_http(sock) - fd.write(b'PUT /c HTTP/1.1\r\n' + common_suffix) - fd.flush() + sock.sendall(b'PUT /c HTTP/1.1\r\n' + common_suffix) read_http(sock) - fd.write(b'PUT /a HTTP/1.1\r\n' + common_suffix) - fd.flush() + sock.sendall(b'PUT /a HTTP/1.1\r\n' + common_suffix) read_http(sock) sock.close() @@ -1127,9 +1094,9 @@ class TestHttpd(_TestBase): eventlet.listen(('localhost', 0)), certfile=certificate_file, keyfile=private_key_file, server_side=True) - port = srv_sock.getsockname()[1] + addr = srv_sock.getsockname() g = eventlet.spawn_n(server, srv_sock) - client = eventlet.connect(('localhost', port)) + client = eventlet.connect(addr) if data: # send non-ssl request client.sendall(data.encode()) else: # close sock prematurely @@ -1138,7 +1105,7 @@ class TestHttpd(_TestBase): 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 = ssl.wrap_socket(eventlet.connect(addr)) client.write(b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n') result = recvall(client) assert result.startswith(b'HTTP'), result @@ -1170,7 +1137,7 @@ class TestHttpd(_TestBase): start_response('200 OK', [('Content-Type', 'text/plain')]) yield b'' self.site.application = one_posthook_app - sock = eventlet.connect(('localhost', self.port)) + sock = eventlet.connect(self.server_addr) fp = sock.makefile('rwb') fp.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') fp.flush() @@ -1193,7 +1160,7 @@ class TestHttpd(_TestBase): start_response('200 OK', [('Content-Type', 'text/plain')]) yield b'' self.site.application = two_posthook_app - sock = eventlet.connect(('localhost', self.port)) + sock = eventlet.connect(self.server_addr) fp = sock.makefile('rwb') fp.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') fp.flush() @@ -1204,7 +1171,7 @@ class TestHttpd(_TestBase): self.assertEqual(posthook2_count[0], 25) def test_030_reject_long_header_lines(self): - sock = eventlet.connect(('localhost', self.port)) + sock = eventlet.connect(self.server_addr) request = 'GET / HTTP/1.0\r\nHost: localhost\r\nLong: %s\r\n\r\n' % \ ('a' * 10000) send_expect_close(sock, request.encode()) @@ -1212,7 +1179,7 @@ class TestHttpd(_TestBase): 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)) + sock = eventlet.connect(self.server_addr) 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()) @@ -1238,13 +1205,10 @@ class TestHttpd(_TestBase): '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() + sock = eventlet.connect(self.server_addr) + sock.sendall(request.encode()) result = read_http(sock) self.assertEqual(result.body, upload_data) - fd.close() self.assertEqual(g[0], 1) def test_zero_length_chunked_response(self): @@ -1253,13 +1217,10 @@ class TestHttpd(_TestBase): yield b"" self.site.application = zero_chunked_app - sock = eventlet.connect( - ('localhost', self.port)) + sock = eventlet.connect(self.server_addr) - 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') + sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') + response = recvall(sock).split(b'\r\n') headers = [] while True: h = response.pop(0) @@ -1273,8 +1234,7 @@ class TestHttpd(_TestBase): def test_configurable_url_length_limit(self): self.spawn_server(url_length_limit=20000) - sock = eventlet.connect( - ('localhost', self.port)) + sock = eventlet.connect(self.server_addr) path = 'x' * 15000 request = 'GET /%s HTTP/1.0\r\nHost: localhost\r\n\r\n' % path fd = sock.makefile('rwb') @@ -1301,6 +1261,7 @@ class TestHttpd(_TestBase): 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', @@ -1309,7 +1270,71 @@ class TestHttpd(_TestBase): 'def', expected_body]) # start PUT-ing some chunked data but close prematurely - sock = eventlet.connect(('127.0.0.1', self.port)) + sock = eventlet.connect(self.server_addr) + 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_aborted_chunked_post_between_chunks(self): + read_content = event.Event() + blew_up = [False] + + def chunk_reader(env, start_response): + try: + content = env['wsgi.input'].read(1024) + except wsgi.ChunkReadError: + blew_up[0] = True + content = b'ok' + except Exception as err: + blew_up[0] = True + content = b'wrong exception: ' + str(err).encode() + read_content.send(content) + start_response('200 OK', [('Content-Type', 'text/plain')]) + return [content] + self.site.application = chunk_reader + expected_body = 'A' * 0xdb + data = "\r\n".join(['PUT /somefile HTTP/1.0', + 'Transfer-Encoding: chunked', + '', + 'db', + expected_body]) + # start PUT-ing some chunked data but close prematurely + sock = eventlet.connect(self.server_addr) + 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_aborted_chunked_post_bad_chunks(self): + read_content = event.Event() + blew_up = [False] + + def chunk_reader(env, start_response): + try: + content = env['wsgi.input'].read(1024) + except wsgi.ChunkReadError: + blew_up[0] = True + content = b'ok' + except Exception as err: + blew_up[0] = True + content = b'wrong exception: ' + str(err).encode() + read_content.send(content) + start_response('200 OK', [('Content-Type', 'text/plain')]) + return [content] + self.site.application = chunk_reader + expected_body = 'look here is some data for you' + data = "\r\n".join(['PUT /somefile HTTP/1.0', + 'Transfer-Encoding: chunked', + '', + 'cats', + expected_body]) + # start PUT-ing some garbage + sock = eventlet.connect(self.server_addr) sock.sendall(data.encode()) sock.close() # the test passes if we successfully get here, and read all the data @@ -1321,10 +1346,8 @@ class TestHttpd(_TestBase): 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() + sock = eventlet.connect(self.server_addr) + 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 500 Internal Server Error') self.assertEqual(result.headers_lower['connection'], 'close') @@ -1336,10 +1359,8 @@ class TestHttpd(_TestBase): yield b"oh hai, " yield u"xxx" 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() + sock = eventlet.connect(self.server_addr) + sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') result = read_http(sock) assert b'xxx' in result.body @@ -1349,10 +1370,8 @@ class TestHttpd(_TestBase): yield b"oh hai, " yield u"xxx \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() + sock = eventlet.connect(self.server_addr) + sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') result = read_http(sock) self.assertEqual(result.status, 'HTTP/1.1 500 Internal Server Error') self.assertEqual(result.headers_lower['connection'], 'close') @@ -1363,10 +1382,8 @@ class TestHttpd(_TestBase): 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() + sock = eventlet.connect(self.server_addr) + sock.sendall(b'GET /a*b@%40%233 HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') result = read_http(sock) self.assertEqual(result.status, 'HTTP/1.1 200 OK') assert b'decoded: /a*b@@#3' in result.body @@ -1402,10 +1419,8 @@ class TestHttpd(_TestBase): 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() + sock = eventlet.connect(self.server_addr) + sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') result1 = read_http(sock) self.assertEqual(result1.status, 'HTTP/1.1 500 Internal Server Error') self.assertEqual(result1.body, b'') @@ -1415,10 +1430,8 @@ class TestHttpd(_TestBase): # 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() + sock = eventlet.connect(self.server_addr) + sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') result2 = read_http(sock) self.assertEqual(result2.status, 'HTTP/1.1 500 Internal Server Error') assert b'intentional crash' in result2.body, result2.body @@ -1435,7 +1448,7 @@ class TestHttpd(_TestBase): yield b'a' * 9876 server_sock = eventlet.listen(('localhost', 0)) - self.port = server_sock.getsockname()[1] + self.server_addr = server_sock.getsockname() server = wsgi.Server(server_sock, server_sock.getsockname(), long_response, log=self.logfile) @@ -1459,7 +1472,7 @@ class TestHttpd(_TestBase): def test_server_socket_timeout(self): self.spawn_server(socket_timeout=0.1) - sock = eventlet.connect(('localhost', self.port)) + sock = eventlet.connect(self.server_addr) sock.send(b'GET / HTTP/1.1\r\n') eventlet.sleep(0.1) try: @@ -1480,7 +1493,7 @@ class TestHttpd(_TestBase): self.spawn_server(site=wsgi_app, capitalize_response_headers=False) - sock = eventlet.connect(('localhost', self.port)) + sock = eventlet.connect(self.server_addr) sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') result = read_http(sock) sock.close() @@ -1488,6 +1501,20 @@ class TestHttpd(_TestBase): 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 test_log_unix_address(self): + tempdir = tempfile.mkdtemp('eventlet_test_log_unix_address') + path = '' + try: + sock = eventlet.listen(tempdir + '/socket', socket.AF_UNIX) + path = sock.getsockname() + + log = six.StringIO() + self.spawn_server(sock=sock, log=log) + eventlet.sleep(0) # need to enter server loop + assert 'http:' + path in log.getvalue() + finally: + shutil.rmtree(tempdir) + def read_headers(sock): fd = sock.makefile('rb') @@ -1527,18 +1554,14 @@ class IterableAlreadyHandledTest(_TestBase): def test_iterable_app_keeps_socket_open_unless_connection_close_sent(self): self.site.application = self.get_app() - sock = eventlet.connect( - ('localhost', self.port)) + sock = eventlet.connect(self.server_addr) - fd = sock.makefile('rwb') - fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - - fd.flush() + sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') 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() + + sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\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') @@ -1558,7 +1581,6 @@ class ProxiedIterableAlreadyHandledTest(IterableAlreadyHandledTest): class TestChunkedInput(_TestBase): - dirt = "" validator = None def application(self, env, start_response): @@ -1601,22 +1623,19 @@ class TestChunkedInput(_TestBase): return response def connect(self): - return eventlet.connect(('localhost', self.port)) + return eventlet.connect(self.server_addr) 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 - + def chunk_encode(self, chunks, 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): + def body(self, dirt=""): return self.chunk_encode(["this", " is ", "chunked", "\nline", " 2", "\n", "line3", ""], dirt=dirt)